Complete /new-game campaign

Add scorebug function to Game
Add card links prop to players
Add scorebug prop to play
This commit is contained in:
Cal Corum 2024-10-15 01:23:04 -05:00
parent 5fc89b5ce4
commit 19e781137e
11 changed files with 206 additions and 25 deletions

View File

@ -10,7 +10,7 @@ from api_calls import *
from helpers import * from helpers import *
import in_game import in_game
from in_game import ai_manager 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}' # 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() lineups = session.exec(select(Lineup.id)).all()
teams = session.exec(select(Team.id)).all() teams = session.exec(select(Team.id)).all()
players = session.exec(select(Player.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) await message.edit(content=output)

View File

@ -8,7 +8,7 @@ from discord.ext import commands, tasks
import pygsheets import pygsheets
from api_calls import db_get 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 helpers import PD_PLAYERS_ROLE_NAME, team_role, user_has_role, random_gif, random_from_list
# from in_game import ai_manager # from in_game import ai_manager
@ -218,7 +218,7 @@ class Gameplay(commands.Cog):
await final_message.edit( await final_message.edit(
content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' content=f'{away_role.mention} @ {home_role.mention} is set!\n\n'
f'Go ahead and set lineups with the `/read-lineup` command!', 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') @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( await ctx.send(
content=None, 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') view = Confirm(responders=[ctx.author], timeout=60, label_type='confirm')
@ -316,7 +316,7 @@ class Gameplay(commands.Cog):
session.commit() 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): async def setup(bot):

View File

@ -13,7 +13,7 @@ from typing import Optional, Literal
from in_game import data_cache from in_game import data_cache
import in_game.gameplay_models as iggm 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 from in_game.gameplay_queries import get_or_create_ai_card, get_player_id_from_dict, get_player_or_none
db = SqliteDatabase( db = SqliteDatabase(

View File

@ -1,5 +1,6 @@
import datetime import datetime
import logging import logging
from typing import Literal
import discord import discord
from sqlmodel import Session, SQLModel, create_engine, select, or_, Field, Relationship from sqlmodel import Session, SQLModel, create_engine, select, or_, Field, Relationship
from sqlalchemy import func from sqlalchemy import func
@ -105,15 +106,79 @@ class Game(SQLModel, table=True):
back_cardsets += f'&backup_cardset_id={link.cardset_id}' back_cardsets += f'&backup_cardset_id={link.cardset_id}'
return f'{pri_cardsets}{back_cardsets}' return f'{pri_cardsets}{back_cardsets}'
def get_scorebug(self, full_length: bool = True) -> discord.Embed: def current_play_or_none(self, session: Session):
return discord.Embed( this_play = session.exec(select(Play).where(Play.game == self).order_by(Play.id.desc()).limit(1)).all()
title=f'{self.away_team_id} @ {self.home_team_id}', 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) color=int('a6ce39', 16)
) )
logging.info(f'gameplay_models - Game.get_scorebug_embed - embed: {embed}')
def current_play(self, session: Session):
this_play = session.exec(select(Play).where(Play.game == self).order_by(Play.id.desc()).limit(1)).one() curr_play = self.current_play_or_none(session)
return this_play 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 # @property
# def game_prop(self) -> str: # def game_prop(self) -> str:
@ -179,6 +244,13 @@ class PlayerBase(SQLModel):
else: else:
logging.error(f'gameplay_models - PlayerBase - batting card url not found for {self.id}. {self.description} {self.name}') logging.error(f'gameplay_models - PlayerBase - batting card url not found for {self.id}. {self.description} {self.name}')
return self.image 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): 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_id: int = Field(foreign_key='card.id', index=True, ondelete='CASCADE')
card: Card = Relationship(back_populates='lineups') card: Card = Relationship(back_populates='lineups')
# TODO: add function to return string value of game stats
class PlayBase(SQLModel): class PlayBase(SQLModel):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
@ -290,7 +364,7 @@ class PlayBase(SQLModel):
wpa: float = Field(default=0) wpa: float = Field(default=0)
re24: 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') defender_id: int | None = Field(default=None, foreign_key='lineup.id')
runner_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]") 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 BEGIN DEVELOPMENT HELPERS

View File

@ -111,6 +111,9 @@ def session_fixture():
team_id=420, team_id=420,
created=datetime.datetime.now() - datetime.timedelta(days=60) 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: for player in all_players:
session.add(player) session.add(player)
@ -153,6 +156,7 @@ def session_fixture():
batter=all_lineups[0], batter=all_lineups[0],
batter_pos=all_lineups[0].position, batter_pos=all_lineups[0].position,
pitcher=all_lineups[19], pitcher=all_lineups[19],
catcher=all_lineups[10],
pa=1, pa=1,
so=1 so=1
) )
@ -162,6 +166,7 @@ def session_fixture():
batter=all_lineups[1], batter=all_lineups[1],
batter_pos=all_lineups[1].position, batter_pos=all_lineups[1].position,
pitcher=all_lineups[19], pitcher=all_lineups[19],
catcher=all_lineups[10],
starting_outs=1 starting_outs=1
) )

View File

@ -1,8 +1,8 @@
import datetime import datetime
from sqlmodel import Session from sqlmodel import Session
from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select, get_card_or_none from in_game.gameplay_models import CACHE_LIMIT, Card, Player, Team, select
from in_game.gameplay_queries import get_or_create_ai_card from in_game.gameplay_queries import get_or_create_ai_card, get_card_or_none
from factory import session_fixture from factory import session_fixture

View File

@ -109,7 +109,7 @@ def test_delete_game(session: Session):
def test_get_scorebug(session: Session): def test_get_scorebug(session: Session):
game_1 = session.get(Game, 1) game_1 = session.get(Game, 1)
scorebug = game_1.get_scorebug() scorebug = game_1.get_scorebug(session)
assert scorebug.title == '31 @ 400' assert scorebug.title == '31 @ 400'
assert scorebug.color.value == int('a6ce39', 16) assert scorebug.color.value == int('a6ce39', 16)

View File

@ -1,6 +1,6 @@
from sqlmodel import Session, select 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 from factory import session_fixture
@ -18,6 +18,26 @@ def test_create_play(session: Session):
def test_get_current_play(session: Session): def test_get_current_play(session: Session):
game_1 = session.get(Game, 1) 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 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

View File

@ -58,3 +58,12 @@ def test_player_id_from_dict(session: Session):
get_player_id_from_dict({}) get_player_id_from_dict({})
assert str(exc_info) == "<ExceptionInfo KeyError('Player ID could not be extracted from json data') tblen=2>" assert str(exc_info) == "<ExceptionInfo KeyError('Player ID could not be extracted from json data') tblen=2>"
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)'

View File

@ -3,9 +3,9 @@ from sqlmodel import Session, select
from in_game.gameplay_models import Team, CACHE_LIMIT from in_game.gameplay_models import Team, CACHE_LIMIT
from in_game.gameplay_queries import get_team_or_none 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_31 = session.get(Team, 31)
team_400 = session.get(Team, 400) team_400 = session.get(Team, 400)
team_69 = session.get(Team, 69) 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' 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( 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 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) == "<ExceptionInfo IntegrityError('(sqlite3.IntegrityError) NOT NULL constraint failed: team.ranking') tblen=24>" assert str(exc_info) == "<ExceptionInfo IntegrityError('(sqlite3.IntegrityError) NOT NULL constraint failed: team.ranking') tblen=24>"
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_31 = session.get(Team, 31)
team_3 = session.get(Team, 3) team_3 = session.get(Team, 3)