- card_layout.py: Port PlayResult, PLAY_RESULTS, EXACT_CHANCES, get_chances(), CardResult, CardColumn, FullCard, FullBattingCard, FullPitchingCard from database/app/card_creation.py. card_output() uses col_* key names. get_chances() always returns Decimal to avoid float/Decimal type errors. - batters/card_builder.py: Port get_batter_card_data() algorithm as build_batter_full_cards(ratings_vl, ratings_vr, offense_col, player_id, hand). assign_bchances() returns float tuples for compatibility with float-based BattingCardRatingsModel fields. - pitchers/card_builder.py: Port get_pitcher_card_data() algorithm as build_pitcher_full_cards(). assign_pchances() returns float tuples. Includes card.add_fatigue() at end of each card iteration. - batters/calcs_batter.py: Integrate card builder in get_batter_ratings(). After computing raw ratings, call build_batter_full_cards() and merge 9 col_* rendered column fields into each ratings dict. Lazy import to avoid circular dependency. - pitchers/calcs_pitcher.py: Same integration for get_pitcher_ratings(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1016 lines
46 KiB
Python
1016 lines
46 KiB
Python
"""
|
|
Card layout models: PlayResult, CardResult, CardColumn, FullCard, FullBattingCard, FullPitchingCard.
|
|
|
|
Adapted from database/app/card_creation.py for use in the card-creation pipeline.
|
|
These models represent the actual card layout (three 2d6 columns with text results)
|
|
as opposed to the raw rating chances stored in BattingCardRatingsModel / PitchingCardRatingsModel.
|
|
"""
|
|
import logging
|
|
import math
|
|
import re
|
|
|
|
import pydantic
|
|
|
|
from decimal import Decimal
|
|
from pydantic import validator
|
|
from typing import Optional
|
|
|
|
|
|
EXACT_CHANCES = [
|
|
Decimal('5.7'), Decimal('5.4'), Decimal('5.1'), Decimal('4.8'), Decimal('4.75'), Decimal('4.5'), Decimal('4.25'),
|
|
Decimal('4.2'), Decimal('3.9'), Decimal('3.8'), Decimal('3.75'), Decimal('3.6'), Decimal('3.5'), Decimal('3.4'),
|
|
Decimal('3.3'), Decimal('3.25'), Decimal('3.2'), Decimal('2.85'), Decimal('2.8'), Decimal('2.75'), Decimal('2.7'),
|
|
Decimal('2.6'), Decimal('2.55'), Decimal('2.5'), Decimal('2.4'), Decimal('2.25'), Decimal('2.2'), Decimal('2.1'),
|
|
Decimal('1.95'), Decimal('1.9'), Decimal('1.8'), Decimal('1.75'), Decimal('1.7'), Decimal('1.65'), Decimal('1.6'),
|
|
Decimal('1.5'), Decimal('1.4'), Decimal('1.35'), Decimal('1.3'), Decimal('1.25'), Decimal('1.2'), Decimal('1.1'),
|
|
Decimal('1.05')
|
|
]
|
|
|
|
|
|
class PlayResult(pydantic.BaseModel):
|
|
full_name: str
|
|
short_name: str
|
|
is_offense: bool = True
|
|
|
|
@validator("is_offense", always=True)
|
|
def offense_validator(cls, v, values, **kwargs):
|
|
return values['short_name'][:2] in ['HR', 'TR', 'DO', 'SI', 'WA', 'HB', '◆B', '▼B']
|
|
|
|
|
|
PLAY_RESULTS = {
|
|
'hr': PlayResult(full_name='HOMERUN', short_name='HR'),
|
|
'bp-hr': PlayResult(full_name='◆BP-HR', short_name='◆BP-HR'),
|
|
'tr': PlayResult(full_name='TRIPLE', short_name='TR'),
|
|
'do-lf': PlayResult(full_name=f'DOUBLE (lf)', short_name=f'DO (lf)'),
|
|
'do-cf': PlayResult(full_name=f'DOUBLE (cf)', short_name=f'DO (cf)'),
|
|
'do-rf': PlayResult(full_name=f'DOUBLE (rf)', short_name=f'DO (rf)'),
|
|
'do***': PlayResult(full_name=f'DOUBLE***', short_name=f'DO***'),
|
|
'do**': PlayResult(full_name=f'DOUBLE**', short_name=f'DO**'),
|
|
'si**': PlayResult(full_name='SINGLE**', short_name='SI**'),
|
|
'si*': PlayResult(full_name='SINGLE*', short_name='SI*'),
|
|
'si-cf': PlayResult(full_name='SINGLE (cf)', short_name='SI (cf)'),
|
|
'bp-si': PlayResult(full_name='▼BP-SI', short_name='▼BP-SI'),
|
|
'walk': PlayResult(full_name='WALK', short_name='WALK'),
|
|
'fly-rf': PlayResult(full_name=f'fly (rf) B', short_name=f'fly (rf) B'),
|
|
'fly-lf': PlayResult(full_name=f'fly (lf) B', short_name=f'fly (lf) B'),
|
|
'fly-cf': PlayResult(full_name=f'fly (cf) B', short_name=f'fly (cf) B'),
|
|
'fly-bq': PlayResult(full_name=f'fly B?', short_name=f'fly B?')
|
|
}
|
|
|
|
|
|
def get_chances(total_chances, apply_limits=True) -> Decimal:
|
|
"""Convert a raw chance value to a Decimal suitable for card slot assignment."""
|
|
if total_chances > 12.5 and apply_limits:
|
|
return Decimal(6)
|
|
elif total_chances > 10.5 and apply_limits:
|
|
return Decimal(5)
|
|
elif total_chances > 8.5 and apply_limits:
|
|
return Decimal(4)
|
|
elif total_chances > 5.5 and apply_limits:
|
|
return Decimal(3)
|
|
else:
|
|
val = min(float(total_chances), 6.0)
|
|
return Decimal(str(val))
|
|
|
|
|
|
class CardResult(pydantic.BaseModel):
|
|
result_one: str = None
|
|
result_two: str = None
|
|
d20_one: str = None
|
|
d20_two: str = None
|
|
bold_one: bool = False
|
|
bold_two: bool = False
|
|
|
|
def __str__(self):
|
|
res_text = f'Empty'
|
|
if self.result_one is not None:
|
|
res_text = f'{self.result_one}'
|
|
if self.d20_one is not None:
|
|
res_text += f' | {self.d20_one}'
|
|
if self.result_two is not None:
|
|
res_text += f'\n{self.result_two} | {self.d20_two}'
|
|
return res_text
|
|
|
|
def is_full(self):
|
|
return self.result_one is not None
|
|
|
|
def assign_play(self, play: PlayResult, secondary_play: Optional[PlayResult] = None, d20: Optional[int] = None):
|
|
if secondary_play is None:
|
|
self.result_one = play.full_name
|
|
if '++' in play.full_name:
|
|
logging.warning(f'Too many plus symbols: {play.full_name}')
|
|
self.result_one = re.sub(r'\++', '+', play.full_name)
|
|
|
|
if play.is_offense:
|
|
self.bold_one = True
|
|
else:
|
|
self.result_one = play.short_name
|
|
self.result_two = secondary_play.short_name
|
|
self.d20_one = f'1-{d20}'
|
|
if d20 == 19:
|
|
self.d20_two = f'20'
|
|
else:
|
|
self.d20_two = f'{d20 + 1}-20'
|
|
|
|
if play.is_offense:
|
|
self.bold_one = True
|
|
if secondary_play.is_offense:
|
|
self.bold_two = True
|
|
|
|
logging.debug(f'this result: {self}')
|
|
|
|
|
|
class CardColumn(pydantic.BaseModel):
|
|
two: CardResult = CardResult() # 1 chance
|
|
three: CardResult = CardResult() # 2 chances
|
|
four: CardResult = CardResult() # 3 chances
|
|
five: CardResult = CardResult() # 4 chances
|
|
six: CardResult = CardResult() # 5 chances
|
|
seven: CardResult = CardResult() # 6 chances
|
|
eight: CardResult = CardResult() # 5 chances
|
|
nine: CardResult = CardResult() # 4 chances
|
|
ten: CardResult = CardResult() # 3 chances
|
|
eleven: CardResult = CardResult() # 2 chances
|
|
twelve: CardResult = CardResult() # 1 chance
|
|
num_splits: int = 0
|
|
num_lomax: int = 0
|
|
num_plusgb: int = 0
|
|
|
|
def __str__(self):
|
|
return (f'2-{self.two}\n'
|
|
f'3-{self.three}\n'
|
|
f'4-{self.four}\n'
|
|
f'5-{self.five}\n'
|
|
f'6-{self.six}\n'
|
|
f'7-{self.seven}\n'
|
|
f'8-{self.eight}\n'
|
|
f'9-{self.nine}\n'
|
|
f'10-{self.ten}\n'
|
|
f'11-{self.eleven}\n'
|
|
f'12-{self.twelve}')
|
|
|
|
def get_text(self) -> dict:
|
|
sixes = ''
|
|
results = ''
|
|
d20 = ''
|
|
|
|
def bold(text):
|
|
return f'<b>{text}</b>'
|
|
|
|
def blank():
|
|
return ' '
|
|
|
|
for count, x in enumerate(
|
|
[self.two, self.three, self.four, self.five, self.six, self.seven, self.eight, self.nine,
|
|
self.ten, self.eleven, self.twelve], start=2):
|
|
if x.bold_one:
|
|
this_six = bold(f'{count}-')
|
|
this_result = bold(x.result_one)
|
|
this_d20 = bold(x.d20_one) if x.d20_one is not None else blank()
|
|
else:
|
|
this_six = f'{count}-'
|
|
this_result = f'{x.result_one}'
|
|
this_d20 = f'{x.d20_one}' if x.d20_one is not None else blank()
|
|
|
|
if x.result_two is not None:
|
|
if x.bold_two:
|
|
this_six += f'<br>{bold(blank())}'
|
|
this_result += f'<br>{bold(x.result_two)}'
|
|
this_d20 += f'<br>{bold(x.d20_two)}'
|
|
else:
|
|
this_six += f'<br>{blank()}'
|
|
this_result += f'<br>{x.result_two}'
|
|
this_d20 += f'<br>{x.d20_two}'
|
|
|
|
sixes += f'{this_six}<br>'
|
|
results += f'{this_result}<br>'
|
|
d20 += f'{this_d20}<br>'
|
|
|
|
return {'sixes': sixes, 'results': results, 'd20': d20}
|
|
|
|
def is_full(self):
|
|
return (self.two.is_full() and self.three.is_full() and self.four.is_full() and self.five.is_full() and
|
|
self.six.is_full() and self.seven.is_full() and self.eight.is_full() and self.nine.is_full() and
|
|
self.ten.is_full() and self.eleven.is_full() and self.twelve.is_full())
|
|
|
|
def add_result(
|
|
self, play: PlayResult, alt_direction: int, chances: Decimal,
|
|
secondary_play: Optional[PlayResult] = None):
|
|
if chances > Decimal(6.0):
|
|
logging.error(f'Cannot assign more than 6 chances per call\n'
|
|
f'Play: {play}\nAlt Direction: {alt_direction}\nChances: {chances}\n'
|
|
f'Secondary Play: {secondary_play}')
|
|
raise ValueError(f'Cannot assign more than 6 chances per call')
|
|
elif math.floor(chances) != chances and secondary_play is None:
|
|
if chances > Decimal(1.0):
|
|
chances = Decimal(math.floor(chances))
|
|
else:
|
|
logging.error(f'Must have secondary play for fractional chances; could not round down to an integer\n'
|
|
f'Play: {play}\nChances: {chances}\nSecondary Play: {secondary_play}')
|
|
return False
|
|
|
|
# Chances is whole number
|
|
if math.floor(chances) == chances:
|
|
if chances == Decimal(6):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.six.is_full():
|
|
if not self.two.is_full():
|
|
self.six.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.six.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.eight.is_full():
|
|
if not self.two.is_full():
|
|
self.eight.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.eight.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus two
|
|
if not self.five.is_full():
|
|
if not self.three.is_full():
|
|
self.five.assign_play(play)
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
elif not self.eleven.is_full():
|
|
self.five.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Bulk 2, 3, 4 and 10, 11, 12
|
|
if not self.three.is_full() and not self.two.is_full() and not self.four.is_full():
|
|
self.four.assign_play(play)
|
|
self.three.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.ten.is_full() and not self.eleven.is_full() and not self.twelve.is_full():
|
|
self.ten.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.nine.is_full():
|
|
if not self.three.is_full():
|
|
self.nine.assign_play(play)
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
elif not self.eleven.is_full():
|
|
self.nine.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
if chances == Decimal(5):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.eight.is_full():
|
|
self.eight.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Bulk 3, 4 and 10, 11
|
|
if not self.three.is_full() and not self.four.is_full():
|
|
self.four.assign_play(play)
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.ten.is_full() and not self.eleven.is_full():
|
|
self.ten.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.five.is_full():
|
|
if not self.two.is_full():
|
|
self.five.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.five.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.nine.is_full():
|
|
if not self.two.is_full():
|
|
self.nine.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.nine.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus two
|
|
if not self.four.is_full():
|
|
if not self.three.is_full():
|
|
self.four.assign_play(play)
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
elif not self.eleven.is_full():
|
|
self.four.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus two
|
|
if not self.ten.is_full():
|
|
if not self.three.is_full():
|
|
self.ten.assign_play(play)
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
elif not self.eleven.is_full():
|
|
self.ten.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
if chances == Decimal(4):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.four.is_full():
|
|
if not self.two.is_full():
|
|
self.four.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.four.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.ten.is_full():
|
|
if not self.two.is_full():
|
|
self.ten.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.ten.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.three.is_full() and not self.eleven.is_full():
|
|
self.three.assign_play(play)
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
if chances == Decimal(3):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.three.is_full():
|
|
if not self.two.is_full():
|
|
self.three.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
elif not self.twelve.is_full():
|
|
self.three.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
# Plus one
|
|
if not self.eleven.is_full():
|
|
if not self.twelve.is_full():
|
|
self.eleven.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
if not self.two.is_full():
|
|
self.eleven.assign_play(play)
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
|
|
if chances == Decimal(2):
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.eleven.is_full():
|
|
self.eleven.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.two.is_full() and not self.twelve.is_full():
|
|
self.two.assign_play(play)
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
if chances == Decimal(1):
|
|
if not self.two.is_full():
|
|
self.two.assign_play(play)
|
|
return chances, 0
|
|
|
|
if not self.twelve.is_full():
|
|
self.twelve.assign_play(play)
|
|
return chances, 0
|
|
|
|
return False
|
|
|
|
logging.debug(f'Not a whole number | Chances: {chances}')
|
|
if chances in EXACT_CHANCES and self.num_splits < 4 and secondary_play is not None:
|
|
logging.debug(f'In Exact Chances!')
|
|
if chances >= 3:
|
|
self.num_splits += 1
|
|
logging.debug(f'Chances is greater than 3')
|
|
if chances == Decimal('3.2'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.8')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.8')
|
|
elif chances == Decimal('3.25'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.75')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.75')
|
|
elif chances == Decimal('3.3') and not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('2.7')
|
|
elif chances == Decimal('3.4'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.6')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.6')
|
|
elif chances == Decimal('3.5'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('1.5')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('1.5')
|
|
elif chances == Decimal('3.6'):
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.4')
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.4')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('2.4')
|
|
elif chances == Decimal('3.75'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('1.25')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('1.25')
|
|
elif chances == Decimal('3.8'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.2')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.2')
|
|
elif chances == Decimal('3.9'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('2.1')
|
|
elif chances == Decimal('4.2'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('1.8')
|
|
elif chances == Decimal('4.25'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.75')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.75')
|
|
elif chances == Decimal('4.5'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.5')
|
|
if not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.5')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('1.5')
|
|
elif chances == Decimal('4.75'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.25')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.25')
|
|
elif chances == Decimal('4.8'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('1.2')
|
|
elif chances == Decimal('5.1'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.9')
|
|
elif chances == Decimal('5.4'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.6')
|
|
elif chances == Decimal('5.7'):
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.3')
|
|
elif chances >= 1:
|
|
self.num_splits += 1
|
|
logging.debug(f'Chances is greater than 1')
|
|
if chances == Decimal('1.05'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('1.95')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('1.95')
|
|
if chances == Decimal('1.1'):
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('0.9')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('0.9')
|
|
if chances == Decimal('1.2'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 6)
|
|
return chances, Decimal('2.8')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 6)
|
|
return chances, Decimal('2.8')
|
|
elif not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 8)
|
|
return chances, Decimal('1.8')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 8)
|
|
return chances, Decimal('1.8')
|
|
elif not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('0.8')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('0.8')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 4)
|
|
return chances, Decimal('4.8')
|
|
if chances == Decimal('1.25'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 5)
|
|
return chances, Decimal('3.75')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 5)
|
|
return chances, Decimal('3.75')
|
|
if chances == Decimal('1.3'):
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('0.7')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('0.7')
|
|
if chances == Decimal('1.35'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('1.65')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('1.65')
|
|
if chances == Decimal('1.4'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('2.6')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('2.6')
|
|
elif not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('0.6')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('0.6')
|
|
if chances == Decimal('1.5'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 6)
|
|
return chances, Decimal('3.5')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 6)
|
|
return chances, Decimal('3.5')
|
|
elif not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 10)
|
|
return chances, Decimal('1.5')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 10)
|
|
return chances, Decimal('1.5')
|
|
elif not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('0.5')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('0.5')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 5)
|
|
return chances, Decimal('4.5')
|
|
if chances == Decimal('1.6'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 8)
|
|
return chances, Decimal('2.4')
|
|
elif not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 8)
|
|
return chances, Decimal('2.4')
|
|
elif not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.4')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.4')
|
|
if chances == Decimal('1.65'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('1.35')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('1.35')
|
|
if chances == Decimal('1.7'):
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.3')
|
|
elif not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.3')
|
|
if chances == Decimal('1.75'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('3.25')
|
|
elif not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('3.25')
|
|
if chances == Decimal('1.8'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('2.2')
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('2.2')
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('1.2')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('1.2')
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.2')
|
|
if not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.2')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 6)
|
|
return chances, Decimal('4.2')
|
|
if chances == Decimal('1.9'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.1')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.1')
|
|
if not self.three.is_full():
|
|
self.three.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.1')
|
|
if not self.eleven.is_full():
|
|
self.eleven.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.1')
|
|
if chances == Decimal('1.95'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.05')
|
|
elif not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.05')
|
|
if chances == Decimal('2.1'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('0.9')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('0.9')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 7)
|
|
return chances, Decimal('3.9')
|
|
if chances == Decimal('2.2'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('1.8')
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('1.8')
|
|
if chances == Decimal('2.25'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('2.75')
|
|
if not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('2.75')
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('0.75')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 15)
|
|
return chances, Decimal('0.75')
|
|
if chances == Decimal('2.4'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('1.6')
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 12)
|
|
return chances, Decimal('1.6')
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.6')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 16)
|
|
return chances, Decimal('0.6')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 8)
|
|
return chances, Decimal('3.6')
|
|
if chances == Decimal('2.5'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 10)
|
|
return chances, Decimal('2.5')
|
|
if not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 10)
|
|
return chances, Decimal('2.5')
|
|
if chances == Decimal('2.55'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.45')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 17)
|
|
return chances, Decimal('0.45')
|
|
if chances == Decimal('2.6'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.4')
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 13)
|
|
return chances, Decimal('1.4')
|
|
if chances == Decimal('2.7'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.3')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 18)
|
|
return chances, Decimal('0.3')
|
|
if not self.seven.is_full():
|
|
self.seven.assign_play(play, secondary_play, 9)
|
|
return chances, Decimal('3.3')
|
|
if chances == Decimal('2.75'):
|
|
if not self.six.is_full():
|
|
self.six.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('2.25')
|
|
if not self.eight.is_full():
|
|
self.eight.assign_play(play, secondary_play, 11)
|
|
return chances, Decimal('2.25')
|
|
if chances == Decimal('2.8'):
|
|
if not self.five.is_full():
|
|
self.five.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('1.2')
|
|
if not self.nine.is_full():
|
|
self.nine.assign_play(play, secondary_play, 14)
|
|
return chances, Decimal('1.2')
|
|
if chances == Decimal('2.85'):
|
|
if not self.four.is_full():
|
|
self.four.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.15')
|
|
if not self.ten.is_full():
|
|
self.ten.assign_play(play, secondary_play, 19)
|
|
return chances, Decimal('0.15')
|
|
else:
|
|
logging.debug(f'Chances is less than 1')
|
|
return False
|
|
|
|
self.num_splits -= 1
|
|
|
|
else:
|
|
logging.debug(f'Not a whole number and not in Exact Chances! Trying to add a subset')
|
|
for x in EXACT_CHANCES:
|
|
if x < chances and ((chances - x) == round(chances - x)):
|
|
logging.debug(f'Trying to add {x} chances')
|
|
return self.add_result(play, alt_direction, x, secondary_play)
|
|
logging.debug(f'Could not find a valid match')
|
|
return False
|
|
|
|
def total_chances(self):
|
|
total = 0
|
|
total += 1 if self.two.is_full() else 0
|
|
total += 2 if self.three.is_full() else 0
|
|
total += 3 if self.four.is_full() else 0
|
|
total += 4 if self.five.is_full() else 0
|
|
total += 5 if self.six.is_full() else 0
|
|
total += 6 if self.seven.is_full() else 0
|
|
total += 5 if self.eight.is_full() else 0
|
|
total += 4 if self.nine.is_full() else 0
|
|
total += 3 if self.ten.is_full() else 0
|
|
total += 2 if self.eleven.is_full() else 0
|
|
total += 1 if self.twelve.is_full() else 0
|
|
return total
|
|
|
|
def add_fatigue(self, num_chances: int, k_only: bool = False):
|
|
def is_valid_result(this_result: CardResult):
|
|
if k_only:
|
|
return this_result.result_one == 'strikeout' and '•' not in this_result.result_one
|
|
else:
|
|
return (this_result.result_two is None and not this_result.bold_one
|
|
and 'X' not in this_result.result_one and '•' not in this_result.result_one)
|
|
|
|
if num_chances == 6:
|
|
if is_valid_result(self.seven):
|
|
self.seven.result_one += ' •'
|
|
return 6
|
|
elif num_chances == 5:
|
|
if is_valid_result(self.six):
|
|
self.six.result_one += ' •'
|
|
return 5
|
|
if is_valid_result(self.eight):
|
|
self.eight.result_one += ' •'
|
|
return 5
|
|
elif num_chances == 4:
|
|
if is_valid_result(self.five):
|
|
self.five.result_one += ' •'
|
|
return 4
|
|
if is_valid_result(self.nine):
|
|
self.nine.result_one += ' •'
|
|
return 4
|
|
|
|
return 0
|
|
|
|
|
|
class FullCard(pydantic.BaseModel):
|
|
col_one: CardColumn = CardColumn()
|
|
col_two: CardColumn = CardColumn()
|
|
col_three: CardColumn = CardColumn()
|
|
offense_col: int
|
|
alt_direction: int = 1
|
|
num_plusgb: int = 0
|
|
num_lomax: int = 0
|
|
is_batter: bool = False
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
def get_columns(self, is_offense: bool):
|
|
if is_offense:
|
|
if self.offense_col == 1:
|
|
first = self.col_one
|
|
second, third = (self.col_two, self.col_three) if self.alt_direction else (self.col_three, self.col_two)
|
|
elif self.offense_col == 2:
|
|
first = self.col_two
|
|
second, third = (self.col_three, self.col_one) if self.alt_direction else (self.col_one, self.col_three)
|
|
else:
|
|
first = self.col_three
|
|
second, third = (self.col_one, self.col_two) if self.alt_direction else (self.col_two, self.col_one)
|
|
else:
|
|
if self.offense_col == 1:
|
|
third = self.col_one
|
|
first, second = (self.col_two, self.col_three) if self.alt_direction else (self.col_three, self.col_two)
|
|
elif self.offense_col == 2:
|
|
third = self.col_two
|
|
first, second = (self.col_three, self.col_one) if self.alt_direction else (self.col_one, self.col_three)
|
|
else:
|
|
third = self.col_three
|
|
first, second = (self.col_one, self.col_two) if self.alt_direction else (self.col_two, self.col_one)
|
|
|
|
return first, second, third
|
|
|
|
def is_complete(self):
|
|
return self.col_one.is_full() and self.col_two.is_full() and self.col_three.is_full()
|
|
|
|
def sample_output(self):
|
|
return (f'{"" if self.is_complete() else "NOT "}COMPLETE\n'
|
|
f'Column 1\n{self.col_one}\n\n'
|
|
f'Column 2\n{self.col_two}\n\n'
|
|
f'Column 3\n{self.col_three}')
|
|
|
|
def add_result(self, play: PlayResult, chances: Decimal, secondary_play: Optional[PlayResult] = None):
|
|
first, second, third = self.get_columns(is_offense=play.is_offense)
|
|
|
|
if 'gb' in play.full_name and chances + self.num_plusgb <= 6 and self.is_batter:
|
|
play.full_name += '+'
|
|
|
|
for x in [first, second, third]:
|
|
r_data = x.add_result(play, self.alt_direction, chances, secondary_play)
|
|
if r_data:
|
|
if '+' in play.full_name:
|
|
self.num_plusgb += r_data[0]
|
|
elif 'max' in play.full_name:
|
|
self.num_lomax += r_data[0]
|
|
return r_data
|
|
|
|
return False
|
|
|
|
def card_fill(self, play: PlayResult):
|
|
for x in range(6, 0, -1):
|
|
r_data = self.add_result(play, Decimal(x))
|
|
if r_data:
|
|
return r_data
|
|
|
|
return 0, 0
|
|
|
|
def card_output(self) -> dict:
|
|
"""Return the pre-rendered card columns as 9 HTML strings suitable for direct storage/display."""
|
|
c1_output = self.col_one.get_text()
|
|
c2_output = self.col_two.get_text()
|
|
c3_output = self.col_three.get_text()
|
|
|
|
return {
|
|
'col_one_2d6': c1_output['sixes'],
|
|
'col_one_results': c1_output['results'],
|
|
'col_one_d20': c1_output['d20'],
|
|
'col_two_2d6': c2_output['sixes'],
|
|
'col_two_results': c2_output['results'],
|
|
'col_two_d20': c2_output['d20'],
|
|
'col_three_2d6': c3_output['sixes'],
|
|
'col_three_results': c3_output['results'],
|
|
'col_three_d20': c3_output['d20'],
|
|
}
|
|
|
|
def total_chances(self):
|
|
return self.col_one.total_chances() + self.col_two.total_chances() + self.col_three.total_chances()
|
|
|
|
def add_fatigue(self):
|
|
first, second, third = self.get_columns(is_offense=False)
|
|
|
|
total_added = 0
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(6, k_only=True)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added == 0:
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(6, k_only=False)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added == 0:
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(5, k_only=True)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added == 0:
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(5, k_only=False)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added != 10:
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(10 - total_added, k_only=True)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added != 10:
|
|
for x in [first, second, third]:
|
|
resp = x.add_fatigue(10 - total_added, k_only=False)
|
|
if resp:
|
|
total_added += resp
|
|
break
|
|
|
|
if total_added != 10:
|
|
logging.error(f'FullCard add_fatigue - Could not add all fatigue results / total_added: {total_added}')
|
|
|
|
|
|
class FullBattingCard(FullCard):
|
|
is_batter: bool = True
|
|
|
|
|
|
class FullPitchingCard(FullCard):
|
|
is_batter: bool = False
|