From 2718f79c19b842ff09f5b76141d6090c853286f0 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 3 Jan 2021 15:19:48 -0800 Subject: [PATCH 01/11] Draft controller class --- .gitignore | 2 + onomancer.py | 18 +++++++ the_draft.py | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 the_draft.py diff --git a/.gitignore b/.gitignore index 72644a6..6b7e8e2 100644 --- a/.gitignore +++ b/.gitignore @@ -350,3 +350,5 @@ matteo.db matteo.db-wal matteo.db-shm /matteo_env/Lib/site-packages/flask_socketio/__init__.py + +env diff --git a/onomancer.py b/onomancer.py index f270a48..2031ec9 100644 --- a/onomancer.py +++ b/onomancer.py @@ -7,6 +7,7 @@ import database as db onomancer_url = "https://onomancer.sibr.dev/api/" name_stats_hook = "getOrGenerateStats?name=" collection_hook = "getCollection?token=" +names_hook = "getNames" def get_stats(name): player = db.get_stats(name) @@ -36,3 +37,20 @@ def get_collection(collection_url): db.cache_stats(player['name'], json.dumps(player)) return json.dumps(response.json()) + + +def get_names(limit=20, threshold=1): + """ + Get `limit` random players that have at least `threshold` upvotes. + Returns dictionary keyed by player name of stats. + """ + response = requests.get( + onomancer_url + names_hook, + params={ + 'limit': limit, + 'threshold': threshold, + 'with_stats': 1, + 'random': 1, + }, + ) + return {p['name']: p for p in response.json()} diff --git a/the_draft.py b/the_draft.py new file mode 100644 index 0000000..94fba62 --- /dev/null +++ b/the_draft.py @@ -0,0 +1,145 @@ +from collections import namedtuple +import games +import uuid + +import onomancer + +DRAFT_SIZE = 20 +REFRESH_DRAFT_SIZE = 4 # fewer players remaining than this and the list refreshes +DRAFT_ROUNDS = 13 + +Participant = namedtuple('Participant', ['handle', 'team']) +BOOKMARK = Participant(handle="bookmark", team=None) # keep track of start/end of draft round + + +class Draft: + """ + Represents a draft party with n participants constructing their team from a pool + of names. + """ + + _ongoing_drafts = {} + + @classmethod + def make_draft(cls): + draft = cls() + cls._ongoing_drafts[draft._id] = draft + return draft + + def __init__(self): + self._id = str(uuid.uuid4())[:6] + self._participants = [] + self._active_participant = BOOKMARK # draft mutex + self._players = onomancer.get_names(limit=DRAFT_SIZE) + self._round = 0 + + @property + def round(self): + """ + Current draft round. 1 indexed. + """ + return self._round + + @property + def active_drafter(self): + """ + Handle of whomever is currently up to draft. + """ + return self._active_participant.handle + + def add_participant(self, handle, team_name, slogan): + """ + A participant is someone participating in this draft. Initializes an empty team for them + in memory. + + `handle`: discord @ handle, for ownership and identification + """ + team = games.team() + team.name = team_name + team.slogan = slogan + self._participants.append(Participant(handle=handle, team=team)) + + def start_draft(self): + """ + Call after adding all participants and confirming they're good to go. + """ + self.advance_draft() + + def refresh_players(self): + self._players = onomancer.get_names(limit=DRAFT_SIZE) + + def advance_draft(self): + """ + The participant list is treated as a circular queue with the head being popped off + to act as the draftign mutex. + """ + if self._active_participant == BOOKMARK: + self._round += 1 + self._participants.append(self._active_participant) + self._active_participant = self._participants.pop(0) + + def get_draftees(self): + return list(self._players.keys()) + + def draft_player(self, handle, player_name): + """ + `handle` is the participant's discord handle. + """ + if self._active_participant.handle != handle: + raise ValueError('Invalid drafter') + + player = self._players.get(player_name) + if not player: + # might be some whitespace shenanigans + for name, stats in self._players.items(): + if name.replace('\xa0', ' ') == player_name: + player = stats + break + else: + # still not found + raise ValueError('Player not in draft list') + del self._players[player['name']] + + if len(self._players) <= REFRESH_DRAFT_SIZE: + self.refresh_players() + + if self._round < DRAFT_ROUNDS: + self._active_participant.team.add_lineup(player['name']) + elif self._round == DRAFT_ROUNDS: + self._active_participant.team.set_pitcher(player['name']) + + self.advance_draft() + if self._active_participant == BOOKMARK: + self.advance_draft() + + return player + + def get_teams(self): + teams = {} + teams[self._active_participant.handle] = self._active_participant.team + for participant in self._participants: + teams[participant.handle] = participant.team + return teams + + +if __name__ == '__main__': + # extremely robust testing OC do not steal + # DRAFT_ROUNDS = 3 + draft = Draft.make_draft() + draft.add_participant('@bluh', 'Bluhstein Bluhs', 'bluh bluh bluh') + draft.add_participant('@what', 'Barcelona IDK', 'huh') + draft.start_draft() + + while draft.round <= DRAFT_ROUNDS: + print(draft.get_draftees()) + cmd = input(f'{draft.round} {draft.active_drafter}:') + drafter, player = cmd.split(' ', 1) + try: + draft.draft_player(drafter, player) + except ValueError as e: + print(e) + print(draft.get_teams()) + print(draft.get_teams()['@bluh'].lineup) + print(draft.get_teams()['@bluh'].pitcher) + print(draft.get_teams()['@what'].lineup) + print(draft.get_teams()['@what'].pitcher) From e22a1b3345aa12612b3b263ee8ffffed5c29a284 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 3 Jan 2021 15:30:00 -0800 Subject: [PATCH 02/11] filter bookmark out of teams --- the_draft.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/the_draft.py b/the_draft.py index 94fba62..aa50892 100644 --- a/the_draft.py +++ b/the_draft.py @@ -116,15 +116,17 @@ class Draft: def get_teams(self): teams = {} - teams[self._active_participant.handle] = self._active_participant.team + if self._active_participant != BOOKMARK: + teams[self._active_participant.handle] = self._active_participant.team for participant in self._participants: - teams[participant.handle] = participant.team + if participant != BOOKMARK: + teams[participant.handle] = participant.team return teams if __name__ == '__main__': # extremely robust testing OC do not steal - # DRAFT_ROUNDS = 3 + DRAFT_ROUNDS = 3 draft = Draft.make_draft() draft.add_participant('@bluh', 'Bluhstein Bluhs', 'bluh bluh bluh') draft.add_participant('@what', 'Barcelona IDK', 'huh') From 996d35ec06852883886673cafe0c0c2e0d0c09b8 Mon Sep 17 00:00:00 2001 From: joe Date: Sun, 3 Jan 2021 15:41:36 -0800 Subject: [PATCH 03/11] more whitepace handling --- the_draft.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/the_draft.py b/the_draft.py index aa50892..bab2b05 100644 --- a/the_draft.py +++ b/the_draft.py @@ -88,11 +88,13 @@ class Draft: if self._active_participant.handle != handle: raise ValueError('Invalid drafter') + player_name = player_name.strip() + player = self._players.get(player_name) if not player: # might be some whitespace shenanigans for name, stats in self._players.items(): - if name.replace('\xa0', ' ') == player_name: + if name.replace('\xa0', ' ').strip() == player_name: player = stats break else: From 5bc2c520ea42c19abe03769947882bb9622f9e3b Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 15:52:34 -0800 Subject: [PATCH 04/11] discord commands --- games.py | 2 +- the_draft.py | 18 ++++--- the_prestige.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/games.py b/games.py index 8ca356d..b89d970 100644 --- a/games.py +++ b/games.py @@ -806,4 +806,4 @@ class weather(object): self.counter_home = 0 def __str__(self): - return f"{self.emoji} {self.name}" \ No newline at end of file + return f"{self.emoji} {self.name}" diff --git a/the_draft.py b/the_draft.py index bab2b05..4b424d3 100644 --- a/the_draft.py +++ b/the_draft.py @@ -1,5 +1,6 @@ from collections import namedtuple import games +import json import uuid import onomancer @@ -18,12 +19,9 @@ class Draft: of names. """ - _ongoing_drafts = {} - @classmethod def make_draft(cls): draft = cls() - cls._ongoing_drafts[draft._id] = draft return draft def __init__(self): @@ -86,7 +84,7 @@ class Draft: `handle` is the participant's discord handle. """ if self._active_participant.handle != handle: - raise ValueError('Invalid drafter') + raise ValueError(f'{self._active_participant.handle} is drafting, not you') player_name = player_name.strip() @@ -99,16 +97,16 @@ class Draft: break else: # still not found - raise ValueError('Player not in draft list') + raise ValueError(f'Player `{player_name}` not in draft list') del self._players[player['name']] if len(self._players) <= REFRESH_DRAFT_SIZE: self.refresh_players() if self._round < DRAFT_ROUNDS: - self._active_participant.team.add_lineup(player['name']) + self._active_participant.team.add_lineup(games.player(json.dumps(player))) elif self._round == DRAFT_ROUNDS: - self._active_participant.team.set_pitcher(player['name']) + self._active_participant.team.set_pitcher(games.player(json.dumps(player))) self.advance_draft() if self._active_participant == BOOKMARK: @@ -125,6 +123,12 @@ class Draft: teams[participant.handle] = participant.team return teams + def finish_draft(self): + for handle, team in self.get_teams().items(): + success = games.save_team(team, int(handle[3:-1])) + if not success: + raise Exception(f'Error saving team for {handle}') + if __name__ == '__main__': # extremely robust testing OC do not steal diff --git a/the_prestige.py b/the_prestige.py index 87a1959..6f80414 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1,6 +1,8 @@ import discord, json, math, os, roman, games, asyncio, random, main_controller, threading, time, urllib, leagues import database as db import onomancer as ono +import random +from the_draft import Draft, DRAFT_ROUNDS from flask import Flask from uuid import uuid4 @@ -14,6 +16,12 @@ class Command: async def execute(self, msg, command): return +class DraftError(Exception): + pass + +class SlowDraftError(DraftError): + pass + class CommandError(Exception): pass @@ -572,6 +580,119 @@ class StartTournamentCommand(Command): await start_tournament_round(channel, tourney) +class DraftPlayerCommand(Command): + name = "draft" + template = "m;draft [playername]" + description = "On your turn during a draft, use this command to pick your player." + + async def execute(self, msg, command): + """ + This is a no-op definition. `StartDraftCommand` handles the orchestration directly, + this is just here to provide a help entry and so the command dispatcher recognizes it + as valid. + """ + pass + + +class StartDraftCommand(Command): + name = "startdraft" + template = "m;startdraft [mention] [teamname] [slogan]" + description = """Starts a draft with an arbitrary number of participants. Send this command at the +top of the list with each mention, teamname, and slogan on a new line (shift+enter in discord). + - The draft will proceed in the order that participants were entered. + - 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small. + - Each participant will be asked to draft 12 hitters then finally one pitcher. + - The draft will start only once every participant has given a 👍 to begin. + - use the command `m;draft` on your turn to draft someone + """ + + async def execute(self, msg, command): + draft = Draft.make_draft() + mentions = {f'<@!{m.id}>' for m in msg.mentions} + content = msg.content.split('\n')[1:] # drop command out of message + if len(content) % 3: + await msg.channel.send('Invalid list') + raise ValueError('Invalid length') + + for i in range(0, len(content), 3): + handle = content[i].strip() + team_name = content[i + 1].strip() + if games.get_team(team_name): + await msg.channel.send(f'Sorry {handle}, {team_name} already exists') + raise ValueError('Existing team') + slogan = content[i + 2].strip() + draft.add_participant(handle, team_name, slogan) + + success = await self.wait_start(msg.channel, mentions) + if not success: + return + + draft.start_draft() + while draft.round <= DRAFT_ROUNDS: + choosing = 'pitcher' if draft.round == DRAFT_ROUNDS else 'hitter' + await msg.channel.send( + f'Round {draft.round}/{DRAFT_ROUNDS}: {draft.active_drafter}, choose a {choosing}.', + embed=build_draft_embed(draft.get_draftees()), + ) + try: + draft_message = await self.wait_draft(msg.channel, draft) + draft.draft_player(f'<@!{draft_message.author.id}>', draft_message.content.split(' ', 1)[1]) + except SlowDraftError: + player = random.choice(draft.get_draftees()) + await msg.channel.send(f"I'm not waiting forever. You get {player}. Next") + draft.draft_player(draft.active_drafter, player) + except ValueError as e: + await msg.channel.send(str(e)) + + for handle, team in draft.get_teams().items(): + await msg.channel.send(f'{handle} behold your team', embed=build_team_embed(team)) + try: + draft.finish_draft() + except Exception as e: + await msg.channel.send(str(e)) + + async def wait_start(self, channel, mentions): + start_msg = await channel.send('Are we good to go? ' + ' '.join(mentions)) + await start_msg.add_reaction("👍") + await start_msg.add_reaction("👎") + + def react_check(react, user): + return f'<@!{user.id}>' in mentions and react.message == start_msg + + while True: + try: + react, _ = await client.wait_for('reaction_add', timeout=60.0, check=react_check) + if react.emoji == "👎": + await channel.send("Got it, stopping the draft") + return False + if react.emoji == "👍": + reactors = set() + async for user in react.users(): + reactors.add(f'<@!{user.id}>') + if reactors.intersection(mentions) == mentions: + return True + except asyncio.TimeoutError: + await channel.send("Y'all aren't ready.") + return False + return False + + async def wait_draft(self, channel, draft): + + def check(m): + if m.channel != channel: + return False + for prefix in config()['prefix']: + if m.content.startswith(prefix + 'draft'): + return True + return False + + try: + draft_message = await client.wait_for('message', timeout=120.0, check=check) + except asyncio.TimeoutError: + raise SlowDraftError('Too slow') + return draft_message + + commands = [ IntroduceCommand(), CountActiveGamesCommand(), @@ -596,6 +717,8 @@ commands = [ CreditCommand(), RomanCommand(), HelpCommand(), + StartDraftCommand(), + DraftPlayerCommand(), ] client = discord.Client() @@ -1015,6 +1138,16 @@ async def team_delete_confirm(channel, team, owner): return +def build_draft_embed(names): + embed = discord.Embed(color=discord.Color.purple(), title="The Draft") + column_size = 7 + for i in range(0, len(names), column_size): + draft = '\n'.join(names[i:i + column_size]) + embed.add_field(name="-", value=draft, inline=True) + embed.set_footer(text="You must choose") + return embed + + def build_team_embed(team): embed = discord.Embed(color=discord.Color.purple(), title=team.name) lineup_string = "" From 56a1b06d28b223076465db75b828309b5ac72b8f Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 15:54:28 -0800 Subject: [PATCH 05/11] rebase latest master --- the_prestige.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index 6f80414..654614d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -597,7 +597,7 @@ class DraftPlayerCommand(Command): class StartDraftCommand(Command): name = "startdraft" template = "m;startdraft [mention] [teamname] [slogan]" - description = """Starts a draft with an arbitrary number of participants. Send this command at the + description = """Starts a draft with an arbitrary number of participants. Send this command at the top of the list with each mention, teamname, and slogan on a new line (shift+enter in discord). - The draft will proceed in the order that participants were entered. - 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small. @@ -636,7 +636,7 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent ) try: draft_message = await self.wait_draft(msg.channel, draft) - draft.draft_player(f'<@!{draft_message.author.id}>', draft_message.content.split(' ', 1)[1]) + draft.draft_player(f'<@!{draft_message.author.id}>', draft_message.content.split(' ', 1)[1]) except SlowDraftError: player = random.choice(draft.get_draftees()) await msg.channel.send(f"I'm not waiting forever. You get {player}. Next") @@ -1349,4 +1349,22 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team +#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"]) From ba70ac4f313c5cc85b18071ac4ac791f4fe3fd22 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 19:19:12 -0800 Subject: [PATCH 06/11] some fixes --- onomancer.py | 30 ++++++++++++++++++++++++++---- the_prestige.py | 15 ++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/onomancer.py b/onomancer.py index 2031ec9..278d128 100644 --- a/onomancer.py +++ b/onomancer.py @@ -1,6 +1,8 @@ #interfaces with onomancer import requests, json, urllib +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry import database as db @@ -9,13 +11,27 @@ name_stats_hook = "getOrGenerateStats?name=" collection_hook = "getCollection?token=" names_hook = "getNames" + +def _retry_session(retries=3, backoff=0.3, status=(500, 501, 502, 503, 504)): + session = requests.Session() + retry = Retry( + total=retries, + read=retries, + connect=retries, + backoff_factor=backoff, + status_forcelist=status, + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount('https://', adapter) + return session + def get_stats(name): player = db.get_stats(name) if player is not None: return player #returns json_string #yell at onomancer if not in cache or too old - response = requests.get(onomancer_url + name_stats_hook + urllib.parse.quote_plus(name)) + response = _retry_session().get(onomancer_url + name_stats_hook + urllib.parse.quote_plus(name)) if response.status_code == 200: stats = json.dumps(response.json()) db.cache_stats(name, stats) @@ -31,7 +47,7 @@ def get_scream(username): return scream def get_collection(collection_url): - response = requests.get(onomancer_url + collection_hook + urllib.parse.quote(collection_url)) + response = _retry_session().get(onomancer_url + collection_hook + urllib.parse.quote(collection_url)) if response.status_code == 200: for player in response.json()['lineup'] + response.json()['rotation']: db.cache_stats(player['name'], json.dumps(player)) @@ -44,7 +60,7 @@ def get_names(limit=20, threshold=1): Get `limit` random players that have at least `threshold` upvotes. Returns dictionary keyed by player name of stats. """ - response = requests.get( + response = _retry_session().get( onomancer_url + names_hook, params={ 'limit': limit, @@ -53,4 +69,10 @@ def get_names(limit=20, threshold=1): 'random': 1, }, ) - return {p['name']: p for p in response.json()} + response.raise_for_status() + res = {} + for stats in response.json(): + name = stats['name'] + db.cache_stats(name, json.dumps(stats)) + res[name] = stats + return res diff --git a/the_prestige.py b/the_prestige.py index 654614d..5f0e424 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -603,7 +603,7 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent - 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small. - Each participant will be asked to draft 12 hitters then finally one pitcher. - The draft will start only once every participant has given a 👍 to begin. - - use the command `m;draft` on your turn to draft someone + - use the command `d`, `draft`, or `m;draft` on your turn to draft someone """ async def execute(self, msg, command): @@ -615,11 +615,18 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent raise ValueError('Invalid length') for i in range(0, len(content), 3): - handle = content[i].strip() + handle_token = content[i].strip() + for mention in mentions: + if mention in handle_token: + handle = mention + break + else: + await msg.channel.send(f"I don't recognize {handle_token}") + return team_name = content[i + 1].strip() if games.get_team(team_name): await msg.channel.send(f'Sorry {handle}, {team_name} already exists') - raise ValueError('Existing team') + return slogan = content[i + 2].strip() draft.add_participant(handle, team_name, slogan) @@ -681,6 +688,8 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent def check(m): if m.channel != channel: return False + if m.content.startswith('d') or m.content.startswith('draft'): + return True for prefix in config()['prefix']: if m.content.startswith(prefix + 'draft'): return True From b17d3a30425918223bf3398f6c9f53e42651d2ad Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 20:32:42 -0800 Subject: [PATCH 07/11] pitcher fix --- the_draft.py | 6 +++++- the_prestige.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/the_draft.py b/the_draft.py index 4b424d3..aec71c4 100644 --- a/the_draft.py +++ b/the_draft.py @@ -45,6 +45,10 @@ class Draft: """ return self._active_participant.handle + @property + def active_drafting_team(self): + return self._active_participant.team.name + def add_participant(self, handle, team_name, slogan): """ A participant is someone participating in this draft. Initializes an empty team for them @@ -106,7 +110,7 @@ class Draft: if self._round < DRAFT_ROUNDS: self._active_participant.team.add_lineup(games.player(json.dumps(player))) elif self._round == DRAFT_ROUNDS: - self._active_participant.team.set_pitcher(games.player(json.dumps(player))) + self._active_participant.team.add_pitcher(games.player(json.dumps(player))) self.advance_draft() if self._active_participant == BOOKMARK: diff --git a/the_prestige.py b/the_prestige.py index 5f0e424..9635345 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -636,9 +636,9 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent draft.start_draft() while draft.round <= DRAFT_ROUNDS: - choosing = 'pitcher' if draft.round == DRAFT_ROUNDS else 'hitter' + choosing = '⚾️pitcher⚾️' if draft.round == DRAFT_ROUNDS else '🏏hitter🏏' await msg.channel.send( - f'Round {draft.round}/{DRAFT_ROUNDS}: {draft.active_drafter}, choose a {choosing}.', + f'Round {draft.round}/{DRAFT_ROUNDS}: {draft.active_drafter}, choose a {choosing} for {draft.active_drafting_team}.', embed=build_draft_embed(draft.get_draftees()), ) try: From b76c7de8bc9ef1615413053f350fbd40a0073592 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 21:41:51 -0800 Subject: [PATCH 08/11] text tweaks --- the_draft.py | 8 ++++---- the_prestige.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/the_draft.py b/the_draft.py index aec71c4..8795a1b 100644 --- a/the_draft.py +++ b/the_draft.py @@ -119,16 +119,16 @@ class Draft: return player def get_teams(self): - teams = {} + teams = [] if self._active_participant != BOOKMARK: - teams[self._active_participant.handle] = self._active_participant.team + teams.append((self._active_participant.handle, self._active_participant.team)) for participant in self._participants: if participant != BOOKMARK: - teams[participant.handle] = participant.team + teams.append((self._active_participant.handle, self._active_participant.team)) return teams def finish_draft(self): - for handle, team in self.get_teams().items(): + for handle, team in self.get_teams(): success = games.save_team(team, int(handle[3:-1])) if not success: raise Exception(f'Error saving team for {handle}') diff --git a/the_prestige.py b/the_prestige.py index 9635345..4572be3 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -639,7 +639,7 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent choosing = '⚾️pitcher⚾️' if draft.round == DRAFT_ROUNDS else '🏏hitter🏏' await msg.channel.send( f'Round {draft.round}/{DRAFT_ROUNDS}: {draft.active_drafter}, choose a {choosing} for {draft.active_drafting_team}.', - embed=build_draft_embed(draft.get_draftees()), + embed=build_draft_embed(draft.get_draftees(), footer=f"{choosing[0]}You must choose"), ) try: draft_message = await self.wait_draft(msg.channel, draft) @@ -651,7 +651,7 @@ top of the list with each mention, teamname, and slogan on a new line (shift+ent except ValueError as e: await msg.channel.send(str(e)) - for handle, team in draft.get_teams().items(): + for handle, team in draft.get_teams(): await msg.channel.send(f'{handle} behold your team', embed=build_team_embed(team)) try: draft.finish_draft() @@ -1147,13 +1147,13 @@ async def team_delete_confirm(channel, team, owner): return -def build_draft_embed(names): - embed = discord.Embed(color=discord.Color.purple(), title="The Draft") +def build_draft_embed(names, title="The Draft", footer="You must choose"): + embed = discord.Embed(color=discord.Color.purple(), title=title) column_size = 7 for i in range(0, len(names), column_size): draft = '\n'.join(names[i:i + column_size]) embed.add_field(name="-", value=draft, inline=True) - embed.set_footer(text="You must choose") + embed.set_footer(text=footer) return embed From c65d81a5e6cdbfaf82b261db4b9ec625f7a0a2f7 Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 22:10:06 -0800 Subject: [PATCH 09/11] fix help text --- the_prestige.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index 4572be3..3a57660 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -597,8 +597,7 @@ class DraftPlayerCommand(Command): class StartDraftCommand(Command): name = "startdraft" template = "m;startdraft [mention] [teamname] [slogan]" - description = """Starts a draft with an arbitrary number of participants. Send this command at the -top of the list with each mention, teamname, and slogan on a new line (shift+enter in discord). + description = """Starts a draft with an arbitrary number of participants. Send this command at the top of the list with each mention, teamname, and slogan on a new line (shift+enter in discord). - The draft will proceed in the order that participants were entered. - 20 players will be available for draft at a time, and the pool will refresh automatically when it becomes small. - Each participant will be asked to draft 12 hitters then finally one pitcher. From c15f500058c290211aca84c7a8a45bd8e5903a0a Mon Sep 17 00:00:00 2001 From: joe Date: Mon, 4 Jan 2021 23:38:53 -0800 Subject: [PATCH 10/11] Final tweaks --- the_draft.py | 10 +++------- the_prestige.py | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/the_draft.py b/the_draft.py index 8795a1b..cfb4cbc 100644 --- a/the_draft.py +++ b/the_draft.py @@ -96,7 +96,7 @@ class Draft: if not player: # might be some whitespace shenanigans for name, stats in self._players.items(): - if name.replace('\xa0', ' ').strip() == player_name: + if name.replace('\xa0', ' ').strip().lower() == player_name.lower(): player = stats break else: @@ -124,7 +124,7 @@ class Draft: teams.append((self._active_participant.handle, self._active_participant.team)) for participant in self._participants: if participant != BOOKMARK: - teams.append((self._active_participant.handle, self._active_participant.team)) + teams.append((participant.handle, participant.team)) return teams def finish_draft(self): @@ -136,7 +136,7 @@ class Draft: if __name__ == '__main__': # extremely robust testing OC do not steal - DRAFT_ROUNDS = 3 + DRAFT_ROUNDS = 2 draft = Draft.make_draft() draft.add_participant('@bluh', 'Bluhstein Bluhs', 'bluh bluh bluh') draft.add_participant('@what', 'Barcelona IDK', 'huh') @@ -151,7 +151,3 @@ if __name__ == '__main__': except ValueError as e: print(e) print(draft.get_teams()) - print(draft.get_teams()['@bluh'].lineup) - print(draft.get_teams()['@bluh'].pitcher) - print(draft.get_teams()['@what'].lineup) - print(draft.get_teams()['@what'].pitcher) diff --git a/the_prestige.py b/the_prestige.py index 3a57660..c17c26a 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -634,31 +634,56 @@ class StartDraftCommand(Command): return draft.start_draft() + footer = f"The draft class of {random.randint(2007, 2075)}" while draft.round <= DRAFT_ROUNDS: - choosing = '⚾️pitcher⚾️' if draft.round == DRAFT_ROUNDS else '🏏hitter🏏' + message_prefix = f'Round {draft.round}/{DRAFT_ROUNDS}:' + if draft.round == DRAFT_ROUNDS: + body = random.choice([ + f"Now just choose a pitcher and we can finish off this paperwork for you, {draft.active_drafter}", + f"Pick a pitcher, {draft.active_drafter}, and we can all go home happy. 'Cept your players. They'll have to play bllaseball.", + f"Almost done, {draft.active_drafter}. Pick your pitcher.", + ]) + message = f"⚾️ {message_prefix} {body}" + else: + body = random.choice([ + f"Choose a batter, {draft.active_drafter}", + f"{draft.active_drafter}, your turn. Pick one.", + f"Pick one to fill your next lineup slot, {draft.active_drafter}", + f"Alright, {draft.active_drafter}, choose a batter.", + ]) + message = f"🏏 {message_prefix} {body}" await msg.channel.send( - f'Round {draft.round}/{DRAFT_ROUNDS}: {draft.active_drafter}, choose a {choosing} for {draft.active_drafting_team}.', - embed=build_draft_embed(draft.get_draftees(), footer=f"{choosing[0]}You must choose"), + message, + embed=build_draft_embed(draft.get_draftees(), footer=footer), ) try: draft_message = await self.wait_draft(msg.channel, draft) draft.draft_player(f'<@!{draft_message.author.id}>', draft_message.content.split(' ', 1)[1]) except SlowDraftError: player = random.choice(draft.get_draftees()) - await msg.channel.send(f"I'm not waiting forever. You get {player}. Next") + await msg.channel.send(f"I'm not waiting forever. You get {player}. Next.") draft.draft_player(draft.active_drafter, player) except ValueError as e: await msg.channel.send(str(e)) + except IndexError: + await msg.channel.send("Quit the funny business.") for handle, team in draft.get_teams(): - await msg.channel.send(f'{handle} behold your team', embed=build_team_embed(team)) + await msg.channel.send( + random.choice([ + f"Done and dusted, {handle}. Here's your squad.", + f"Behold the {team.name}, {handle}. Flawless, we think.", + f"Oh, huh. Interesting stat distribution. Good luck, {handle}.", + ]), + embed=build_team_embed(team), + ) try: draft.finish_draft() except Exception as e: await msg.channel.send(str(e)) async def wait_start(self, channel, mentions): - start_msg = await channel.send('Are we good to go? ' + ' '.join(mentions)) + start_msg = await channel.send("Sound off, folks. 👍 if you're good to go " + " ".join(mentions)) await start_msg.add_reaction("👍") await start_msg.add_reaction("👎") @@ -669,7 +694,7 @@ class StartDraftCommand(Command): try: react, _ = await client.wait_for('reaction_add', timeout=60.0, check=react_check) if react.emoji == "👎": - await channel.send("Got it, stopping the draft") + await channel.send("We dragged out the photocopier for this! Fine, putting it back.") return False if react.emoji == "👍": reactors = set() @@ -687,10 +712,10 @@ class StartDraftCommand(Command): def check(m): if m.channel != channel: return False - if m.content.startswith('d') or m.content.startswith('draft'): + if m.content.startswith('d ') or m.content.startswith('draft '): return True for prefix in config()['prefix']: - if m.content.startswith(prefix + 'draft'): + if m.content.startswith(prefix + 'draft '): return True return False From 97546d7be5006a267fba7727b2abe34209a0d6c3 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 6 Jan 2021 17:45:39 -0800 Subject: [PATCH 11/11] erroneous merge --- the_prestige.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/the_prestige.py b/the_prestige.py index c17c26a..fa0206d 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -1382,22 +1382,4 @@ def get_team_fuzzy_search(team_name): team = teams[0] return team -#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"])