From 19e781137e4f456cd51be23889348c1507f6cdf1 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 15 Oct 2024 01:23:04 -0500 Subject: [PATCH] Complete /new-game campaign Add scorebug function to Game Add card links prop to players Add scorebug prop to play --- cogs/admins.py | 5 +- cogs/gameplay.py | 8 +- .../{gameplay.py => logic_gameplay.py} | 0 in_game/ai_manager.py | 2 +- in_game/gameplay_models.py | 162 +++++++++++++++++- tests/gameplay_models/factory.py | 5 + tests/gameplay_models/test_card_model.py | 4 +- tests/gameplay_models/test_game_model.py | 2 +- tests/gameplay_models/test_play_model.py | 26 ++- tests/gameplay_models/test_player_model.py | 9 + tests/gameplay_models/test_team_model.py | 8 +- 11 files changed, 206 insertions(+), 25 deletions(-) rename command_logic/{gameplay.py => logic_gameplay.py} (100%) diff --git a/cogs/admins.py b/cogs/admins.py index 5518d91..cf45c06 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -10,7 +10,7 @@ from api_calls import * from helpers import * import in_game from in_game import ai_manager -from in_game.gameplay_models import Session, select, engine, Game, Cardset, Lineup, Team, Player +from in_game.gameplay_models import Play, Session, select, engine, Game, Cardset, Lineup, Team, Player # date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' @@ -570,8 +570,9 @@ class Admins(commands.Cog): lineups = session.exec(select(Lineup.id)).all() teams = session.exec(select(Team.id)).all() players = session.exec(select(Player.id)).all() + plays = session.exec(select(Play.id)).all() - output = f'## Database Counts\nGames: {len(games)}\nCardsets: {len(cardsets)}\nLineups: {len(lineups)}\nTeams: {len(teams)}\nPlayers: {len(players)}' + output = f'## Database Counts\nGames: {len(games)}\nCardsets: {len(cardsets)}\nLineups: {len(lineups)}\nTeams: {len(teams)}\nPlayers: {len(players)}\nPlays: {len(plays)}' await message.edit(content=output) diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 7783110..7314f47 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -8,7 +8,7 @@ from discord.ext import commands, tasks import pygsheets from api_calls import db_get -from command_logic.gameplay import get_lineups_from_sheets +from command_logic.logic_gameplay import get_lineups_from_sheets from helpers import PD_PLAYERS_ROLE_NAME, team_role, user_has_role, random_gif, random_from_list # from in_game import ai_manager @@ -218,7 +218,7 @@ class Gameplay(commands.Cog): await final_message.edit( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', - embed=this_game.get_scorebug(full_length=False) + embed=this_game.get_scorebug_embed(session) ) @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') @@ -232,7 +232,7 @@ class Gameplay(commands.Cog): await ctx.send( content=None, - embed=this_game.get_scorebug(full_length=True) + embed=this_game.get_scorebug_embed(session, include_lineups=True) ) view = Confirm(responders=[ctx.author], timeout=60, label_type='confirm') @@ -316,7 +316,7 @@ class Gameplay(commands.Cog): session.commit() - await interaction.edit_original_response(content=f'Lineups are read and logged!') + await interaction.edit_original_response(content=None, embed=this_game.get_scorebug_embed(session)) async def setup(bot): diff --git a/command_logic/gameplay.py b/command_logic/logic_gameplay.py similarity index 100% rename from command_logic/gameplay.py rename to command_logic/logic_gameplay.py diff --git a/in_game/ai_manager.py b/in_game/ai_manager.py index 4e0d2f8..13695ca 100644 --- a/in_game/ai_manager.py +++ b/in_game/ai_manager.py @@ -13,7 +13,7 @@ from typing import Optional, Literal from in_game import data_cache import in_game.gameplay_models as iggm -from in_game.gameplay_models import Session, Game, Team +from in_game.gameplay_models import Play, Session, Game, Team from in_game.gameplay_queries import get_or_create_ai_card, get_player_id_from_dict, get_player_or_none db = SqliteDatabase( diff --git a/in_game/gameplay_models.py b/in_game/gameplay_models.py index 7bb4bde..03141c0 100644 --- a/in_game/gameplay_models.py +++ b/in_game/gameplay_models.py @@ -1,5 +1,6 @@ import datetime import logging +from typing import Literal import discord from sqlmodel import Session, SQLModel, create_engine, select, or_, Field, Relationship from sqlalchemy import func @@ -105,15 +106,79 @@ class Game(SQLModel, table=True): back_cardsets += f'&backup_cardset_id={link.cardset_id}' return f'{pri_cardsets}{back_cardsets}' - def get_scorebug(self, full_length: bool = True) -> discord.Embed: - return discord.Embed( - title=f'{self.away_team_id} @ {self.home_team_id}', + def current_play_or_none(self, session: Session): + this_play = session.exec(select(Play).where(Play.game == self).order_by(Play.id.desc()).limit(1)).all() + if len(this_play) == 1: + return this_play[0] + else: + return None + + def get_scorebug_embed(self, session: Session, full_length: bool = True, classic: bool = True) -> discord.Embed: + gt_string = ' - Unlimited' + if self.game_type == 'minor-league': + gt_string = ' - Minor League' + elif self.game_type == 'major-league': + gt_string = ' - Major League' + elif self.game_type == 'hall-of-fame': + gt_string = ' - Hall of Fame' + elif 'gauntlet' in self.game_type: + gt_string = ' - Gauntlet' + elif 'flashback' in self.game_type: + gt_string = ' - Flashback' + elif 'exhibition' in self.game_type: + gt_string = ' - Exhibition' + logging.info(f'gameplay_models - Game.get_scorebug_embed - this_game: {self} / gt_string: {gt_string}') + + embed = discord.Embed( + title=f'{self.away_team.sname} @ {self.home_team.sname}{gt_string}', color=int('a6ce39', 16) ) - - def current_play(self, session: Session): - this_play = session.exec(select(Play).where(Play.game == self).order_by(Play.id.desc()).limit(1)).one() - return this_play + logging.info(f'gameplay_models - Game.get_scorebug_embed - embed: {embed}') + + curr_play = self.current_play_or_none(session) + logging.info(f'gameplay_models - Game.get_scorebug_embed - curr_play: {self}') + + if curr_play is not None: + embed.add_field( + name='Game State', + value=curr_play.scorebug_ascii, + inline=False + ) + logging.info(f'gameplay_models - Game.get_scorebug_embed - embed post gamestate: {embed}') + + if classic: + embed.add_field( + name='Pitcher', + value=curr_play.pitcher.player.name_card_link('pitching') + ) + embed.add_field( + name='Batter', + value=curr_play.batter.player.name_card_link('batting') + ) + logging.info(f'gameplay_models - Game.get_scorebug_embed - embed post batter: {embed}') + + baserunner_string = '' + if curr_play.on_first is not None: + baserunner_string += f'On First: {curr_play.on_first.player.name_card_link}\n' + if curr_play.on_second is not None: + baserunner_string += f'On Second: {curr_play.on_second.player.name_card_link}\n' + if curr_play.on_third is not None: + baserunner_string += f'On Third: {curr_play.on_third.player.name_card_link}' + logging.info(f'gameplay_models - Game.get_scorebug_embed - baserunner_string: {baserunner_string}') + + if len(baserunner_string) > 0: + embed.add_field(name=' ', value=' ', inline=False) + embed.add_field(name='Baserunners', value=baserunner_string) + embed.add_field(name='Catcher', value=curr_play.catcher.player.name_card_link) + logging.info(f'gameplay_models - Game.get_scorebug_embed - embed post runners: {embed}') + + ai_note = curr_play.ai_note + logging.info(f'gameplay_models - Game.get_scorebug_embed - ai_note: {ai_note}') + if len(ai_note) > 0: + gm_name = self.home_team.gmname if self.ai_team == 'home' else self.away_team.gmname + embed.add_field(name=f'{gm_name} will...', value=ai_note, inline=False) + + return embed # @property # def game_prop(self) -> str: @@ -179,6 +244,13 @@ class PlayerBase(SQLModel): else: logging.error(f'gameplay_models - PlayerBase - batting card url not found for {self.id}. {self.description} {self.name}') return self.image + + def name_card_link(self, which: Literal['pitching', 'batting']): + if which == 'pitching': + return f'[{self.name}]({self.p_card_url})' + else: + return f'[{self.name}]({self.b_card_url})' + class Player(PlayerBase, table=True): @@ -239,6 +311,8 @@ class Lineup(SQLModel, table=True): card_id: int = Field(foreign_key='card.id', index=True, ondelete='CASCADE') card: Card = Relationship(back_populates='lineups') + # TODO: add function to return string value of game stats + class PlayBase(SQLModel): id: int | None = Field(default=None, primary_key=True) @@ -290,7 +364,7 @@ class PlayBase(SQLModel): wpa: float = Field(default=0) re24: float = Field(default=0) - catcher_id: int | None = Field(default=None, foreign_key='lineup.id') + catcher_id: int = Field(foreign_key='lineup.id') defender_id: int | None = Field(default=None, foreign_key='lineup.id') runner_id: int | None = Field(default=None, foreign_key='lineup.id') @@ -340,6 +414,78 @@ class Play(PlayBase, table=True): sa_relationship_kwargs=dict(foreign_keys="[Play.runner_id]") ) + @property + def scorebug_ascii(self): + occupied = '●' + unoccupied = '○' + + first_base = unoccupied if not self.on_first else occupied + second_base = unoccupied if not self.on_second else occupied + third_base = unoccupied if not self.on_third else occupied + half = '▲' if self.inning_half == 'Top' else '▼' + + if self.game.active: + inning = f'{half} {self.inning_num}' + outs = f'{self.starting_outs} Out{"s" if self.starting_outs != 1 else ""}' + else: + inning = f'F/{self.inning_num if self.inning_half == "Bot" else self.inning_num - 1}' + outs = '' + + game_string = f'```\n' \ + f'{self.game.away_team.abbrev.replace("Gauntlet-", ""): ^5}{self.away_score: ^3} {second_base}{inning: >10}\n' \ + f'{self.game.home_team.abbrev.replace("Gauntlet-", ""): ^5}{self.home_score: ^3} {third_base} {first_base}{outs: >8}\n```' + + return game_string + + @property + def pitching_ai_note(self) -> str: + ai_note = '' + # Holding Baserunners + if self.starting_outs == 2 and self.on_base_code > 0: + if self.on_base_code in [1, 2]: + ai_note += f'- hold the runner\n' + elif self.on_base_code in [4, 7]: + ai_note += f'- hold the runners\n' + elif self.on_base_code == 5: + ai_note += f'- hold the runner on first\n' + elif self.on_base_code == 6: + ai_note += f'- hold the runner on second\n' + elif self.on_base_code in [1, 5]: + ai_note += f'- hold the runner on 1st if they have ***** auto-jump\n' + elif self.on_base_code == 2: + ai_note += f'- hold the runner on 2nd if safe range is 14+\n' + + # Defensive Alignment + if self.on_third and self.starting_outs < 2: + if self.on_first: + ai_note += f'- play the corners in\n' + elif abs(self.away_score - self.home_score) <= 3: + ai_note += f'- play the whole infield in\n' + else: + ai_note += f'- play the corners in\n' + + return ai_note + + + @property + def batting_ai_note(self) -> str: + ai_note = '' # TODO: migrate Manager AI to their own local model + + return ai_note + + @property + def ai_note(self) -> str: # TODO: test these three functions with specific OBCs + if self.inning_half == 'top': + if self.game.ai_team == 'away': + return self.batting_ai_note + else: + return self.pitching_ai_note + else: + if self.game.ai_team == 'away': + return self.pitching_ai_note + else: + return self.batting_ai_note + """ BEGIN DEVELOPMENT HELPERS diff --git a/tests/gameplay_models/factory.py b/tests/gameplay_models/factory.py index eb23bb0..2ab6219 100644 --- a/tests/gameplay_models/factory.py +++ b/tests/gameplay_models/factory.py @@ -111,6 +111,9 @@ def session_fixture(): team_id=420, created=datetime.datetime.now() - datetime.timedelta(days=60) )) + all_players.append(Player( + id=70, name='Player 69', cost=70*3, mlbclub='Junior All-Stars', franchise='Junior All-Stars', cardset=cardset_1, set_num=70, pos_1='SP', pos_2='DH', description='Live', created=datetime.datetime.today() - datetime.timedelta(days=60), image='player_69_pitchingcard', image2='player_69_battingcard', rarity_id=1 + )) for player in all_players: session.add(player) @@ -153,6 +156,7 @@ def session_fixture(): batter=all_lineups[0], batter_pos=all_lineups[0].position, pitcher=all_lineups[19], + catcher=all_lineups[10], pa=1, so=1 ) @@ -162,6 +166,7 @@ def session_fixture(): batter=all_lineups[1], batter_pos=all_lineups[1].position, pitcher=all_lineups[19], + catcher=all_lineups[10], starting_outs=1 ) diff --git a/tests/gameplay_models/test_card_model.py b/tests/gameplay_models/test_card_model.py index 57bcdc0..be2d66c 100644 --- a/tests/gameplay_models/test_card_model.py +++ b/tests/gameplay_models/test_card_model.py @@ -1,8 +1,8 @@ import datetime from sqlmodel import Session -from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select, get_card_or_none -from in_game.gameplay_queries import get_or_create_ai_card +from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select +from in_game.gameplay_queries import get_or_create_ai_card, get_card_or_none from factory import session_fixture diff --git a/tests/gameplay_models/test_game_model.py b/tests/gameplay_models/test_game_model.py index 40d2371..d79fde9 100644 --- a/tests/gameplay_models/test_game_model.py +++ b/tests/gameplay_models/test_game_model.py @@ -109,7 +109,7 @@ def test_delete_game(session: Session): def test_get_scorebug(session: Session): game_1 = session.get(Game, 1) - scorebug = game_1.get_scorebug() + scorebug = game_1.get_scorebug(session) assert scorebug.title == '31 @ 400' assert scorebug.color.value == int('a6ce39', 16) diff --git a/tests/gameplay_models/test_play_model.py b/tests/gameplay_models/test_play_model.py index 20fd5e5..9a7c4f7 100644 --- a/tests/gameplay_models/test_play_model.py +++ b/tests/gameplay_models/test_play_model.py @@ -1,6 +1,6 @@ from sqlmodel import Session, select -from in_game.gameplay_models import Play, Game +from in_game.gameplay_models import Lineup, Play, Game from factory import session_fixture @@ -18,6 +18,26 @@ def test_create_play(session: Session): def test_get_current_play(session: Session): game_1 = session.get(Game, 1) - curr_play = game_1.current_play(session) + curr_play = game_1.current_play_or_none(session) - assert curr_play.play_num == 2 \ No newline at end of file + assert curr_play.play_num == 2 + + +def test_scorebug_ascii(session: Session): + new_play = Play( + game_id=3, + play_num=69, + batter=session.get(Lineup, 1), + batter_pos='DH', + pitcher=session.get(Lineup, 20), + catcher=session.get(Lineup, 11), + starting_outs=1, + inning_num=6 + ) + session.add(new_play) + session.commit() + + assert new_play.scorebug_ascii == '```\nNCB3 0 ○ ▼ 6\n WV4 0 ○ ○ 1 Out\n```' + + +# TODO: test get_ai_note \ No newline at end of file diff --git a/tests/gameplay_models/test_player_model.py b/tests/gameplay_models/test_player_model.py index 563e91f..06b9da9 100644 --- a/tests/gameplay_models/test_player_model.py +++ b/tests/gameplay_models/test_player_model.py @@ -58,3 +58,12 @@ def test_player_id_from_dict(session: Session): get_player_id_from_dict({}) assert str(exc_info) == "" + + +def test_player_card_link(session: Session): + player_1 = session.get(Player, 70) + + assert player_1.b_card_url == 'player_69_battingcard' + assert player_1.p_card_url == 'player_69_pitchingcard' + assert player_1.name_card_link('pitching') == '[Player 69](player_69_pitchingcard)' + assert player_1.name_card_link('batting') == '[Player 69](player_69_battingcard)' diff --git a/tests/gameplay_models/test_team_model.py b/tests/gameplay_models/test_team_model.py index 1bfc59b..4477c38 100644 --- a/tests/gameplay_models/test_team_model.py +++ b/tests/gameplay_models/test_team_model.py @@ -3,9 +3,9 @@ from sqlmodel import Session, select from in_game.gameplay_models import Team, CACHE_LIMIT from in_game.gameplay_queries import get_team_or_none -from factory import session_fixture, new_teams_fixture, pytest +from factory import session_fixture, pytest -def test_create_team(session: Session, new_teams: list[Team]): +def test_create_team(session: Session): team_31 = session.get(Team, 31) team_400 = session.get(Team, 400) team_69 = session.get(Team, 69) @@ -19,7 +19,7 @@ def test_create_team(session: Session, new_teams: list[Team]): assert team_3.abbrev == 'BAL' -def test_create_incomplete_team(session: Session, new_teams: list[Team]): +def test_create_incomplete_team(session: Session): team_1 = Team( id=446, abbrev='CLS', sname='Macho Men', lname='Columbus Macho Men', gmid=181818, gmname='Mason Socc', gsheet='asdf1234', wallet=6969, team_value=69420, collection_value=169420, color='https://i.postimg.cc/8kLZCYXh/S10CLS.png', season=7, event=False, career=0 ) @@ -32,7 +32,7 @@ def test_create_incomplete_team(session: Session, new_teams: list[Team]): assert str(exc_info) == "" -async def test_team_cache(session: Session, new_teams: list[Team]): +async def test_team_cache(session: Session): team_31 = session.get(Team, 31) team_3 = session.get(Team, 3)