1016 lines
41 KiB
Python
1016 lines
41 KiB
Python
import json, random, os, math, jsonpickle, weather, archetypes
|
||
import database as db
|
||
from league_storage import get_mods, get_team_mods
|
||
from gametext import base_string, appearance_outcomes, game_strings_base
|
||
|
||
data_dir = "data"
|
||
games_config_file = os.path.join(data_dir, "games_config.json")
|
||
|
||
def config():
|
||
if not os.path.exists(os.path.dirname(games_config_file)):
|
||
os.makedirs(os.path.dirname(games_config_file))
|
||
if not os.path.exists(games_config_file):
|
||
#generate default config
|
||
config_dic = {
|
||
"default_length" : 3,
|
||
"stlat_weights" : {
|
||
"batting_stars" : 1, #batting
|
||
"pitching_stars" : 0.8, #pitching
|
||
"baserunning_stars" : 1, #baserunning
|
||
"defense_stars" : 1 #defense
|
||
},
|
||
"stolen_base_chance_mod" : 1,
|
||
"stolen_base_success_mod" : 1
|
||
}
|
||
with open(games_config_file, "w") as config_file:
|
||
json.dump(config_dic, config_file, indent=4)
|
||
return config_dic
|
||
else:
|
||
with open(games_config_file) as config_file:
|
||
return json.load(config_file)
|
||
|
||
|
||
class player(object):
|
||
def __init__(self, json_string):
|
||
self.stlats = json.loads(json_string)
|
||
self.id = self.stlats["id"]
|
||
self.name = self.stlats["name"]
|
||
self.game_stats = {
|
||
"outs_pitched" : 0,
|
||
"walks_allowed" : 0,
|
||
"hits_allowed" : 0,
|
||
"strikeouts_given" : 0,
|
||
"runs_allowed" : 0,
|
||
"plate_appearances" : 0,
|
||
"walks_taken" : 0,
|
||
"sacrifices" : 0,
|
||
"hits" : 0,
|
||
"home_runs" : 0,
|
||
"total_bases" : 0,
|
||
"rbis" : 0,
|
||
"strikeouts_taken" : 0
|
||
}
|
||
self.stat_name = self.name
|
||
if self.name == "Tim Locastro":
|
||
self.randomize_stars()
|
||
|
||
def star_string(self, key):
|
||
str_out = ""
|
||
starstring = str(self.stlats[key])
|
||
if ".5" in starstring:
|
||
starnum = int(starstring[0])
|
||
addhalf = True
|
||
else:
|
||
starnum = int(starstring[0])
|
||
addhalf = False
|
||
str_out += "⭐" * starnum
|
||
if addhalf:
|
||
str_out += "✨"
|
||
return str_out
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
def randomize_stars(self):
|
||
for key in ["batting_stars", "pitching_stars", "baserunning_stars", "defense_stars"]:
|
||
#random star value between 0 and 6.5
|
||
stars = random.randint(0,6)
|
||
half_star = random.random() < 0.5 #half star addition
|
||
if half_star:
|
||
stars = half_star + 0.5
|
||
self.stlats[key] = stars
|
||
|
||
def apply_mods(self, mod_dic):
|
||
for stat in iter(mod_dic.keys()):
|
||
self.stlats[stat] = self.stlats[stat] + mod_dic[stat]
|
||
|
||
|
||
class team(object):
|
||
def __init__(self):
|
||
self.name = None
|
||
self.lineup = []
|
||
self.lineup_position = 0
|
||
self.rotation = []
|
||
self.archetypes = {}
|
||
self.pitcher = None
|
||
self.score = 0
|
||
self.slogan = None
|
||
|
||
def find_player(self, name):
|
||
for index in range(0,len(self.lineup)):
|
||
if self.lineup[index].name.replace(" ", " ") == name:
|
||
return (self.lineup[index], index, self.lineup)
|
||
for index in range(0,len(self.rotation)):
|
||
if self.rotation[index].name.replace(" ", " ") == name:
|
||
return (self.rotation[index], index, self.rotation)
|
||
else:
|
||
return (None, None, None)
|
||
|
||
def find_player_spec(self, name, roster):
|
||
for s_index in range(0,len(roster)):
|
||
if roster[s_index].name.replace(" ", " ") == name:
|
||
return (roster[s_index], s_index)
|
||
|
||
def average_stars(self):
|
||
total_stars = 0
|
||
for _player in self.lineup:
|
||
total_stars += _player.stlats["batting_stars"]
|
||
for _player in self.rotation:
|
||
total_stars += _player.stlats["pitching_stars"]
|
||
return total_stars/(len(self.lineup) + len(self.rotation))
|
||
|
||
def swap_player(self, name):
|
||
this_player, index, roster = self.find_player(name)
|
||
if this_player is not None and len(roster) > 1:
|
||
if roster == self.lineup:
|
||
if self.add_pitcher(this_player):
|
||
roster.pop(index)
|
||
return True
|
||
else:
|
||
if self.add_lineup(this_player)[0]:
|
||
self.rotation.pop(index)
|
||
return True
|
||
return False
|
||
|
||
def delete_player(self, name):
|
||
this_player, index, roster = self.find_player(name)
|
||
if this_player is not None and len(roster) > 1:
|
||
roster.pop(index)
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def slide_player(self, name, new_spot):
|
||
this_player, index, roster = self.find_player(name)
|
||
if this_player is not None and new_spot <= len(roster):
|
||
roster.pop(index)
|
||
roster.insert(new_spot-1, this_player)
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def slide_player_spec(self, this_player_name, new_spot, roster):
|
||
index = None
|
||
for s_index in range(0,len(roster)):
|
||
if roster[s_index].name == this_player_name:
|
||
index = s_index
|
||
this_player = roster[s_index]
|
||
if index is None:
|
||
return False
|
||
elif new_spot <= len(roster):
|
||
roster.pop(index)
|
||
roster.insert(new_spot-1, this_player)
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def add_lineup(self, new_player):
|
||
if len(self.lineup) < 20:
|
||
self.lineup.append(new_player)
|
||
return (True,)
|
||
else:
|
||
return (False, "20 players in the lineup, maximum. We're being really generous here.")
|
||
|
||
def add_pitcher(self, new_player):
|
||
if len(self.rotation) < 8:
|
||
self.rotation.append(new_player)
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def set_pitcher(self, rotation_slot = None, use_lineup = False):
|
||
temp_rotation = self.rotation.copy()
|
||
if use_lineup:
|
||
for batter in self.lineup:
|
||
temp_rotation.append(batter)
|
||
if rotation_slot is None:
|
||
self.pitcher = random.choice(temp_rotation)
|
||
else:
|
||
self.pitcher = temp_rotation[(rotation_slot-1) % len(temp_rotation)]
|
||
|
||
def is_ready(self):
|
||
try:
|
||
return (len(self.lineup) >= 1 and len(self.rotation) > 0)
|
||
except AttributeError:
|
||
self.rotation = [self.pitcher]
|
||
self.pitcher = None
|
||
return (len(self.lineup) >= 1 and len(self.rotation) > 0)
|
||
|
||
def apply_team_mods(self, league_name):
|
||
mod_dic = get_team_mods(league_name, self.name)
|
||
if mod_dic != {} and mod_dic != None:
|
||
for player_name in iter(mod_dic.keys()):
|
||
this_player = self.find_player(player_name)[0]
|
||
if this_player is not None:
|
||
this_player.apply_mods(mod_dic[player_name])
|
||
|
||
def prepare_for_save(self):
|
||
self.lineup_position = 0
|
||
self.score = 0
|
||
if self.pitcher is not None and self.pitcher not in self.rotation:
|
||
self.rotation.append(self.pitcher)
|
||
self.pitcher = None
|
||
for this_player in self.lineup:
|
||
for stat in this_player.game_stats.keys():
|
||
this_player.game_stats[stat] = 0
|
||
for this_player in self.rotation:
|
||
for stat in this_player.game_stats.keys():
|
||
this_player.game_stats[stat] = 0
|
||
return self
|
||
|
||
def finalize(self):
|
||
if self.is_ready():
|
||
if self.pitcher == None:
|
||
self.set_pitcher()
|
||
while len(self.lineup) <= 4:
|
||
self.lineup.append(random.choice(self.lineup))
|
||
return self
|
||
else:
|
||
return False
|
||
|
||
|
||
class game(object):
|
||
|
||
def __init__(self, team1, team2, length=None):
|
||
self.over = False
|
||
self.random_weather_flag = False
|
||
self.teams = {"away" : team1, "home" : team2}
|
||
|
||
self.offense_archetypes = {}
|
||
self.defense_archetypes = {}
|
||
self.inning = 1
|
||
self.outs = 0
|
||
self.top_of_inning = True
|
||
self.last_update = ({},0) #this is a ({outcome}, runs) tuple
|
||
self.play_has_begun = False
|
||
self.owner = None
|
||
self.victory_lap = False
|
||
if length is not None:
|
||
self.max_innings = length
|
||
else:
|
||
self.max_innings = config()["default_length"]
|
||
self.bases = {1 : None, 2 : None, 3 : None}
|
||
self.weather = weather.Weather(self)
|
||
self.voice = None
|
||
self.current_batter = None
|
||
|
||
try:
|
||
self.archetypes = {team1.name : team1.archetypes, team2.name : team2.archetypes}
|
||
for this_team in [team1, team2]:
|
||
for this_player in this_team.lineup + this_team.rotation:
|
||
if this_player.name in this_team.archetypes.keys():
|
||
this_team.archetypes[this_player.name].modify_player_stats(this_player)
|
||
except:
|
||
pass
|
||
|
||
def occupied_bases(self):
|
||
occ_dic = {}
|
||
for base in self.bases.keys():
|
||
if self.bases[base] is not None:
|
||
occ_dic[base] = self.bases[base]
|
||
return occ_dic
|
||
|
||
def choose_next_batter(self):
|
||
if self.top_of_inning:
|
||
bat_team = self.teams["away"]
|
||
else:
|
||
bat_team = self.teams["home"]
|
||
|
||
self.current_batter = bat_team.lineup[bat_team.lineup_position % len(bat_team.lineup)]
|
||
self.weather.on_choose_next_batter(self)
|
||
|
||
def get_batter(self):
|
||
if self.current_batter == None:
|
||
self.choose_next_batter()
|
||
return self.current_batter
|
||
|
||
def get_pitcher(self):
|
||
if self.top_of_inning:
|
||
return self.teams["home"].pitcher
|
||
else:
|
||
return self.teams["away"].pitcher
|
||
|
||
def at_bat(self):
|
||
outcome = {}
|
||
pitcher = self.get_pitcher()
|
||
batter = self.get_batter()
|
||
|
||
if self.top_of_inning:
|
||
defender_list = self.teams["home"].lineup.copy()
|
||
else:
|
||
defender_list = self.teams["away"].lineup.copy()
|
||
|
||
defender_list.append(pitcher)
|
||
defender = random.choice(defender_list) #make pitchers field
|
||
|
||
outcome["batter"] = batter
|
||
outcome["pitcher"] = pitcher
|
||
outcome["defender"] = ""
|
||
|
||
if pitcher.name in self.defense_archetypes.keys():
|
||
outcome["pitcher_archetype"] = self.defense_archetypes[pitcher.name]
|
||
else:
|
||
outcome["pitcher_archetype"] = archetypes.Archetype
|
||
|
||
if batter.name in self.offense_archetypes.keys():
|
||
outcome["batter_archetype"] = self.offense_archetypes[batter.name]
|
||
else:
|
||
outcome["batter_archetype"] = archetypes.Archetype
|
||
|
||
if defender.name in self.defense_archetypes.keys():
|
||
outcome["defender_archetype"] = self.defense_archetypes[defender.name]
|
||
else:
|
||
outcome["defender_archetype"] = archetypes.Archetype
|
||
|
||
player_rolls = {}
|
||
player_rolls["bat_stat"] = random_star_gen("batting_stars", batter)
|
||
player_rolls["pitch_stat"] = random_star_gen("pitching_stars", pitcher)
|
||
|
||
self.weather.modify_atbat_stats(player_rolls)
|
||
|
||
roll = {}
|
||
roll["pb_system_stat"] = (random.gauss(1*math.erf((player_rolls["bat_stat"] - player_rolls["pitch_stat"])*1.5)-1.8,2.2))
|
||
roll["hitnum"] = random.gauss(2*math.erf(player_rolls["bat_stat"]/4)-1,3)
|
||
|
||
self.weather.modify_atbat_roll(outcome, roll, defender)
|
||
|
||
outcome["batter_archetype"].modify_bat_rolls(outcome, roll)
|
||
outcome["pitcher_archetype"].modify_bat_rolls(outcome, roll)
|
||
|
||
|
||
if roll["pb_system_stat"] <= 0:
|
||
outcome["ishit"] = False
|
||
fc_flag = False
|
||
if roll["hitnum"] < -1.5:
|
||
outcome["outcome"] = random.choice([appearance_outcomes.strikeoutlooking, appearance_outcomes.strikeoutswinging])
|
||
elif roll["hitnum"] < 1:
|
||
outcome["outcome"] = appearance_outcomes.groundout
|
||
outcome["defender"] = defender
|
||
elif roll["hitnum"] < 4:
|
||
outcome["outcome"] = appearance_outcomes.flyout
|
||
outcome["defender"] = defender
|
||
else:
|
||
outcome["outcome"] = appearance_outcomes.walk
|
||
|
||
outcome["batter_archetype"].modify_out_type(outcome)
|
||
outcome["pitcher_archetype"].modify_out_type(outcome)
|
||
|
||
if self.bases[1] is not None and roll["hitnum"] < -2 and (self.outs != 2 or self.weather.out_extension):
|
||
outcome["outcome"] = appearance_outcomes.doubleplay
|
||
outcome["defender"] = ""
|
||
|
||
#for base in self.bases.values():
|
||
#if base is not None:
|
||
#fc_flag = True
|
||
|
||
runners = [(0,self.get_batter())]
|
||
for base in range(1,4):
|
||
if self.bases[base] == None:
|
||
break
|
||
runners.append((base, self.bases[base]))
|
||
outcome["runners"] = runners #list of consecutive baserunners: (base number, player object)
|
||
|
||
if self.outs < 2 and len(runners) > 1: #fielder's choice replaces not great groundouts if any forceouts are present
|
||
def_stat = random_star_gen("defense_stars", defender)
|
||
if -1.5 <= roll["hitnum"] and roll["hitnum"] < -0.5: #poorly hit groundouts
|
||
outcome["outcome"] = appearance_outcomes.fielderschoice
|
||
outcome["defender"] = ""
|
||
|
||
if outcome["outcome"] not in [appearance_outcomes.strikeoutlooking, appearance_outcomes.strikeoutswinging] and 2.5 <= roll["hitnum"] and self.outs < 2: #well hit flyouts can lead to sacrifice flies/advanced runners
|
||
if self.bases[2] is not None or self.bases[3] is not None:
|
||
outcome["advance"] = True
|
||
else:
|
||
outcome["ishit"] = True
|
||
if roll["hitnum"] < 1:
|
||
outcome["outcome"] = appearance_outcomes.single
|
||
elif roll["hitnum"] < 2.85 or "error" in outcome.keys():
|
||
outcome["outcome"] = appearance_outcomes.double
|
||
elif roll["hitnum"] < 3.1:
|
||
outcome["outcome"] = appearance_outcomes.triple
|
||
else:
|
||
if self.bases[1] is not None and self.bases[2] is not None and self.bases[3] is not None:
|
||
outcome["outcome"] = appearance_outcomes.grandslam
|
||
else:
|
||
outcome["outcome"] = appearance_outcomes.homerun
|
||
|
||
outcome["batter_archetype"].modify_hit_type(outcome)
|
||
outcome["pitcher_archetype"].modify_hit_type(outcome)
|
||
|
||
return outcome
|
||
|
||
def thievery_attempts(self): #returns either false or "at-bat" outcome
|
||
thieves = []
|
||
attempts = []
|
||
outcome = {}
|
||
for base in self.bases.keys():
|
||
if self.bases[base] is not None and base != 3: #no stealing home in simsim, sorry stu
|
||
if self.bases[base+1] is None: #if there's somewhere to go
|
||
thieves.append((self.bases[base], base))
|
||
for baserunner, start_base in thieves:
|
||
stats = {
|
||
"run_stars": random_star_gen("baserunning_stars", baserunner)*config()["stolen_base_chance_mod"],
|
||
"def_stars": random_star_gen("defense_stars", self.get_pitcher())
|
||
}
|
||
|
||
self.weather.modify_steal_stats(stats)
|
||
|
||
if self.get_pitcher().name in self.defense_archetypes.keys():
|
||
outcome["pitcher_archetype"] = self.defense_archetypes[self.get_pitcher().name]
|
||
else:
|
||
outcome["pitcher_archetype"] = archetypes.Archetype
|
||
|
||
if baserunner.name in self.offense_archetypes.keys():
|
||
outcome["baserunner_archetype"] = self.offense_archetypes[baserunner.name]
|
||
else:
|
||
outcome["baserunner_archetype"] = archetypes.Archetype
|
||
|
||
outcome["pitcher_archetype"].hold_runner(outcome, stats)
|
||
|
||
if stats["run_stars"] >= (stats["def_stars"] - 1.5): #if baserunner isn't worse than pitcher
|
||
roll = random.random()
|
||
outcome["baserunner_archetype"].steal_check(outcome, roll)
|
||
if roll >= (-(((stats["run_stars"]+1)/14)**2)+1): #plug it into desmos or something, you'll see
|
||
attempts.append((baserunner, start_base))
|
||
|
||
if len(attempts) == 0:
|
||
return False
|
||
else:
|
||
return (self.steals_check(attempts, outcome), 0) #effectively an at-bat outcome with no score
|
||
|
||
def steals_check(self, attempts, outcome):
|
||
if self.top_of_inning:
|
||
defense_team = self.teams["home"]
|
||
else:
|
||
defense_team = self.teams["away"]
|
||
|
||
for baserunner, start_base in attempts:
|
||
defender = random.choice(defense_team.lineup) #excludes pitcher
|
||
run_stat = random_star_gen("baserunning_stars", baserunner)
|
||
def_stat = random_star_gen("defense_stars", defender)
|
||
run_roll = random.gauss(2*math.erf((run_stat-def_stat)/4)-1,3)*config()["stolen_base_success_mod"]
|
||
|
||
outcome["baserunner_archetype"].modify_steal_attempt(outcome, run_roll)
|
||
outcome["pitcher_archetype"].modify_steal_attempt(outcome, run_roll)
|
||
|
||
if defender.name in self.defense_archetypes.keys():
|
||
self.defense_archetypes[defender.name].modify_steal_attempt(outcome, run_roll)
|
||
|
||
if start_base == 2:
|
||
run_roll = run_roll * .9 #stealing third is harder
|
||
if run_roll < 1:
|
||
successful = False
|
||
self.get_pitcher().game_stats["outs_pitched"] += 1
|
||
self.outs += 1
|
||
else:
|
||
successful = True
|
||
self.bases[start_base+1] = baserunner
|
||
self.bases[start_base] = None
|
||
|
||
self.voice.stealing(outcome, baserunner.name, base_string(start_base+1), defender.name, successful)
|
||
|
||
self.weather.steal_post_activate(self, outcome)
|
||
|
||
if self.outs >= 3:
|
||
self.flip_inning()
|
||
|
||
return outcome
|
||
|
||
def baserunner_check(self, defender, outcome):
|
||
def_stat = random_star_gen("defense_stars", defender)
|
||
if outcome["outcome"] == appearance_outcomes.homerun or outcome["outcome"] == appearance_outcomes.grandslam:
|
||
runs = 1
|
||
for base in self.bases.values():
|
||
if base is not None:
|
||
runs += 1
|
||
self.bases = {1 : None, 2 : None, 3 : None}
|
||
if "veil" in outcome.keys():
|
||
if runs < 4:
|
||
self.bases[runs] = self.get_batter()
|
||
else:
|
||
runs += 1
|
||
return runs
|
||
|
||
elif "advance" in outcome.keys():
|
||
runs = 0
|
||
if self.bases[3] is not None:
|
||
outcome["outcome"] = appearance_outcomes.sacrifice
|
||
self.get_batter().game_stats["sacrifices"] += 1
|
||
self.bases[3] = None
|
||
runs = 1
|
||
if self.bases[2] is not None:
|
||
run_roll = random.gauss(2*math.erf((random_star_gen("baserunning_stars", self.bases[2])-def_stat)/4)-1,3)
|
||
|
||
if self.bases[2].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[2].name].modify_tag_up_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_tag_up_roll(outcome, run_roll)
|
||
|
||
if run_roll > 2:
|
||
self.bases[3] = self.bases[2]
|
||
self.bases[2] = None
|
||
return runs
|
||
|
||
elif outcome["outcome"] == appearance_outcomes.fielderschoice:
|
||
furthest_base, runner = outcome["runners"].pop() #get furthest baserunner
|
||
self.bases[furthest_base] = None
|
||
outcome["fc_out"] = (runner.name, base_string(furthest_base+1)) #runner thrown out
|
||
outcome["runner"] = runner.name
|
||
outcome["base"] = furthest_base+1
|
||
for index in range(0,len(outcome["runners"])):
|
||
base, this_runner = outcome["runners"].pop()
|
||
self.bases[base+1] = this_runner #includes batter, at base 0
|
||
if self.bases[3] is not None and furthest_base == 1: #fielders' choice with runners on the corners
|
||
self.bases[3] = None
|
||
return 1
|
||
return 0
|
||
|
||
elif outcome["outcome"] == appearance_outcomes.groundout or outcome["outcome"] == appearance_outcomes.doubleplay:
|
||
runs = 0
|
||
if self.bases[3] is not None:
|
||
runs += 1
|
||
self.bases[3] = None
|
||
if self.bases[2] is not None:
|
||
run_roll = random.gauss(2*math.erf((random_star_gen("baserunning_stars", self.bases[2])-def_stat)/4)-1,3)
|
||
|
||
if self.bases[2].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[2].name].modify_advance_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_advance_roll(outcome, run_roll)
|
||
|
||
if run_roll > 1.5 or outcome["outcome"] == appearance_outcomes.doubleplay: #double play gives them time to run, guaranteed
|
||
self.bases[3] = self.bases[2]
|
||
self.bases[2] = None
|
||
if self.bases[1] is not None: #double plays set this to None before this call
|
||
run_roll = random.gauss(2*math.erf((random_star_gen("baserunning_stars", self.bases[1])-def_stat)/4)-1,3)
|
||
|
||
if self.bases[1].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[1].name].modify_advance_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_advance_roll(outcome, run_roll)
|
||
|
||
if run_roll < 2 or self.bases[2] is not None: #if runner can't make it or if baserunner blocking on second, convert to fielder's choice
|
||
outcome["outcome"] == appearance_outcomes.fielderschoice
|
||
runners = [(0,self.get_batter())]
|
||
for base in range(1,4):
|
||
if self.bases[base] == None:
|
||
break
|
||
runners.append((base, self.bases[base]))
|
||
outcome["runners"] = runners #rebuild consecutive runners
|
||
return runs + self.baserunner_check(defender, outcome) #run again as fielder's choice instead
|
||
else:
|
||
self.bases[2] = self.bases[1]
|
||
self.bases[1] = None
|
||
return runs
|
||
|
||
elif outcome["ishit"]:
|
||
runs = 0
|
||
if outcome["outcome"] == appearance_outcomes.single:
|
||
if self.bases[3] is not None:
|
||
runs += 1
|
||
self.bases[3] = None
|
||
if self.bases[2] is not None:
|
||
run_roll = random.gauss(math.erf(random_star_gen("baserunning_stars", self.bases[2])-def_stat)-.5,1.5)
|
||
|
||
if self.bases[2].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[2].name].modify_extra_running_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_extra_running_roll(outcome, run_roll)
|
||
|
||
if run_roll > 0:
|
||
runs += 1
|
||
else:
|
||
self.bases[3] = self.bases[2]
|
||
self.bases[2] = None
|
||
if self.bases[1] is not None:
|
||
if self.bases[3] is None:
|
||
run_roll = random.gauss(math.erf(random_star_gen("baserunning_stars", self.bases[1])-def_stat)-.5,1.5)
|
||
|
||
if self.bases[1].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[1].name].modify_extra_running_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_extra_running_roll(outcome, run_roll)
|
||
|
||
if run_roll > 0.75:
|
||
self.bases[3] = self.bases[1]
|
||
else:
|
||
self.bases[2] = self.bases[1]
|
||
else:
|
||
self.bases[2] = self.bases[1]
|
||
self.bases[1] = None
|
||
|
||
self.bases[1] = self.get_batter()
|
||
return runs
|
||
|
||
elif outcome["outcome"] == appearance_outcomes.double:
|
||
runs = 0
|
||
if self.bases[3] is not None:
|
||
runs += 1
|
||
self.bases[3] = None
|
||
if self.bases[2] is not None:
|
||
runs += 1
|
||
self.bases[2] = None
|
||
if self.bases[1] is not None:
|
||
run_roll = random.gauss(math.erf(random_star_gen("baserunning_stars", self.bases[1])-def_stat)-.5,1.5)
|
||
|
||
if self.bases[1].name in self.offense_archetypes.keys():
|
||
self.offense_archetypes[self.bases[1].name].modify_extra_running_roll(outcome, run_roll)
|
||
outcome["defender_archetype"].modify_extra_running_roll(outcome, run_roll)
|
||
|
||
if run_roll > 1:
|
||
runs += 1
|
||
self.bases[1] = None
|
||
else:
|
||
self.bases[3] = self.bases[1]
|
||
self.bases[1] = None
|
||
self.bases[2] = self.get_batter()
|
||
return runs
|
||
|
||
|
||
elif outcome["outcome"] == appearance_outcomes.triple:
|
||
runs = 0
|
||
for basenum in self.bases.keys():
|
||
if self.bases[basenum] is not None:
|
||
runs += 1
|
||
self.bases[basenum] = None
|
||
self.bases[3] = self.get_batter()
|
||
return runs
|
||
|
||
|
||
def batterup(self):
|
||
scores_to_add = 0
|
||
|
||
if "twopart" not in self.last_update[0]:
|
||
result = self.at_bat()
|
||
|
||
if self.top_of_inning:
|
||
offense_team = self.teams["away"]
|
||
defense_team = self.teams["home"]
|
||
else:
|
||
offense_team = self.teams["home"]
|
||
defense_team = self.teams["away"]
|
||
|
||
self.offense_archetypes = self.archetypes[offense_team.name]
|
||
self.defense_archetypes = self.archetypes[defense_team.name]
|
||
|
||
|
||
defenders = defense_team.lineup.copy()
|
||
defenders.append(defense_team.pitcher)
|
||
defender = random.choice(defenders) #pitcher can field outs now :3
|
||
result["defender"] = defender
|
||
result["defense_team"] = defense_team
|
||
result["offense_team"] = offense_team
|
||
|
||
if "advance" in result.keys() and self.bases[3] is not None:
|
||
result["outcome"] = appearance_outcomes.sacrifice
|
||
result["runner"] = self.bases[3].name
|
||
|
||
text_list = getattr(self.voice, result["outcome"].name)
|
||
voice_index = random.randrange(0, len(text_list))
|
||
result["voiceindex"] = voice_index
|
||
else:
|
||
result = {}
|
||
|
||
result = self.voice.activate(self.last_update[0], result, self)
|
||
|
||
if "twopart" not in result:
|
||
self.weather.activate(self, result) # possibly modify result in-place
|
||
|
||
if "text_only" in result:
|
||
return (result, 0)
|
||
|
||
if "twopart" in result:
|
||
if self.voice.post_format != []:
|
||
format_list = []
|
||
for extra_format in self.voice.post_format:
|
||
try:
|
||
if extra_format == "base":
|
||
format_list.append(base_string(result["base"]))
|
||
elif extra_format == "runner":
|
||
format_list.append(result["runner"])
|
||
except KeyError:
|
||
format_list.append("None")
|
||
self.voice.post_format = []
|
||
result["displaytext"] = result["displaytext"].format(*format_list)
|
||
return (result, 0)
|
||
|
||
if result["ishit"]: #if batter gets a hit:
|
||
self.get_batter().game_stats["hits"] += 1
|
||
self.get_pitcher().game_stats["hits_allowed"] += 1
|
||
|
||
if result["outcome"] == appearance_outcomes.single:
|
||
self.get_batter().game_stats["total_bases"] += 1
|
||
elif result["outcome"] == appearance_outcomes.double:
|
||
self.get_batter().game_stats["total_bases"] += 2
|
||
elif result["outcome"] == appearance_outcomes.triple:
|
||
self.get_batter().game_stats["total_bases"] += 3
|
||
elif result["outcome"] == appearance_outcomes.homerun or result["outcome"] == appearance_outcomes.grandslam:
|
||
self.get_batter().game_stats["total_bases"] += 4
|
||
self.get_batter().game_stats["home_runs"] += 1
|
||
|
||
|
||
|
||
scores_to_add += self.baserunner_check(result["defender"], result)
|
||
|
||
else: #batter did not get a hit
|
||
if result["outcome"] == appearance_outcomes.walk:
|
||
walkers = [(0,self.get_batter())]
|
||
for base in range(1,4):
|
||
if self.bases[base] == None:
|
||
break
|
||
walkers.append((base, self.bases[base]))
|
||
for i in range(0, len(walkers)):
|
||
this_walker = walkers.pop()
|
||
if this_walker[0] == 3:
|
||
self.bases[3] = None
|
||
scores_to_add += 1
|
||
else:
|
||
self.bases[this_walker[0]+1] = this_walker[1] #this moves all consecutive baserunners one forward
|
||
|
||
self.get_batter().game_stats["walks_taken"] += 1
|
||
self.get_pitcher().game_stats["walks_allowed"] += 1
|
||
|
||
elif result["outcome"] == appearance_outcomes.doubleplay:
|
||
self.get_pitcher().game_stats["outs_pitched"] += 2
|
||
self.outs += 2
|
||
self.bases[1] = None
|
||
if self.outs < 3:
|
||
scores_to_add += self.baserunner_check(result["defender"], result)
|
||
self.get_batter().game_stats["rbis"] -= scores_to_add #remove the fake rbi from the player in advance
|
||
|
||
elif result["outcome"] == appearance_outcomes.fielderschoice or result["outcome"] == appearance_outcomes.groundout:
|
||
self.get_pitcher().game_stats["outs_pitched"] += 1
|
||
self.outs += 1
|
||
if self.outs < 3:
|
||
scores_to_add += self.baserunner_check(result["defender"], result)
|
||
|
||
elif "advance" in result.keys() or result["outcome"] == appearance_outcomes.sacrifice:
|
||
self.get_pitcher().game_stats["outs_pitched"] += 1
|
||
self.outs += 1
|
||
if self.outs < 3:
|
||
if self.bases[3] is not None:
|
||
result["runner"] = self.bases[3].name
|
||
self.get_batter().game_stats["sacrifices"] += 1
|
||
scores_to_add += self.baserunner_check(result["defender"], result)
|
||
|
||
elif result["outcome"] == appearance_outcomes.strikeoutlooking or result["outcome"] == appearance_outcomes.strikeoutswinging:
|
||
self.get_pitcher().game_stats["outs_pitched"] += 1
|
||
self.outs += 1
|
||
self.get_batter().game_stats["strikeouts_taken"] += 1
|
||
self.get_pitcher().game_stats["strikeouts_given"] += 1
|
||
|
||
else:
|
||
self.get_pitcher().game_stats["outs_pitched"] += 1
|
||
self.outs += 1
|
||
|
||
self.get_batter().game_stats["plate_appearances"] += 1
|
||
|
||
if self.voice.post_format != []:
|
||
format_list = []
|
||
for extra_format in self.voice.post_format:
|
||
try:
|
||
if extra_format == "base":
|
||
format_list.append(base_string(result["base"]))
|
||
elif extra_format == "runner":
|
||
format_list.append(result["runner"])
|
||
except KeyError:
|
||
format_list.append("None")
|
||
self.voice.post_format = []
|
||
result["displaytext"] = result["displaytext"].format(*format_list)
|
||
|
||
if self.outs < 3:
|
||
result["offense_team"].score += scores_to_add #only add points if inning isn't over
|
||
else:
|
||
scores_to_add = 0
|
||
self.get_batter().game_stats["rbis"] += scores_to_add
|
||
self.get_pitcher().game_stats["runs_allowed"] += scores_to_add
|
||
result["offense_team"].lineup_position += 1 #put next batter up
|
||
self.choose_next_batter()
|
||
|
||
self.weather.post_activate(self, result)
|
||
|
||
if self.outs >= 3:
|
||
self.flip_inning()
|
||
|
||
|
||
|
||
return (result, scores_to_add) #returns ab information and scores
|
||
|
||
def flip_inning(self):
|
||
for base in self.bases.keys():
|
||
self.bases[base] = None
|
||
self.outs = 0
|
||
|
||
self.top_of_inning = not self.top_of_inning
|
||
|
||
if self.random_weather_flag and self.top_of_inning:
|
||
setattr(self, "weather", random.choice(list(weather.safe_weathers().values()))(self))
|
||
|
||
self.weather.on_flip_inning(self)
|
||
|
||
self.choose_next_batter()
|
||
|
||
if self.top_of_inning:
|
||
self.inning += 1
|
||
if self.inning > self.max_innings and self.teams["home"].score != self.teams["away"].score: #game over
|
||
self.over = True
|
||
try: #if something goes wrong with OBL don't erase game
|
||
if self.max_innings >= 9 or self.weather.name in ["Leaf Eddies", "Torrential Downpour"]:
|
||
this_xvi_team = None
|
||
db.save_obl_results(self.teams["home"] if self.teams["home"].score > self.teams["away"].score else self.teams["away"], self.teams["home"] if self.teams["home"].score < self.teams["away"].score else self.teams["away"], xvi_team=this_xvi_team)
|
||
except:
|
||
pass
|
||
|
||
|
||
|
||
def end_of_game_report(self):
|
||
return {
|
||
"away_team" : self.teams["away"],
|
||
"away_pitcher" : self.teams["away"].pitcher,
|
||
"home_team" : self.teams["home"],
|
||
"home_pitcher" : self.teams["home"].pitcher
|
||
}
|
||
|
||
def named_bases(self):
|
||
name_bases = {}
|
||
for base in range(1,4):
|
||
if self.bases[base] is not None:
|
||
name_bases[base] = self.bases[base].name
|
||
else:
|
||
name_bases[base] = None
|
||
|
||
return name_bases
|
||
|
||
|
||
def gamestate_update_full(self):
|
||
self.play_has_begun = True
|
||
attempts = self.thievery_attempts()
|
||
if attempts == False or "twopart" in self.last_update[0]:
|
||
self.last_update = self.batterup()
|
||
else:
|
||
self.last_update = attempts
|
||
return self.gamestate_display_full()
|
||
|
||
def gamestate_display_full(self):
|
||
if not self.over:
|
||
return "Still in progress."
|
||
else:
|
||
return f"""Game over! Final score: **{self.teams['away'].score} - {self.teams['home'].score}**"""
|
||
|
||
def add_stats(self):
|
||
players = self.get_stats()
|
||
db.add_stats(players)
|
||
|
||
def get_stats(self):
|
||
players = []
|
||
for this_player in self.teams["away"].lineup:
|
||
players.append((this_player.stat_name, this_player.game_stats))
|
||
for this_player in self.teams["home"].lineup:
|
||
players.append((this_player.stat_name, this_player.game_stats))
|
||
players.append((self.teams["home"].pitcher.stat_name, self.teams["home"].pitcher.game_stats))
|
||
players.append((self.teams["away"].pitcher.stat_name, self.teams["away"].pitcher.game_stats))
|
||
return players
|
||
|
||
def get_team_specific_stats(self):
|
||
players = {
|
||
self.teams["away"].name : [],
|
||
self.teams["home"].name : []
|
||
}
|
||
for this_player in self.teams["away"].lineup:
|
||
try:
|
||
players[self.teams["away"].name].append((this_player.stat_name, this_player.game_stats))
|
||
except AttributeError:
|
||
players[self.teams["away"].name].append((this_player.name, this_player.game_stats))
|
||
for this_player in self.teams["home"].lineup:
|
||
try:
|
||
players[self.teams["home"].name].append((this_player.stat_name, this_player.game_stats))
|
||
except AttributeError:
|
||
players[self.teams["home"].name].append((this_player.name, this_player.game_stats))
|
||
try:
|
||
players[self.teams["home"].name].append((self.teams["home"].pitcher.stat_name, self.teams["home"].pitcher.game_stats))
|
||
except AttributeError:
|
||
players[self.teams["home"].name].append((self.teams["home"].pitcher.name, self.teams["home"].pitcher.game_stats))
|
||
try:
|
||
players[self.teams["away"].name].append((self.teams["away"].pitcher.stat_name, self.teams["away"].pitcher.game_stats))
|
||
except AttributeError:
|
||
players[self.teams["away"].name].append((self.teams["away"].pitcher.name, self.teams["away"].pitcher.game_stats))
|
||
return players
|
||
|
||
|
||
|
||
def random_star_gen(key, player):
|
||
return random.gauss(config()["stlat_weights"][key] * player.stlats[key],1)
|
||
# innings_pitched
|
||
# walks_allowed
|
||
# strikeouts_given
|
||
# runs_allowed
|
||
# plate_appearances
|
||
# walks
|
||
# hits
|
||
# total_bases
|
||
# rbis
|
||
# walks_taken
|
||
# strikeouts_taken
|
||
|
||
|
||
def get_team(name):
|
||
try:
|
||
team_json = jsonpickle.decode(db.get_team(name)[0], keys=True, classes=team)
|
||
if team_json is not None:
|
||
if team_json.pitcher is not None: #detects old-format teams, adds pitcher
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
for player in team_json.rotation + team_json.lineup:
|
||
if player.name == "Tim Locastro":
|
||
player.randomize_stars()
|
||
if not hasattr(team_json, "archetypes"):
|
||
team_json.archetypes = {}
|
||
return team_json
|
||
return None
|
||
except AttributeError:
|
||
team_json.rotation = []
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
return team_json
|
||
except:
|
||
return None
|
||
|
||
def get_team_and_owner(name):
|
||
try:
|
||
counter, name, team_json_string, timestamp, owner_id = db.get_team(name, owner=True)
|
||
team_json = jsonpickle.decode(team_json_string, keys=True, classes=team)
|
||
if team_json is not None:
|
||
if team_json.pitcher is not None: #detects old-format teams, adds pitcher
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
for player in team_json.rotation + team_json.lineup:
|
||
if player.name == "Tim Locastro":
|
||
player.randomize_stars()
|
||
if not hasattr(team_json, "archetypes"):
|
||
team_json.archetypes = {}
|
||
return (team_json, owner_id)
|
||
return (None, None)
|
||
except AttributeError:
|
||
team_json.rotation = []
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
return (team_json, owner_id)
|
||
except:
|
||
return (None, None)
|
||
|
||
def save_team(this_team, user_id):
|
||
try:
|
||
this_team.prepare_for_save()
|
||
team_json_string = jsonpickle.encode(this_team, keys=True)
|
||
db.save_team(this_team.name, team_json_string, user_id)
|
||
return True
|
||
except:
|
||
return None
|
||
|
||
def update_team(this_team):
|
||
try:
|
||
this_team.prepare_for_save()
|
||
team_json_string = jsonpickle.encode(this_team, keys=True)
|
||
db.update_team(this_team.name, team_json_string)
|
||
return True
|
||
except:
|
||
return None
|
||
|
||
def get_all_teams():
|
||
teams = []
|
||
for team_pickle in db.get_all_teams():
|
||
this_team = jsonpickle.decode(team_pickle[0], keys=True, classes=team)
|
||
teams.append(this_team)
|
||
return teams
|
||
|
||
def search_team(search_term):
|
||
teams = []
|
||
for team_pickle in db.search_teams(search_term):
|
||
team_json = jsonpickle.decode(team_pickle[0], keys=True, classes=team)
|
||
try:
|
||
if team_json.pitcher is not None:
|
||
if len(team_json.rotation) == 0: #detects old-format teams, adds pitcher
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
for player in team_json.rotation + team_json.lineup:
|
||
if player.name == "Tim Locastro":
|
||
player.randomize_stars()
|
||
if not hasattr(team_json, "archetypes"):
|
||
team_json.archetypes = {}
|
||
except AttributeError:
|
||
team_json.rotation = []
|
||
team_json.rotation.append(team_json.pitcher)
|
||
team_json.pitcher = None
|
||
update_team(team_json)
|
||
except:
|
||
return None
|
||
|
||
teams.append(team_json)
|
||
return teams
|
||
|
||
def get_filtered_teams(teams_to_remove):
|
||
teams = []
|
||
for team_pickle in db.get_all_teams():
|
||
this_team = jsonpickle.decode(team_pickle[0], keys=True, classes=team)
|
||
if this_team.name not in teams_to_remove:
|
||
teams.append(this_team)
|
||
return teams |