""" 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'{text}' 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'
{bold(blank())}' this_result += f'
{bold(x.result_two)}' this_d20 += f'
{bold(x.d20_two)}' else: this_six += f'
{blank()}' this_result += f'
{x.result_two}' this_d20 += f'
{x.d20_two}' sixes += f'{this_six}
' results += f'{this_result}
' d20 += f'{this_d20}
' 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