Merge pull request #196 from Sakimori/indev

add the One Big League
This commit is contained in:
Sakimori 2021-02-22 19:01:06 -05:00 committed by GitHub
commit 160cd845c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 738 additions and 218 deletions

View file

@ -1,6 +1,7 @@
#handles the database interactions
import os, json, datetime, re
import sqlite3 as sql
from random import sample
data_dir = "data"
@ -71,6 +72,15 @@ def initialcheck():
owner_id integer
); """
one_big_league_check_string = """ CREATE TABLE IF NOT EXISTS one_big_league (
counter integer PRIMARY KEY,
team_name text NOT NULL,
teams_beaten_list text,
current_opponent_pool text,
obl_points int DEFAULT 0,
rival_name text
);"""
if conn is not None:
c = conn.cursor()
c.execute(soulscream_table_check_string)
@ -78,6 +88,7 @@ def initialcheck():
c.execute(player_table_check_string)
c.execute(player_stats_table_check_string)
c.execute(teams_table_check_string)
c.execute(one_big_league_check_string)
conn.commit()
conn.close()
@ -297,6 +308,26 @@ def get_all_teams():
conn.close()
return None
def get_all_team_names():
conn = create_connection()
if conn is not None:
c = conn.cursor()
c.execute("SELECT name FROM teams")
team_names = c.fetchall()
team_names_out = [name for (name,) in team_names]
conn.close()
return team_names_out
conn.close()
return None
def get_filtered_teams(filter_list):
teams_list = get_all_team_names()
out_list = []
for team in teams_list:
if team not in filter_list:
out_list.append(team)
return out_list
def search_teams(search_string):
conn = create_connection()
if conn is not None:
@ -327,3 +358,97 @@ def add_stats(player_game_stats_list):
c.execute(f"UPDATE stats SET {stat} = ? WHERE name=?",(player_stats_dic[stat],name))
conn.commit()
conn.close()
def add_team_obl(team):
conn = create_connection()
if conn is not None:
c=conn.cursor()
opponents = sample(get_filtered_teams([team.name]), 5)
c.execute("INSERT INTO one_big_league(team_name, current_opponent_pool) VALUES (?, ?)", (team.name, list_to_newline_string(opponents)))
conn.commit()
conn.close()
def save_obl_results(winning_team, losing_team):
conn = create_connection()
if conn is not None:
c=conn.cursor()
c.execute("SELECT teams_beaten_list, current_opponent_pool, obl_points FROM one_big_league WHERE team_name = ?", (winning_team.name,))
try:
beaten_string, opponents_string, obl_points = c.fetchone()
except TypeError: #add team to OBL
add_team_obl(winning_team)
add_team_obl(losing_team)
else:
beaten_teams = newline_string_to_list(beaten_string)
opponent_teams = newline_string_to_list(opponents_string)
if losing_team.name in opponent_teams:
beaten_teams.append(losing_team.name)
opponent_teams = sample(get_filtered_teams([winning_team.name]), 5)
obl_points += 1
c.execute("UPDATE one_big_league SET teams_beaten_list = ?, current_opponent_pool = ?, obl_points = ? WHERE team_name = ?", (list_to_newline_string(beaten_teams), list_to_newline_string(opponent_teams), obl_points, winning_team.name))
conn.commit()
conn.close()
return
def get_obl_stats(team, full = False):
conn = create_connection()
if conn is not None:
c=conn.cursor()
opponents_string = None
while opponents_string is None:
c.execute("SELECT teams_beaten_list, current_opponent_pool, rival_name FROM one_big_league WHERE team_name = ?", (team.name,))
try:
beaten_string, opponents_string, rival_name = c.fetchone()
except TypeError: #add team to OBL
add_team_obl(team)
beaten_teams = newline_string_to_list(beaten_string)
opponent_teams = opponents_string
obl_points = len(beaten_teams)
teams_list = [name for name, points in obl_leaderboards()]
rank = teams_list.index(team.name) + 1
if not full:
return (obl_points, opponent_teams, rank)
else:
return (obl_points, beaten_teams, opponent_teams, rank, rival_name)
conn.close()
return (None, None)
def obl_leaderboards():
conn = create_connection()
if conn is not None:
c=conn.cursor()
c.execute("SELECT team_name, obl_points FROM one_big_league ORDER BY obl_points DESC")
teams_list = c.fetchall()
return teams_list #element (team_name, obl_points)
conn.close()
return False
def set_obl_rival(base_team, rival):
conn = create_connection()
if conn is not None:
c=conn.cursor()
c.execute("UPDATE one_big_league SET rival_name = ? WHERE team_name = ?", (rival.name, base_team.name))
conn.commit()
conn.close()
def list_to_newline_string(list):
string = ""
for element in list:
if string != "":
string += "\n"
string += element
return string
def newline_string_to_list(string):
if string is not None and string != "":
return string.split("\n")
else:
return []

225
games.py
View file

@ -1,6 +1,7 @@
import json, random, os, math, jsonpickle
from enum import Enum
import database as db
import weather
from gametext import base_string, appearance_outcomes
data_dir = "data"
games_config_file = os.path.join(data_dir, "games_config.json")
@ -28,34 +29,6 @@ def config():
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):
@ -240,27 +213,30 @@ class game(object):
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.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","🌞")
self.weather = weather.Weather(self)
self.current_batter = None
def get_batter(self):
def choose_next_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)]
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:
@ -284,38 +260,34 @@ class game(object):
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
player_rolls = {}
player_rolls["bat_stat"] = random_star_gen("batting_stars", batter)
player_rolls["pitch_stat"] = random_star_gen("pitching_stars", pitcher)
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)
self.weather.modify_atbat_stats(player_rolls)
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
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)
if pb_system_stat <= 0:
if roll["pb_system_stat"] <= 0:
outcome["ishit"] = False
fc_flag = False
if hitnum < -1.5:
if roll["hitnum"] < -1.5:
outcome["text"] = random.choice([appearance_outcomes.strikeoutlooking, appearance_outcomes.strikeoutswinging])
elif hitnum < 1:
elif roll["hitnum"] < 1:
outcome["text"] = appearance_outcomes.groundout
outcome["defender"] = defender
elif hitnum < 4:
elif roll["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:
if self.bases[1] is not None and roll["hitnum"] < -2 and self.outs != 2:
outcome["text"] = appearance_outcomes.doubleplay
outcome["defender"] = ""
@ -332,20 +304,20 @@ class game(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
if -1.5 <= roll["hitnum"] and roll["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 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 hitnum < 1:
if roll["hitnum"] < 1:
outcome["text"] = appearance_outcomes.single
elif hitnum < 2.85 or "error" in outcome.keys():
elif roll["hitnum"] < 2.85 or "error" in outcome.keys():
outcome["text"] = appearance_outcomes.double
elif hitnum < 3.1:
elif roll["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:
@ -362,13 +334,16 @@ class game(object):
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
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 stats["run_stars"] >= (stats["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
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:
@ -533,25 +508,19 @@ class game(object):
def batterup(self):
scores_to_add = 0
result = self.at_bat()
self.weather.activate(self, result) # possibly modify result in-place
if "text_only" in result:
return (result, 0)
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)
@ -570,8 +539,6 @@ class game(object):
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
@ -636,27 +603,10 @@ class game(object):
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
self.choose_next_batter()
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
@ -665,27 +615,19 @@ class game(object):
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.top_of_inning = not self.top_of_inning
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
self.top_of_inning = not self.top_of_inning
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"])
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 {
@ -707,6 +649,7 @@ class game(object):
def gamestate_update_full(self):
self.play_has_begun = True
attempts = self.thievery_attempts()
if attempts == False:
self.last_update = self.batterup()
@ -715,29 +658,10 @@ class game(object):
return self.gamestate_display_full()
def gamestate_display_full(self):
if "steals" in self.last_update[0].keys():
if not self.over:
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."
return f"""Game over! Final score: **{self.teams['away'].score} - {self.teams['home'].score}**"""
def add_stats(self):
players = self.get_stats()
@ -868,25 +792,10 @@ def search_team(search_term):
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
self.counter_away = 0
self.counter_home = 0
def __str__(self):
return f"{self.emoji} {self.name}"
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

27
gametext.py Normal file
View file

@ -0,0 +1,27 @@
from enum import Enum
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!"
def base_string(base):
if base == 1:
return "first"
elif base == 2:
return "second"
elif base == 3:
return "third"
elif base == 4:
return "None"

View file

@ -140,7 +140,7 @@ def update_loop():
state["away_score"] = this_game.teams["away"].score #top_of_inning = True
state["home_score"] = this_game.teams["home"].score #update_pause = 0
#victory_lap = False
if test_string == "Game not started.": #weather_emoji
if not this_game.play_has_begun: #weather_emoji
state["update_emoji"] = "🍿" #weather_text
state["update_text"] = "Play blall!" #they also need a timestamp
state["start_delay"] -= 1
@ -156,9 +156,9 @@ def update_loop():
state["display_inning"] -= 1
state["display_top_of_inning"] = False
if state["update_pause"] == 1:
if state["update_pause"] == 1: #generate the top of the inning message before displaying the at bat result
state["update_emoji"] = "🍿"
if this_game.over:
if this_game.over: # game over message
state["display_inning"] -= 1
state["display_top_of_inning"] = False
winning_team = this_game.teams['home'].name if this_game.teams['home'].score > this_game.teams['away'].score else this_game.teams['away'].name
@ -170,28 +170,23 @@ def update_loop():
state["update_text"] = f"{winning_team} wins!"
state["pitcher"] = "-"
state["batter"] = "-"
elif this_game.top_of_inning:
state["update_text"] = f"Top of {this_game.inning}. {this_game.teams['away'].name} batting!"
if this_game.weather.name == "Drizzle":
state["update_emoji"] = "🌧"
state["update_text"] += f' Due to inclement weather, {this_game.teams["away"].lineup[(this_game.teams["away"].lineup_position-1) % len(this_game.teams["away"].lineup)].name} is placed on second base.'
elif this_game.weather.name == "Heat Wave" and hasattr(this_game.weather, "home_pitcher") and this_game.weather.home_pitcher.name != state["pitcher"]:
state["update_emoji"] = "🌄"
state["update_text"] += f' {this_game.weather.home_pitcher} is exhausted from the heat. {state["pitcher"]} is forced to pitch!'
else:
if this_game.inning >= this_game.max_innings:
if this_game.teams["home"].score > this_game.teams["away"].score:
this_game.victory_lap = True
state["update_text"] = f"Bottom of {this_game.inning}. {this_game.teams['home'].name} batting!"
if this_game.weather.name == "Drizzle":
state["update_emoji"] = "🌧"
state["update_text"] += f' Due to inclement weather, {this_game.teams["home"].lineup[(this_game.teams["home"].lineup_position-1) % len(this_game.teams["home"].lineup)].name} is placed on second base.'
elif this_game.weather.name == "Heat Wave" and hasattr(this_game.weather, "away_pitcher") and this_game.weather.away_pitcher.name != state["pitcher"]:
state["update_emoji"] = "🌄"
state["update_text"] += f' {this_game.weather.away_pitcher} is exhausted from the heat. {state["pitcher"]} is forced to pitch!'
if this_game.top_of_inning:
state["update_text"] = f"Top of {this_game.inning}. {this_game.teams['away'].name} batting!"
else:
if this_game.inning >= this_game.max_innings:
if this_game.teams["home"].score > this_game.teams["away"].score:
this_game.victory_lap = True
state["update_text"] = f"Bottom of {this_game.inning}. {this_game.teams['home'].name} batting!"
this_game.weather.modify_top_of_inning_message(this_game, state)
elif state["update_pause"] != 1 and this_game.play_has_begun:
if "weather_message" in this_game.last_update[0].keys():
state["update_emoji"] = this_game.weather.emoji
elif state["update_pause"] != 1 and test_string != "Game not started.":
if "steals" in this_game.last_update[0].keys():
updatestring = ""
for attempt in this_game.last_update[0]["steals"]:
@ -200,30 +195,17 @@ def update_loop():
state["update_emoji"] = "💎"
state["update_text"] = updatestring
elif "mulligan" in this_game.last_update[0].keys():
updatestring = ""
punc = ""
if this_game.last_update[0]["defender"] != "":
punc = ", "
state["update_emoji"] = "🏌️‍♀️"
state["update_text"] = f"{this_game.last_update[0]['batter']} would have gone out, but they took a mulligan!"
elif "snow_atbat" in this_game.last_update[0].keys():
state["update_emoji"] = ""
elif "text_only" in this_game.last_update[0].keys(): #this handles many weather messages
state["update_text"] = this_game.last_update[0]["text"]
else:
updatestring = ""
punc = ""
if this_game.last_update[0]["defender"] != "":
punc = ". "
if "fc_out" in this_game.last_update[0].keys():
name, base_string = this_game.last_update[0]['fc_out']
updatestring = f"{this_game.last_update[0]['batter']} {this_game.last_update[0]['text'].value.format(name, base_string)} {this_game.last_update[0]['defender']}{punc}"
name, out_at_base_string = this_game.last_update[0]['fc_out']
updatestring = f"{this_game.last_update[0]['batter']} {this_game.last_update[0]['text'].value.format(name, out_at_base_string)} {this_game.last_update[0]['defender']}{punc}"
else:
updatestring = f"{this_game.last_update[0]['batter']} {this_game.last_update[0]['text'].value} {this_game.last_update[0]['defender']}{punc}"
if this_game.last_update[1] > 0:
@ -231,15 +213,8 @@ def update_loop():
state["update_emoji"] = "🏏"
state["update_text"] = updatestring
if "veil" in this_game.last_update[0].keys():
state["update_emoji"] = "🌌"
state["update_text"] += f" {this_game.last_update[0]['batter']}'s will manifests on {games.base_string(this_game.last_update[1])} base."
elif "error" in this_game.last_update[0].keys():
state["update_emoji"] = "👻"
state["update_text"] = f"{this_game.last_update[0]['batter']}'s hit goes ethereal, and {this_game.last_update[0]['defender']} can't catch it! {this_game.last_update[0]['batter']} reaches base safely."
if this_game.last_update[1] > 0:
updatestring += f"{this_game.last_update[1]} runs scored!"
this_game.weather.modify_atbat_message(this_game, state)
state["bases"] = this_game.named_bases()
@ -261,4 +236,4 @@ def update_loop():
state["update_pause"] -= 1
socketio.emit("states_update", game_states)
time.sleep(8)
time.sleep(8)

View file

@ -29,6 +29,7 @@
<Compile Include="debug storage.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="gametext.py" />
<Compile Include="leagues.py">
<SubType>Code</SubType>
</Compile>
@ -46,6 +47,7 @@
</Compile>
<Compile Include="roman.py" />
<Compile Include="the_prestige.py" />
<Compile Include="weather.py" />
</ItemGroup>
<ItemGroup>
<Interpreter Include="matteo_env\">

View file

@ -5,6 +5,7 @@ from league_storage import league_exists, season_save, season_restart
from the_draft import Draft, DRAFT_ROUNDS
from flask import Flask
from uuid import uuid4
import weather
data_dir = "data"
config_filename = os.path.join(data_dir, "config.json")
@ -189,8 +190,8 @@ class StartGameCommand(Command):
game.teams['home'].set_pitcher(rotation_slot = day)
channel = msg.channel
if weather_name is not None and weather_name in games.all_weathers().keys():
game.weather = games.all_weathers()[weather_name]
if weather_name is not None and weather_name in weather.all_weathers().keys():
game.weather = weather.all_weathers()[weather_name](game)
game_task = asyncio.create_task(watch_game(channel, game, user=msg.author, league=league))
@ -1232,6 +1233,147 @@ class LeagueRenameCommand(Command):
else:
await msg.channel.send("We can't find that league.")
class OBLExplainCommand(Command):
name = "oblhelp"
template = "m;oblhelp"
description = "Explains the One Big League!"
async def execute(self, msg, command):
await msg.channel.send("""The One Big League, or OBL, is an asynchronous league that includes every team in the simsim's database. To participate, just use the m;oblteam command with your team of choice. **No signup is required!** This will give you a list of five opponents; playing against one of them and winning nets you a point, and will refresh the list with five new opponents. **Losing results in no penalty!** Each meta-season will last for a few weeks, after which the leaderboards are reset to start the race again!
Look out for the people wrestling emoji, which indicates the potential for a :people_wrestling:Wrassle Match:people_wrestling:, where both teams are on each others' lists and both have the opportunity to score a point. Team rankings and points can also be viewed in the oblteam command, and the overall OBL leaderboard can be checked with the m;oblstandings command. Best of luck!!
""")
class OBLLeaderboardCommand(Command):
name = "oblstandings"
template = "m;oblstandings"
description = "Displays the 15 teams with the most OBL points in this meta-season."
async def execute(self, msg, command):
leaders_list = db.obl_leaderboards()[:15]
leaders = {}
rank = 1
for team, points in leaders_list:
leaders[team] = {"rank" : rank, "points" : points}
rank += 1
embed = discord.Embed(color=discord.Color.red(), title="The One Big League")
for team in leaders.keys():
embed.add_field(name=f"{leaders[team]['rank']}. {team}", value=f"{leaders[team]['points']} points" , inline = False)
await msg.channel.send(embed=embed)
class OBLTeamCommand(Command):
name = "oblteam"
template = "m;oblteam [team name]"
description = "Displays a team's rank, current OBL points, and current opponent selection."
async def execute(self, msg, command):
team = get_team_fuzzy_search(command.strip())
if team is None:
await msg.channel.send("Sorry boss, we can't find that team.")
return
rival_team = None
points, beaten_teams_list, opponents_string, rank, rival_name = db.get_obl_stats(team, full=True)
opponents_list = db.newline_string_to_list(opponents_string)
for index in range(0, len(opponents_list)):
oppteam = get_team_fuzzy_search(opponents_list[index])
opplist = db.get_obl_stats(oppteam)[1]
if team.name in opplist:
opponents_list[index] = opponents_list[index] + " 🤼"
if rival_name == opponents_list[index]:
opponents_list[index] = opponents_list[index] + " 😈"
if rival_name is not None:
rival_team = games.get_team(rival_name)
opponents_string = db.list_to_newline_string(opponents_list)
embed = discord.Embed(color=discord.Color.red(), title=f"{team.name} in the One Big League")
embed.add_field(name="OBL Points", value=points)
embed.add_field(name="Rank", value=rank)
embed.add_field(name="Bounty Board", value=opponents_string, inline=False)
if rival_team is not None:
r_points, r_beaten_teams_list, r_opponents_string, r_rank, r_rival_name = db.get_obl_stats(rival_team, full=True)
embed.add_field(name="Rival", value=f"**{rival_team.name}**: Rank {r_rank}\n{rival_team.slogan}\nPoints: {r_points}")
if r_rival_name == team.name:
embed.set_footer(text="🔥")
else:
embed.set_footer(text="Set a rival with m;oblrival!")
await msg.channel.send(embed=embed)
class OBLSetRivalCommand(Command):
name = "oblrival"
template = "m;oblrival\n[your team name]\n[rival team name]"
description = "Sets your team's OBL rival. Can be changed at any time. Requires ownership."
async def execute(self, msg, command):
team_i = get_team_fuzzy_search(command.split("\n")[1].strip())
team_r = get_team_fuzzy_search(command.split("\n")[2].strip())
team, owner_id = games.get_team_and_owner(team_i.name)
if team is None or team_r is None:
await msg.channel.send("Can't find one of those teams, boss. Typo?")
return
elif owner_id != msg.author.id and msg.author.id not in config()["owners"]:
await msg.channel.send("You're not authorized to mess with this team. Sorry, boss.")
return
#try:
db.set_obl_rival(team, team_r)
await msg.channel.send("One pair of mortal enemies, coming right up. Unless you're more of the 'enemies to lovers' type. We can manage that too, don't worry.")
#except:
#await msg.channel.send("Hm. We don't think that team has tried to do anything in the One Big League yet, so you'll have to wait for consent. Get them to check their bounty board.")
class OBLConqueredCommand(Command):
name = "oblwins"
template = "m;oblwins [team name]"
description = "Displays all teams that a given team has won points off of."
async def execute(self, msg, command):
team = get_team_fuzzy_search(command.strip())
if team is None:
await msg.channel.send("Sorry boss, we can't find that team.")
return
points, teams, oppTeams, rank, rivalName = db.get_obl_stats(team, full=True)
pages = []
page_max = math.ceil(len(teams)/25)
title_text = f"Rank {rank}: {team.name}"
for page in range(0,page_max):
embed = discord.Embed(color=discord.Color.red(), title=title_text)
embed.set_footer(text = f"{points} OBL Points")
for i in range(0,25):
try:
thisteam = games.get_team(teams[i+25*page])
if thisteam.slogan.strip() != "":
embed.add_field(name=thisteam.name, value=thisteam.slogan)
else:
embed.add_field(name=thisteam.name, value="404: Slogan not found")
except:
break
pages.append(embed)
teams_list = await msg.channel.send(embed=pages[0])
current_page = 0
if page_max > 1:
await teams_list.add_reaction("")
await teams_list.add_reaction("")
def react_check(react, user):
return user == msg.author and react.message == teams_list
while True:
try:
react, user = await client.wait_for('reaction_add', timeout=60.0, check=react_check)
if react.emoji == "" and current_page > 0:
current_page -= 1
await react.remove(user)
elif react.emoji == "" and current_page < (page_max-1):
current_page += 1
await react.remove(user)
await teams_list.edit(embed=pages[current_page])
except asyncio.TimeoutError:
return
commands = [
IntroduceCommand(),
@ -1255,6 +1397,11 @@ commands = [
StartGameCommand(),
StartRandomGameCommand(),
StartTournamentCommand(),
OBLExplainCommand(),
OBLTeamCommand(),
OBLSetRivalCommand(),
OBLConqueredCommand(),
OBLLeaderboardCommand(),
LeagueClaimCommand(),
LeagueAddOwnersCommand(),
StartLeagueCommand(),
@ -1516,9 +1663,9 @@ async def watch_game(channel, newgame, user = None, league = None):
main_controller.master_games_dic[id] = (newgame, state_init, discrim_string)
def prepare_game(newgame, league = None, weather_name = None):
if weather_name is None and newgame.weather.name == "Sunny": #if no weather name supplied and the game's weather is default, pick random weather
weathers = games.all_weathers()
newgame.weather = weathers[random.choice(list(weathers.keys()))]
if weather_name is None and newgame.weather.name == "Sunny":
weathers = weather.all_weathers()
newgame.weather = weathers[random.choice(list(weathers.keys()))](newgame)
state_init = {
"away_name" : newgame.teams['away'].name,
@ -1538,12 +1685,6 @@ def prepare_game(newgame, league = None, weather_name = None):
else:
state_init["is_league"] = True
if newgame.weather.name == "Heavy Snow":
newgame.weather.counter_away = random.randint(0,len(newgame.teams['away'].lineup)-1)
newgame.weather.counter_home = random.randint(0,len(newgame.teams['home'].lineup)-1)
elif newgame.weather.name == "Heat Wave":
newgame.weather.counter_away = random.randint(2,4)
newgame.weather.counter_home = random.randint(2,4)
return newgame, state_init
async def start_tournament_round(channel, tourney, seeding = None):

341
weather.py Normal file
View file

@ -0,0 +1,341 @@
import random
import math
from gametext import appearance_outcomes, base_string
class Weather:
def __init__(self, game):
self.name = "Sunny"
self.emoji = "🌞"
def __str__(self):
return f"{self.emoji} {self.name}"
def modify_atbat_stats(self, player_rolls):
# Activates before batting
pass
def modify_steal_stats(self, roll):
pass
def modify_atbat_roll(self, outcome, roll, defender):
# activates after batter roll
pass
def activate(self, game, result):
# activates after the batter calculation. modify result, or just return another thing
pass
def on_choose_next_batter(self, game):
pass
def on_flip_inning(self, game):
pass
def modify_top_of_inning_message(self, game, state):
pass
def modify_atbat_message(self, game, state):
pass
class Supernova(Weather):
def __init__(self, game):
self.name = "Supernova"
self.emoji = "🌟"
def modify_atbat_stats(self, roll):
roll["pitch_stat"] *= 0.9
class Midnight(Weather):
def __init__(self, game):
self.name = "Midnight"
self.emoji = "🕶"
def modify_steal_stats(self, roll):
roll["run_stars"] *= 2
class SlightTailwind(Weather):
def __init__(self, game):
self.name = "Slight Tailwind"
self.emoji = "🏌️‍♀️"
def activate(self, game, result):
if game.top_of_inning:
offense_team = game.teams["away"]
defense_team = game.teams["home"]
else:
offense_team = game.teams["home"]
defense_team = game.teams["away"]
if "mulligan" not in game.last_update[0].keys() and not result["ishit"] and result["text"] != appearance_outcomes.walk:
mulligan_roll_target = -((((game.get_batter().stlats["batting_stars"])-5)/6)**2)+1
if random.random() > mulligan_roll_target and game.get_batter().stlats["batting_stars"] <= 5:
result.clear()
result.update({
"text": f"{game.get_batter()} would have gone out, but they took a mulligan!",
"text_only": True,
"weather_message": True,
})
class HeavySnow(Weather):
def __init__(self, game):
self.name = "Heavy Snow"
self.emoji = ""
self.counter_away = random.randint(0,len(game.teams['away'].lineup)-1)
self.counter_home = random.randint(0,len(game.teams['home'].lineup)-1)
self.swapped_batter_data = None
def activate(self, game, result):
if self.swapped_batter_data:
original, sub = self.swapped_batter_data
self.swapped_batter_data = None
result.clear()
result.update({
"snow_atbat": True,
"text": f"{original.name}'s hands are too cold! {sub.name} is forced to bat!",
"text_only": True,
"weather_message": True,
})
def on_flip_inning(self, game):
if game.top_of_inning and self.counter_away < game.teams["away"].lineup_position:
self.counter_away = self.pitcher_insert_index(game.teams["away"])
if not game.top_of_inning and self.counter_home < game.teams["home"].lineup_position:
self.counter_home = self.pitcher_insert_index(game.teams["home"])
def pitcher_insert_index(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 on_choose_next_batter(self, game):
if game.top_of_inning:
bat_team = game.teams["away"]
counter = self.counter_away
else:
bat_team = game.teams["home"]
counter = self.counter_home
if bat_team.lineup_position == counter:
self.swapped_batter_data = (game.current_batter, bat_team.pitcher) # store this to generate the message during activate()
game.current_batter = bat_team.pitcher
class Twilight(Weather):
def __init__(self,game):
self.name = "Twilight"
self.emoji = "👻"
def modify_atbat_roll(self, outcome, roll, defender):
error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1
error_roll = random.random()
if error_roll > error_line:
outcome["error"] = True
outcome["weather_message"] = True
outcome["defender"] = defender
roll["pb_system_stat"] = 0.1
def modify_atbat_message(self, this_game, state):
result = this_game.last_update[0]
if "error" in result.keys():
state["update_text"] = f"{result['batter']}'s hit goes ethereal, and {result['defender']} can't catch it! {result['batter']} reaches base safely."
if this_game.last_update[1] > 0:
state["update_text"] += f" {this_game.last_update[1]} runs scored!"
class ThinnedVeil(Weather):
def __init__(self,game):
self.name = "Thinned Veil"
self.emoji = "🌌"
def activate(self, game, result):
if result["ishit"]:
if result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam:
result["veil"] = True
def modify_atbat_message(self, game, state):
if "veil" in game.last_update[0].keys():
state["update_emoji"] = self.emoji
state["update_text"] += f" {game.last_update[0]['batter']}'s will manifests on {base_string(game.last_update[1])} base."
class HeatWave(Weather):
def __init__(self,game):
self.name = "Heat Wave"
self.emoji = "🌄"
self.counter_away = random.randint(2,4)
self.counter_home = random.randint(2,4)
self.swapped_pitcher_data = None
def on_flip_inning(self, game):
original_pitcher = game.get_pitcher()
if game.top_of_inning:
bat_team = game.teams["home"]
counter = self.counter_home
else:
bat_team = game.teams["away"]
counter = self.counter_away
if game.inning == counter:
if game.top_of_inning:
self.counter_home = self.counter_home - (self.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
else:
self.counter_away = self.counter_away - (self.counter_away % 5) + 5 + random.randint(1,4)
#swap, accounting for teams where where someone's both batter and pitcher
tries = 0
while game.get_pitcher() == original_pitcher and tries < 3:
bat_team.set_pitcher(use_lineup = True)
tries += 1
if game.get_pitcher() != original_pitcher:
self.swapped_pitcher_data = (original_pitcher, game.get_pitcher())
def modify_top_of_inning_message(self, game, state):
if self.swapped_pitcher_data:
original, sub = self.swapped_pitcher_data
self.swapped_pitcher_data = None
state["update_emoji"] = self.emoji
state["update_text"] += f' {original} is exhausted from the heat. {sub} is forced to pitch!'
class Drizzle(Weather):
def __init__(self,game):
self.name = "Drizzle"
self.emoji = "🌧"
def on_flip_inning(self, game):
if game.top_of_inning:
next_team = "away"
else:
next_team = "home"
lineup = game.teams[next_team].lineup
game.bases[2] = lineup[(game.teams[next_team].lineup_position-1) % len(lineup)]
def modify_top_of_inning_message(self, game, state):
if game.top_of_inning:
next_team = "away"
else:
next_team = "home"
placed_player = game.teams[next_team].lineup[(game.teams[next_team].lineup_position-1) % len(game.teams[next_team].lineup)]
state["update_emoji"] = self.emoji
state["update_text"] += f' Due to inclement weather, {placed_player.name} is placed on second base.'
class Sun2(Weather):
def __init__(self, game):
self.name = "Sun 2"
def activate(self, game):
for teamtype in game.teams:
team = game.teams[teamtype]
if team.score >= 10:
team.score -= 10
# no win counting yet :(
result.clear()
result.update({
"text": "The {} collect 10! Sun 2 smiles.".format(team.name),
"text_only": True,
"weather_message": True
})
class Breezy(Weather):
def __init__(self, game):
self.name = "Breezy"
self.emoji = "🎐"
self.activation_chance = 0.05
def activate(self, game, result):
if random.random() < self.activation_chance:
teamtype = random.choice(["away","home"])
team = game.teams[teamtype]
player = random.choice(team.lineup)
old_player_name = player.name
if ' ' in player.name:
names = player.name.split(" ")
first_first_letter = names[0][0]
last_first_letter = names[-1][0]
names[0] = last_first_letter + names[0][1:]
names[-1] = first_first_letter + names[-1][1:]
player.name = ' '.join(names)
else:
#name is one word, so turn 'bartholemew' into 'martholebew'
first_letter = player.name[0]
last_letter = player.name[-1]
player.name = last_letter + player.name[1:-1] + last_letter
book_adjectives = ["action-packed", "historical", "friendly", "rude", "mystery", "thriller", "horror", "sci-fi", "fantasy", "spooky","romantic"]
book_types = ["novel","novella","poem","anthology","fan fiction","tablet","carving", "autobiography"]
book = "{} {}".format(random.choice(book_adjectives),random.choice(book_types))
result.clear()
result.update({
"text": "{} stopped to read a {} and became Literate! {} is now {}!".format(old_player_name, book, old_player_name, player.name),
"text_only": True,
"weather_message": True
})
class Feedback(Weather):
def __init__(self, game):
self.name = "Feedback"
self.emoji = "🎤"
self.activation_chance = 0.02
self.swap_batter_vs_pitcher_chance = 0.8
def activate(self, game, result):
if random.random() < self.activation_chance:
# feedback time
player1 = None
player2 = None
team1 = game.teams["home"]
team2 = game.teams["away"]
if random.random() < self.swap_batter_vs_pitcher_chance:
# swapping batters
# theoretically this could swap players already on base :(
team1 = game.teams["home"]
team2 = game.teams["away"]
homePlayerIndex = random.randint(0,len(team1.lineup)-1)
awayPlayerIndex = random.randint(0,len(team2.lineup)-1)
player1 = team1.lineup[homePlayerIndex]
player2 = team2.lineup[awayPlayerIndex]
team1.lineup[homePlayerIndex] = player2
team2.lineup[awayPlayerIndex] = player1
else:
# swapping pitchers
player1 = team1.pitcher
player2 = team2.pitcher
team1.pitcher = player2
team2.pitcher = player1
result.clear()
result.update({
"text": "{} and {} switched teams in the feedback!".format(player1.name,player2.name),
"text_only": True,
"weather_message": True,
})
def all_weathers():
weathers_dic = {
"Supernova" : Supernova,
"Midnight": Midnight,
"Slight Tailwind": SlightTailwind,
"Heavy Snow": HeavySnow,
"Twilight" : Twilight,
"Thinned Veil" : ThinnedVeil,
"Heat Wave" : HeatWave,
"Drizzle" : Drizzle,
# "Sun 2": Sun2,
"Feedback": Feedback,
"Breezy": Breezy,
}
return weathers_dic