
Appends "Unicode Variant Selector 1" to the emoji property of weather, so that systems will default to drawing them as images instead of text, when able. Currently the heavy snow emoji is drawn as text in the discord end-of-game report on windows 10, similar to the Moist Talkers' ORB on blaseball.com. This change is untested and superficial. But it's been every so slightly annoying me and I wanted to fix it.
893 lines
36 KiB
Python
893 lines
36 KiB
Python
import json, random, os, math, jsonpickle
|
|
from enum import Enum
|
|
import database as db
|
|
|
|
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)
|
|
|
|
def all_weathers():
|
|
weathers_dic = {
|
|
#"Supernova" : weather("Supernova", "🌟"),
|
|
#"Midnight": weather("Midnight", "🕶"),
|
|
#"Slight Tailwind": weather("Slight Tailwind", "🏌️♀️"),
|
|
"Heavy Snow": weather("Heavy Snow", "❄"),
|
|
"Twilight" : weather("Twilight", "👻"),
|
|
"Thinned Veil" : weather("Thinned Veil", "🌌"),
|
|
"Heat Wave" : weather("Heat Wave", "🌄"),
|
|
"Drizzle" : weather("Drizzle", "🌧")
|
|
}
|
|
return weathers_dic
|
|
|
|
class appearance_outcomes(Enum):
|
|
strikeoutlooking = "strikes out looking."
|
|
strikeoutswinging = "strikes out swinging."
|
|
groundout = "grounds out to"
|
|
flyout = "flies out to"
|
|
fielderschoice = "reaches on fielder's choice. {} is out at {} base." #requires .format(player, base_string)
|
|
doubleplay = "grounds into a double play!"
|
|
sacrifice = "hits a sacrifice fly towards"
|
|
walk = "draws a walk."
|
|
single = "hits a single!"
|
|
double = "hits a double!"
|
|
triple = "hits a triple!"
|
|
homerun = "hits a dinger!"
|
|
grandslam = "hits a grand slam!"
|
|
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
|
|
|
|
class team(object):
|
|
def __init__(self):
|
|
self.name = None
|
|
self.lineup = []
|
|
self.lineup_position = 0
|
|
self.rotation = []
|
|
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 == name:
|
|
return (self.lineup[index], index, self.lineup)
|
|
for index in range(0,len(self.rotation)):
|
|
if self.rotation[index].name == 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 == 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 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.teams = {"away" : team1, "home" : team2}
|
|
self.inning = 1
|
|
self.outs = 0
|
|
self.top_of_inning = True
|
|
self.last_update = ({},0) #this is a ({outcome}, runs) tuple
|
|
self.owner = None
|
|
self.ready = False
|
|
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("Sunny","🌞")
|
|
|
|
def get_batter(self):
|
|
if self.top_of_inning:
|
|
bat_team = self.teams["away"]
|
|
counter = self.weather.counter_away
|
|
else:
|
|
bat_team = self.teams["home"]
|
|
counter = self.weather.counter_home
|
|
|
|
if self.weather.name == "Heavy Snow" and counter == bat_team.lineup_position:
|
|
return bat_team.pitcher
|
|
return bat_team.lineup[bat_team.lineup_position % len(bat_team.lineup)]
|
|
|
|
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["defender"] = ""
|
|
|
|
bat_stat = random_star_gen("batting_stars", batter)
|
|
pitch_stat = random_star_gen("pitching_stars", pitcher)
|
|
if weather.name == "Supernova":
|
|
pitch_stat = pitch_stat * 0.9
|
|
|
|
pb_system_stat = (random.gauss(1*math.erf((bat_stat - pitch_stat)*1.5)-1.8,2.2))
|
|
hitnum = random.gauss(2*math.erf(bat_stat/4)-1,3)
|
|
|
|
if self.weather.name == "Twilight":
|
|
error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1
|
|
error_roll = random.random()
|
|
if error_roll > error_line:
|
|
outcome["error"] = True
|
|
outcome["defender"] = defender
|
|
pb_system_stat = 0.1
|
|
|
|
|
|
if pb_system_stat <= 0:
|
|
outcome["ishit"] = False
|
|
fc_flag = False
|
|
if hitnum < -1.5:
|
|
outcome["text"] = random.choice([appearance_outcomes.strikeoutlooking, appearance_outcomes.strikeoutswinging])
|
|
elif hitnum < 1:
|
|
outcome["text"] = appearance_outcomes.groundout
|
|
outcome["defender"] = defender
|
|
elif hitnum < 4:
|
|
outcome["text"] = appearance_outcomes.flyout
|
|
outcome["defender"] = defender
|
|
else:
|
|
outcome["text"] = appearance_outcomes.walk
|
|
|
|
if self.bases[1] is not None and hitnum < -2 and self.outs != 2:
|
|
outcome["text"] = 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 <= hitnum and hitnum < -0.5: #poorly hit groundouts
|
|
outcome["text"] = appearance_outcomes.fielderschoice
|
|
outcome["defender"] = ""
|
|
|
|
if 2.5 <= 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 hitnum < 1:
|
|
outcome["text"] = appearance_outcomes.single
|
|
elif hitnum < 2.85 or "error" in outcome.keys():
|
|
outcome["text"] = appearance_outcomes.double
|
|
elif hitnum < 3.1:
|
|
outcome["text"] = 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["text"] = appearance_outcomes.grandslam
|
|
else:
|
|
outcome["text"] = appearance_outcomes.homerun
|
|
return outcome
|
|
|
|
def thievery_attempts(self): #returns either false or "at-bat" outcome
|
|
thieves = []
|
|
attempts = []
|
|
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:
|
|
run_stars = random_star_gen("baserunning_stars", baserunner)*config()["stolen_base_chance_mod"]
|
|
if self.weather.name == "Midnight":
|
|
run_stars = run_stars*2
|
|
def_stars = random_star_gen("defense_stars", self.get_pitcher())
|
|
if run_stars >= (def_stars - 1.5): #if baserunner isn't worse than pitcher
|
|
roll = random.random()
|
|
if roll >= (-(((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), 0) #effectively an at-bat outcome with no score
|
|
|
|
def steals_check(self, attempts):
|
|
if self.top_of_inning:
|
|
defense_team = self.teams["home"]
|
|
else:
|
|
defense_team = self.teams["away"]
|
|
|
|
outcome = {}
|
|
outcome["steals"] = []
|
|
|
|
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"]
|
|
if start_base == 2:
|
|
run_roll = run_roll * .9 #stealing third is harder
|
|
if run_roll < 1:
|
|
outcome["steals"].append(f"{baserunner} was caught stealing {base_string(start_base+1)} base by {defender}!")
|
|
self.get_pitcher().game_stats["outs_pitched"] += 1
|
|
self.outs += 1
|
|
else:
|
|
outcome["steals"].append(f"{baserunner} steals {base_string(start_base+1)} base!")
|
|
self.bases[start_base+1] = baserunner
|
|
self.bases[start_base] = None
|
|
|
|
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["text"] == appearance_outcomes.homerun or outcome["text"] == 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["text"] = 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 run_roll > 2:
|
|
self.bases[3] = self.bases[2]
|
|
self.bases[2] = None
|
|
return runs
|
|
|
|
elif outcome["text"] == 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
|
|
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["text"] == appearance_outcomes.groundout or outcome["text"] == 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 run_roll > 1.5 or outcome["text"] == 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 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["text"] == 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["text"] == 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 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 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["text"] == 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 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["text"] == 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
|
|
result = self.at_bat()
|
|
if self.top_of_inning:
|
|
offense_team = self.teams["away"]
|
|
weather_count = self.weather.counter_away
|
|
defense_team = self.teams["home"]
|
|
else:
|
|
offense_team = self.teams["home"]
|
|
weather_count = self.weather.counter_home
|
|
defense_team = self.teams["away"]
|
|
|
|
if self.weather.name == "Slight Tailwind" and "mulligan" not in self.last_update[0].keys() and not result["ishit"] and result["text"] != appearance_outcomes.walk:
|
|
mulligan_roll_target = -((((self.get_batter().stlats["batting_stars"])-5)/6)**2)+1
|
|
if random.random() > mulligan_roll_target and self.get_batter().stlats["batting_stars"] <= 5:
|
|
result["mulligan"] = True
|
|
return (result, 0)
|
|
|
|
if self.weather.name == "Heavy Snow" and weather_count == offense_team.lineup_position and "snow_atbat" not in self.last_update[0].keys():
|
|
result["snow_atbat"] = True
|
|
result["text"] = f"{offense_team.lineup[offense_team.lineup_position % len(offense_team.lineup)].name}'s hands are too cold! {self.get_batter().name} is forced to bat!"
|
|
return (result, 0)
|
|
|
|
defenders = defense_team.lineup.copy()
|
|
defenders.append(defense_team.pitcher)
|
|
defender = random.choice(defenders) #pitcher can field outs now :3
|
|
|
|
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["text"] == appearance_outcomes.single:
|
|
self.get_batter().game_stats["total_bases"] += 1
|
|
elif result["text"] == appearance_outcomes.double:
|
|
self.get_batter().game_stats["total_bases"] += 2
|
|
elif result["text"] == appearance_outcomes.triple:
|
|
self.get_batter().game_stats["total_bases"] += 3
|
|
elif result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam:
|
|
self.get_batter().game_stats["total_bases"] += 4
|
|
self.get_batter().game_stats["home_runs"] += 1
|
|
if self.weather.name == "Thinned Veil":
|
|
result["veil"] = True
|
|
|
|
|
|
|
|
scores_to_add += self.baserunner_check(defender, result)
|
|
|
|
else: #batter did not get a hit
|
|
if result["text"] == 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["text"] == 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(defender, result)
|
|
self.get_batter().game_stats["rbis"] -= scores_to_add #remove the fake rbi from the player in advance
|
|
|
|
elif result["text"] == appearance_outcomes.fielderschoice or result["text"] == appearance_outcomes.groundout:
|
|
self.get_pitcher().game_stats["outs_pitched"] += 1
|
|
self.outs += 1
|
|
if self.outs < 3:
|
|
scores_to_add += self.baserunner_check(defender, result)
|
|
|
|
elif "advance" in result.keys():
|
|
self.get_pitcher().game_stats["outs_pitched"] += 1
|
|
self.outs += 1
|
|
if self.outs < 3:
|
|
if self.bases[3] is not None:
|
|
self.get_batter().game_stats["sacrifices"] += 1
|
|
scores_to_add += self.baserunner_check(defender, result)
|
|
|
|
elif result["text"] == appearance_outcomes.strikeoutlooking or result["text"] == 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.outs < 3:
|
|
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
|
|
offense_team.lineup_position += 1 #put next batter up
|
|
if self.outs >= 3:
|
|
self.flip_inning()
|
|
if self.weather.name == "Heat Wave":
|
|
if self.top_of_inning:
|
|
self.weather.home_pitcher = self.get_pitcher()
|
|
if self.inning >= self.weather.counter_home:
|
|
self.weather.counter_home = self.weather.counter_home - (self.weather.counter_home % 5) + 5 + random.randint(1,4) #rounds down to last 5, adds up to next 5. then adds a random number 2<=x<=5 to determine next pitcher
|
|
tries = 0
|
|
while self.get_pitcher() == self.weather.home_pitcher and tries < 3:
|
|
self.teams["home"].set_pitcher(use_lineup = True)
|
|
tries += 1
|
|
|
|
|
|
else:
|
|
self.weather.away_pitcher = self.get_pitcher()
|
|
if self.inning >= self.weather.counter_away:
|
|
self.weather.counter_away = self.weather.counter_away - (self.weather.counter_away % 5) + 5 + random.randint(1,4)
|
|
tries = 0
|
|
while self.get_pitcher() == self.weather.away_pitcher and tries < 3:
|
|
self.teams["away"].set_pitcher(use_lineup = True)
|
|
tries += 1
|
|
|
|
|
|
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
|
|
if self.top_of_inning and self.weather.name == "Heavy Snow" and self.weather.counter_away < self.teams["away"].lineup_position:
|
|
self.weather.counter_away = self.pitcher_insert(self.teams["away"])
|
|
|
|
if not self.top_of_inning:
|
|
if self.weather.name == "Heavy Snow" and self.weather.counter_home < self.teams["home"].lineup_position:
|
|
self.weather.counter_home = self.pitcher_insert(self.teams["home"])
|
|
self.inning += 1
|
|
if self.inning > self.max_innings and self.teams["home"].score != self.teams["away"].score: #game over
|
|
self.over = True
|
|
self.top_of_inning = not self.top_of_inning
|
|
|
|
if self.weather.name == "Drizzle":
|
|
if self.top_of_inning:
|
|
self.bases[2] = self.teams["away"].lineup[(self.teams["away"].lineup_position-1) % len(self.teams["away"].lineup)]
|
|
else:
|
|
self.bases[2] = self.teams["home"].lineup[(self.teams["home"].lineup_position-1) % len(self.teams["home"].lineup)]
|
|
|
|
def pitcher_insert(self, this_team):
|
|
rounds = math.ceil(this_team.lineup_position / len(this_team.lineup))
|
|
position = random.randint(0, len(this_team.lineup)-1)
|
|
return rounds * len(this_team.lineup) + position
|
|
|
|
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):
|
|
attempts = self.thievery_attempts()
|
|
if attempts == False:
|
|
self.last_update = self.batterup()
|
|
else:
|
|
self.last_update = attempts
|
|
return self.gamestate_display_full()
|
|
|
|
def gamestate_display_full(self):
|
|
if "steals" in self.last_update[0].keys():
|
|
return "Still in progress."
|
|
else:
|
|
try:
|
|
punc = ""
|
|
if self.last_update[0]["defender"] != "":
|
|
punc = "."
|
|
if not self.over:
|
|
if self.top_of_inning:
|
|
inningtext = "top"
|
|
else:
|
|
inningtext = "bottom"
|
|
|
|
updatestring = "this isn't used but i don't want to break anything"
|
|
|
|
return "this isn't used but i don't want to break anything"
|
|
else:
|
|
return f"""Game over! Final score: **{self.teams['away'].score} - {self.teams['home'].score}**
|
|
Last update: {self.last_update[0]['batter']} {self.last_update[0]['text'].value} {self.last_update[0]['defender']}{punc}"""
|
|
except TypeError:
|
|
return "Game not started."
|
|
except KeyError:
|
|
return "Game not started."
|
|
|
|
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.name, this_player.game_stats))
|
|
for this_player in self.teams["home"].lineup:
|
|
players.append((this_player.name, this_player.game_stats))
|
|
players.append((self.teams["home"].pitcher.name, self.teams["home"].pitcher.game_stats))
|
|
players.append((self.teams["away"].pitcher.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:
|
|
players[self.teams["away"].name].append((this_player.name, this_player.game_stats))
|
|
for this_player in self.teams["home"].lineup:
|
|
players[self.teams["home"].name].append((this_player.name, this_player.game_stats))
|
|
players[self.teams["home"].name].append((self.teams["home"].pitcher.name, self.teams["home"].pitcher.game_stats))
|
|
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)
|
|
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)
|
|
return (team_json, owner_id)
|
|
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, owner_id)
|
|
except:
|
|
return 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)
|
|
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 base_string(base):
|
|
if base == 1:
|
|
return "first"
|
|
elif base == 2:
|
|
return "second"
|
|
elif base == 3:
|
|
return "third"
|
|
elif base == 4:
|
|
return "None"
|
|
|
|
class weather(object):
|
|
name = "Sunny"
|
|
emoji = "🌞"
|
|
|
|
def __init__(self, new_name, new_emoji):
|
|
self.name = new_name
|
|
self.emoji = new_emoji + "\uFE00"
|
|
self.counter_away = 0
|
|
self.counter_home = 0
|
|
|
|
def __str__(self):
|
|
return f"{self.emoji} {self.name}"
|