""" Batter card-building algorithm, ported from database/app/card_creation.py (~lines 1357-2226). Converts BattingCardRatingsModel instances (vL and vR) into FullBattingCard objects that represent the physical card layout. """ import copy import math import logging from decimal import Decimal from card_layout import FullBattingCard, PLAY_RESULTS, PlayResult, EXACT_CHANCES, get_chances from batters.models import BattingCardRatingsModel logger = logging.getLogger(__name__) def build_batter_full_cards( ratings_vl: BattingCardRatingsModel, ratings_vr: BattingCardRatingsModel, offense_col: int, player_id: int, hand: str, # player's batting hand: 'R', 'L', or 'S' ) -> tuple: """Build vL and vR FullBattingCard objects from pre-calculated ratings. Returns (vl_card, vr_card). """ player_binary = player_id % 2 vl = FullBattingCard(offense_col=offense_col, alt_direction=player_binary) vr = FullBattingCard(offense_col=offense_col, alt_direction=player_binary) def assign_bchances(this_card, play, chances, secondary_play=None): r_data = this_card.add_result(play, chances, secondary_play) if r_data: return float(r_data[0]), float(r_data[1]) else: for x in EXACT_CHANCES: if x < math.floor(chances): r_data = this_card.add_result(play, Decimal(math.floor(chances)), secondary_play) if r_data: return float(r_data[0]), float(r_data[1]) break if x < chances: r_data = this_card.add_result(play, x, secondary_play) if r_data: return float(r_data[0]), float(r_data[1]) return 0, 0 def get_pullside_of(vs_hand): if hand == 'L': return 'rf' elif hand == 'R': return 'lf' elif vs_hand == 'L': return 'lf' else: return 'rf' def get_preferred_mif(ratings): if hand == 'L' and ratings.slap_rate > .24: return 'ss' elif hand == 'L' or (hand == 'R' and ratings.slap_rate > .24): return '2b' else: return 'ss' for card, data, vs_hand in [ (vl, copy.deepcopy(ratings_vl), 'L'), (vr, copy.deepcopy(ratings_vr), 'R'), ]: logger.info(f'\n\nBeginning v{vs_hand}') new_ratings = BattingCardRatingsModel( battingcard_id=data.battingcard_id, bat_hand=data.bat_hand, vs_hand=vs_hand, hard_rate=data.hard_rate, med_rate=data.med_rate, soft_rate=data.soft_rate, pull_rate=data.pull_rate, center_rate=data.center_rate, slap_rate=data.slap_rate, ) pull_of = get_pullside_of(vs_hand) pref_mif = get_preferred_mif(data) # BP Homerun res_chances = data.bp_homerun while res_chances > 0: ch = get_chances(res_chances) r_val = assign_bchances(card, PLAY_RESULTS['bp-hr'], ch) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.bp_homerun += r_val[0] # HBP retries = 0 res_chances = data.hbp while res_chances > 0: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances(card, PlayResult(full_name='HBP', short_name='HBP'), ch) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.hbp += r_val[0] if r_val[0] == 0: retries += 1 # Homerun retries = 0 res_chances = data.homerun while res_chances > 0: if res_chances < 1 or retries > 0: if data.double_pull > 0: data.double_pull += res_chances elif data.double_two > 0: data.double_two += res_chances elif data.triple > 0: data.triple += res_chances elif data.single_two > 0: data.single_two += res_chances elif data.single_center > 0: data.single_center += res_chances break ch = get_chances(res_chances) if data.double_pull > (data.flyout_rf_b + data.flyout_lf_b) and data.double_pull > max(1 - ch, 0): secondary = PLAY_RESULTS[f'do-{pull_of}'] elif data.flyout_lf_b > data.flyout_rf_b and data.flyout_lf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-lf'] elif data.flyout_rf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-rf'] elif data.double_pull > max(1 - ch, 0): secondary = PLAY_RESULTS[f'do-{pull_of}'] elif data.double_three > max(1 - ch, 0): secondary = PLAY_RESULTS['do***'] elif data.double_two > max(1 - ch, 0): secondary = PLAY_RESULTS['do**'] elif data.triple > max(1 - ch, 0): secondary = PLAY_RESULTS['tr'] else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['hr'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.homerun += r_val[0] if r_val[1] > 0: if secondary.short_name[:4] == 'DO (': data.double_pull -= r_val[1] new_ratings.double_pull += r_val[1] elif 'lf' in secondary.short_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'rf' in secondary.short_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif '***' in secondary.short_name: data.double_three -= r_val[1] new_ratings.double_three += r_val[1] elif '**' in secondary.short_name: data.double_two -= r_val[1] new_ratings.double_two += r_val[1] elif 'TR' in secondary.short_name: data.triple -= r_val[1] new_ratings.triple += r_val[1] if r_val[0] == 0: retries += 1 # Triple retries = 0 res_chances = data.triple while res_chances > 0: if res_chances < 1 or retries > 0: if data.double_pull > 0: data.double_pull += res_chances elif data.double_two > 0: data.double_two += res_chances elif data.single_two > 0: data.single_two += res_chances elif data.single_center > 0: data.single_center += res_chances elif data.single_one > 0: data.single_one += res_chances break ch = get_chances(res_chances) if data.single_two > max(1 - ch, 0): secondary = PLAY_RESULTS['si**'] elif data.flyout_lf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-lf'] elif data.flyout_rf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-rf'] elif data.double_pull > max(1 - ch, 0): secondary = PLAY_RESULTS[f'do-{pull_of}'] elif data.double_three > max(1 - ch, 0): secondary = PLAY_RESULTS['do***'] elif data.double_two > max(1 - ch, 0): secondary = PLAY_RESULTS['do**'] else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['tr'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.triple += r_val[0] if r_val[1] > 0: if 'DO (' in secondary.short_name: data.double_pull -= r_val[1] new_ratings.double_pull += r_val[1] elif 'lf' in secondary.short_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'rf' in secondary.short_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif '***' in secondary.short_name: data.double_three -= r_val[1] new_ratings.double_three += r_val[1] elif 'SI' in secondary.short_name: data.single_two -= r_val[1] new_ratings.single_two += r_val[1] elif '**' in secondary.short_name: data.double_two -= r_val[1] new_ratings.double_two += r_val[1] if r_val[0] == 0: retries += 1 # Double*** retries = 0 res_chances = data.double_three while res_chances > 0: if res_chances < 1 or retries > 0: if data.double_pull > 0: data.double_pull += res_chances elif data.double_two > 0: data.double_two += res_chances elif data.single_two > 0: data.single_two += res_chances elif data.single_center > 0: data.single_center += res_chances elif data.single_one > 0: data.single_one += res_chances break ch = get_chances(res_chances) if data.single_two > max(1 - ch, 0): secondary = PLAY_RESULTS['si**'] elif data.flyout_lf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-lf'] elif data.flyout_rf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-rf'] elif data.double_pull > max(1 - ch, 0): secondary = PLAY_RESULTS[f'do-{pull_of}'] elif data.double_two > max(1 - ch, 0): secondary = PLAY_RESULTS['do**'] else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['do***'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.double_three += r_val[0] if r_val[1] > 0: if 'DO (' in secondary.short_name: data.double_pull -= r_val[1] new_ratings.double_pull += r_val[1] elif 'lf' in secondary.short_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'rf' in secondary.short_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif 'SI' in secondary.short_name: data.single_two -= r_val[1] new_ratings.single_two += r_val[1] elif '**' in secondary.short_name: data.double_two -= r_val[1] new_ratings.double_two += r_val[1] if r_val[0] == 0: retries += 1 # Double pull-side retries = 0 res_chances = data.double_pull while res_chances > 0: if res_chances < 1 or retries > 0: if data.double_two > 0: data.double_two += res_chances elif data.single_two > 0: data.single_two += res_chances elif data.single_center > 0: data.single_center += res_chances elif data.single_one > 0: data.single_one += res_chances break ch = get_chances(res_chances) if data.flyout_lf_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'fly (lf) B', short_name=f'fly B') elif data.flyout_rf_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'fly (rf) B', short_name=f'fly b') elif data.single_one > max(1 - ch, 0): secondary = PLAY_RESULTS['si*'] elif data.single_two > max(1 - ch, 0): secondary = PLAY_RESULTS['si**'] else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS[f'do-{pull_of}'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.double_pull += r_val[0] if r_val[1] > 0: if 'lf' in secondary.full_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'rf' in secondary.full_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif '***' in secondary.short_name: data.double_three -= r_val[1] new_ratings.double_three += r_val[1] elif 'SI' in secondary.short_name: data.single_two -= r_val[1] new_ratings.single_two += r_val[1] elif '**' in secondary.short_name: data.double_two -= r_val[1] new_ratings.double_two += r_val[1] if r_val[0] == 0: retries += 1 # Double** retries = 0 res_chances = data.double_two while res_chances > 0: if res_chances < 1 or retries > 0: if data.single_two > 0: data.single_two += res_chances elif data.single_center > 0: data.single_center += res_chances elif data.single_one > 0: data.single_one += res_chances elif data.walk > 0: data.walk += res_chances break ch = get_chances(res_chances) if data.single_two > max(1 - ch, 0): secondary = PLAY_RESULTS['si**'] elif data.single_center > max(1 - ch, 0): secondary = PLAY_RESULTS['si-cf'] elif data.flyout_lf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-lf'] elif data.flyout_rf_b > max(1 - ch, 0): secondary = PLAY_RESULTS['fly-rf'] else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['do**'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.double_two += r_val[0] if r_val[1] > 0: if 'lf' in secondary.full_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'rf' in secondary.full_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif 'SI' in secondary.short_name: data.single_two -= r_val[1] new_ratings.single_two += r_val[1] if r_val[0] == 0: retries += 1 # Single** retries = 0 res_chances = data.single_two while res_chances > 0: if res_chances < 1 or retries > 0: if data.single_center > 0: data.single_center += res_chances elif data.single_one > 0: data.single_one += res_chances elif data.walk > 0: data.walk += res_chances break ch = get_chances(res_chances) if data.groundout_a > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) A', short_name=f'gb ({pref_mif}) A') elif data.groundout_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) B', short_name=f'gb ({pref_mif}) B') elif data.groundout_c > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) C', short_name=f'gb ({pref_mif}) C') elif data.lineout > max(1 - ch, 0): secondary = PlayResult(full_name=f'lo ({pref_mif})', short_name=f'lo ({pref_mif})') else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['si**'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.single_two += r_val[0] if r_val[1] > 0: if 'C' in secondary.short_name: data.groundout_c -= r_val[1] new_ratings.groundout_c += r_val[1] elif 'B' in secondary.short_name: data.groundout_b -= r_val[1] new_ratings.groundout_b += r_val[1] elif 'A' in secondary.short_name: data.groundout_a -= r_val[1] new_ratings.groundout_a += r_val[1] elif 'lo' in secondary.short_name: data.lineout -= r_val[1] new_ratings.lineout += r_val[1] if r_val[0] == 0: retries += 1 # Single (cf) retries = 0 res_chances = data.single_center while res_chances > 0: if res_chances < 1 or retries > 0: if data.single_one > 0: data.single_one += res_chances elif data.walk > 0: data.walk += res_chances break ch = get_chances(res_chances) if data.flyout_bq > max(1 - ch, 0): secondary = PlayResult(full_name=f'fly B?', short_name=f'fly B?') elif data.flyout_lf_b > max(1 - ch, 0) and data.flyout_lf_b > data.flyout_rf_b: secondary = PlayResult(full_name=f'fly (LF) B', short_name=f'fly B') elif data.flyout_rf_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'fly (RF) B', short_name=f'fly B') elif data.flyout_lf_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'fly (LF) B', short_name=f'fly B') elif data.lineout > max(1 - ch, 0): secondary = PlayResult(full_name=f'lo ({pref_mif})', short_name=f'lo ({pref_mif})') else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['si-cf'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.single_center += r_val[0] if r_val[1] > 0: if '?' in secondary.short_name: data.flyout_bq -= r_val[1] new_ratings.flyout_bq += r_val[1] elif 'LF' in secondary.full_name: data.flyout_lf_b -= r_val[1] new_ratings.flyout_lf_b += r_val[1] elif 'RF' in secondary.full_name: data.flyout_rf_b -= r_val[1] new_ratings.flyout_rf_b += r_val[1] elif 'lo' in secondary.short_name: data.lineout -= r_val[1] new_ratings.lineout += r_val[1] if r_val[0] == 0: retries += 1 # Single* retries = 0 res_chances = data.single_one while res_chances > 0: if res_chances < 1 or retries > 0: if data.walk > 0: data.walk += res_chances break ch = get_chances(res_chances) if data.groundout_c > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) C', short_name=f'gb ({pref_mif}) C') elif data.groundout_b > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) B', short_name=f'gb ({pref_mif}) B') elif data.groundout_a > max(1 - ch, 0): secondary = PlayResult(full_name=f'gb ({pref_mif}) A', short_name=f'gb ({pref_mif}) A') elif data.lineout > max(1 - ch, 0): secondary = PlayResult(full_name=f'lo ({pref_mif})', short_name=f'lo ({pref_mif})') else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['si*'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.single_one += r_val[0] if r_val[1] > 0: if 'C' in secondary.short_name: data.groundout_c -= r_val[1] new_ratings.groundout_c += r_val[1] elif 'B' in secondary.short_name: data.groundout_b -= r_val[1] new_ratings.groundout_b += r_val[1] elif 'A' in secondary.short_name: data.groundout_a -= r_val[1] new_ratings.groundout_a += r_val[1] elif 'lo' in secondary.short_name: data.lineout -= r_val[1] new_ratings.lineout += r_val[1] if r_val[0] == 0: retries += 1 # Walk retries = 0 res_chances = data.walk while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) if data.strikeout > max(1 - ch, 0): secondary = PlayResult(full_name=f'strikeout', short_name=f'so') else: secondary = None r_val = assign_bchances(card, PLAY_RESULTS['walk'], ch, secondary) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.walk += r_val[0] if r_val[1] > 0: data.strikeout -= r_val[1] new_ratings.strikeout += r_val[1] if r_val[0] == 0: retries += 1 # BP Single retries = 0 res_chances = data.bp_single while res_chances > 0: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances(card, PLAY_RESULTS['bp-si'], ch) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') res_chances -= r_val[0] new_ratings.bp_single += r_val[0] if r_val[0] == 0: retries += 1 # Special lomax result r_val = assign_bchances( card, PlayResult(full_name=f'lo ({pref_mif}) max', short_name=f'lo ({pref_mif}) max'), Decimal(1)) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') data.lineout -= r_val[0] new_ratings.lineout += r_val[0] # Popout retries = 0 res_chances = data.popout while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) this_if = '2b' if pref_mif == 'ss' else 'ss' r_val = assign_bchances( card, PlayResult(full_name=f'popout ({this_if})', short_name=f'popout ({this_if})'), Decimal(math.floor(ch)) ) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.lineout += res_chances break else: res_chances -= r_val[0] new_ratings.popout += r_val[0] # Flyout A retries = 0 res_chances = data.flyout_a while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'fly (cf) A', short_name=f'fly (cf) A'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.strikeout += res_chances if data.strikeout > 2 else 0 break else: res_chances -= r_val[0] new_ratings.flyout_a += r_val[0] # Flyout LF B retries = 0 res_chances = data.flyout_lf_b while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'fly (lf) B', short_name=f'fly (lf) B'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.strikeout += res_chances if data.strikeout > 2 else 0 break else: res_chances -= r_val[0] new_ratings.flyout_lf_b += r_val[0] # Flyout RF B retries = 0 res_chances = data.flyout_rf_b while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'fly (rf) B', short_name=f'fly (rf) B'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.strikeout += res_chances if data.strikeout > 2 else 0 break else: res_chances -= r_val[0] new_ratings.flyout_rf_b += r_val[0] # Groundout A count_gb = 0 def get_gb_if(): if count_gb % 4 == 1: return pref_mif elif count_gb % 4 == 2: return '2b' if pref_mif == 'ss' else 'ss' elif count_gb % 4 == 3: return '1b' if pref_mif == '2b' else 'p' else: return '3b' if pref_mif == 'ss' else 'p' retries = 0 res_chances = data.groundout_a while res_chances >= 1: if res_chances < 1 or retries > 0: break count_gb += 1 this_if = get_gb_if() ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'gb ({this_if}) A', short_name=f'gb ({this_if}) A'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.groundout_b += res_chances break else: res_chances -= r_val[0] new_ratings.groundout_a += r_val[0] # Groundout B retries = 0 res_chances = data.groundout_b while res_chances >= 1: if res_chances < 1 or retries > 0: break count_gb += 1 this_if = get_gb_if() ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'gb ({this_if}) B', short_name=f'gb ({this_if}) B'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.groundout_c += res_chances break else: res_chances -= r_val[0] new_ratings.groundout_b += r_val[0] # Groundout C retries = 0 res_chances = data.groundout_c while res_chances >= 1: if res_chances < 1 or retries > 0: break count_gb += 1 this_if = get_gb_if() ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'gb ({this_if}) C', short_name=f'gb ({this_if}) C'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: data.strikeout += res_chances break else: res_chances -= r_val[0] new_ratings.groundout_c += r_val[0] # Lineout retries = 0 res_chances = data.lineout while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) this_if = '3b' if pref_mif == 'ss' else '1b' r_val = assign_bchances( card, PlayResult(full_name=f'lineout ({this_if})', short_name=f'lineout ({this_if})'), Decimal(math.floor(ch)) ) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: break else: res_chances -= r_val[0] new_ratings.lineout += r_val[0] # Strikeout retries = 0 res_chances = data.strikeout while res_chances >= 1: if res_chances < 1 or retries > 0: break ch = get_chances(res_chances) r_val = assign_bchances( card, PlayResult(full_name=f'strikeout', short_name=f'strikeout'), Decimal(math.floor(ch))) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if r_val[0] == 0: break else: res_chances -= r_val[0] new_ratings.strikeout += r_val[0] # Filler loop — fill any remaining empty card slots plays = sorted( [(data.strikeout, 'so'), (data.lineout, 'lo'), (data.groundout_c, 'gb'), (data.popout, 'po')], key=lambda z: z[0], reverse=True ) count_filler = -1 while not card.is_complete(): count_filler += 1 this_play = plays[count_filler % 4] if this_play[1] == 'so': play_res = PlayResult(full_name=f'strikeout', short_name=f'strikeout') elif this_play[1] == 'lo': this_if = '3b' if pref_mif == 'ss' else '1b' play_res = PlayResult(full_name=f'lineout ({this_if})', short_name=f'lineout ({this_if})') elif this_play[1] == 'gb': count_gb += 1 this_if = get_gb_if() play_res = PlayResult(full_name=f'gb ({this_if}) C', short_name=f'gb ({this_if}) C') else: play_res = PlayResult(full_name=f'popout (c)', short_name=f'popout (c)') logger.debug(f'Send Card Fill\n{play_res}') r_raw = card.card_fill(play_res) r_val = (float(r_raw[0]), float(r_raw[1])) logger.debug(f'Returned batting chances: {r_val[0]} / {r_val[1]}\n') if this_play[1] == 'so': new_ratings.strikeout += r_val[0] elif this_play[1] == 'lo': new_ratings.lineout += r_val[0] elif this_play[1] == 'gb': new_ratings.groundout_c += r_val[0] else: new_ratings.popout += r_val[0] new_ratings.calculate_rate_stats() return vl, vr