Added tag_from_second decision for AI

Flyball A and B complete
This commit is contained in:
Cal Corum 2024-11-04 00:12:35 -06:00
parent 933f4ade43
commit 736897efad
8 changed files with 399 additions and 21 deletions

View File

@ -317,7 +317,7 @@ class Economy(commands.Cog):
value=f'{get_roster_sheet({"gsheet": current["gsheet_template"]}, allow_embed=True)}' value=f'{get_roster_sheet({"gsheet": current["gsheet_template"]}, allow_embed=True)}'
) )
embed.add_field( embed.add_field(
name='Paper Dynasty Season 6 Guidelines', name='Paper Dynasty Guidelines',
value='https://docs.google.com/document/d/1ngsjbz8wYv7heSiPMJ21oKPa6JLStTsw6wNdLDnt-k4/edit?usp=sharing', value='https://docs.google.com/document/d/1ngsjbz8wYv7heSiPMJ21oKPa6JLStTsw6wNdLDnt-k4/edit?usp=sharing',
inline=False inline=False
) )
@ -326,11 +326,6 @@ class Economy(commands.Cog):
value='https://docs.google.com/document/d/1wu63XSgfQE2wadiegWaaDda11QvqkN0liRurKm0vcTs/edit?usp=sharing', value='https://docs.google.com/document/d/1wu63XSgfQE2wadiegWaaDda11QvqkN0liRurKm0vcTs/edit?usp=sharing',
inline=False inline=False
) )
embed.add_field(
name='Changelog',
value='https://manticorum.notion.site/Paper-Dynasty-Season-5-Updates-52e35839523d4e198808d6f503230f0a',
inline=False
)
await ctx.send(content=None, embed=embed) await ctx.send(content=None, embed=embed)
@pd_help_command.command(name='rewards', help='How to Earn Rewards in Paper Dynasty') @pd_help_command.command(name='rewards', help='How to Earn Rewards in Paper Dynasty')
@ -685,6 +680,7 @@ class Economy(commands.Cog):
for pack in p_query['packs']: for pack in p_query['packs']:
p_group = None p_group = None
logging.debug(f'pack: {pack}') logging.debug(f'pack: {pack}')
logging.debug(f'pack cardset: {pack["pack_cardset"]}')
if pack['pack_team'] is None and pack['pack_cardset'] is None: if pack['pack_team'] is None and pack['pack_cardset'] is None:
if pack['pack_type']['name'] in p_data: if pack['pack_type']['name'] in p_data:
p_group = pack['pack_type']['name'] p_group = pack['pack_type']['name']
@ -699,6 +695,9 @@ class Economy(commands.Cog):
elif pack['pack_type']['name'] == 'MVP': elif pack['pack_type']['name'] == 'MVP':
p_group = f'MVP-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}' p_group = f'MVP-Team-{pack["pack_team"]["id"]}-{pack["pack_team"]["sname"]}'
if pack['pack_cardset'] is not None:
p_group += f'-Cardset-{pack["pack_cardset"]["id"]}'
elif pack['pack_cardset'] is not None: elif pack['pack_cardset'] is not None:
if pack['pack_type']['name'] == 'Standard': if pack['pack_type']['name'] == 'Standard':
p_group = f'Standard-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}' p_group = f'Standard-Cardset-{pack["pack_cardset"]["id"]}-{pack["pack_cardset"]["name"]}'

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.logic_gameplay import get_lineups_from_sheets, checks_log_interaction from command_logic.logic_gameplay import flyballs, get_lineups_from_sheets, checks_log_interaction
from exceptions import GameNotFoundException, TeamNotFoundException, PlayNotFoundException, GameException from exceptions import GameNotFoundException, TeamNotFoundException, PlayNotFoundException, GameException
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
@ -166,7 +166,7 @@ class Gameplay(commands.Cog):
legal_data = await legal_check([sp_card_id], difficulty_name=league.value) legal_data = await legal_check([sp_card_id], difficulty_name=league.value)
if not legal_data['legal']: if not legal_data['legal']:
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.with_desc} is not legal in {league.name} games. You can start a new game once you pick a new SP.' content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {league.name} games. You can start a new game once you pick a new SP.'
) )
return return
@ -193,7 +193,7 @@ class Gameplay(commands.Cog):
league.value league.value
) )
await interaction.edit_original_response( await interaction.edit_original_response(
content=f'The {ai_team.sname} are starting **{ai_sp_lineup.player.with_desc}**:\n\n{ai_sp_lineup.player.p_card_url}' content=f'The {ai_team.sname} are starting **{ai_sp_lineup.player.name_with_desc}**:\n\n{ai_sp_lineup.player.p_card_url}'
) )
# Get AI Lineup # Get AI Lineup
@ -348,7 +348,13 @@ class Gameplay(commands.Cog):
with Session(engine) as session: with Session(engine) as session:
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='flyball') this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='flyball')
await interaction.edit_original_response(content=f'Pow goes teh fly ball!') this_play.locked = True
session.add(this_play)
session.commit()
this_play = await flyballs(session, interaction, this_game, this_play, flyball_type, comp_play=False)
await interaction.edit_original_response(content=f'Pow goes teh fly ball!\n\n{this_play}')
async def setup(bot): async def setup(bot):

