Fix catcher error chart and re-implement rare plays

This commit is contained in:
Cal Corum 2025-10-21 14:40:31 -05:00
parent a8fbf55c9e
commit 3cc52169b1

View File

@ -29,6 +29,127 @@ class DiceRoll:
rolls: list[int]
total: int
INFIELD_X_CHART = {
'si1': {
'rp': 'Runner on first: Line drive hits the runner! Runner on first is out. Batter goes to first with single '
'and all other runners hold.\nNo runner on first: batter singles, runners advance 1 base.',
'e1': 'Single and Error, batter to second, runners advance 2 bases.',
'e2': 'Single and Error, batter to third, all runners score.',
'no': 'Single, runners advance 1 base.'
},
'spd': {
'rp': 'No effect; proceed with speed check',
'e1': 'Single and Error, batter to second, runners advance 2 bases.',
'e2': 'Single and Error, batter to third, all runners score.',
'no': 'Speed check, safe range equals batter\'s running rating, SI* result if safe, gb C if out'
},
'po': {
'rp': 'The batters hits a popup. None of the fielders take charge on the play and the ball drops in the '
'infield for a single! All runners advance 1 base.',
'e1': 'The catcher drops a popup for an error. All runners advance 1 base.',
'e2': 'The catcher grabs a squib in front of the plate and throws it into right field. The batter goes to '
'second and all runners score.',
'no': 'The batter pops out to the catcher.'
},
'wp': {
'rp': 'Automatic wild pitch. Catcher has trouble finding it and all base runners advance 2 bases.',
'no': 'Automatic wild pitch, all runners advance 1 base and batter rolls AB again.'
},
'x': {
'rp': 'Runner(s) on base: pitcher trips during his delivery and the ball sails for automatic wild pitch, '
'runners advance 1 base and batter rolls AB again.',
'no': 'Wild pitch check (credited as a PB). If a passed ball occurs, batter rerolls AB. '
'If no passed ball occurs, the batter fouls out to the catcher.'
},
'fo': {
'rp': 'Batter swings and misses, but is awarded first base on a catcher interference call! Baserunners advance '
'only if forced.',
'e1': 'The catcher drops a foul popup for an error. Batter rolls AB again.',
'e2': 'The catcher drops a foul popup for an error. Batter rolls AB again.',
'no': 'Runner(s) on base: make a passed ball check. If no passed ball, batter pops out to the catcher. If a '
'passed ball occurs, batter roll his AB again.\nNo runners: batter pops out to the catcher'
},
'g1': {
'rp': 'Runner on first: runner on first breaks up the double play, but umpires call runner interference and '
'the batter is out on GIDP.\nNo runners: Batter grounds out.',
'e1': 'Error, batter to first, runners advance 1 base.',
'e2': 'Error, batter to second, runners advance 2 bases.',
'no': 'Consult Groundball Chart: `!gbA`'
},
'g2': {
'rp': 'Batter lines the ball off the pitcher to the fielder who makes the play to first for the out! Runners '
'advance only if forced.',
'e1': 'Error, batter to first, runners advance 1 base.',
'e2': 'Error, batter to second, runners advance 2 bases.',
'no': 'Consult Groundball Chart: `!gbB`'
},
'g3': {
'rp': 'Batter lines the ball off the mound and deflects to the fielder who makes the play to first for the '
'out! Runners advance 1 base.',
'e1': 'Error, batter to first, runners advance 1 base.',
'e2': 'Error, batter to second, runners advance 2 bases.',
'no': 'Consult Groundball Chart: `!gbC`'
},
}
OUTFIELD_X_CHART = {
'si2': {
'rp': 'Batter singles, baserunners advance 2 bases. As the batter rounds first, the fielder throws behind him '
'and catches him off the bag for an out!',
'e1': 'Single and error, batter to second, runners advance 2 bases.',
'e2': 'Single and error, batter to third, all runners score.',
'e3': 'Single and error, batter to third, all runners score',
'no': 'Single, all runners advance 2 bases.'
},
'do2': {
'rp': 'Batter doubles, runners advance 2 bases. The outfielder throws the ball to the shortstop who executes a '
'hidden ball trick! Runner on second is called out!',
'e1': 'Double and error, batter to third, all runners score.',
'e2': 'Double and error, batter to third, and all runners score.',
'e3': 'Double and error, batter and all runners score. Little league home run!',
'no': 'Double, all runners advance 2 bases.'
},
'do3': {
'rp': 'Runner(s) on base: batter doubles and runners advance three bases as the outfielders collide!\n'
'No runners: Batter doubles, but the play is appealed. The umps rule the batter missed first base so is '
'out on the appeal!',
'e1': 'Double and error, batter to third, all runners score.',
'e2': 'Double and error, batter and all runners score. Little league home run!',
'e3': 'Double and error, batter and all runners score. Little league home run!',
'no': 'Double, all runners score.'
},
'tr3': {
'rp': 'Batter hits a ball into the gap and the outfielders collide trying to make the play! The ball rolls to '
'the wall and the batter trots home with an inside-the-park home run!',
'e1': 'Triple and error, batter and all runners score. Little league home run!',
'e2': 'Triple and error, batter and all runners score. Little league home run!',
'e3': 'Triple and error, batter and all runners score. Little league home run!',
'no': 'Triple, all runners score.'
},
'f1': {
'rp': 'The outfielder races back and makes a diving catch and collides with the wall! In the time he takes to '
'recuperate, all baserunners tag-up and advance 2 bases.',
'e1': '1 base error, runners advance 1 base.',
'e2': '2 base error, runners advance 2 bases.',
'e3': '3 base error, batter to third, all runners score.',
'no': 'Flyball A'
},
'f2': {
'rp': 'The outfielder catches the flyball for an out. If there is a runner on third, he tags-up and scores. '
'The play is appealed and the umps rule that the runner left early and is out on the appeal!',
'e1': '1 base error, runners advance 1 base.',
'e2': '2 base error, runners advance 2 bases.',
'e3': '3 base error, batter to third, all runners score.',
'no': 'Flyball B'
},
'f3': {
'rp': 'The outfielder makes a running catch in the gap! The lead runner lost track of the ball and was '
'advancing - he cannot return in time and is doubled off by the outfielder.',
'e1': '1 base error, runners advance 1 base.',
'e2': '2 base error, runners advance 2 bases.',
'e3': '3 base error, batter to third, all runners score.',
'no': 'Flyball C'
}
}
class DiceRollCommands(commands.Cog):
"""Dice rolling command handlers for gameplay."""
@ -191,16 +312,17 @@ class DiceRollCommands(commands.Cog):
):
"""Roll Super Advanced fielding dice for a defensive position."""
await interaction.response.defer()
embed_color = await self._get_channel_embed_color(interaction)
# Get the position value from the choice
pos_value = position.value
# Roll the dice - 1d20 and 3d6
dice_notation = "1d20;3d6"
dice_notation = "1d20;3d6;1d100"
roll_results = self._parse_and_roll_multiple_dice(dice_notation)
# Create fielding embed
embed = self._create_fielding_embed(pos_value, roll_results, interaction.user)
embed = self._create_fielding_embed(pos_value, roll_results, interaction.user, embed_color)
await interaction.followup.send(embed=embed)
@commands.command(name="f", aliases=["fielding", "saf"])
@ -326,16 +448,23 @@ class DiceRollCommands(commands.Cog):
return position_map.get(pos)
def _create_fielding_embed(self, position: str, roll_results: list[DiceRoll], user) -> discord.Embed:
def _create_fielding_embed(
self,
position: str,
roll_results: list[DiceRoll],
user: discord.User | discord.Member,
embed_color: int = EmbedColors.PRIMARY
) -> discord.Embed:
"""Create an embed for fielding roll results."""
d20_result = roll_results[0].total
d6_total = roll_results[1].total
d6_rolls = roll_results[1].rolls
d100_result = roll_results[2].total
# Create base embed
embed = EmbedTemplate.create_base_embed(
title=f"SA Fielding roll for {user.display_name}",
color=EmbedColors.PRIMARY
color=embed_color
)
# Set user info
@ -364,8 +493,12 @@ class DiceRollCommands(commands.Cog):
inline=False
)
# Add error result
error_result = self._get_error_result(position, d6_total)
# Add rare play
if d100_result >= 1:
error_result = self._get_rare_play(position, d20_result)
else:
# Add error result
error_result = self._get_error_result(position, d6_total)
if error_result:
embed.add_field(
name="Error Result",
@ -373,12 +506,12 @@ class DiceRollCommands(commands.Cog):
inline=False
)
# # Add help commands
# embed.add_field(
# name="Help Commands",
# value="Run !<result> for full chart readout (e.g. !g1 or !do3)",
# inline=False
# )
# Add help commands
embed.add_field(
name="Help Commands",
value="Run /charts for full chart readout",
inline=False
)
# # Add references
# embed.add_field(
@ -564,6 +697,26 @@ class DiceRollCommands(commands.Cog):
20: '--------G1---------'
}
return pitcher_ranges.get(d20_roll, 'Unknown')
def _get_rare_play(self, position: str, d20_total: int) -> str:
"""Get the rare play result for a position and d20 total"""
starter = 'Rare play! Take the range result from above and consult the chart below.\n\n'
if position == 'P':
return starter + self._get_pitcher_rare_play(d20_total)
elif position == '1B':
return starter + self._get_infield_rare_play(d20_total)
elif position == '2B':
return starter + self._get_infield_rare_play(d20_total)
elif position == '3B':
return starter + self._get_infield_rare_play(d20_total)
elif position == 'SS':
return starter + self._get_infield_rare_play(d20_total)
elif position in ['LF', 'RF']:
return starter + self._get_outfield_rare_play(d20_total)
elif position == 'CF':
return starter + self._get_outfield_rare_play(d20_total)
raise ValueError(f'Unknown position: {position}')
def _get_error_result(self, position: str, d6_total: int) -> str:
"""Get the error result for a position and 3d6 total."""
@ -723,22 +876,22 @@ class DiceRollCommands(commands.Cog):
def _get_catcher_error(self, d6_total: int) -> str:
"""Get Catcher error result based on 3d6 total."""
errors = {
18: 'Passed ball for sb2 -> sb12, sb16 -> sb26\nNo error for sb14',
17: 'Passed ball for sb3 -> sb12, sb17 -> sb26\nNo error for sb1, sb13 -> sb15',
16: 'Passed ball for sb4 -> sb12, sb18 -> sb26',
15: 'Passed ball for sb5 -> sb12, sb19 -> sb26',
14: 'Passed ball for sb6 -> sb12, sb20 -> sb26',
13: 'Passed ball for sb7 -> sb12, sb21 -> sb26',
12: 'Passed ball for sb8 -> sb12, sb22 -> sb26',
11: 'Passed ball for sb9 -> sb12, sb23 -> sb26',
10: 'Passed ball for sb10 -> sb12, sb24 -> sb26',
9: 'Passed ball for sb11, sb12, sb25, sb26',
18: '2-base error for e4 -> 16\n1-base error for e2, e3',
17: '1-base error for e1, e2, e4, e5, e12 -> e14, e16',
16: '1-base error for e3 -> e5, e7, e12 -> e14, e16',
15: '1-base error for e7, e8, e12, e13, e15',
14: '1-base error for e6',
13: '1-base error for e9',
12: '1-base error for e10, e14',
11: '1-base error for e11, e15',
10: 'No error',
9: 'No error',
8: 'No error',
7: 'No error',
6: 'No error',
7: '1-base error for e16',
6: '1-base error for e8, e12, e13',
5: 'No error',
4: 'Passed ball for sb1 -> sb12, sb15 -> sb26\nNo error for sb13, sb14',
3: 'Passed ball for sb1 -> sb26'
4: '1-base error for e5, e13',
3: '2-base error for e12 -> e16\n1-base error for e2, e3, e7, e11'
}
return errors.get(d6_total, 'No error')
@ -764,6 +917,34 @@ class DiceRollCommands(commands.Cog):
}
return errors.get(d6_total, 'No error')
def _get_pitcher_rare_play(self, d20_total: int) -> str:
return (
f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n'
f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n'
f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n'
f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n'
)
def _get_infield_rare_play(self, d20_total: int) -> str:
return (
f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n'
f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n'
f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n'
f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n'
f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n'
)
def _get_outfield_rare_play(self, d20_total: int) -> str:
return (
f'**F1**: {OUTFIELD_X_CHART["f1"]["rp"]}\n'
f'**F2**: {OUTFIELD_X_CHART["f2"]["rp"]}\n'
f'**F3**: {OUTFIELD_X_CHART["f3"]["rp"]}\n'
f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n'
f'**DO2**: {OUTFIELD_X_CHART["do2"]["rp"]}\n'
f'**DO3**: {OUTFIELD_X_CHART["do3"]["rp"]}\n'
f'**TR3**: {OUTFIELD_X_CHART["tr3"]["rp"]}\n'
)
def _parse_and_roll_multiple_dice(self, dice_notation: str) -> list[DiceRoll]:
"""Parse dice notation (supports multiple rolls) and return roll results."""
# Split by semicolon for multiple rolls