From 20fc9ba74c9bd9a781c2d5abacf92d9eb2b5a6a6 Mon Sep 17 00:00:00 2001 From: Astrid Date: Fri, 1 Jan 2021 22:30:13 +0100 Subject: [PATCH 01/39] Also send raw json state in the websocket --- main_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/main_controller.py b/main_controller.py index 2191b75..c439608 100644 --- a/main_controller.py +++ b/main_controller.py @@ -127,6 +127,7 @@ def update_loop(): data_to_send.append({ 'timestamp' : timestamp, 'league' : game_states[timestamp]['leagueoruser'] if game_states[timestamp]['is_league'] else '', + 'state' : game_states[timestamp], 'html' : template.render(state=game_states[timestamp]) }) From 500d807c83b75093c5c2dd130cffddb201491743 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Fri, 1 Jan 2021 17:03:04 -0500 Subject: [PATCH 02/39] css changes for accessibility --- static/games_page.css | 28 ++++++++++++++++++++++------ templates/game.html | 4 ++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/static/games_page.css b/static/games_page.css index e12b372..12e4d62 100644 --- a/static/games_page.css +++ b/static/games_page.css @@ -5,6 +5,13 @@ body { } /* Background pattern from Toptal Subtle Patterns */ +: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β„’*/ +} + div, button { font-family: 'Alegreya', serif; color: white; @@ -36,7 +43,7 @@ div, button { grid-gap: 50px 30px; /*space between rows, then columns*/ align-items: center; justify-items: center; - grid-auto-rows: 335px; + grid-auto-rows: 360px; grid-auto-flow: row; } @@ -129,10 +136,10 @@ div, button { text-align: center; display: flex; flex-direction: column; - background: #2f3136; /*discord dark theme background-secondary - the same color as the embeds*/ + background:var(--background-main); border: 4px solid; border-radius: 4px; - border-color: rgb(113, 54, 138); /*matteo purpleβ„’*/ + border-color: var(--highlight); border-top: none; border-right: none; border-bottom: none; @@ -147,7 +154,7 @@ h2 { .header { width: 100%; - background-color: #4f545c; /*discord's background-tertiary*/ + background-color: var(--background-secondary); border-top-right-radius: 4px; height: max-content; @@ -188,6 +195,7 @@ h2 { display: flex; justify-content: space-between; width: 100%; + margin: 5px 0px; } .team_name { @@ -252,7 +260,7 @@ h2 { } .score { - background: #4f545c; /*discord's background-accent*/ + background: var(--background-accent); width: 40px; min-width: 40px; height: 40px; @@ -274,6 +282,7 @@ h2 { align-items: end; width: 100%; flex-direction: column; + margin: 5px 0px; } .player_name { @@ -286,13 +295,20 @@ h2 { .update { grid-area: update; margin-right: 10px; - margin-top: 10px; + margin-top: 5px; min-height: 50px; + height: 100%; + background: var(--background-secondary); + border-radius: 4px; + align-items: center; + display: flex; + justify-content: center; } .player_type { width: 100%; text-align: start; + font-weight: bolder; } .update_emoji, .update_text { diff --git a/templates/game.html b/templates/game.html index cc52467..6e49d0a 100644 --- a/templates/game.html +++ b/templates/game.html @@ -38,11 +38,11 @@
-
Pitcher:
+
PITCHER
{{ state.pitcher }}
-
Batter:
+
BATTER
{{ state.batter }}
From 067fe13cd04e262d9826eb45a506302a71d8859c Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Fri, 1 Jan 2021 19:37:55 -0500 Subject: [PATCH 03/39] more css tweaks --- static/games_page.css | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/static/games_page.css b/static/games_page.css index 12e4d62..62ea548 100644 --- a/static/games_page.css +++ b/static/games_page.css @@ -43,10 +43,13 @@ div, button { grid-gap: 50px 30px; /*space between rows, then columns*/ align-items: center; justify-items: center; - grid-auto-rows: 360px; grid-auto-flow: row; } +.container > div { + min-height: 350px; +} + #header { width: 100%; height: 150px; @@ -125,7 +128,6 @@ div, button { justify-self: stretch; text-align: center; color: white; - flex: 1; } .game { @@ -175,12 +177,12 @@ h2 { .body { margin: 10px; display: grid; - grid-template-columns: 60% 40%; + grid-template-columns: 66% 33%; grid-template-areas: "teams info" "players info" "update update"; grid-template-rows: 90px; grid-row-gap: 8px; - grid-column-gap: 10px; + grid-column-gap: 14px; flex: 1; } @@ -189,6 +191,7 @@ h2 { display: flex; flex-direction: column; justify-content: space-around; + margin-right: 12px; } .team { @@ -200,6 +203,7 @@ h2 { .team_name { overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } @@ -210,9 +214,7 @@ h2 { align-items: center; justify-content: space-around; background: #4f545c; - padding-top: 8px; - padding-bottom: 4px; - margin-left: 15%; + padding: 25px 0px; margin-right: 10px; border-radius: 4px; } @@ -287,6 +289,7 @@ h2 { .player_name { overflow: hidden; + text-overflow: ellipsis; text-align: start; white-space: nowrap; width: 95%; @@ -297,12 +300,13 @@ h2 { margin-right: 10px; margin-top: 5px; min-height: 50px; + padding: 0px 10px; height: 100%; background: var(--background-secondary); border-radius: 4px; align-items: center; display: flex; - justify-content: center; + justify-content: start; } .player_type { @@ -311,8 +315,13 @@ h2 { font-weight: bolder; } -.update_emoji, .update_text { - display: inline +.update_emoji { + margin-right: 10px; + margin-left: 4px; +} + +.update_text { + text-align: start; } .field { From f0d56c2edd5739f2f187772d2896adfd263da343 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Fri, 1 Jan 2021 20:06:33 -0500 Subject: [PATCH 04/39] tiny bump to the left --- static/games_page.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/games_page.css b/static/games_page.css index 62ea548..566005e 100644 --- a/static/games_page.css +++ b/static/games_page.css @@ -317,7 +317,7 @@ h2 { .update_emoji { margin-right: 10px; - margin-left: 4px; + margin-left: 2px; } .update_text { From 07a7c5c3c32fa533039dd50d2d327f837db4ccfe Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sat, 2 Jan 2021 01:10:52 -0500 Subject: [PATCH 05/39] added rotations to teams, and automatic conversion. updated saveteam to new format, supporting rotation. games choose random pitcher --- database.py | 15 ++++++ games.py | 114 +++++++++++++++++++++++++++++++++++++------- leagues.py | 19 ++++++++ main_controller.py | 2 +- the-prestige.pyproj | 3 ++ the_prestige.py | 62 ++++++++++++++++++++---- 6 files changed, 188 insertions(+), 27 deletions(-) create mode 100644 leagues.py 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..a077faf 100644 --- a/games.py +++ b/games.py @@ -102,10 +102,26 @@ class team(object): self.name = None self.lineup = [] self.lineup_position = 0 + self.rotation = [] self.pitcher = None self.score = 0 self.slogan = None + def swap_player(self, name): + if len(self.lineup) > 1: + for index in range(0,len(self.lineup)): + if self.lineup[index].name == name: + if self.add_pitcher(self.lineup[index]): + self.lineup.pop(index) + return True + if len(self.rotation) > 1: + for index in range(0,len(self.rotation)): + if self.rotation[index].name == name: + if self.add_lineup(self.rotation[index])[0]: + self.rotation.pop(index) + return True + return False + def add_lineup(self, new_player): if len(self.lineup) < 20: self.lineup.append(new_player) @@ -113,27 +129,48 @@ 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) + 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 True def finalize(self): if self.is_ready(): + if self.pitcher is None: + 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 @@ -619,20 +656,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 +700,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,9 +719,23 @@ 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) - return teams + 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: diff --git a/leagues.py b/leagues.py new file mode 100644 index 0000000..bf44a2e --- /dev/null +++ b/leagues.py @@ -0,0 +1,19 @@ +import time, asyncio, jsonpickle +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 name, value: {wins; losses; run diff} + diff --git a/main_controller.py b/main_controller.py index c439608..91df7c2 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 diff --git a/the-prestige.pyproj b/the-prestige.pyproj index c9f6968..351afa6 100644 --- a/the-prestige.pyproj +++ b/the-prestige.pyproj @@ -29,6 +29,9 @@ Code + + Code + Code diff --git a/the_prestige.py b/the_prestige.py index 89584a2..239595b 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,4 +1,4 @@ -import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time +import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, leagues import database as db import onomancer as ono from flask import Flask @@ -169,7 +169,7 @@ class StartGameCommand(Command): return if team1 is not None and team2 is not None: - game = games.game(msg.author.name, team1, team2, length=innings) + game = games.game(msg.author.name, team1.finalize(), team2.finalize(), length=innings) channel = msg.channel await msg.delete() @@ -205,12 +205,19 @@ class SetupGameCommand(Command): class SaveTeamCommand(Command): name = "saveteam" - template = "m;saveteam [name] [slogan] [players]" + template = """m;saveteam + [name] + [slogan] + [lineup] + [rotation]""" + description = """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. + - 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. + - there must be another blank line between your batters and your pitchers. + - the final lines are your pitchers' names. if you did it correctly, you'll get a team embed with a prompt to confirm. hit the πŸ‘ and it'll be saved.""" async def execute(self, msg, command): @@ -287,6 +294,31 @@ class CreditCommand(Command): async def execute(self, msg, command): await msg.channel.send("Our avatar was graciously provided to us, with permission, by @HetreaSky on Twitter.") +class SwapPlayerCommand(Command): + name = "swap" + template = """m;swap + [team name] + [player name]""" + description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership." + + async def execute(self, msg, command): + team_name = command.split("\n")[1].strip() + player_name = command.split("\n")[2].strip() + team, owner_id = games.get_team_and_owner(team_name) + if team is None: + await msg.channel.send("Can't find that team, boss. Typo?") + return + elif owner_id != msg.author.id or msg.author.id not in config()["owners"]: + await msg.channel.send("You're not authorized to mess with this team. Sorry, boss.") + return + elif not team.swap_player(player_name): + await msg.channel.send("Either we can't find that player, or they're your last member of that side of the roster. Can't field an empty lineup, chief.") + return + else: + await msg.channel.send(embed=build_team_embed(team)) + games.update_team(team) + await msg.channel.send("Paperwork signed, stamped, and copied.") + class HelpCommand(Command): name = "help" template = "m;help [command]" @@ -353,6 +385,7 @@ commands = [ #SetupGameCommand(), SaveTeamCommand(), ImportCommand(), + SwapPlayerCommand(), DeleteTeamCommand(), ShowTeamCommand(), ShowAllTeamsCommand(), @@ -660,7 +693,10 @@ def build_team_embed(team): for player in team.lineup: lineup_string += f"{player.name} {player.star_string('batting_stars')}\n" - embed.add_field(name="Pitcher:", value=f"{team.pitcher.name} {team.pitcher.star_string('pitching_stars')}", inline = False) + rotation_string = "" + for player in team.rotation: + rotation_string += f"{player.name} {player.star_string('pitching_stars')}\n" + embed.add_field(name="Rotation:", value=rotation_string, inline = False) embed.add_field(name="Lineup:", value=lineup_string, inline = False) embed.set_footer(text=team.slogan) return embed @@ -710,14 +746,22 @@ def team_from_message(command): roster = command.split("\n",1)[1].split("\n") newteam.name = roster[0] #first line is team name newteam.slogan = roster[1] #second line is slogan - for rosternum in range(2,len(roster)-1): + if not roster[2].strip() == "": + raise CommandError("The third line should be blank. It wasn't, so just in case, we've not done anything on our end.") + pitchernum = len(roster)-2 + for rosternum in range(3,len(roster)-1): if roster[rosternum] != "": if len(roster[rosternum]) > 70: raise CommandError(f"{roster[rosternum]} is too long, chief. 70 or less.") newteam.add_lineup(games.player(ono.get_stats(roster[rosternum].rstrip()))) - if len(roster[len(roster)-1]) > 70: - raise CommandError(f"{roster[len(roster)-1]} is too long, chief. 70 or less.") - newteam.set_pitcher(games.player(ono.get_stats(roster[len(roster)-1].rstrip()))) #last line is pitcher name + else: + pitchernum = rosternum + 1 + break + + for rosternum in range(pitchernum, len(roster)): + if len(roster[rosternum]) > 70: + raise CommandError(f"{roster[len(roster)-1]} is too long, chief. 70 or less.") + newteam.add_pitcher(games.player(ono.get_stats(roster[rosternum].rstrip()))) if len(newteam.name) > 30: raise CommandError("Team names have to be less than 30 characters! Try again.") From 09ce4edb43172994fa0d5504d3f025b0d3eb8ff9 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Sat, 2 Jan 2021 01:52:51 -0500 Subject: [PATCH 06/39] give leagues their own page --- main_controller.py | 7 ++- static/game.html | 50 -------------------- static/games_page.css | 2 + static/loader.js | 106 ++++++++++++++++++++++++++---------------- templates/game.html | 8 ++-- templates/index.html | 3 +- 6 files changed, 80 insertions(+), 96 deletions(-) delete mode 100644 static/game.html diff --git a/main_controller.py b/main_controller.py index c439608..e8a5a75 100644 --- a/main_controller.py +++ b/main_controller.py @@ -11,13 +11,18 @@ socketio = SocketIO(app) def index(): return render_template("index.html") +@app.route('/league') +def league(): + print(request.args) + return render_template("index.html", league=request.args['name']) + thread2 = threading.Thread(target=socketio.run,args=(app,'0.0.0.0')) thread2.start() master_games_dic = {} #key timestamp : (game game, {} state) data_to_send = [] -@socketio.on("recieved") +@socketio.on("get_states") def handle_new_conn(data): socketio.emit("states_update", data_to_send, room=request.sid) 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 @@ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
-
-
-
OUTS
-
- - -
-
-
-
-
-
Pitcher:
-
-
-
-
Batter:
-
-
-
-
-
-
-
-
- \ No newline at end of file diff --git a/static/games_page.css b/static/games_page.css index 566005e..68c032a 100644 --- a/static/games_page.css +++ b/static/games_page.css @@ -83,6 +83,8 @@ div, button { min-width: 100px; text-align: center; border: none; + color: white; + text-decoration: none; } #selected_filter { diff --git a/static/loader.js b/static/loader.js index fa83ceb..53f1441 100644 --- a/static/loader.js +++ b/static/loader.js @@ -1,52 +1,20 @@ +var socket = io.connect(); + $(document).ready(function (){ - var socket = io.connect(); var lastupdate; var grid = document.getElementById("container"); socket.on('connect', function () { - socket.emit('recieved', { data: 'I\'m connected!' }); + socket.emit('get_states', {}); }); socket.on("states_update", function (json) { //json is an object containing all game updates + console.log(json) lastupdate = json; updateGames(json, $('#selected_filter').text()); - - //get all leagues - leagues = [] - for (var game of json) { - if (game.league != "" && !leagues.includes(game.league)) { - leagues.push(game.league) - } - } - - //remove leagues no longer present - $('#filters .filter').each(function(index) { - if (!leagues.includes($(this).text())) { - if ($(this).attr('id') != 'selected_filter' && $(this).text() != "All") { //don't remove the currently selected filter or the "all" filter - $(this).remove(); - } - } else { - leagues.splice(leagues.indexOf($(this).text()), 1); - } - }) - - // add leagues not already present - for (var league of leagues) { // we removed the entries that are already there in the loop above - $('#filters').append(""); - } - - //add click handlers to each filter - $('#filters .filter').each(function(index) { - $(this).click(function() { - if ($('#filters #selected_filter').text() == 'All') { - updateGames([], ""); // clear grid when switching off of All, to make games collapse to top - } - $('#filters #selected_filter').attr('id', ''); - $(this).attr('id', 'selected_filter'); - updateGames(lastupdate, $(this).text()); - }) - }) + updateLeagues(json); + document.getElementsByTagName("html")[0].style.visibility = "visible"; }); const updateGames = (json, filter) => { @@ -109,7 +77,7 @@ $(document).ready(function (){ } const insertEmpty = (grid) => { - newBox = document.createElement("DIV"); + var newBox = document.createElement("DIV"); newBox.className = "emptyslot"; grid.appendChild(newBox); } @@ -120,4 +88,62 @@ $(document).ready(function (){ thisBox.className = "game"; thisBox.timestamp = game.timestamp; }; -}); \ No newline at end of file + + const updateLeagues = (games) => { + //get all leagues + var leagues = [] + for (var game of games) { + if (game.league != "" && !leagues.includes(game.league)) { + leagues.push(game.league) + } + } + + //remove leagues no longer present + $('#filters .filter').each(function(index) { + if (!leagues.includes($(this).text())) { + if (this.id != 'selected_filter' && $(this).text() != "All") { //don't remove the currently selected filter or the "all" filter + $(this).remove(); + } + } else { + leagues.splice(leagues.indexOf($(this).text()), 1); + } + }) + + // add leagues not already present + for (var league of leagues) { // we removed the entries that are already there in the loop above + var btn = document.createElement("BUTTON"); + btn.className = "filter" + btn.innerHTML = league + $('#filters').append(btn); + } + + //add click handlers to each filter + $('#filters .filter').each(function(index) { + this.onclick = function() { + if ($('#filters #selected_filter').text() == 'All') { + updateGames([], ""); // clear grid when switching off of All, to make games collapse to top + } + $('#filters #selected_filter').attr('id', ''); + this.id = 'selected_filter'; + + var search = new URLSearchParams(); + search.append("name", this.textContent); + console.log("pushing state due to:"); + console.log(this); + history.pushState({'filters' : $('#filters').html()}, "", (this.textContent != 'All' ? "/league?" + search.toString() : "/")); + updateGames(lastupdate, this.textContent); + } + }) + } +}); + +window.onpopstate = function(e) { + console.log(e) + if (e.state) { + $('#filters').html(e.state.filters) + } else { + $('#filters').html("") + $('#filters').append("") + } + socket.emit('get_states', {}); +} \ No newline at end of file diff --git a/templates/game.html b/templates/game.html index 6e49d0a..9c0306f 100644 --- a/templates/game.html +++ b/templates/game.html @@ -1,5 +1,5 @@ {% macro base(number) -%} -{% if state.bases[number] %}/static/img/base_filled.png{% else %}/static/img/base_empty.png{% endif %} +src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases[number]}}"{% else %}"/static/img/base_empty.png"{% endif %} {%- endmacro %} {% macro out(number) -%} {% if number <= state.outs %}/static/img/out_out.png{% else %}/static/img/out_in.png{% endif %} @@ -22,10 +22,10 @@
- +
- - + +
diff --git a/templates/index.html b/templates/index.html index fc9e1ce..f9968a5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -23,7 +23,8 @@
Filter:
- + + {% if league %}{% endif %}
From 594fef822993cfbc784fe8c97d2b1ea88c1a075c Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Sat, 2 Jan 2021 02:29:03 -0500 Subject: [PATCH 07/39] use history.pushState to make site experience smoother --- main_controller.py | 2 +- static/loader.js | 266 ++++++++++++++++++++++----------------------- 2 files changed, 133 insertions(+), 135 deletions(-) diff --git a/main_controller.py b/main_controller.py index e8a5a75..9d995cc 100644 --- a/main_controller.py +++ b/main_controller.py @@ -22,7 +22,7 @@ thread2.start() master_games_dic = {} #key timestamp : (game game, {} state) data_to_send = [] -@socketio.on("get_states") +@socketio.on("recieved") def handle_new_conn(data): socketio.emit("states_update", data_to_send, room=request.sid) diff --git a/static/loader.js b/static/loader.js index 53f1441..9c07c32 100644 --- a/static/loader.js +++ b/static/loader.js @@ -1,149 +1,147 @@ var socket = io.connect(); +var lastupdate; +var grid; $(document).ready(function (){ - var lastupdate; - var grid = document.getElementById("container"); - + grid = document.getElementById("container") socket.on('connect', function () { - socket.emit('get_states', {}); + socket.emit('recieved', {}); }); - socket.on("states_update", function (json) { //json is an object containing all game updates - console.log(json) + socket.on("states_update", function (json) { //json is an object containing all game updates\ lastupdate = json; updateGames(json, $('#selected_filter').text()); updateLeagues(json); - document.getElementsByTagName("html")[0].style.visibility = "visible"; }); - - const updateGames = (json, filter) => { - - filterjson = []; - for (var game of json) { - if (game.league == filter || filter == "All") { - filterjson.push(game); - } - } - - if (filterjson.length == 0) { - $('#footer div').html("No games right now. Why not head over to Discord and start one?"); - } else { - $('#footer div').html(""); - } - - //replace games that have ended with empty slots - for (var slotnum = 0; slotnum < grid.children.length; slotnum++) { - if (grid.children[slotnum].className == "game" && !filterjson.some((x) => x.timestamp == grid.children[slotnum].timestamp)) { - grid.children[slotnum].className = "emptyslot"; - grid.children[slotnum].timestamp = null; - grid.children[slotnum].innerHTML = ""; - } - } - - for (var game of filterjson) { - //updates game in list - for (var slotnum = 0; slotnum < grid.children.length; slotnum++) { - if (grid.children[slotnum].timestamp == game.timestamp) { - insertGame(slotnum, game); - }; - }; - - //adds game to list if not there already - if (!Array.prototype.slice.call(grid.children).some((x) => x.timestamp == game.timestamp)) { - for (var slotnum = 0; true; slotnum++) { //this is really a while loop but shh don't tell anyone - if (slotnum >= grid.children.length) { - for (var i = 0; i < 3; i ++) { - insertEmpty(grid); - } - } - if (grid.children[slotnum].className == "emptyslot") { - insertGame(slotnum, game); - break; - }; - }; - } - }; - - //remove last rows if not needed - while (grid.children[grid.children.length-1].className == "emptyslot" && - grid.children[grid.children.length-2].className == "emptyslot" && - grid.children[grid.children.length-3].className == "emptyslot" && - grid.children.length > 3) { - for (var i = 0; i < 3; i++) { - grid.removeChild(grid.children[grid.children.length-1]); - } - } - } - - const insertEmpty = (grid) => { - var newBox = document.createElement("DIV"); - newBox.className = "emptyslot"; - grid.appendChild(newBox); - } - - const insertGame = (gridboxnum, game) => { - var thisBox = grid.children[gridboxnum]; - thisBox.innerHTML = game.html; - thisBox.className = "game"; - thisBox.timestamp = game.timestamp; - }; - - const updateLeagues = (games) => { - //get all leagues - var leagues = [] - for (var game of games) { - if (game.league != "" && !leagues.includes(game.league)) { - leagues.push(game.league) - } - } - - //remove leagues no longer present - $('#filters .filter').each(function(index) { - if (!leagues.includes($(this).text())) { - if (this.id != 'selected_filter' && $(this).text() != "All") { //don't remove the currently selected filter or the "all" filter - $(this).remove(); - } - } else { - leagues.splice(leagues.indexOf($(this).text()), 1); - } - }) - - // add leagues not already present - for (var league of leagues) { // we removed the entries that are already there in the loop above - var btn = document.createElement("BUTTON"); - btn.className = "filter" - btn.innerHTML = league - $('#filters').append(btn); - } - - //add click handlers to each filter - $('#filters .filter').each(function(index) { - this.onclick = function() { - if ($('#filters #selected_filter').text() == 'All') { - updateGames([], ""); // clear grid when switching off of All, to make games collapse to top - } - $('#filters #selected_filter').attr('id', ''); - this.id = 'selected_filter'; - - var search = new URLSearchParams(); - search.append("name", this.textContent); - console.log("pushing state due to:"); - console.log(this); - history.pushState({'filters' : $('#filters').html()}, "", (this.textContent != 'All' ? "/league?" + search.toString() : "/")); - updateGames(lastupdate, this.textContent); - } - }) - } }); -window.onpopstate = function(e) { - console.log(e) - if (e.state) { - $('#filters').html(e.state.filters) - } else { - $('#filters').html("") - $('#filters').append("") +const updateGames = (json, filter) => { + + filterjson = []; + for (var game of json) { + if (game.league == filter || filter == "All") { + filterjson.push(game); + } + } + + if (filterjson.length == 0) { + $('#footer div').html("No games right now. Why not head over to Discord and start one?"); + } else { + $('#footer div').html(""); + } + + //replace games that have ended with empty slots + for (var slotnum = 0; slotnum < grid.children.length; slotnum++) { + if (grid.children[slotnum].className == "game" && !filterjson.some((x) => x.timestamp == grid.children[slotnum].timestamp)) { + grid.children[slotnum].className = "emptyslot"; + grid.children[slotnum].timestamp = null; + grid.children[slotnum].innerHTML = ""; + } + } + + for (var game of filterjson) { + //updates game in list + for (var slotnum = 0; slotnum < grid.children.length; slotnum++) { + if (grid.children[slotnum].timestamp == game.timestamp) { + insertGame(slotnum, game); + }; + }; + + //adds game to list if not there already + if (!Array.prototype.slice.call(grid.children).some((x) => x.timestamp == game.timestamp)) { + for (var slotnum = 0; true; slotnum++) { //this is really a while loop but shh don't tell anyone + if (slotnum >= grid.children.length) { + for (var i = 0; i < 3; i ++) { + insertEmpty(grid); + } + } + if (grid.children[slotnum].className == "emptyslot") { + insertGame(slotnum, game); + break; + }; + }; + } + }; + + //remove last rows if not needed + while (grid.children[grid.children.length-1].className == "emptyslot" && + grid.children[grid.children.length-2].className == "emptyslot" && + grid.children[grid.children.length-3].className == "emptyslot" && + grid.children.length > 3) { + for (var i = 0; i < 3; i++) { + grid.removeChild(grid.children[grid.children.length-1]); + } + } +} + +const insertEmpty = (grid) => { + var newBox = document.createElement("DIV"); + newBox.className = "emptyslot"; + grid.appendChild(newBox); +} + +const insertGame = (gridboxnum, game) => { + var thisBox = grid.children[gridboxnum]; + thisBox.innerHTML = game.html; + thisBox.className = "game"; + thisBox.timestamp = game.timestamp; +}; + +const updateLeagues = (games) => { + //get all leagues + var leagues = [] + for (var game of games) { + if (game.league != "" && !leagues.includes(game.league)) { + leagues.push(game.league) + } + } + + //remove leagues no longer present + $('#filters .filter').each(function(index) { + if (!leagues.includes($(this).text())) { + if (this.id != 'selected_filter' && $(this).text() != "All") { //don't remove the currently selected filter or the "all" filter + $(this).remove(); + } + } else { + leagues.splice(leagues.indexOf($(this).text()), 1); + } + }) + + // add leagues not already present + for (var league of leagues) { // we removed the entries that are already there in the loop above + var btn = document.createElement("BUTTON"); + btn.className = "filter" + btn.innerHTML = league + $('#filters').append(btn); + } + + //add click handlers to each filter + $('#filters .filter').each(function(index) { + this.onclick = function() { + if ($('#filters #selected_filter').text() == 'All') { + updateGames([], ""); // clear grid when switching off of All, to make games collapse to top + } + $('#filters #selected_filter').attr('id', ''); + this.id = 'selected_filter'; + + var search = new URLSearchParams(); + search.append("name", this.textContent); + history.pushState({}, "", (this.textContent != 'All' ? "/league?" + search.toString() : "/")); + updateGames(lastupdate, this.textContent); + } + }) +} + +window.onpopstate = function(e) { + var searchparams = new URLSearchParams(window.location.search) + updateLeagues(lastupdate); + $('#filters #selected_filter').attr('id', ''); + if (searchparams.has('name')) { + $('#filters .filter').each(function(i) { if (this.textContent == searchparams.get('name')) { this.id = 'selected_filter' }}) + updateGames(lastupdate, searchparams.get('name')); + } else { + $('#filters .filter').each(function(i) { if (this.textContent == 'All') { this.id = 'selected_filter' }}) + updateGames(lastupdate, "All"); } - socket.emit('get_states', {}); } \ No newline at end of file From 5bd45bacf810e8e1ff535ee5c2309681d33e0cd5 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Sat, 2 Jan 2021 03:05:54 -0500 Subject: [PATCH 08/39] make startgame command post link to league --- main_controller.py | 1 - the_prestige.py | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/main_controller.py b/main_controller.py index 0fdfb38..01d35a5 100644 --- a/main_controller.py +++ b/main_controller.py @@ -13,7 +13,6 @@ def index(): @app.route('/league') def league(): - print(request.args) return render_template("index.html", league=request.args['name']) thread2 = threading.Thread(target=socketio.run,args=(app,'0.0.0.0')) diff --git a/the_prestige.py b/the_prestige.py index 239595b..31535e4 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,4 +1,4 @@ -import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, leagues +import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, leagues, urllib import database as db import onomancer as ono from flask import Flask @@ -646,7 +646,11 @@ async def watch_game(channel, newgame, user = None, league = None): discrim_string = "Unclaimed game." state_init["is_league"] = False - await channel.send(f"{newgame.teams['away'].name} vs. {newgame.teams['home'].name}, starting at {config()['simmadome_url']}") + if league is not None: + league_ext = "league?name=" + urllib.parse.quote_plus(league) + else: + league_ext = "" + await channel.send(f"{newgame.teams['away'].name} vs. {newgame.teams['home'].name}, starting at {config()['simmadome_url']+league_ext}") timestamp = str(time.time() * 1000.0) gamesarray.append((newgame, channel, user, timestamp)) From 00e4426e56fac4c87e21c6570c8745caeba50e87 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sat, 2 Jan 2021 04:02:57 -0500 Subject: [PATCH 09/39] added moveplayer, removeplayer, and addplayer for roster management --- games.py | 51 +++++++++++++++++++------- the_prestige.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 18 deletions(-) diff --git a/games.py b/games.py index a077faf..f2b0368 100644 --- a/games.py +++ b/games.py @@ -107,20 +107,45 @@ class team(object): 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 swap_player(self, name): - if len(self.lineup) > 1: - for index in range(0,len(self.lineup)): - if self.lineup[index].name == name: - if self.add_pitcher(self.lineup[index]): - self.lineup.pop(index) - return True - if len(self.rotation) > 1: - for index in range(0,len(self.rotation)): - if self.rotation[index].name == name: - if self.add_lineup(self.rotation[index])[0]: - self.rotation.pop(index) - return True + 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: @@ -735,7 +760,7 @@ def search_team(search_term): return None teams.append(team_json) - return teams + return teams def base_string(base): if base == 1: diff --git a/the_prestige.py b/the_prestige.py index 239595b..cfce42d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -295,8 +295,8 @@ class CreditCommand(Command): await msg.channel.send("Our avatar was graciously provided to us, with permission, by @HetreaSky on Twitter.") class SwapPlayerCommand(Command): - name = "swap" - template = """m;swap + name = "swapsection" + template = """m;swapsection [team name] [player name]""" description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership." @@ -308,16 +308,98 @@ class SwapPlayerCommand(Command): if team is None: await msg.channel.send("Can't find that team, boss. Typo?") return - elif owner_id != msg.author.id or msg.author.id not in config()["owners"]: + 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 elif not team.swap_player(player_name): - await msg.channel.send("Either we can't find that player, or they're your last member of that side of the roster. Can't field an empty lineup, chief.") + await msg.channel.send("Either we can't find that player, you've got no space on the other side, or they're your last member of that side of the roster. Can't field an empty lineup, and we *do* have rules, chief.") return else: await msg.channel.send(embed=build_team_embed(team)) games.update_team(team) - await msg.channel.send("Paperwork signed, stamped, and copied.") + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + +class MovePlayerCommand(Command): + name = "move" + template = """m;move + [team name] + [player name] + [new lineup/rotation position number] (indexed with 1 being the top)""" + description = "Moves a player in your lineup or rotation. Requires team ownership." + + async def execute(self, msg, command): + team_name = command.split("\n")[1].strip() + player_name = command.split("\n")[2].strip() + team, owner_id = games.get_team_and_owner(team_name) + try: + new_pos = int(command.split("\n")[3].strip()) + except ValueError: + await msg.channel.send("Hey, quit being cheeky. We're just trying to help. Third line has to be a natural number, boss.") + return + if 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 + elif not team.slide_player(player_name, new_pos): + await msg.channel.send("You either gave us a number that was bigger than your current roster, or we couldn't find the player on the team. Try again.") + return + else: + await msg.channel.send(embed=build_team_embed(team)) + games.update_team(team) + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + +class AddPlayerCommand(Command): + name = "addplayer" + template = """m;addplayer pitcher (or m;addplayer batter) + [team name] + [player name]""" + description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership." + + async def execute(self, msg, command): + team_name = command.split("\n")[1].strip() + player_name = command.split("\n")[2].strip() + team, owner_id = games.get_team_and_owner(team_name) + if 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 + + new_player = games.player(ono.get_stats(player_name)) + + if "batter" in command.split("\n")[0]: + if not team.add_lineup(new_player)[0]: + await msg.channel.send("Too many batters 🎢") + return + elif "pitcher" in command.split("\n")[0]: + if not team.add_pitcher(new_player): + await msg.channel.send("8 pitchers is quite enough, we think.") + return + + await msg.channel.send(embed=build_team_embed(team)) + games.update_team(team) + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + +class DeletePlayerCommand(Command): + name = "removeplayer" + template = """m;removeplayer + [team name] + [player name]""" + + async def execute(self, msg, command): + team_name = command.split("\n")[1].strip() + player_name = command.split("\n")[2].strip() + team, owner_id = games.get_team_and_owner(team_name) + if 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 + + if not team.delete_player(player_name): + await msg.channel.send("We've got bad news: that player isn't on your team. The good news is that... that player isn't on your team?") + return + + else: + await msg.channel.send(embed=build_team_embed(team)) + games.update_team(team) + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + class HelpCommand(Command): name = "help" @@ -386,6 +468,9 @@ commands = [ SaveTeamCommand(), ImportCommand(), SwapPlayerCommand(), + MovePlayerCommand(), + AddPlayerCommand(), + DeletePlayerCommand(), DeleteTeamCommand(), ShowTeamCommand(), ShowAllTeamsCommand(), From e2eb77b1efd8bd0695398ac9cfae1619233b9d3c Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sat, 2 Jan 2021 05:42:14 -0500 Subject: [PATCH 10/39] fully implemented heavy snow and slight tailwind weathers. removed supernova from the pool --- games.py | 67 ++++++++++++++++++++++++++-------------------- main_controller.py | 13 +++++++++ the_prestige.py | 3 +++ 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/games.py b/games.py index f2b0368..b8dafb0 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): @@ -222,8 +213,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): @@ -484,14 +480,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 @@ -581,12 +589,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 { @@ -629,19 +646,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}""" @@ -779,6 +786,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/main_controller.py b/main_controller.py index 01d35a5..3bf30f3 100644 --- a/main_controller.py +++ b/main_controller.py @@ -88,6 +88,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 = "" diff --git a/the_prestige.py b/the_prestige.py index 988bb8d..df06614 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -720,6 +720,9 @@ async def watch_game(channel, newgame, user = None, league = None): "start_delay" : 5, "end_delay" : 10 } + 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) if league is not None: discrim_string = league From 9a70ec02c12f56bee17eeb06e08e150c8d36a660 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Sat, 2 Jan 2021 06:21:53 -0500 Subject: [PATCH 11/39] add shareeable game links --- main_controller.py | 12 +- static/css/common.css | 64 +++++++++ static/{games_page.css => css/game.css} | 171 +----------------------- static/css/game_page.css | 9 ++ static/css/games_page.css | 91 +++++++++++++ static/{ => css}/prism.png | Bin static/js/game_loader.js | 23 ++++ static/{loader.js => js/grid_loader.js} | 2 +- templates/base.html | 30 +++++ templates/game.html | 66 ++------- templates/game_box.html | 56 ++++++++ templates/index.html | 42 ++---- 12 files changed, 312 insertions(+), 254 deletions(-) create mode 100644 static/css/common.css rename static/{games_page.css => css/game.css} (57%) create mode 100644 static/css/game_page.css create mode 100644 static/css/games_page.css rename static/{ => css}/prism.png (100%) create mode 100644 static/js/game_loader.js rename static/{loader.js => js/grid_loader.js} (99%) create mode 100644 templates/base.html create mode 100644 templates/game_box.html diff --git a/main_controller.py b/main_controller.py index 01d35a5..8875d38 100644 --- a/main_controller.py +++ b/main_controller.py @@ -12,9 +12,14 @@ def index(): return render_template("index.html") @app.route('/league') -def league(): +def league_page(): return render_template("index.html", league=request.args['name']) +@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() @@ -125,14 +130,15 @@ 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 '', 'state' : game_states[timestamp], - 'html' : template.render(state=game_states[timestamp]) + 'html' : template.render(state=game_states[timestamp], timestamp=timestamp) }) socketio.emit("states_update", data_to_send) diff --git a/static/css/common.css b/static/css/common.css new file mode 100644 index 0000000..056a08b --- /dev/null +++ b/static/css/common.css @@ -0,0 +1,64 @@ +@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("prism.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; +} + +#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; +} \ No newline at end of file diff --git a/static/games_page.css b/static/css/game.css similarity index 57% rename from static/games_page.css rename to static/css/game.css index 68c032a..2a3d921 100644 --- a/static/games_page.css +++ b/static/css/game.css @@ -1,9 +1,3 @@ -@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("prism.png"); -} -/* Background pattern from Toptal Subtle Patterns */ :root { --background-main: #2f3136; /*discord dark theme background-secondary - the same color as the embeds*/ @@ -12,129 +6,7 @@ body { --highlight: rgb(113, 54, 138); /*matteo purpleβ„’*/ } -div, button { - font-family: 'Alegreya', serif; - color: white; -} - -#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; -} - -.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: 350px; -} - -#header { - width: 100%; - height: 150px; - margin-bottom: 20px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -#header > .page_header { - margin: auto -} - -#filters { - display: flex; - justify-content: space-around; - width: max-content; - margin-top: 10px; -} - -#filters > * { - padding: 4px 8px; - margin: 0px 8px; - font-size: 16pt; - background: rgba(0,0,0,0); -} - -#filters > .filter { - border-radius: 8px; - min-width: 100px; - 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: 75px; -} - -#footer > div { - text-align: center; - font-size: 20px; - position: relative; - top: 5px; -} - -.link{ - position: relative; - top: 10px; -} - -.h1 { - margin: auto; - width: 45%; - color: white; - font-family: 'Alegreya', serif; -} - -.page_header { - color: white; - font-family: 'Goldman', cursive; -} - -.emptyslot { - border: 2px dashed white; - border-radius: 15px; - align-self: stretch; - justify-self: stretch; - text-align: center; - color: white; -} - .game { - font-family: 'Alegreya', serif; - color: white; align-self: stretch; justify-self: stretch; text-align: center; @@ -147,13 +19,7 @@ div, button { border-top: none; border-right: none; border-bottom: none; - flex: 1; -} - -h2 { - font-family: 'Alegreya', serif; - color: white; - text-align: center; + height: max-content; } .header { @@ -181,9 +47,11 @@ h2 { display: grid; grid-template-columns: 66% 33%; grid-template-areas: - "teams info" "players info" "update update"; - grid-template-rows: 90px; - grid-row-gap: 8px; + "teams info" + "players info" + "update update"; + grid-template-rows: 90px 85px; + grid-row-gap: 4px; grid-column-gap: 14px; flex: 1; } @@ -278,7 +146,6 @@ h2 { flex-direction: column; justify-content: space-around; align-items: start; - height: max-content; } .player { @@ -286,7 +153,6 @@ h2 { align-items: end; width: 100%; flex-direction: column; - margin: 5px 0px; } .player_name { @@ -295,6 +161,7 @@ h2 { text-align: start; white-space: nowrap; width: 95%; + margin-top: -4px; } .update { @@ -342,30 +209,6 @@ h2 { } @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; - } - .batting { font-size: 15pt; text-align: left; 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..d86bfdc --- /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: 325px; +} + +#filters { + display: flex; + justify-content: center; + width: 100%; + align-items: center; + margin-top: 10px; + margin-bottom: 20px; +} + +#filters > * { + padding: 4px 8px; + margin: 0px 8px; + font-size: 16pt; + background: rgba(0,0,0,0); +} + +#filters > .filter { + border-radius: 8px; + min-width: 100px; + 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: 75px; +} + +#footer > div { + text-align: center; + font-size: 20px; + position: relative; + top: 5px; +} + +.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/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/js/game_loader.js b/static/js/game_loader.js new file mode 100644 index 0000000..4890329 --- /dev/null +++ b/static/js/game_loader.js @@ -0,0 +1,23 @@ +$(document).ready(function (){ + var socket = io.connect(); + + socket.on('connect', function () { + console.log("connected") + socket.emit('recieved', {}); + }); + + socket.on("states_update", function (json) { //json is an object containing all game updates + console.log(json) + var searchparams = new URLSearchParams(window.location.search); + var exists = false; + for (game of json) { + if (searchparams.get('timestamp') == game.timestamp) { + $('.game').html(game.html); + exists = true; + } + } + if (!exists) { + // inform the user the game has ended + } + }); +}); \ No newline at end of file diff --git a/static/loader.js b/static/js/grid_loader.js similarity index 99% rename from static/loader.js rename to static/js/grid_loader.js index 9c07c32..ae14544 100644 --- a/static/loader.js +++ b/static/js/grid_loader.js @@ -9,7 +9,7 @@ $(document).ready(function (){ socket.emit('recieved', {}); }); - socket.on("states_update", function (json) { //json is an object containing all game updates\ + socket.on("states_update", function (json) { //json is an object containing all game updates lastupdate = json; updateGames(json, $('#selected_filter').text()); updateLeagues(json); diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..8eaaf00 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,30 @@ +ο»Ώ + + + + + + ⚾ The Simmadome + + + + + + + + {% block head_tags %}{% endblock %} + + + + + {% block body %}{% endblock %} + + diff --git a/templates/game.html b/templates/game.html index 9c0306f..944037e 100644 --- a/templates/game.html +++ b/templates/game.html @@ -1,57 +1,11 @@ -{% macro base(number) -%} -src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases[number]}}"{% else %}"/static/img/base_empty.png"{% endif %} -{%- endmacro %} -{% macro out(number) -%} -{% if number <= state.outs %}/static/img/out_out.png{% else %}/static/img/out_in.png{% endif %} -{%- endmacro %} - -
-
Inning: {% if state.display_top_of_inning == true %}πŸ”Ό{% else %}πŸ”½{% endif %} {{ state.display_inning }}/{{ state.max_innings }}
-
{{ state.weather_emoji }} {{ state.weather_text }}
-
-
-
-
-
{{ state.away_name }}
-
{{ state.away_score }}
-
-
-
{{ state.home_name }}
-
{{ state.home_score }}
-
+{% extends "base.html" %} +{% block head_tags %} + + + +{% endblock %} +{% block body %} +
+
-
-
- -
- - -
-
-
-
OUTS
-
- - -
-
-
-
-
-
PITCHER
-
{{ state.pitcher }}
-
-
-
BATTER
-
{{ state.batter }}
-
-
-
-
{{ state.update_emoji }}
-
{{ state.update_text }}
-
-
- \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/game_box.html b/templates/game_box.html new file mode 100644 index 0000000..39e18b9 --- /dev/null +++ b/templates/game_box.html @@ -0,0 +1,56 @@ +{% macro base(number) -%} +src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases[number]}}"{% else %}"/static/img/base_empty.png"{% endif %} +{%- endmacro %} +{% macro out(number) -%} +{% if number <= state.outs %}/static/img/out_out.png{% else %}/static/img/out_in.png{% endif %} +{%- endmacro %} +
+
Inning: {% if state.display_top_of_inning == true %}πŸ”Ό{% else %}πŸ”½{% endif %} {{ state.display_inning }}/{{ state.max_innings }}
+
{{ state.weather_emoji }} {{ state.weather_text }}
+
+
+
+
+
{{ state.away_name }}
+
{{ state.away_score }}
+
+
+
{{ state.home_name }}
+
{{ state.home_score }}
+
+
+
+
+ +
+ + +
+
+
+
OUTS
+
+ + +
+
+
+
+
+
PITCHER
+
{{ state.pitcher }}
+
+
+
BATTER
+
{{ state.batter }}
+
+
+
+
{{ state.update_emoji }}
+
{{ state.update_text }}
+
+
+ \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index f9968a5..0385b01 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,31 +1,14 @@ - - - - - - - - ⚾ The Simmadome - - - - - - - - - +
{% block body %}{% endblock %} diff --git a/templates/game_box.html b/templates/game_box.html index 39e18b9..c4d1d1d 100644 --- a/templates/game_box.html +++ b/templates/game_box.html @@ -6,6 +6,7 @@ src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases {%- endmacro %}
Inning: {% if state.display_top_of_inning == true %}πŸ”Ό{% else %}πŸ”½{% endif %} {{ state.display_inning }}/{{ state.max_innings }}
+
{{ state.title }}
{{ state.weather_emoji }} {{ state.weather_text }}
@@ -36,14 +37,10 @@ src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases
-
-
PITCHER
-
{{ state.pitcher }}
-
-
-
BATTER
-
{{ state.batter }}
-
+
PITCHER
+
{{ state.pitcher }}
+
BATTER
+
{{ state.batter }}
{{ state.update_emoji }}
From 1eaa1d3cd89415b224b7f34e23b0fc41b2d534a2 Mon Sep 17 00:00:00 2001 From: Elijah Steres Date: Sun, 3 Jan 2021 15:24:50 -0500 Subject: [PATCH 27/39] css fixes x_x --- static/css/common.css | 5 +++-- templates/base.html | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/static/css/common.css b/static/css/common.css index 6e179e5..df22339 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -28,6 +28,7 @@ h2 { .page_header { color: white; font-family: 'Goldman', cursive; + text-decoration: none; } #header { @@ -39,8 +40,8 @@ h2 { align-items: center; } -#header > .page_header { - margin: auto +#header .page_header { + margin: auto; } #link_div { diff --git a/templates/base.html b/templates/base.html index 19d0cce..da90c9e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -23,7 +23,7 @@ Github
Twitter
- + {% block body %}{% endblock %} From 222d2f2fba593b266477a9d4078581332fa9e910 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 16:29:56 -0500 Subject: [PATCH 28/39] fixed some inconsestencies with new commands --- the_prestige.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index 68578bc..96d2002 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -323,8 +323,8 @@ class SwapPlayerCommand(Command): await msg.channel.send("Three lines, remember? Command, then team, then name.") class MovePlayerCommand(Command): - name = "move" - template = """m;move + name = "moveplayer" + template = """m;moveplayer [team name] [player name] [new lineup/rotation position number] (indexed with 1 being the top)""" @@ -386,7 +386,7 @@ class AddPlayerCommand(Command): except IndexError: await msg.channel.send("Three lines, remember? Command, then team, then name.") -class DeletePlayerCommand(Command): +class RemovePlayerCommand(Command): name = "removeplayer" template = """m;removeplayer [team name] @@ -482,7 +482,7 @@ commands = [ SwapPlayerCommand(), MovePlayerCommand(), AddPlayerCommand(), - DeletePlayerCommand(), + RemovePlayerCommand(), DeleteTeamCommand(), ShowTeamCommand(), ShowAllTeamsCommand(), From 7191e3ca8cab428992c4b5a21cebe39dab8337e7 Mon Sep 17 00:00:00 2001 From: Genderdruid Date: Sun, 3 Jan 2021 13:35:09 -0800 Subject: [PATCH 29/39] Update README.md --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6e5ff86..c04dbac 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # 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 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 +custom players, custom teams, custom leagues (that last one is coming soonβ„’) all set up in discord and watchable at https://simsim.sibr.dev, all powered by this bot and onomancer. -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. @@ -16,38 +16,38 @@ accepting pull requests, check the issues for to-dos. #### 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 each of these in a new line: - - your team's name (cannot contain emoji). - - your team's icon and slogan, this should begin with an emoji followed by a space, followed by a short slogan. - - your batters' names in the order you want them to appear in your lineup, each in its own line, lineups can contain any number of batters between 1 and 12. + - saves a team to the database allowing it to be used for games. use this command at the top of a list with each of these in a new line: + - the team's name (cannot contain emoji). + - the team's icon and slogan, generally this is an emoji followed by a space, followed by a short slogan. - a blank line. - - your pitchers' names in the order you want them to appear in your rotation, rotations can contain any number of pitchers between 1 and 8. + - the batters' names in the order you want them to appear in your lineup, each in its own line. lineups can contain any number of batters between 1 and 12. + - a blank line. + - the pitchers' names in the order you want them to appear 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 it'll be saved. - 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. requires team ownership. + - allows you to delete the team with the provided name. requires team ownership. gives a confirmation first to prevent accidental deletions. - m;import - imports an onomancer collection as a new team. you can use the new onomancer simsim setting to ensure compatibility. -#### editing: --m;addplayer batter/pitcher [team name] [player name] - - adds a new player to your team, either in the lineup or rotation. requires team ownership. use addplayer batter of addplayer pitcher, depending on where you want them added to, at the top of a list with each of these in a new line: +#### editing (all of these commands require ownership of the team used): +- m;addplayer batter/pitcher [team name] [player name] + - adds a new player to the end of your team, either in the lineup or rotation depending on which version you use. use addplayer batter or addplayer pitcher at the top of a list with each of these in a new line: -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;move [team name] [player name] [new lineup/rotation position number] - - changes the position of a player in the lineup and rotation. requires team ownership. use this command at the top of a list with each of these in a new line: +- m;moveplayer [team name] [player name] [new lineup/rotation position number] + - changes the position of a player in the lineup and rotation. use this command at the top of a list with each of these in a new line: - 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. - [ASK WHAT THIS DOES TO THE REST OF THE TEAM, PUSHES THEM DOWN?] - m;swapsection [team name] [player name] - - swaps a player from the lineup to rotation or rotation to lineup. requires team ownership. use this command at the top of a list followed by each of these in a new line: + - swaps a player from the lineup to rotation or rotation to lineup. use this command at the top of a list followed by each of these in a new line: - the name of the team you want to swap the player on. - the name of the player you want to swap. - [ASK XVI ABOUT WHERE THEY GO SPECIFICALLY, POTENTIALLY EDIT CODE DESCRIPTION WITH ANSWER TOO] - m;removeplayer [team name] [player name] - [POINT OUT INCONSISTENT NAME IN CODE AND ALSO MAKE SURE THIS WORKS CORRECTLY WITH MULTIPLE INSTANCES OF THE SAME PLAYER AND MAKE SURE IT CHECKS IF THEY'RE THE LAST PLAYER] + - removes a player from your lineup or rotation. if there are multiple copies of the same player on a team this will delete the first one. use this command at the top of a list with each of these in a new line: - the name of the team you want to remove the player from. - - the name of the player you want to remove + - the name of the player you want to remove. + #### viewing and searching: - m;showteam [name] - shows information about any saved team. @@ -66,10 +66,10 @@ accepting pull requests, check the issues for to-dos. ### 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: + - 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: - 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. ### other commands: - m;help [command] From 5e00d0d27137fdc2bdd5d5974a7816548a68055d Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 16:48:51 -0500 Subject: [PATCH 30/39] moved the team fuzzy searching to a get_team_fuzzy_search for ease of coding --- main_controller.py | 2 +- the_prestige.py | 60 ++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/main_controller.py b/main_controller.py index d6ae7ff..65892a4 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 diff --git a/the_prestige.py b/the_prestige.py index 8bd079a..a3bb61d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,4 +1,4 @@ -import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib +import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues import database as db import onomancer as ono from flask import Flask @@ -127,32 +127,19 @@ class StartGameCommand(Command): innings = None try: team_name1 = command.split("\n")[1].strip() - team1 = games.get_team(team_name1) - if team1 is None: - teams = games.search_team(team_name1.lower()) - if len(teams) == 1: - team1 = teams[0] + team1 = get_team_fuzzy_search(team_name1) + team_name2 = command.split("\n")[2].strip() - team2 = games.get_team(team_name2) - if team2 is None: - teams = games.search_team(team_name2.lower()) - if len(teams) == 1: - team2 = teams[0] + team2 = get_team_fuzzy_search(team_name2) + innings = int(command.split("\n")[3]) except IndexError: try: team_name1 = command.split("\n")[1].strip() - team1 = games.get_team(team_name1) - if team1 is None: - teams = games.search_team(team_name1.lower()) - if len(teams) == 1: - team1 = teams[0] + team1 = get_team_fuzzy_search(team_name1) + team_name2 = command.split("\n")[2].strip() - team2 = games.get_team(team_name2) - if team2 is None: - teams = games.search_team(team_name2.lower()) - if len(teams) == 1: - team2 = teams[0] + team2 = get_team_fuzzy_search(team_name2) except IndexError: await msg.channel.send("We need at least three lines: startgame, away team, and home team are required. Optionally, the number of innings can go at the end, if you want a change of pace.") return @@ -164,8 +151,8 @@ class StartGameCommand(Command): await msg.channel.send("Anything less than 2 innings isn't even an outing. Try again.") return - elif innings is not None and innings > 30 and msg.author.id not in config()["owners"]: - await msg.channel.send("Y'all can't behave, so we've limited games to 30 innings. Ask xvi to start it with more if you really want to.") + elif innings is not None and innings > 200 and msg.author.id not in config()["owners"]: + await msg.channel.send("Y'all can behave, so we've upped the limit on game length to 200 innings.") return if team1 is not None and team2 is not None: @@ -230,15 +217,11 @@ class ShowTeamCommand(Command): async def execute(self, msg, command): team_name = command.strip() - team = games.get_team(team_name) + team = get_team_fuzzy_search(team_name) if team is not None: await msg.channel.send(embed=build_team_embed(team)) - else: - teams = games.search_team(team_name.lower()) - if len(teams) == 1: - await msg.channel.send(embed=build_team_embed(teams[0])) - else: - await msg.channel.send("Can't find that team, boss. Typo?") + return + await msg.channel.send("Can't find that team, boss. Typo?") class ShowAllTeamsCommand(Command): name = "showallteams" @@ -275,7 +258,7 @@ class SwapPlayerCommand(Command): template = """m;swapsection [team name] [player name]""" - description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership." + description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -304,7 +287,7 @@ class MovePlayerCommand(Command): [team name] [player name] [new lineup/rotation position number] (indexed with 1 being the top)""" - description = "Moves a player in your lineup or rotation. Requires team ownership." + description = "Moves a player in your lineup or rotation. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -334,7 +317,7 @@ class AddPlayerCommand(Command): template = """m;addplayer pitcher (or m;addplayer batter) [team name] [player name]""" - description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership." + description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -367,6 +350,7 @@ class RemovePlayerCommand(Command): template = """m;removeplayer [team name] [player name]""" + description = "Removes a player from your team. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -412,7 +396,7 @@ class HelpCommand(Command): class DeleteTeamCommand(Command): name = "deleteteam" template = "m;deleteteam [name]" - description = "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." + description = "Allows you to delete the team with the provided name. Requires team ownership. If you are the owner and the bot is telling you it's not yours, contact xvi and xie can assist." async def execute(self, msg, command): team_name = command.strip() @@ -1005,5 +989,13 @@ def game_over_embed(game): embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) embed.add_field(name="Final score:", value=winstring) return embed + +def get_team_fuzzy_search(team_name): + team = games.get_team(team_name) + if team is None: + teams = games.search_team(team_name.lower()) + if len(teams) == 1: + team = teams[0] + return team client.run(config()["token"]) From 1838e9198aa994a3f14337c9b672126bbfaa494e Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 16:57:41 -0500 Subject: [PATCH 31/39] added weather to discord embed at end of game --- the_prestige.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index a3bb61d..46adffb 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -743,6 +743,7 @@ async def start_tournament_round(channel, tourney, seeding = None): this_game, state_init = prepare_game(this_game) state_init["is_league"] = True + state_init["title"] = f"{tourney.name}: Round of {len(games_to_start)*2}" discrim_string = tourney.name print(discrim_string) @@ -973,9 +974,10 @@ async def game_watcher(): def game_over_embed(game): title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" if (game.inning - 1) > game.max_innings: #if extra innings - title_string += f" with {game.inning - (game.max_innings+1)} extra innings." + title_string += f" with {game.inning - (game.max_innings+1)} extra innings.\n" else: - title_string += "." + title_string += ".\n" + title_string += game.weather.emoji + game.weather.name winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" From 63db3ba1bece20d68c5622d6eff48809399ffad8 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 17:16:05 -0500 Subject: [PATCH 32/39] added handling for teams without slogans --- the_prestige.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/the_prestige.py b/the_prestige.py index 46adffb..817e95f 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -924,7 +924,10 @@ async def team_pages(msg, all_teams, search_term=None): embed.set_footer(text = f"Page {page+1} of {page_max}") for i in range(0,25): try: - embed.add_field(name=all_teams[i+25*page].name, value=all_teams[i+25*page].slogan) + if all_teams[i+25*page].slogan.strip() != "": + embed.add_field(name=all_teams[i+25*page].name, value=all_teams[i+25*page].slogan) + else: + embed.add_field(name=all_teams[i+25*page].name, value="404: Slogan not found") except: break pages.append(embed) From 56b5d9f45f755381a7c92819e361caf0245984a7 Mon Sep 17 00:00:00 2001 From: Genderdruid Date: Sun, 3 Jan 2021 14:28:11 -0800 Subject: [PATCH 33/39] Update README.md --- README.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c04dbac..67ed99f 100644 --- a/README.md +++ b/README.md @@ -1,60 +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, all powered by this bot and onomancer. +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! -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 +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. use this command at the top of a list with each of these in a new line: - - the team's name (cannot contain emoji). + - saves a team to the database allowing it to be used for games. use this command at the top of a list with each of these in a new line after: + - the team's name. - the team's icon and slogan, generally this is an emoji followed by a space, followed by a short slogan. - a blank line. - - the batters' names in the order you want them to appear in your lineup, each in its own line. lineups can contain any number of batters between 1 and 12. + - the batters' names in the order you want them to appear in your lineup, each on its own line. lineups can contain any number of batters between 1 and 12. - a blank line. - the pitchers' names in the order you want them to appear 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 it'll be saved. -- m;deleteteam [teamname] - - allows you to delete the team with the provided name. requires team ownership. gives a confirmation first to prevent accidental deletions. + - if you did everything 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. + - 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 of the team used): - m;addplayer batter/pitcher [team name] [player name] - - adds a new player to the end of your team, either in the lineup or rotation depending on which version you use. use addplayer batter or addplayer pitcher at the top of a list with each of these in a new line: + - 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 each of these in a new line after: -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] - - changes the position of a player in the lineup and rotation. use this command at the top of a list with each of these in a new line: + - changes the position of 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 each of these in a new line after: - 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. + - 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 the lineup to rotation or rotation to lineup. use this command at the top of a list followed by each of these in a new line: + - 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 followed by each of these in a new line after: - 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 lineup or rotation. if there are multiple copies of the same player on a team this will delete the first one. use this command at the top of a list with each of these in a new line: + - removes a player from your lineup or rotation. 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 each of these in a new line after: - 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;showallteams + - shows a paginated list of all teams available for games which can be scrolled through. ### player commands: - m;showplayer [name] @@ -66,14 +65,15 @@ accepting pull requests, check the issues for to-dos. ### 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: + - starts a game with premade teams made using saveteam. provide's a link to the website where you can watch the game. use this command at the top of a list with each of these in a new line after: - the away team's name. - the home team's name. - - 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. ### 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] From a2a6e9d7bbeb677a28d8c40f14ab43195596d544 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 17:59:13 -0500 Subject: [PATCH 34/39] added m;randomgame --- games.py | 7 ++++- the_prestige.py | 81 +++++++++++++++++++++++++++++++------------------ 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/games.py b/games.py index 8a375a4..b6763b2 100644 --- a/games.py +++ b/games.py @@ -171,7 +171,12 @@ class team(object): self.pitcher = temp_rotation[rotation_slot % len(temp_rotation)] def is_ready(self): - return (len(self.lineup) >= 1 and len(self.rotation) > 0) + 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 diff --git a/the_prestige.py b/the_prestige.py index 96d2002..675ddf0 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -179,6 +179,22 @@ class StartGameCommand(Command): await msg.channel.send("We can't find one or both of those teams. Check your staging, chief.") return +class StartRandomGameCommand(Command): + name = "randomgame" + template = "m;randomgame" + description = "Starts a 9-inning game between 2 entirely random teams. Embrace chaos." + + async def execute(self, msg, command): + channel = msg.channel + await msg.delete() + await channel.send("Rolling the bones...") + teamslist = games.get_all_teams() + + game = games.game(msg.author.name, random.choice(teamslist).finalize(), random.choice(teamslist).finalize(), length=3) + + game_task = asyncio.create_task(watch_game(channel, game, user="the winds of chaos")) + await game_task + class SetupGameCommand(Command): name = "setupgame" template = "m;setupgame" @@ -488,6 +504,7 @@ commands = [ ShowAllTeamsCommand(), SearchTeamsCommand(), StartGameCommand(), + StartRandomGameCommand(), CreditCommand(), RomanCommand(), HelpCommand(), @@ -740,7 +757,10 @@ async def watch_game(channel, newgame, user = None, league = None): discrim_string = league state_init["is_league"] = True elif user is not None: - discrim_string = f"Started by {user.name}" + if isinstance(user, str): + discrim_string = f"Started by {user}" + else: + discrim_string = f"Started by {user.name}" state_init["is_league"] = False else: discrim_string = "Unclaimed game." @@ -941,38 +961,41 @@ async def team_pages(msg, all_teams, search_term=None): async def game_watcher(): while True: - try: - this_array = gamesarray.copy() - for i in range(0,len(this_array)): - game, channel, user, key = this_array[i] - if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: - title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" - if (game.inning - 1) > game.max_innings: #if extra innings - title_string += f" with {game.inning - (game.max_innings+1)} extra innings." - else: - title_string += "." + #try: + this_array = gamesarray.copy() + for i in range(0,len(this_array)): + game, channel, user, key = this_array[i] + if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: + title_string = f"{game.teams['away'].name} at {game.teams['home'].name} ended after {game.inning-1} innings" + if (game.inning - 1) > game.max_innings: #if extra innings + title_string += f" with {game.inning - (game.max_innings+1)} extra innings." + else: + title_string += "." - winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name - winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" - if game.victory_lap and winning_team == game.teams['home'].name: - winstring += f"{winning_team} wins with a victory lap!" - elif winning_team == game.teams['home'].name: - winstring += f"{winning_team} wins, shaming {game.teams['away'].name}!" - else: - winstring += f"{winning_team} wins!" + winning_team = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name + winstring = f"{game.teams['away'].score} to {game.teams['home'].score}\n" + if game.victory_lap and winning_team == game.teams['home'].name: + winstring += f"{winning_team} wins with a victory lap!" + elif winning_team == game.teams['home'].name: + winstring += f"{winning_team} wins, shaming {game.teams['away'].name}!" + else: + winstring += f"{winning_team} wins!" - if user is not None: + if user is not None: + if isinstance(user, str): + await channel.send(f"A game from {user} just ended.") + else: await channel.send(f"{user.mention}'s game just ended.") - else: - await channel.send("A game started from this channel just ended.") + else: + await channel.send("A game started from this channel just ended.") - final_embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) - final_embed.add_field(name="Final score:", value=winstring) - await channel.send(embed=final_embed) - gamesarray.pop(i) - break - except: - print("something broke in game_watcher") + final_embed = discord.Embed(color=discord.Color.dark_purple(), title=title_string) + final_embed.add_field(name="Final score:", value=winstring) + await channel.send(embed=final_embed) + gamesarray.pop(i) + break + #except: + #print("something broke in game_watcher") await asyncio.sleep(6) From ff582d870998fc881b1dae3985aad324ec9b5056 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 23:18:45 -0500 Subject: [PATCH 35/39] finished implementing tournaments, slowed game ticks to 8 seconds --- games.py | 5 +- leagues.py | 55 +++++++++++-- main_controller.py | 2 +- the_prestige.py | 196 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 203 insertions(+), 55 deletions(-) diff --git a/games.py b/games.py index 3f38d7a..26ad1b3 100644 --- a/games.py +++ b/games.py @@ -185,12 +185,11 @@ class team(object): for this_player in self.rotation: for stat in this_player.game_stats.keys(): this_player.game_stats[stat] = 0 - return True + return self def finalize(self): if self.is_ready(): - if self.pitcher is None: - self.set_pitcher() + self.set_pitcher() while len(self.lineup) <= 4: self.lineup.append(random.choice(self.lineup)) return self diff --git a/leagues.py b/leagues.py index 8291c20..0551d91 100644 --- a/leagues.py +++ b/leagues.py @@ -20,14 +20,25 @@ class division(object): self.teams = {} #key: team object, value: {wins; rd (run diff)} class tournament(object): - def __init__(self, name, team_dic, series_length = 5, max_innings = 9): + 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() @@ -45,13 +56,13 @@ class tournament(object): 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) - print(teams_list) previous_bracket_layer = teams_list.copy() for i in range(0, bracket_layers - 1): @@ -60,19 +71,27 @@ class tournament(object): 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(0), previous_bracket_layer.pop(-1)]) #every other pair goes at end of list, moving backward + 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 - print(previous_bracket_layer) self.bracket = bracket(previous_bracket_layer, bracket_layers) - self.bracket.get_bottom_row() + + 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 @@ -81,4 +100,28 @@ class bracket(object): if not isinstance(branch[0], list): #if it's a pair of games self.bottom_row.append(branch) else: - return self.dive(branch[0]), self.dive(branch[1]) \ No newline at end of file + 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 65892a4..2347120 100644 --- a/main_controller.py +++ b/main_controller.py @@ -153,4 +153,4 @@ def update_loop(): }) socketio.emit("states_update", data_to_send) - time.sleep(6) + time.sleep(8) diff --git a/the_prestige.py b/the_prestige.py index 817e95f..a986fe4 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -427,25 +427,38 @@ class AssignOwnerCommand(Command): class StartTournamentCommand(Command): name = "starttournament" - template = "m;starttournament" - description = "We'll DM you and get your own tournament set up. Just follow our instructions and we'll be right as rain." + template = """m;starttournament + [tournament name] + [list of teams, each on a new line]""" + description = "Starts a tournament with the teams given. Byes will be given to teams to allow for numbers other than powers of two. The current tournament format is:\nBest of 5 until the finals, which are Best of 7" async def execute(self, msg, command): - test_bracket = { - games.get_team("Milwaukee Lockpicks") : {"wins": 10, "rd": 0}, - games.get_team("Madagascar Penguins") : {"wins": 2, "rd": 0}, - games.get_team("Twin Cities Evening") : {"wins": 1, "rd": 0}, - games.get_team("Washington State Houses") : {"wins": 9, "rd": 0}, - games.get_team("Appalachian Underground") : {"wins": 8, "rd": 0}, - games.get_team("Pacific2 Rams") : {"wins": 3, "rd": 0}, - games.get_team("New Jersey Radio") : {"wins": 45, "rd": 0}, - games.get_team("Moline Jolenes") : {"wins": 44, "rd": 0}, - games.get_team("California Commissioners") : {"wins": 41, "rd": 0}, - games.get_team("Pigeon’s Reckoning") : {"wins": 45, "rd": 0}, - games.get_team("Kernow Technologists") : {"wins": 42, "rd": 0} - } - tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) - tourney.build_bracket(by_wins=True) + to_parse = command.split("\n")[0] + if "--rounddelay " in to_parse: + try: + round_delay = int(to_parse.split("--rounddelay ")[1].split(" ")[0]) + except ValueError: + await msg.channel.send("The delay between rounds should be a whole number.") + return + if round_delay < 1 or round_delay > 120: + await msg.channel.send("The delay between rounds has to be between 1 and 120 minutes.") + else: + round_delay = 10 + + tourney_name = command.split("\n")[1] + list_of_team_names = command.split("\n")[2:] + team_dic = {} + for name in list_of_team_names: + team = get_team_fuzzy_search(name.strip()) + if team == None: + await msg.channel.send(f"We couldn't find {name}. Try again?") + return + team_dic[team] = {"wins": 0} + + id = random.randint(1111,9999) + + tourney = leagues.tournament(tourney_name, team_dic, id=id, secs_between_rounds = round_delay * 60) + tourney.build_bracket(random_sort = True) await start_tournament_round(msg.channel, tourney) @@ -477,7 +490,7 @@ commands = [ client = discord.Client() gamesarray = [] -gamesqueue = [] +active_tournaments = [] setupmessages = {} thread1 = threading.Thread(target=main_controller.update_loop) @@ -684,7 +697,10 @@ async def watch_game(channel, newgame, user = None, league = None): discrim_string = league state_init["is_league"] = True elif user is not None: - discrim_string = f"Started by {user.name}" + if isinstance(user, str): + discrim_string = f"Started by {user}" + else: + discrim_string = f"Started by {user.name}" state_init["is_league"] = False else: discrim_string = "Unclaimed game." @@ -739,42 +755,113 @@ async def start_tournament_round(channel, tourney, seeding = None): for pair in games_to_start: if pair[0] is not None and pair[1] is not None: - this_game = games.game(pair[0].finalize(), pair[1].finalize(), length = tourney.game_length) + this_game = games.game(pair[0].prepare_for_save().finalize(), pair[1].prepare_for_save().finalize(), length = tourney.game_length) this_game, state_init = prepare_game(this_game) state_init["is_league"] = True - state_init["title"] = f"{tourney.name}: Round of {len(games_to_start)*2}" + state_init["title"] = f"0 - 0" discrim_string = tourney.name - print(discrim_string) timestamp = str(time.time() * 1000.0 + random.randint(0,3000)) current_games.append((this_game, timestamp)) main_controller.master_games_dic[timestamp] = (this_game, state_init, discrim_string) ext = "?league=" + urllib.parse.quote_plus(tourney.name) - - await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") - await tourney_watcher(channel, tourney, current_games) - -async def tourney_watcher(channel, tourney, games_list): + if tourney.round_check(): #if finals + await channel.send(f"The {tourney.name} finals are starting now, at {config()['simmadome_url']+ext}") + finals = True + else: + await channel.send(f"{len(current_games)} games started for the {tourney.name} tournament, at {config()['simmadome_url']+ext}") + finals = False + await tourney_round_watcher(channel, tourney, current_games, config()['simmadome_url']+ext, finals) + +async def continue_tournament_series(tourney, queue, games_list, wins_in_series): + for oldgame in queue: + away_team = games.get_team(oldgame.teams["away"].name) + home_team = games.get_team(oldgame.teams["home"].name) + this_game = games.game(away_team.finalize(), home_team.finalize(), length = tourney.game_length) + this_game, state_init = prepare_game(this_game) + + state_init["is_league"] = True + + state_init["title"] = f"{wins_in_series[oldgame.teams['away'].name]} - {wins_in_series[oldgame.teams['home'].name]}" + + discrim_string = tourney.name + + timestamp = str(time.time() * 1000.0 + random.randint(0,3000)) + games_list.append((this_game, timestamp)) + main_controller.master_games_dic[timestamp] = (this_game, state_init, discrim_string) + + return games_list + +async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals = False): tourney.active = True - while len(games_list) > 0: - try: - for i in range(0, len(games_list)): - game, key = games_list[i] - if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: - final_embed = game_over_embed(game) - await channel.send(f"A {tourney.name} game just ended!") - await channel.send(embed=final_embed) - games_list.pop(i) - break - except: - print("something went wrong in tourney_watcher") + active_tournaments.append(tourney) + wins_in_series = {} + winner_list = [] + while tourney.active: + queued_games = [] + while len(games_list) > 0: + try: + for i in range(0, len(games_list)): + game, key = games_list[i] + if game.over and main_controller.master_games_dic[key][1]["end_delay"] <= 9: + if game.teams['home'].name not in wins_in_series.keys(): + wins_in_series[game.teams["home"].name] = 0 + if game.teams['away'].name not in wins_in_series.keys(): + wins_in_series[game.teams["away"].name] = 0 + + winner_name = game.teams['home'].name if game.teams['home'].score > game.teams['away'].score else game.teams['away'].name + + if winner_name in wins_in_series.keys(): + wins_in_series[winner_name] += 1 + else: + wins_in_series[winner_name] = 1 + + final_embed = game_over_embed(game) + await channel.send(f"A {tourney.name} game just ended!") + await channel.send(embed=final_embed) + if wins_in_series[winner_name] >= int((tourney.series_length+1)/2) and not finals: + winner_list.append(winner_name) + elif wins_in_series[winner_name] >= int((tourney.finals_length+1)/2): + winner_list.append(winner_name) + else: + queued_games.append(game) + + games_list.pop(i) + break + except: + print("something went wrong in tourney_watcher") + await asyncio.sleep(4) + + + if len(queued_games) > 0: + await channel.send(f"The next batch of games for {tourney.name} will start in {int(tourney.delay/60)} minutes.") + await asyncio.sleep(tourney.delay) + await channel.send(f"{len(queued_games)} games for {tourney.name}, starting at {filter_url}") + games_list = await continue_tournament_series(tourney, queued_games, games_list, wins_in_series) + else: + tourney.active = False + + if finals: #if this last round was finals + embed = discord.Embed(color = discord.Color.dark_purple(), title = f"{winner_list[0]} win the {tourney.name} finals!") + await channel.send(embed=embed) + active_tournaments.pop(active_tournaments.index(tourney)) + return + + tourney.bracket.set_winners_dive(winner_list) + + winners_string = "" + for game in tourney.bracket.get_bottom_row(): + winners_string += f"{game[0].name}\n{game[1].name}\n" + await channel.send(f""" +This round of games for {tourney.name} is now complete! The next round will be starting in {int(tourney.round_delay/60)} minutes. +Advancing teams: +{winners_string}""") + await asyncio.sleep(tourney.round_delay) + await start_tournament_round(channel, tourney) - await asyncio.sleep(4) - tourney.active = False - await channel.send(f"This round of games for {tourney.name} is now complete!") async def team_delete_confirm(channel, team, owner): @@ -843,8 +930,8 @@ def team_from_collection(newteam_json): raise CommandError("We've given you 100 characters for the slogan. Discord puts limits on us and thus, we put limits on you. C'est la vie.") if len(newteam_json["lineup"]) > 20: raise CommandError("20 players in the lineup, maximum. We're being really generous here.") - if not len(newteam_json["rotation"]) == 1: - raise CommandError("One and only one pitcher per team, thanks.") + if not len(newteam_json["rotation"]) > 8: + raise CommandError("8 pitchers on the rotation, max. That's a *lot* of pitchers.") for player in newteam_json["lineup"] + newteam_json["rotation"]: if len(player["name"]) > 70: raise CommandError(f"{player['name']} is too long, chief. 70 or less.") @@ -1003,4 +1090,23 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team -client.run(config()["token"]) + +#test_bracket = { +# "Milwaukee Lockpicks" : {"wins": 4, "rd": 0}, +# "Madagascar Penguins" : {"wins": 2, "rd": 0}, +# "Twin Cities Evening" : {"wins": 1, "rd": 0}, +# "Washington State Houses" : {"wins": 9, "rd": 0}, +# "Appalachian Underground" : {"wins": 8, "rd": 0}, +# "Pacific2 Rams" : {"wins": 3, "rd": 0}, +# "New Jersey Radio" : {"wins": 11, "rd": 0}, +# "Moline Jolenes" : {"wins": 6, "rd": 0}, +# "California Commissioners" : {"wins": 10, "rd": 0}, +# "Pigeon’s Reckoning" : {"wins": 7, "rd": 0}, +# "Kernow Technologists" : {"wins": 5, "rd": 0} +# } +#tourney = leagues.tournament("Test Tourney", test_bracket, max_innings=3) +#tourney.build_bracket(by_wins=True) +#tourney.bracket.set_winners_dive(['Twin Cities Evening','Madagascar Penguins', 'Pacific2 Rams']) +#print(tourney.bracket.this_bracket) + +client.run(config()["token"]) \ No newline at end of file From ee11aca3c0cec54bb23362cbdb27d58891037c87 Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 23:41:31 -0500 Subject: [PATCH 36/39] added a -d or --day flag to force rotation slots in games --- the_prestige.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index d401b2b..d357b4d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -115,14 +115,23 @@ class StartGameCommand(Command): async def execute(self, msg, command): league = None + day = None if config()["game_freeze"]: await msg.channel.send("Patch incoming. We're not allowing new games right now.") return if "-l " in command.split("\n")[0]: - league = command.split("\n")[0].split("-l ")[1] + league = command.split("\n")[0].split("-l ")[1].split("-")[0].strip() elif "--league " in command.split("\n")[0]: - league = command.split("\n")[0].split("--league ")[1] + league = command.split("\n")[0].split("--league ")[1].split("-")[0].strip() + try: + if "-d " in command.split("\n")[0]: + day = int(command.split("\n")[0].split("-d ")[1].split("-")[0].strip()) + elif "--day " in command.split("\n")[0]: + day = int(command.split("\n")[0].split("--day ")[1].split("-")[0].strip()) + except ValueError: + await msg.channel.send("Make sure you put an integer after the -d flag.") + return innings = None try: @@ -157,6 +166,9 @@ class StartGameCommand(Command): if team1 is not None and team2 is not None: game = games.game(team1.finalize(), team2.finalize(), length=innings) + if day is not None: + game.teams['away'].set_pitcher(rotation_slot = day) + game.teams['home'].set_pitcher(rotation_slot = day) channel = msg.channel await msg.delete() @@ -177,7 +189,7 @@ class StartRandomGameCommand(Command): await channel.send("Rolling the bones...") teamslist = games.get_all_teams() - game = games.game(msg.author.name, random.choice(teamslist).finalize(), random.choice(teamslist).finalize(), length=3) + game = games.game(random.choice(teamslist).finalize(), random.choice(teamslist).finalize()) game_task = asyncio.create_task(watch_game(channel, game, user="the winds of chaos")) await game_task From 864c1af655cf6783ad6dcbd0368d7ff29e5b533a Mon Sep 17 00:00:00 2001 From: Sakimori Date: Sun, 3 Jan 2021 23:43:04 -0500 Subject: [PATCH 37/39] made day=1 give first pitcher, not second --- the_prestige.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index d357b4d..07c2e14 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -126,9 +126,9 @@ class StartGameCommand(Command): league = command.split("\n")[0].split("--league ")[1].split("-")[0].strip() try: if "-d " in command.split("\n")[0]: - day = int(command.split("\n")[0].split("-d ")[1].split("-")[0].strip()) + day = int(command.split("\n")[0].split("-d ")[1].split("-")[0].strip())-1 elif "--day " in command.split("\n")[0]: - day = int(command.split("\n")[0].split("--day ")[1].split("-")[0].strip()) + day = int(command.split("\n")[0].split("--day ")[1].split("-")[0].strip())-1 except ValueError: await msg.channel.send("Make sure you put an integer after the -d flag.") return From 7a4f8d1f0f0f02de61d9254e438d996628e52ec4 Mon Sep 17 00:00:00 2001 From: Genderdruid Date: Sun, 3 Jan 2021 21:45:49 -0800 Subject: [PATCH 38/39] Update README.md --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67ed99f..e518393 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,22 @@ accepting pull requests, check the issues for to-dos. - displays your idol's name and stars. ### game commands: -- m;startgame - - starts a game with premade teams made using saveteam. provide's a link to the website where you can watch the game. use this command at the top of a list with each of these in a new line after: +- 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 each of these in a new line after: - the away team's name. - the home team's name. - 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 nescesary. all rounds are best of 5 until the finals which is best of 7. all series have a 5 minute break between games. + - 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 each of these in a new line after: + - the name of the tournament. + - the name of each participating team on its own line. ### other commands: - m;help [command] From 4e9e02ba12ca4d1076f5978cfcf78b46c2e06cc2 Mon Sep 17 00:00:00 2001 From: Genderdruid Date: Mon, 4 Jan 2021 08:42:02 -0800 Subject: [PATCH 39/39] updated the readme and the help commands to keep them consistent when it makes sense --- README.md | 38 +++++++++++++++++++------------------- the_prestige.py | 30 +++++++++++++++--------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index e518393..04105fa 100644 --- a/README.md +++ b/README.md @@ -15,35 +15,35 @@ accepting pull requests, check the issues for to-dos. #### creation and deletion: - m;saveteam - - saves a team to the database allowing it to be used for games. use this command at the top of a list with each of these in a new line after: - - the team's name. - - the team's icon and slogan, generally this is an emoji followed by a space, followed by a short slogan. - - a blank line. - - the batters' names in the order you want them to appear in your lineup, each on its own line. lineups can contain any number of batters between 1 and 12. - - a blank line. - - the pitchers' names in the order you want them to appear in your rotation. rotations can contain any number of pitchers between 1 and 8. - - if you did everything correctly you'll get a team embed with a prompt to confirm. hit the πŸ‘ and your team will be saved! + - 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. + - 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 of the team used): +#### 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 each of these in a new line after: - -the name of the team you want to add the player to. - -the name of the player you want to add to the team. + - 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] - - changes the position of 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 each of these in a new line after: + - 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 followed by each of these in a new line after: + - 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 lineup or rotation. 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 each of these in a new line after: + - 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. @@ -61,13 +61,13 @@ 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 --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 each of these in a new line after: + - 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. - optionally, the number of innings, which must be greater than 2 and less than 31. if not included it will default to 9. @@ -75,9 +75,9 @@ accepting pull requests, check the issues for to-dos. - 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 nescesary. all rounds are best of 5 until the finals which is best of 7. all series have a 5 minute break between games. + - 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 each of these in a new line after: + - 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. diff --git a/the_prestige.py b/the_prestige.py index 07c2e14..e5949cc 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -99,7 +99,7 @@ class ShowIdolCommand(Command): class ShowPlayerCommand(Command): name = "showplayer" template = "m;showplayer [name]" - description = "Displays any name's stars in a nice discord embed, there's a limit of 70 characters. That should be *plenty*. Note: if you want to lookup a lot of different players you can do it on onomancer instead of spamming this command a bunch and clogging up discord: https://onomancer.sibr.dev/reflect" + description = "Displays any name's stars in a nice discord embed, there's a limit of 70 characters. That should be *plenty*. Note: if you want to lookup a lot of different players you can do it on onomancer here instead of spamming this command a bunch and clogging up discord: " async def execute(self, msg, command): player_name = json.loads(ono.get_stats(command.split(" ",1)[1])) @@ -108,7 +108,7 @@ class ShowPlayerCommand(Command): class StartGameCommand(Command): name = "startgame" template = "m;startgame [away] [home] [innings]" - description ="""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 (shift+enter in discord, or copy+paste from notepad): + description ="""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 (shift+enter in discord, or copy+paste from notepad) (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.): - 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.""" @@ -181,7 +181,7 @@ class StartGameCommand(Command): class StartRandomGameCommand(Command): name = "randomgame" template = "m;randomgame" - description = "Starts a 9-inning game between 2 entirely random teams. Embrace chaos." + description = "Starts a 9-inning game between 2 entirely random teams. Embrace chaos!" async def execute(self, msg, command): channel = msg.channel @@ -227,13 +227,13 @@ class SaveTeamCommand(Command): [rotation]""" description = """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. + - 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. - there must be another blank line between your batters and your pitchers. - - the final lines are your pitchers' names. -if you did it correctly, you'll get a team embed with a prompt to confirm. hit the πŸ‘ and it'll be saved.""" + - 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!""" async def execute(self, msg, command): if db.get_team(command.split('\n',1)[1].split("\n")[0]) == None: @@ -248,7 +248,7 @@ if you did it correctly, you'll get a team embed with a prompt to confirm. hit t class ImportCommand(Command): name = "import" template = "m;import [onomancer collection URL]" - description = "Imports an onomancer collection as a new team. You can use the new onomancer simsim setting to ensure compatibility." + description = "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!" async def execute(self, msg, command): team_raw = ono.get_collection(command.strip()) @@ -265,7 +265,7 @@ class ImportCommand(Command): class ShowTeamCommand(Command): name = "showteam" template = "m;showteam [name]" - description = "Shows information about any saved team." + description = "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." async def execute(self, msg, command): team_name = command.strip() @@ -310,7 +310,7 @@ class SwapPlayerCommand(Command): template = """m;swapsection [team name] [player name]""" - description = "Swaps a player from lineup to rotation, or from rotation to lineup. Requires team ownership and exact spelling of team name." + description = "Swaps a player from your lineup to the end of your rotation or your rotation to the end of your lineup. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -339,7 +339,7 @@ class MovePlayerCommand(Command): [team name] [player name] [new lineup/rotation position number] (indexed with 1 being the top)""" - description = "Moves a player in your lineup or rotation. Requires team ownership and exact spelling of team name." + description = "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. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -369,7 +369,7 @@ class AddPlayerCommand(Command): template = """m;addplayer pitcher (or m;addplayer batter) [team name] [player name]""" - description = "Recruits a new player to your team, as either a pitcher or a batter. Requires team ownership and exact spelling of team name." + description = "Adds a new player to the end of your team, either in the lineup or the rotation depending on which version you use. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -402,7 +402,7 @@ class RemovePlayerCommand(Command): template = """m;removeplayer [team name] [player name]""" - description = "Removes a player from your team. Requires team ownership and exact spelling of team name." + description = "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. Requires team ownership and exact spelling of team name." async def execute(self, msg, command): try: @@ -448,7 +448,7 @@ class HelpCommand(Command): class DeleteTeamCommand(Command): name = "deleteteam" template = "m;deleteteam [name]" - description = "Allows you to delete the team with the provided name. Requires team ownership. If you are the owner and the bot is telling you it's not yours, contact xvi and xie can assist." + description = "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.. Requires team ownership. If you are the owner and the bot is telling you it's not yours, contact xvi and xie can assist." async def execute(self, msg, command): team_name = command.strip() @@ -482,7 +482,7 @@ class StartTournamentCommand(Command): template = """m;starttournament [tournament name] [list of teams, each on a new line]""" - description = "Starts a tournament with the teams given. Byes will be given to teams to allow for numbers other than powers of two. The current tournament format is:\nBest of 5 until the finals, which are Best of 7" + description = "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 and by default there is a 10 minute break between rounds. The current tournament format is:\nBest of 5 until the finals, which are Best of 7." async def execute(self, msg, command): to_parse = command.split("\n")[0]