View File

@ -2,11 +2,22 @@
import logging import logging
import discord import discord
from sqlmodel import Session, select from sqlmodel import Session, select
from typing import Literal
from exceptions import * from exceptions import *
from in_game.game_helpers import legal_check from in_game.game_helpers import legal_check
from in_game.gameplay_models import Game, Lineup, Team, Play from in_game.gameplay_models import Game, Lineup, Team, Play
from in_game.gameplay_queries import get_card_or_none, get_channel_game_or_none, get_team_or_none from in_game.gameplay_queries import get_card_or_none, get_channel_game_or_none, get_one_lineup, get_team_or_none, get_players_last_pa
from utilities.buttons import ButtonOptions, Confirm
from utilities.embeds import image_embed
from utilities.pages import Pagination
def complete_play(session:Session, this_play: Play):
this_play.locked = False
this_play.complete = True
session.add(this_play)
session.commit()
async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]: async def get_lineups_from_sheets(session: Session, sheets, this_game: Game, this_team: Team, lineup_num: int, roster_num: int) -> list[Lineup]:
@ -115,6 +126,307 @@ async def checks_log_interaction(session: Session, interaction: discord.Interact
return this_game, owner_team, this_play return this_game, owner_team, this_play
def log_run_scored(session: Session, runner: Lineup, is_earned: bool = True):
last_ab = get_players_last_pa(session, lineup_member=runner)
last_ab.run = 1
last_ab.e_run = 1 if is_earned else 0
session.add(last_ab)
session.commit()
return True
def advance_runners(session: Session, this_play: Play, num_bases: int, is_error: bool = False, only_forced: bool = False):
this_play.rbi = 0
if only_forced:
if not this_play.on_first:
if this_play.on_second:
this_play.on_second_final = 2
if this_play.on_third:
this_play.on_third_final = 3
return
if this_play.on_second:
if this_play.on_third:
if num_bases > 0:
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third)
this_play.rbi += 1 if not is_error else 0
if num_bases > 1:
this_play.on_second_final = 4
log_run_scored(session, this_play.on_second)
this_play.rbi += 1 if not is_error else 0
elif num_bases == 1:
this_play.on_second_final = 3
else:
this_play.on_second_final = 2
else:
if this_play.on_third:
this_play.on_third_final = 3
if num_bases > 2:
this_play.on_first_final = 4
log_run_scored(session, this_play.on_first)
this_play.rbi += 1 if not is_error else 0
elif num_bases == 2:
this_play.on_first_final = 3
elif num_bases == 1:
this_play.on_first_final = 2
else:
this_play.on_first_final = 1
else:
if this_play.on_third:
if num_bases > 0:
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third)
this_play.rbi += 1 if not is_error else 0
else:
this_play.on_third_final = 3
if this_play.on_second:
if num_bases > 1:
this_play.on_second_final = 4
log_run_scored(session, this_play.on_second)
this_play.rbi += 1 if not is_error else 0
elif num_bases == 1:
this_play.on_second_final = 3
else:
this_play.on_second_final = 2
if this_play.on_first:
if num_bases > 2:
this_play.on_first_final = 4
log_run_scored(session, this_play.on_first)
this_play.rbi += 1 if not is_error else 0
elif num_bases == 2:
this_play.on_first_final = 3
elif num_bases == 1:
this_play.on_first_final = 2
else:
this_play.on_first_final = 1
if num_bases == 4:
this_play.batter_final = 4
this_play.rbi += 1
this_play.run = 1
async def show_outfield_cards(session: Session, interaction: discord.Interaction, this_play: Play):
lf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='LF')
cf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='CF')
rf = get_one_lineup(session, this_game=this_play.game, this_team=this_play.pitcher.team, position='RF')
this_team = this_play.pitcher.team
logging.debug(f'lf: {lf.player.name_with_desc}\n\ncf: {cf.player.name_with_desc}\n\nrf: {rf.player.name_with_desc}\n\nteam: {this_team.lname}')
view = Pagination([interaction.user], timeout=10)
view.left_button.label = f'Left Fielder'
view.left_button.style = discord.ButtonStyle.secondary
lf_embed = image_embed(
image_url=lf.player.image,
title=f'{this_team.sname} LF',
color=this_team.color,
desc=lf.player.name,
author_name=this_team.lname,
author_icon=this_team.logo
)
view.cancel_button.label = f'Center Fielder'
view.cancel_button.style = discord.ButtonStyle.blurple
cf_embed = image_embed(
image_url=cf.player.image,
title=f'{this_team.sname} CF',
color=this_team.color,
desc=cf.player.name,
author_name=this_team.lname,
author_icon=this_team.logo
)
view.right_button.label = f'Right Fielder'
view.right_button.style = discord.ButtonStyle.secondary
rf_embed = image_embed(
image_url=rf.player.image,
title=f'{this_team.sname} RF',
color=this_team.color,
desc=rf.player.name,
author_name=this_team.lname,
author_icon=this_team.logo
)
page_num = 1
embeds = [lf_embed, cf_embed, rf_embed]
msg = await interaction.channel.send(embed=embeds[page_num], view=view)
await view.wait()
if view.value:
if view.value == 'left':
page_num = 0
if view.value == 'cancel':
page_num = 1
if view.value == 'right':
page_num = 2
else:
await msg.edit(content=None, embed=embeds[page_num], view=None)
view.value = None
if page_num == 0:
view.left_button.style = discord.ButtonStyle.blurple
view.cancel_button.style = discord.ButtonStyle.secondary
view.right_button.style = discord.ButtonStyle.secondary
if page_num == 1:
view.left_button.style = discord.ButtonStyle.secondary
view.cancel_button.style = discord.ButtonStyle.blurple
view.right_button.style = discord.ButtonStyle.secondary
if page_num == 2:
view.left_button.style = discord.ButtonStyle.secondary
view.cancel_button.style = discord.ButtonStyle.secondary
view.right_button.style = discord.ButtonStyle.blurple
view.left_button.disabled = True
view.cancel_button.disabled = True
view.right_button.disabled = True
await msg.edit(content=None, embed=embeds[page_num], view=view)
return [lf, cf, rf][page_num]
async def flyballs(session: Session, interaction: discord.Interaction, this_game: Game, this_play: Play, flyball_type: Literal['a', 'ballpark', 'b', 'b?', 'c'], comp_play: bool = True) -> Play:
num_outs = 1
if flyball_type == 'a':
this_play.pa = 1
this_play.ab = 1
this_play.outs = 1
if this_play.starting_outs < 2:
advance_runners(session, this_play, num_bases=1)
if this_play.on_third:
this_play.ab = 0
session.add(this_play)
session.commit()
elif flyball_type == 'b' or flyball_type == 'ballpark':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
this_play.bpfo = 1 if flyball_type == 'ballpark' else 0
advance_runners(session, this_play, num_bases=0)
if this_play.starting_outs < 2 and this_play.on_third:
this_play.ab = 0
this_play.rbi = 1
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third)
if this_play.starting_outs < 2 and this_play.on_second:
logging.debug(f'calling of embed')
await show_outfield_cards(session, interaction, this_play)
logging.debug(f'done with of embed')
runner = this_play.on_second.player
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
if this_play.ai_is_batting:
tag_resp = this_play.managerai.tag_from_second(session, this_game)
q_text = f'{runner.name} will attempt to advance to third if the safe range is **{tag_resp.min_safe}+**, are they going?'
else:
q_text = f'Is {runner.name} attempting to tag up to third?'
question = await interaction.channel.send(
content=q_text,
view=view
)
await view.wait()
if view.value:
await question.delete()
view = ButtonOptions(
responders=[interaction.user], timeout=60,
labels=['Tagged Up', 'Hold at 2nd', 'Out at 3rd', None, None]
)
question = await interaction.channel.send(
f'What was the result of {runner.name} tagging from second?', view=view
)
await view.wait()
if view.value:
await question.delete()
if view.value == 'Tagged Up':
this_play.on_second_final = 3
elif view.value == 'Out at 3rd':
num_outs += 1
this_play.on_second_final = None
this_play.outs = num_outs
else:
await question.delete()
else:
await question.delete()
session.add(this_play)
session.commit()
elif flyball_type == 'b?':
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
if this_play.starting_outs < 2 and this_play.on_third:
logging.debug(f'calling of embed')
await show_outfield_cards(interaction, this_play)
logging.debug(f'done with of embed')
runner = this_play.on_second.player
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
if this_play.ai_is_batting:
tag_resp = this_play.managerai.tag_from_second(session, this_game)
q_text = f'{runner.name} will attempt to advance home if the safe range is **{tag_resp.min_safe}+**, are they going?'
else:
q_text = f'Is {runner.name} attempting to tag up and go home?'
question = await interaction.channel.send(
content=q_text,
view=view
)
await view.wait()
if view.value:
await question.delete()
view = Confirm(responders=[interaction.user], timeout=60, label_type='yes')
question = await interaction.channel.send(
f'Was {runner.name} thrown out?', view=view
)
await view.wait()
if view.value:
await question.delete()
num_outs += 1
this_play.on_third_final = 99
this_play.outs = num_outs
else:
await question.delete()
this_play.ab = 0
this_play.rbi = 1
this_play.on_third_final = 4
log_run_scored(session, this_play.on_third)
else:
await question.delete()
elif flyball_type == 'c':
patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1)
advance_runners(this_play.id, num_bases=0)
if comp_play:
complete_play(this_play.id)
session.refresh(this_play)
return this_play

