2024-06-22 00:05:00 +00:00
|
|
|
from flask import Flask
|
2024-06-22 21:29:19 +00:00
|
|
|
from flask_cors import CORS
|
2024-06-22 13:22:39 +00:00
|
|
|
from flask_restx import Api, Resource
|
2024-06-22 10:29:05 +00:00
|
|
|
from marshmallow import Schema, fields, validate
|
2024-06-22 22:56:55 +00:00
|
|
|
from models import dice_model, ability_model, hp_model, character_model, encounter_model
|
|
|
|
|
|
|
|
from encounters import EncounterTable
|
2024-06-22 18:32:17 +00:00
|
|
|
from mutations import Mutations
|
2024-06-21 21:49:11 +00:00
|
|
|
import random
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
2024-06-22 21:29:19 +00:00
|
|
|
CORS(app)
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
app.config.SWAGGER_UI_JSONEDITOR = True
|
|
|
|
app.config['SWAGGER_UI_JSONEDITOR'] = True
|
|
|
|
api = Api(app, version='1.0', title='Gamma World Dice', description='Rolled Dice As A Service')
|
|
|
|
|
|
|
|
dice = api.namespace('dice', description='Dice operations')
|
|
|
|
ability = api.namespace('ability', description='Ability operations')
|
|
|
|
hp = api.namespace('hp', description='HP operations')
|
2024-06-22 13:22:39 +00:00
|
|
|
character = api.namespace('character', description='Character operations')
|
2024-06-22 22:56:55 +00:00
|
|
|
encounter = api.namespace('encounter', description='Encounter operations')
|
2024-06-22 00:05:00 +00:00
|
|
|
|
2024-06-22 10:29:05 +00:00
|
|
|
dice_model = dice.model('Dice', dice_model)
|
|
|
|
ability_model = ability.model('Ability', ability_model)
|
|
|
|
hp_model = hp.model('HP', hp_model)
|
2024-06-22 13:22:39 +00:00
|
|
|
character_model = character.model('Character', character_model)
|
2024-06-22 22:56:55 +00:00
|
|
|
encounter_model = encounter.model('Encounter', encounter_model)
|
2024-06-22 00:05:00 +00:00
|
|
|
|
|
|
|
|
2024-06-22 10:29:05 +00:00
|
|
|
class DiceSchema(Schema):
|
|
|
|
quantity = fields.Int(required=True, validate=validate.Range(min=1), description='The number of dice to roll')
|
|
|
|
geometry = fields.Int(required=True, validate=validate.Range(min=2), description='The number of sides on each die')
|
|
|
|
discard_lowest = fields.Bool(default=False, description='Drop the lowest score')
|
|
|
|
|
|
|
|
|
|
|
|
dice_schema = DiceSchema()
|
2024-06-22 00:05:00 +00:00
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
class CharacterSchema(Schema):
|
|
|
|
chartype = fields.String(
|
|
|
|
required=True,
|
|
|
|
validate=validate.OneOf(["human", "mutant", "android", "robot"]),
|
|
|
|
description='The characters type of being'
|
|
|
|
)
|
|
|
|
emphasis = fields.String(
|
|
|
|
required=True,
|
|
|
|
validate=validate.OneOf(["physical", "mental", "random"]),
|
|
|
|
description='Valid inputs: physical, mental, random'
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
character_schema = CharacterSchema()
|
|
|
|
|
|
|
|
|
2024-06-22 22:56:55 +00:00
|
|
|
class EncounterSchema(Schema):
|
|
|
|
terrain = fields.String(
|
|
|
|
required=True,
|
|
|
|
validate=validate.OneOf(["clear", "mountains", "forest", "desert", "watery", "ruins", "deathlands"]),
|
|
|
|
description='The terrain traversed at the time of the encounter roll'
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
encounter_schema = EncounterSchema()
|
|
|
|
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
@api.route('/roll/dice', methods=['POST'])
|
|
|
|
class RollDice(Resource):
|
|
|
|
@dice.expect(dice_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
2024-06-22 10:29:05 +00:00
|
|
|
errors = dice_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
2024-06-22 00:05:00 +00:00
|
|
|
quantity = data.get('quantity')
|
|
|
|
geometry = data.get('geometry')
|
|
|
|
discard_lowest = data.get('discard_lowest')
|
|
|
|
if quantity is None or geometry is None:
|
|
|
|
return {"message": "Required dice data not provided"}, 400
|
|
|
|
return roll_dices(quantity, geometry, discard_lowest), 200
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/roll/ability', methods=['POST'])
|
|
|
|
class RollAbility(Resource):
|
|
|
|
@ability.expect(ability_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
chartype = data.get('chartype')
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
return roll_dices(4, geometry, True), 200
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/roll/hp', methods=['POST'])
|
|
|
|
class RollHP(Resource):
|
|
|
|
@hp.expect(hp_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
chartype = data.get('chartype')
|
|
|
|
conscore = data.get('conscore')
|
|
|
|
if conscore is None:
|
|
|
|
return {"message": "A constitution score is required"}, 400
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
return roll_dices(conscore, geometry, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 22:56:55 +00:00
|
|
|
@api.route('/roll/encounter', methods=['POST'])
|
|
|
|
class RollEncounter(Resource):
|
|
|
|
@encounter.expect(encounter_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
errors = encounter_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
|
|
|
|
terrain = data.get('terrain').lower()
|
|
|
|
roll = roll_dices(1, 20, False).get('result')
|
|
|
|
et = EncounterTable()
|
|
|
|
creature = et.get_encounter(roll, terrain)
|
|
|
|
if creature is None:
|
|
|
|
creature = "All clear!"
|
|
|
|
|
|
|
|
return {"encounter": creature}, 200
|
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
@api.route('/roll/tohit', methods=['GET'])
|
|
|
|
class RollToHit(Resource):
|
|
|
|
def get(self):
|
|
|
|
return roll_dices(1, 20, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 00:05:00 +00:00
|
|
|
@api.route('/roll/chance', methods=['GET'])
|
|
|
|
class RollChance(Resource):
|
|
|
|
def get(self):
|
|
|
|
return roll_dices(1, 100, False), 200
|
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
@api.route('/coinflip', methods=['GET'])
|
|
|
|
class RollCoinflip(Resource):
|
2024-06-22 00:05:00 +00:00
|
|
|
def get(self):
|
2024-06-22 13:22:39 +00:00
|
|
|
return random.choice(['Heads', 'Tails']), 200
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/character/generate')
|
|
|
|
class GenerateCharacter(Resource):
|
|
|
|
@character.expect(character_model)
|
|
|
|
def post(self):
|
|
|
|
data = api.payload
|
|
|
|
errors = character_schema.validate(data)
|
|
|
|
if errors:
|
|
|
|
return errors, 400
|
|
|
|
chartype = data.get('chartype')
|
|
|
|
char_emphasis = data.get('emphasis')
|
|
|
|
|
|
|
|
character_sheet = {}
|
|
|
|
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
scores = roll_ability_scores(chartype)
|
|
|
|
assigned_abilities = assign_ability_scores(scores, char_emphasis)
|
|
|
|
character_sheet['abilities'] = assigned_abilities
|
|
|
|
character_sheet['hp'] = roll_hp(chartype, assigned_abilities['constitution'])
|
|
|
|
character_sheet['gold'] = roll_dices(4, 4, False).get('result')
|
|
|
|
character_sheet['domar'] = roll_dices(2, 4, False).get('result')
|
2024-06-22 18:32:17 +00:00
|
|
|
|
|
|
|
if chartype == 'mutant':
|
|
|
|
character_sheet['mutations'] = (
|
|
|
|
roll_mutations(assigned_abilities['constitution'], assigned_abilities['intelligence'])
|
|
|
|
)
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
return character_sheet, 200
|
2024-06-21 21:49:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
def roll_dices(dice_count, dice_sides, discard_lowest):
|
|
|
|
roll_results = []
|
2024-06-22 10:29:05 +00:00
|
|
|
discarded = []
|
2024-06-21 21:49:11 +00:00
|
|
|
for _ in range(dice_count):
|
|
|
|
roll_results.append(random.randint(1, dice_sides))
|
|
|
|
if discard_lowest and len(roll_results) > 0:
|
2024-06-22 10:29:05 +00:00
|
|
|
discarded.append(min(roll_results))
|
2024-06-21 21:49:11 +00:00
|
|
|
roll_results.remove(min(roll_results))
|
|
|
|
|
2024-06-22 10:29:05 +00:00
|
|
|
if discarded:
|
2024-06-22 13:22:39 +00:00
|
|
|
net_dice_count = dice_count - 1
|
|
|
|
mnemonic = str(dice_count) + "(-1)d" + str(dice_sides)
|
2024-06-22 10:29:05 +00:00
|
|
|
else:
|
2024-06-22 13:22:39 +00:00
|
|
|
mnemonic = str(dice_count) + 'd' + str(dice_sides)
|
2024-06-22 10:29:05 +00:00
|
|
|
net_dice_count = dice_count
|
|
|
|
|
2024-06-21 21:49:11 +00:00
|
|
|
result = {
|
2024-06-22 10:29:05 +00:00
|
|
|
'dice-set': {
|
|
|
|
'mnemonic': mnemonic,
|
|
|
|
'min-roll': net_dice_count,
|
2024-06-22 13:22:39 +00:00
|
|
|
'max-roll': dice_sides * net_dice_count
|
2024-06-22 10:29:05 +00:00
|
|
|
},
|
2024-06-21 21:49:11 +00:00
|
|
|
'rolls': roll_results,
|
2024-06-22 10:29:05 +00:00
|
|
|
'result': sum(roll_results)
|
2024-06-21 21:49:11 +00:00
|
|
|
}
|
2024-06-22 10:29:05 +00:00
|
|
|
if discarded:
|
|
|
|
result['discarded'] = discarded
|
2024-06-21 21:49:11 +00:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
def roll_ability_scores(chartype):
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
|
|
|
|
scores = []
|
|
|
|
for _ in range(6):
|
|
|
|
scores.append(roll_dices(4, geometry, True)['result'])
|
|
|
|
|
|
|
|
return scores
|
|
|
|
|
|
|
|
|
|
|
|
def assign_ability_scores(scores, char_emphasis):
|
|
|
|
ability_scores = {}
|
|
|
|
if char_emphasis == 'physical':
|
|
|
|
ability_list = ['p-strength', 'constitution', 'dexterity',
|
|
|
|
'charisma', 'intelligence', 'm-strength']
|
|
|
|
for ab in ability_list:
|
|
|
|
max_score = max(scores)
|
|
|
|
ability_scores[ab] = max_score
|
|
|
|
scores.remove(max_score)
|
|
|
|
|
|
|
|
elif char_emphasis == 'mental':
|
|
|
|
ability_list = ['m-strength', 'intelligence', 'charisma',
|
|
|
|
'constitution', 'dexterity', 'p-strength']
|
|
|
|
for ab in ability_list:
|
|
|
|
max_score = max(scores)
|
|
|
|
ability_scores[ab] = max_score
|
|
|
|
scores.remove(max_score)
|
|
|
|
|
|
|
|
else:
|
|
|
|
ability_list = ['p-strength', 'constitution', 'dexterity',
|
|
|
|
'charisma', 'intelligence', 'm-strength']
|
|
|
|
random.shuffle(scores)
|
|
|
|
for ab, score in zip(ability_list, scores):
|
|
|
|
ability_scores[ab] = score
|
|
|
|
|
|
|
|
return ability_scores
|
|
|
|
|
2024-06-22 18:32:17 +00:00
|
|
|
|
2024-06-22 13:22:39 +00:00
|
|
|
def roll_hp(chartype, conscore):
|
|
|
|
if chartype == 'human':
|
|
|
|
geometry = 8
|
|
|
|
else:
|
|
|
|
geometry = 6
|
|
|
|
return roll_dices(conscore, geometry, False).get('result')
|
|
|
|
|
2024-06-22 18:32:17 +00:00
|
|
|
|
|
|
|
def roll_mutations(conscore, intscore):
|
|
|
|
"""
|
|
|
|
:param conscore: modifier for physical mutation determination
|
|
|
|
:param intscore: modifier for mental mutation determination
|
|
|
|
:return: table of mutations (in json form)
|
|
|
|
"""
|
|
|
|
mutations_table = {}
|
|
|
|
|
2024-06-22 21:29:19 +00:00
|
|
|
mental_mutations_cnt = roll_dices(1, 4, False).get('result')
|
2024-06-22 18:32:17 +00:00
|
|
|
physical_mutations_cnt = roll_dices(1, 4, False).get('result')
|
|
|
|
mutations_table['count'] = {'mental': mental_mutations_cnt, 'physical': physical_mutations_cnt}
|
|
|
|
|
|
|
|
mental_mutations_scores = []
|
|
|
|
physical_mutations_scores = []
|
|
|
|
|
|
|
|
for _ in range(mental_mutations_cnt):
|
|
|
|
mscore = roll_dices(1, 100, False).get('result') + intscore
|
|
|
|
if mscore in mental_mutations_scores:
|
2024-06-22 21:29:19 +00:00
|
|
|
mscore = mscore - 3
|
2024-06-22 18:32:17 +00:00
|
|
|
|
|
|
|
if mscore > 100:
|
|
|
|
mscore = 100
|
|
|
|
mental_mutations_scores.append(mscore)
|
|
|
|
|
|
|
|
for _ in range(physical_mutations_cnt):
|
|
|
|
pscore = roll_dices(1, 100, False).get('result') + conscore
|
|
|
|
if pscore in physical_mutations_scores:
|
2024-06-22 21:29:19 +00:00
|
|
|
pscore = pscore - 3
|
2024-06-22 18:32:17 +00:00
|
|
|
|
|
|
|
if pscore > 100:
|
|
|
|
pscore = 100
|
|
|
|
physical_mutations_scores.append(pscore)
|
|
|
|
|
|
|
|
mut = Mutations()
|
|
|
|
mmut = []
|
|
|
|
pmut = []
|
|
|
|
|
|
|
|
for score in mental_mutations_scores:
|
|
|
|
mutation = mut.get_mental_mutation(score)
|
|
|
|
mmut.append(mutation)
|
|
|
|
|
|
|
|
mutations_table['mental'] = mmut
|
|
|
|
|
|
|
|
for score in physical_mutations_scores:
|
|
|
|
mutation = mut.get_physical_mutation(score)
|
|
|
|
pmut.append(mutation)
|
|
|
|
|
|
|
|
mutations_table['physical'] = pmut
|
|
|
|
|
|
|
|
return mutations_table
|
|
|
|
|
|
|
|
|
2024-06-21 21:49:11 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
app.run()
|