diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 2a764ab..04105fa 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,59 @@ # matteo-the-prestige # simsim discord bot -blaseball, blaseball, is back! in an unofficial capacity. this is completely unaffiliated with the game band +blaseball, blaseball, is back! in an unofficial capacity. this project is completely unaffiliated with the game band. -custom players, custom teams, custom leagues (that last one is coming soonโข) all set up in discord and watchable at https://simsim.sibr.dev +we have custom players (generated by onomancer), custom teams, custom leagues (that last one is coming soonโข), all set up in discord and watchable at https://simsim.sibr.dev! -we've also got things like custom team creation, easy setup for your teams to play against each other, and player idolization, all powered by this bot and onomancer. +if you would like to add matteo to your server to be able to set up teams and games, you can do so with this link: https://discord.com/api/oauth2/authorize?client_id=789956166796574740&permissions=388160&scope=bot accepting pull requests, check the issues for to-dos. ## commands: (everything here is case sensitive, and can be prefixed with either m; or m!) - ### team commands: + +#### creation and deletion: - m;saveteam - - saves a team to the database allowing it to be used for games. send this command at the top of a list, with entries separated by new lines (shift+enter in discord, or copy+paste from notepad). - - the first line of the list is your team's name (cannot contain emoji). - - the second line is your team's icon and slogan, this should begin with an emoji followed by a space, followed by a short slogan. + - saves a team to the database allowing it to be used for games. use this command at the top of a list with entries separated by new lines: + - the first line of the list is your team's name. + - the second line is the team's icon and slogan, generally this is an emoji followed by a space, followed by a short slogan. + - the third line must be blank. - the next lines are your batters' names in the order you want them to appear in your lineup, lineups can contain any number of batters between 1 and 12. - - the final line is your pitcher's name. - - if you did it correctly, you'll get a team embed with a prompt to confirm. hit the ๐ and it'll be saved. + - then another blank line seperating your batters and your pitchers. + - the final lines are the names of the pitchers in your rotation, rotations can contain any number of pitchers between 1 and 8. + - if you did it correctly, you'll get a team embed with a prompt to confirm. hit the ๐ and your team will be saved! +- m;deleteteam [teamname] (requires team ownership) + - allows you to delete the team with the provided name. you'll get an embed with a confirmation to prevent accidental deletions. hit the ๐ and your team will be deleted. +- m;import + - imports an onomancer collection as a new team. you can use the new onomancer simsim setting to ensure compatibility. similarly to saveteam, you'll get a team embed with a prompt to confirm, hit the ๐ and your team will be saved! + +#### editing (all of these commands require ownership and exact spelling of the team name): +- m;addplayer batter/pitcher [team name] [player name] + - adds a new player to the end of your team, either in the lineup or the rotation depending on which version you use. use addplayer batter or addplayer pitcher at the top of a list with entries separated by new lines: + - the name of the team you want to add the player to. + - the name of the player you want to add to the team. +- m;moveplayer [team name] [player name] [new lineup/rotation position number] + - moves a player within your lineup or rotation. if you want to instead move a player from your rotation to your lineup or vice versa, use m;swapsection instead. use this command at the top of a list with entries separated by new lines: + - the name of the team you want to move the player on. + - the name of the player you want to move. + - the position you want to move them too, indexed with 1 being the first position of the lineup or rotation. all players below the specified position in the lineup or rotation will be pushed down. +- m;swapsection [team name] [player name] + - swaps a player from your lineup to the end of your rotation or your rotation to the end of your lineup. use this command at the top of a list with entries separated by new lines: + - the name of the team you want to swap the player on. + - the name of the player you want to swap. +- m;removeplayer [team name] [player name] + - removes a player from your team. if there are multiple copies of the same player on a team this will only delete the first one. use this command at the top of a list with entries separated by new lines: + - the name of the team you want to remove the player from. + - the name of the player you want to remove. + +#### viewing and searching: - m;showteam [name] - - shows information about any saved team. -- m;showallteams - - shows a paginated list of all teams available for games which can be scrolled through. + - shows the lineup, rotation, and slogan of any saved team in a discord embed with primary stat star ratings for all of the players. this command has fuzzy search so you don't need to type the full name of the team as long as you give enough to identify the team you're looking for. - m;searchteams [searchterm] - shows a paginated list of all teams whose names contain the given search term. -- m;deleteteam [teamname] - - allows you to delete the team with the provided name if you are the owner of it, gives a confirmation first to prevent accidental deletions. if it isn't letting you delete your team, you probably created it before teams having owners was a thing, contact xvi and xie can assign you as the owner. -- m;import - - imports an onomancer collection as a new team. you can use the new onomancer simsim setting to ensure compatibility. +- m;showallteams + - shows a paginated list of all teams available for games which can be scrolled through. ### player commands: - m;showplayer [name] @@ -37,18 +61,29 @@ accepting pull requests, check the issues for to-dos. - m;idolize [name] - records any name as your idol, mostly for fun. - m;showidol - - displays your idol's name and stars. + - displays your idol's name and stars in a discord embed. ### game commands: -- m;startgame - - starts a game with premade teams made using saveteam, use this command at the top of a list followed by each of these in a new line: +- m;startgame --day # or -d # + - starts a game with premade teams made using saveteam. provides a link to the website where you can watch the game. + - the --day/-d is optional, if used it'll force the game to use the #th spot in each team's rotations. if this number is larger than the number of pitchers in one or both of the teams' rotations it'll wrap around. if it is not used pitchers will be chosen randomly from the teams' rotations. + - use this command at the top of a list with entries separated by new lines: - the away team's name. - the home team's name. - - and finally, optionally, the number of innings, which must be greater than 2 and less than 31. if not included it will default to 9. + - optionally, the number of innings, which must be greater than 2 and less than 31. if not included it will default to 9. + - this command has fuzzy search so you don't need to type the full name of the team as long as you give enough to identify the team you're looking for. +- m;randomgame + - starts a 9-inning game between 2 entirely random teams. embrace chaos! +- m;starttournament --rounddelay # + - starts a randomly seeded tournament with up to 64 provided teams, automatically adding byes as necessary. all series have a 5 minute break between games. the current format is: best of 5 until the finals which are best of 7. + - the --rounddelay is optional, if used, # must be between 1 and 120 and it'll set the delay between rounds to be # minutes. if not included it will default to 10. + - use this command at the top of a list with entries separated by new lines: + - the name of the tournament. + - the name of each participating team on its own line. ### other commands: - m;help [command] - - shows the instructions from here for given command. if no command is provided, it will instead provide a list of all of the commands that instructions can be provided for. + - shows instructions for a given command. if no command is provided, it will instead provide a list of all of the commands that instructions can be provided for. - m;credit - shows artist credit for matteo's avatar. - m;roman [number] @@ -60,3 +95,4 @@ these folks are helping me a *ton* via patreon, and i cannot possibly thank them - Ale Humano - Chris Denmark - Astrid Bek +- Kameleon diff --git a/database.py b/database.py index 79b5e49..a53da43 100644 --- a/database.py +++ b/database.py @@ -207,6 +207,21 @@ def save_team(name, team_json_string, user_id): except: return False +def update_team(name, team_json_string): + conn = create_connection() + try: + if conn is not None: + c = conn.cursor() + store_string = "UPDATE teams SET team_json_string = ? WHERE name=?" + c.execute(store_string, (team_json_string, (re.sub('[^A-Za-z0-9 ]+', '', name)))) #this regex removes all non-standard characters + conn.commit() + conn.close() + return True + conn.close() + return False + except: + return False + def get_team(name, owner=False): conn = create_connection() if conn is not None: diff --git a/games.py b/games.py index 805e4c4..f990724 100644 --- a/games.py +++ b/games.py @@ -24,22 +24,13 @@ def config(): return json.load(config_file) def all_weathers(): - if not os.path.exists("weather_config.json"): - #generate default config - super_weather_json = jsonpickle.encode(weather("Supernova", "๐")) - mid_weather_json = jsonpickle.encode(weather("Midnight", "๐ถ")) - config_dic = { - "Supernova" : super_weather_json, - "Midnight": mid_weather_json - } - with open("weather_config.json", "w") as config_file: - json.dump(config_dic, config_file, indent=4) - with open("weather_config.json") as config_file: - weather_dic = {} - for weather_json in json.load(config_file).values(): - this_weather = jsonpickle.decode(weather_json, classes=weather) - weather_dic[this_weather.name] = this_weather - return weather_dic + weathers_dic = { + #"Supernova" : weather("Supernova", "๐"), + "Midnight": weather("Midnight", "๐ถ"), + "Slight Tailwind": weather("Slight Tailwind", "๐๏ธโโ๏ธ"), + "Heavy Snow": weather("Heavy Snow", "โ") + } + return weathers_dic class appearance_outcomes(Enum): @@ -102,10 +93,59 @@ class team(object): 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 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 add_lineup(self, new_player): if len(self.lineup) < 20: self.lineup.append(new_player) @@ -113,34 +153,58 @@ class team(object): else: return (False, "20 players in the lineup, maximum. We're being really generous here.") - def set_pitcher(self, new_player): - self.pitcher = new_player - return (True,) + 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.rotation: + temp_rotation.append(batter) + if rotation_slot is None: + self.pitcher = random.choice(temp_rotation) + else: + self.pitcher = temp_rotation[rotation_slot % len(temp_rotation)] def is_ready(self): - return (len(self.lineup) >= 1 and self.pitcher is not None) + 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 - return True + 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(): + self.set_pitcher() while len(self.lineup) <= 4: - self.lineup.append(random.choice(self.lineup)) - return True - else: + self.lineup.append(random.choice(self.lineup)) + return self + else: return False class game(object): - def __init__(self, name, team1, team2, length=None): - self.name = name + def __init__(self, team1, team2, length=None): self.over = False self.teams = {"away" : team1, "home" : team2} self.inning = 1 @@ -160,8 +224,13 @@ class game(object): 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): @@ -422,14 +491,26 @@ class game(object): def batterup(self): scores_to_add = 0 result = self.at_bat() - self.get_batter() 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"])-7)/7)**2)+1 + if random.random() > mulligan_roll_target: + 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 @@ -519,12 +600,21 @@ 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.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 + 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 { @@ -567,19 +657,9 @@ class game(object): else: inningtext = "bottom" - updatestring = f"{self.last_update[0]['batter']} {self.last_update[0]['text'].value} {self.last_update[0]['defender']}{punc}\n" + updatestring = "this isn't used but i don't want to break anything" - if self.last_update[1] > 0: - updatestring += f"{self.last_update[1]} runs scored!" - - return f"""Last update: {updatestring} - - Score: {self.teams['away'].score} - {self.teams['home'].score}. - Current inning: {inningtext} of {self.inning}. {self.outs} outs. - Pitcher: {self.get_pitcher().name} - Batter: {self.get_batter().name} - Bases: 3: {str(self.bases[3])} 2: {str(self.bases[2])} 1: {str(self.bases[1])} - """ + 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}""" @@ -619,20 +699,40 @@ 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: + 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) - return None - #except: - #return None + except: + return None def save_team(this_team, user_id): try: @@ -643,6 +743,15 @@ def save_team(this_team, user_id): 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(): @@ -653,8 +762,22 @@ def get_all_teams(): def search_team(search_term): teams = [] for team_pickle in db.search_teams(search_term): - this_team = jsonpickle.decode(team_pickle[0], keys=True, classes=team) - teams.append(this_team) + 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): @@ -674,6 +797,8 @@ class weather(object): 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}" \ No newline at end of file diff --git a/leagues.py b/leagues.py new file mode 100644 index 0000000..0551d91 --- /dev/null +++ b/leagues.py @@ -0,0 +1,127 @@ +import time, asyncio, jsonpickle, random, math +from games import team, game +from discord import Embed, Color +import database as db + + + + + +class league(object): + def __init__(self, name, subleagues_dic): + self.subleagues = {} #key: name, value: [divisions] + self.max_days + self.day = 1 + self.name = name + self.subleagues = subleagues_dic + +class division(object): + def __init__(self): + self.teams = {} #key: team object, value: {wins; rd (run diff)} + +class tournament(object): + def __init__(self, name, team_dic, series_length = 5, finals_series_length = 7, max_innings = 9, id = None, secs_between_games = 300, secs_between_rounds = 600): + self.name = name + self.teams = team_dic #same format as division, wins/losses will be used for seeding later + self.bracket = None + self.results = None + self.series_length = series_length + self.finals_length = finals_series_length + self.game_length = max_innings + self.active = False + self.delay = secs_between_games + self.round_delay = secs_between_rounds + self.finals = False + self.id = id + + if id is None: + self.id = random.randint(1111,9999) + else: + self.id = id + + + def build_bracket(self, random_sort = False, by_wins = False): + teams_list = list(self.teams.keys()).copy() + + if random_sort: + def sorter(team_in_list): + return random.random() + + elif by_wins: + def sorter(team_in_list): + return self.teams[team_in_list]["wins"] #sorts by wins + + else: #sort by average stars + def sorter(team_in_list): + return team_in_list.average_stars() + + teams_list.sort(key=sorter, reverse=True) + + + bracket_layers = int(math.ceil(math.log(len(teams_list), 2))) + empty_slots = int(math.pow(2, bracket_layers) - len(teams_list)) + + for i in range(0, empty_slots): + teams_list.append(None) + + previous_bracket_layer = teams_list.copy() + for i in range(0, bracket_layers - 1): + this_layer = [] + for pair in range(0, int(len(previous_bracket_layer)/2)): + if pair % 2 == 0: #if even number + this_layer.insert(0+int(pair/2), [previous_bracket_layer.pop(0), previous_bracket_layer.pop(-1)]) #every other pair goes at front of list, moving forward + else: + this_layer.insert(0-int((1+pair)/2), [previous_bracket_layer.pop(int(len(previous_bracket_layer)/2)-1), previous_bracket_layer.pop(int(len(previous_bracket_layer)/2))]) #every other pair goes at end of list, moving backward + previous_bracket_layer = this_layer + self.bracket = bracket(previous_bracket_layer, bracket_layers) + + def round_check(self): + if self.bracket.depth == 1: + self.finals = True + return True + else: + return False + +class bracket(object): + this_bracket = [] + + def __init__(self, bracket_list, depth): + self.this_bracket = bracket_list + self.depth = depth + self.bottom_row = [] + + def get_bottom_row(self): + self.depth = 1 + self.bottom_row = [] + self.dive(self.this_bracket) + return self.bottom_row + + def dive(self, branch): + if not isinstance(branch[0], list): #if it's a pair of games + self.bottom_row.append(branch) + else: + self.depth += 1 + return self.dive(branch[0]), self.dive(branch[1]) + + #def set_winners(self, branch, winners_list): + #new_bracket = + + def set_winners_dive(self, winners_list, index = 0, branch = None, parent = None): + if branch is None: + branch = self.this_bracket.copy() + if not isinstance(branch[0], list): #if it's a pair of games + if branch[0].name in winners_list or branch[1] is None: + winner = branch[0] + if parent is not None: + parent[index] = winner + elif branch[1].name in winners_list: + winner = branch[1] + if parent is not None: + parent[index] = winner + else: + self.set_winners_dive(winners_list, index = 0, branch = branch[0], parent = branch) + self.set_winners_dive(winners_list, index = 1, branch = branch[1], parent = branch) + + if parent is None: + self.this_bracket = branch + return branch \ No newline at end of file diff --git a/main_controller.py b/main_controller.py index 2191b75..2347120 100644 --- a/main_controller.py +++ b/main_controller.py @@ -1,4 +1,4 @@ -import asyncio, time, datetime, games, json, threading, jinja2 +import asyncio, time, datetime, games, json, threading, jinja2, leagues from flask import Flask, url_for, Response, render_template, request, jsonify from flask_socketio import SocketIO, emit @@ -9,8 +9,15 @@ socketio = SocketIO(app) @app.route('/') def index(): + if ('league' in request.args): + return render_template("index.html", league=request.args['league']) return render_template("index.html") +@app.route('/game') +def game_page(): + return render_template("game.html") + + thread2 = threading.Thread(target=socketio.run,args=(app,'0.0.0.0')) thread2.start() @@ -84,6 +91,19 @@ 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"] = "โ" + state["update_text"] = this_game.last_update[0]["text"] + else: updatestring = "" punc = "" @@ -121,14 +141,16 @@ def update_loop(): state["update_pause"] -= 1 global data_to_send - template = jinja2.Environment(loader=jinja2.FileSystemLoader('templates')).get_template('game.html') data_to_send = [] + template = jinja2.Environment(loader=jinja2.FileSystemLoader('templates')).get_template('game_box.html') + for timestamp in game_states: data_to_send.append({ 'timestamp' : timestamp, 'league' : game_states[timestamp]['leagueoruser'] if game_states[timestamp]['is_league'] else '', - 'html' : template.render(state=game_states[timestamp]) + 'state' : game_states[timestamp], + 'html' : template.render(state=game_states[timestamp], timestamp=timestamp) }) socketio.emit("states_update", data_to_send) - time.sleep(6) + time.sleep(8) diff --git a/static/css/common.css b/static/css/common.css new file mode 100644 index 0000000..df22339 --- /dev/null +++ b/static/css/common.css @@ -0,0 +1,69 @@ +@import url('https://fonts.googleapis.com/css2?family=Alegreya&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Goldman:wght@700&display=swap'); +body { + background-image: url("naturalblack.png"); +} +/* Background pattern from Toptal Subtle Patterns */ + +div, button, h1, h2, a { + font-family: 'Alegreya', serif; + color: white; +} + +h2 { + text-align: center; +} + +.link{ + position: relative; + top: 10px; +} + +.h1 { + margin: auto; + width: 45%; + color: white; +} + +.page_header { + color: white; + font-family: 'Goldman', cursive; + text-decoration: none; +} + +#header { + width: 100%; + height: max-content; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +#header .page_header { + margin: auto; +} + +#link_div { + text-align: right; + position: absolute; + top: 0px; + right: 30px; +} + +#link_div > a { + background-color: transparent; + text-decoration: underline; +} + +#link_div > a:link, #link_div > a:visited { + color: lightblue; +} + +#link_div > a:hover { + color: white; +} + +img.emoji { + height: 14px; +} \ No newline at end of file diff --git a/static/css/game.css b/static/css/game.css new file mode 100644 index 0000000..01624c2 --- /dev/null +++ b/static/css/game.css @@ -0,0 +1,232 @@ + +:root { + --background-main: #2f3136; /*discord dark theme background-secondary - the same color as the embeds*/ + --background-secondary: #4f545c; /*discord's background-tertiary*/ + --background-accent: #4f545c; /*discord's background-accent*/ + --highlight: rgb(113, 54, 138); /*matteo purpleโข*/ +} + +.game { + align-self: stretch; + justify-self: stretch; + text-align: center; + display: flex; + flex-direction: column; + background:var(--background-main); + border: 4px solid; + border-radius: 4px; + border-color: var(--highlight); + border-top: none; + border-right: none; + border-bottom: none; + height: max-content; +} + +.header { + width: 100%; + background-color: var(--background-secondary); + border-top-right-radius: 4px; + height: max-content; + display: flex; + justify-content: space-between +} + +.header > div { + margin: 0.3rem 0.5rem; +} + +.body { + margin: 0.5rem; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-areas: + "teams teams info" "players players info" "update update update"; + grid-template-rows: 5.5rem auto auto; + grid-row-gap: 0.5rem; + grid-column-gap: 0.75rem; +} + +.teams { + grid-area: teams; + display: flex; + flex-direction: column; + justify-content: space-around; + min-width: 95%; + max-width: 100%; + width: min-content; +} + +.team { + display: flex; + justify-content: space-between; + width: 100%; + margin: 0.25rem 0rem; +} + +.team_name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.info { + grid-area: info; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + background: #4f545c; + padding: 0.75rem 0rem; + border-radius: 4px; +} + +.batting { + font-size: 10pt; + text-align: left; + height: max-content; + margin: 0.3rem 0.5rem; +} + +.leagueoruser { + font-size: 10pt; + text-align: right; + height: max-content; + margin: 0.3rem 0.5rem; +} + +.footer { + display: flex; + justify-content: space-between; +} + +.outs { + display: flex; + justify-content: space-between; + align-items: center; + width: 60%; +} + +.outs_title { + font-weight: bolder; +} + +.outs_count { + display: flex; +} + +.out { + height: 20px; +} + +.team_name, .score { + font-size: 25px +} + +.score { + background: var(--background-accent); + width: 40px; + min-width: 40px; + height: 40px; + border-radius: 20px; + margin-left: 10px; +} + +.players { + grid-area: players; + display: grid; + grid-template-columns: auto minmax(0, 1fr); + grid-template-rows: auto auto; + grid-column-gap: 0.5rem; + margin-left: 0.3rem; +} + +.players > div { + margin: 0.25rem 0rem; +} + +.player_type { + text-align: end; + font-weight: bolder; + display: inline-block; + vertical-align: middle; +} + +.player_name { + overflow: hidden; + text-overflow: ellipsis; + text-align: start; + white-space: nowrap; + width: 100%; +} + +.update { + grid-area: update; + min-height: 3.5rem; + padding: 0rem 0.75rem; + height: 100%; + background: var(--background-secondary); + border-radius: 4px; + align-items: center; + display: flex; + justify-content: start; +} + +.update_emoji { + margin-right: 0.75rem; + margin-left: 2px; +} + +.update_text { + text-align: start; +} + +.field { + display: flex; + justify-content: space-around; + align-items: center; + flex-direction: column; +} + +.base { + height: 60px; +} + +.base_2 { + margin-bottom: -25% +} + +@media only screen and (max-device-width: 800px) { + .batting { + font-size: 15pt; + text-align: left; + height: max-content; + padding: 5px; + } + + .leagueoruser { + font-size: 15pt; + text-align: right; + height: max-content; + padding: 5px; + } + + .team_name, .score { + font-size: 23px + } + + .players { + font-size: 20px; + grid-area: players; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + height: 100%; + padding-right: 50px; + } + + .update_emoji, .update_text { + display: inline; + font-size: 20px; + } +} \ No newline at end of file diff --git a/static/css/game_page.css b/static/css/game_page.css new file mode 100644 index 0000000..e707284 --- /dev/null +++ b/static/css/game_page.css @@ -0,0 +1,9 @@ +#game_container { + margin-top: 50px; + display: flex; + justify-content: space-around; +} + +.game { + width: 33%; +} \ No newline at end of file diff --git a/static/css/games_page.css b/static/css/games_page.css new file mode 100644 index 0000000..f95f077 --- /dev/null +++ b/static/css/games_page.css @@ -0,0 +1,91 @@ +.container { + display: grid; + grid-template-columns: repeat(3, minmax(500px, 1fr)); + grid-gap: 50px 30px; /*space between rows, then columns*/ + align-items: center; + justify-items: center; + grid-auto-flow: row; +} + +.container > div { + min-height: 298px; +} + +#filters { + display: flex; + justify-content: center; + width: 100%; + align-items: center; + margin-top: 10px; + margin-bottom: 20px; +} + +#filters > * { + padding: 0.25rem 0.5rem; + margin: 0rem 0.5rem; + font-size: 16pt; + background: rgba(0,0,0,0); +} + +#filters > .filter { + border-radius: 0.5rem; + min-width: 6.25rem; + text-align: center; + border: none; + color: white; + text-decoration: none; +} + +#selected_filter { + background: rgb(113, 54, 138); +} + +#footer { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + height: 4.5rem; +} + +#footer > div { + text-align: center; + font-size: 16pt; + position: relative; + top: 0.25rem; +} + +.emptyslot { + border: 2px dashed white; + border-radius: 15px; + align-self: stretch; + justify-self: stretch; + text-align: center; + color: white; +} + +@media only screen and (max-device-width: 800px) { + .container { + display: grid; + grid-template-columns: repeat(1, minmax(700px, 90%)); + grid-template-rows: 400px; + grid-gap: 50px 30px; /*space between rows, then columns*/ + align-items: center; + justify-items: center; + grid-auto-rows: 400px; + grid-auto-flow: row; + position: absolute; + left: 50%; + transform: translate(-50%, 0); + } + + .emptyslot { + border: none; + border-radius: 15px; + align-self: stretch; + justify-self: stretch; + text-align: center; + color: white; + flex: 1; + } +} \ No newline at end of file diff --git a/static/css/naturalblack.png b/static/css/naturalblack.png new file mode 100644 index 0000000..e600af5 Binary files /dev/null and b/static/css/naturalblack.png differ diff --git a/static/prism.png b/static/css/prism.png similarity index 100% rename from static/prism.png rename to static/css/prism.png diff --git a/static/game.html b/static/game.html deleted file mode 100644 index 633fa17..0000000 --- a/static/game.html +++ /dev/null @@ -1,50 +0,0 @@ -