View File

@ -1,6 +1,6 @@
services: services:
discord-app: discord-app:
image: paper-dynasty-discordapp:sqlmodel-rebuild image: manticorum67/paper-dynasty-discordapp:gameplay-rebuild
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /home/cal/Development/paper-dynasty/dev-storage:/usr/src/app/storage - /home/cal/Development/paper-dynasty/dev-storage:/usr/src/app/storage
@ -15,6 +15,7 @@ services:
- SCOREBOARD_CHANNEL=1000521215703789609 - SCOREBOARD_CHANNEL=1000521215703789609
- TZ=America/Chicago - TZ=America/Chicago
- PYTHONHASHSEED=1749583062 - PYTHONHASHSEED=1749583062
- DATABASE=Dev
networks: networks:
backend: backend:

View File

@ -10,7 +10,7 @@ from sqlalchemy import func, desc
from api_calls import db_get, db_post from api_calls import db_get, db_post
from exceptions import * from exceptions import *
from in_game.managerai_responses import JumpResponse from in_game.managerai_responses import JumpResponse, TagResponse
sqlite_url = 'sqlite:///storage/gameplay.db' sqlite_url = 'sqlite:///storage/gameplay.db'
@ -182,17 +182,17 @@ class Game(SQLModel, table=True):
baserunner_string = '' baserunner_string = ''
if curr_play.on_first is not None: if curr_play.on_first is not None:
baserunner_string += f'On First: {curr_play.on_first.player.name_card_link}\n' baserunner_string += f'On First: {curr_play.on_first.player.name_card_link('batting')}\n'
if curr_play.on_second is not None: if curr_play.on_second is not None:
baserunner_string += f'On Second: {curr_play.on_second.player.name_card_link}\n' baserunner_string += f'On Second: {curr_play.on_second.player.name_card_link('batting')}\n'
if curr_play.on_third is not None: if curr_play.on_third is not None:
baserunner_string += f'On Third: {curr_play.on_third.player.name_card_link}' baserunner_string += f'On Third: {curr_play.on_third.player.name_card_link('batting')}'
logging.info(f'gameplay_models - Game.get_scorebug_embed - baserunner_string: {baserunner_string}') logging.info(f'gameplay_models - Game.get_scorebug_embed - baserunner_string: {baserunner_string}')
if len(baserunner_string) > 0: if len(baserunner_string) > 0:
embed.add_field(name=' ', value=' ', inline=False) embed.add_field(name=' ', value=' ', inline=False)
embed.add_field(name='Baserunners', value=baserunner_string) embed.add_field(name='Baserunners', value=baserunner_string)
embed.add_field(name='Catcher', value=curr_play.catcher.player.name_card_link) embed.add_field(name='Catcher', value=curr_play.catcher.player.name_card_link('batter'))
ai_note = curr_play.ai_note ai_note = curr_play.ai_note
logging.info(f'gameplay_models - Game.get_scorebug_embed - ai_note: {ai_note}') logging.info(f'gameplay_models - Game.get_scorebug_embed - ai_note: {ai_note}')
@ -254,6 +254,10 @@ class Game(SQLModel, table=True):
e_msg = f'Could not set the initial pitcher, catcher, and batter' e_msg = f'Could not set the initial pitcher, catcher, and batter'
log_exception(LineupsMissingException, e_msg) log_exception(LineupsMissingException, e_msg)
manager_ai_id = ((datetime.datetime.now().day * (self.away_team_id if self.ai_team == 'away' else self.home_team_id)) % 3) + 1
if manager_ai_id > 3 or manager_ai_id < 1:
manager_ai_id = 1
new_play = Play( new_play = Play(
game=self, game=self,
play_num=1, play_num=1,
@ -262,7 +266,8 @@ class Game(SQLModel, table=True):
batter_pos=leadoff_batter.position, batter_pos=leadoff_batter.position,
catcher=home_catcher, catcher=home_catcher,
is_tied=True, is_tied=True,
is_new_inning=True is_new_inning=True,
managerai_id=manager_ai_id
) )
session.add(new_play) session.add(new_play)
session.commit() session.commit()
@ -286,7 +291,7 @@ class ManagerAi(ManagerAiBase, table=True):
plays: list['Play'] = Relationship(back_populates='managerai') plays: list['Play'] = Relationship(back_populates='managerai')
def create_ai(session: Session = None): def create_ai(session: Session = None):
def get_new_ai(this_session: Session): def get_new_ai(this_session: Session):
all_ai = session.exec(select(ManagerAi.id)).all() all_ai = this_session.exec(select(ManagerAi.id)).all()
if len(all_ai) == 0: if len(all_ai) == 0:
logging.info(f'Creating ManagerAI records') logging.info(f'Creating ManagerAI records')
new_ai = [ new_ai = [
@ -388,6 +393,30 @@ class ManagerAi(ManagerAiBase, table=True):
this_resp.min_safe = 6 this_resp.min_safe = 6
return this_resp return this_resp
def tag_from_second(self, session: Session, this_game: Game) -> TagResponse:
this_resp = TagResponse()
this_play = this_game.current_play_or_none(session)
if this_play is None:
raise KeyError(f'No game found while checking tag_from_second')
ai_rd = this_play.ai_run_diff()
aggression_mod = abs(self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5)
adjusted_running = self.running + aggression_mod
if adjusted_running >= 8:
this_resp.min_safe = 4
elif adjusted_running >= 5:
this_resp.min_safe = 7
else:
this_resp.min_safe = 10
if this_play.starting_outs == 1:
this_resp.min_safe -= 2
else:
this_resp.min_safe += 2
return this_resp
class CardsetBase(SQLModel): class CardsetBase(SQLModel):
@ -705,6 +734,12 @@ class Play(PlayBase, table=True):
else: else:
return self.batting_ai_note return self.batting_ai_note
@property
def ai_is_batting(self) -> bool:
if (self.game.ai_team.lower() == 'away' and self.inning_half.lower() == 'top') or (self.game.ai_team.lower() == 'home' and self.inning_half.lower() == 'bot'):
return True
else:
return False
""" """
BEGIN DEVELOPMENT HELPERS BEGIN DEVELOPMENT HELPERS

View File

@ -247,7 +247,7 @@ def get_game_lineups(session: Session, this_game: Game, specific_team: Team = No
def get_players_last_pa(session: Session, lineup_member: Lineup, none_okay: bool = False): def get_players_last_pa(session: Session, lineup_member: Lineup, none_okay: bool = False):
last_pa = session.exec(select(Play).where(Play.game == lineup_member.game, Play.batter == lineup_member).order_by(Play.id.desc().limit(1))).all() last_pa = session.exec(select(Play).where(Play.game == lineup_member.game, Play.batter == lineup_member).order_by(Play.id.desc()).limit(1)).all()
if len(last_pa) == 1: if len(last_pa) == 1:
return last_pa[0] return last_pa[0]
else: else:

View File

@ -1,6 +1,12 @@
import pydantic import pydantic
class JumpResponse(pydantic.BaseModel): class JumpResponse(pydantic.BaseModel):
min_safe: int | None = None min_safe: int | None = None
must_auto_jump: bool = False must_auto_jump: bool = False
run_if_auto_jump: bool = False run_if_auto_jump: bool = False
class TagResponse(pydantic.BaseModel):
min_safe: int | None = None

View File

@ -34,4 +34,23 @@ def test_check_jump(session: Session):
assert balanced_ai.check_jump(session, this_game, to_base=4) == JumpResponse(min_safe=None) assert balanced_ai.check_jump(session, this_game, to_base=4) == JumpResponse(min_safe=None)
assert aggressive_ai.check_jump(session, this_game, to_base=4) == JumpResponse(min_safe=5) assert aggressive_ai.check_jump(session, this_game, to_base=4) == JumpResponse(min_safe=5)
def test_tag_from_second(session: Session):
balanced_ai = session.exec(select(ManagerAi).where(ManagerAi.name == 'Balanced')).one()
aggressive_ai = session.exec(select(ManagerAi).where(ManagerAi.name == 'Yolo')).one()
this_game = session.get(Game, 1)
runner = session.get(Lineup, 5)
this_play = session.get(Play, 2)
this_play.on_second = runner
assert this_play.starting_outs == 1
balanced_resp = balanced_ai.tag_from_second(session, this_game)
aggressive_resp = aggressive_ai.tag_from_second(session, this_game)
assert balanced_resp.min_safe == 5
assert aggressive_resp.min_safe == 2