diff --git a/commands/dice/rolls.py b/commands/dice/rolls.py index 355cb74..4672ccd 100644 --- a/commands/dice/rolls.py +++ b/commands/dice/rolls.py @@ -108,6 +108,37 @@ class DiceRollCommands(commands.Cog): embed.title = f'At bat roll for {ctx.author.display_name}' await ctx.send(embed=embed) + @discord.app_commands.command( + name="scout", + description="Roll weighted scouting dice (1d6;2d6;1d20) based on card type" + ) + @discord.app_commands.describe( + card_type="Type of card being scouted" + ) + @discord.app_commands.choices(card_type=[ + discord.app_commands.Choice(name="Batter", value="batter"), + discord.app_commands.Choice(name="Pitcher", value="pitcher") + ]) + @logged_command("/scout") + async def scout_dice( + self, + interaction: discord.Interaction, + card_type: discord.app_commands.Choice[str] + ): + """Roll weighted scouting dice based on card type (batter or pitcher).""" + await interaction.response.defer() + + # Get the card type value + card_type_value = card_type.value + + # Roll weighted scouting dice + roll_results = self._roll_weighted_scout_dice(card_type_value) + + # Create embed for the roll results + embed = self._create_multi_roll_embed("1d6;2d6;1d20", roll_results, interaction.user) + embed.title = f'Scouting roll for {interaction.user.display_name} ({card_type.name})' + await interaction.followup.send(embed=embed) + @discord.app_commands.command( name="fielding", description="Roll Super Advanced fielding dice for a defensive position" @@ -563,6 +594,38 @@ class DiceRollCommands(commands.Cog): 'total': total } + def _roll_weighted_scout_dice(self, card_type: str) -> list[dict]: + """ + Roll scouting dice with weighted first d6 based on card type. + + Args: + card_type: Either "batter" (1-3) or "pitcher" (4-6) for first d6 + + Returns: + List of 3 roll result dicts: weighted 1d6, normal 2d6, normal 1d20 + """ + # First die (1d6) - weighted based on card type + if card_type == "batter": + first_roll = random.randint(1, 3) + else: # pitcher + first_roll = random.randint(4, 6) + + first_d6_result = { + 'dice_notation': '1d6', + 'num_dice': 1, + 'die_sides': 6, + 'rolls': [first_roll], + 'total': first_roll + } + + # Second roll (2d6) - normal + second_result = self._parse_and_roll_single_dice("2d6") + + # Third roll (1d20) - normal + third_result = self._parse_and_roll_single_dice("1d20") + + return [first_d6_result, second_result, third_result] + def _create_multi_roll_embed(self, dice_notation: str, roll_results: list[dict], user: discord.User | discord.Member) -> discord.Embed: """Create an embed for multiple dice roll results.""" embed = EmbedTemplate.create_base_embed( diff --git a/tests/test_commands_dice.py b/tests/test_commands_dice.py index 9f6c34f..76fe925 100644 --- a/tests/test_commands_dice.py +++ b/tests/test_commands_dice.py @@ -551,4 +551,100 @@ class TestDiceRollCommands: # Check 3d6 assert results[1]['dice_notation'] == '3d6' assert results[1]['num_dice'] == 3 - assert results[1]['die_sides'] == 6 \ No newline at end of file + assert results[1]['die_sides'] == 6 + + def test_weighted_scout_dice_batter(self, dice_cog): + """Test that batter scout dice always rolls 1-3 for first d6.""" + # Roll 20 times to ensure consistency + for _ in range(20): + results = dice_cog._roll_weighted_scout_dice("batter") + + # Should have 3 dice groups (1d6, 2d6, 1d20) + assert len(results) == 3 + + # First d6 should ALWAYS be 1-3 for batter + first_d6 = results[0]['rolls'][0] + assert 1 <= first_d6 <= 3, f"Batter first d6 was {first_d6}, expected 1-3" + + # Second roll (2d6) should be normal + assert results[1]['num_dice'] == 2 + assert results[1]['die_sides'] == 6 + assert all(1 <= roll <= 6 for roll in results[1]['rolls']) + + # Third roll (1d20) should be normal + assert results[2]['num_dice'] == 1 + assert results[2]['die_sides'] == 20 + assert 1 <= results[2]['rolls'][0] <= 20 + + def test_weighted_scout_dice_pitcher(self, dice_cog): + """Test that pitcher scout dice always rolls 4-6 for first d6.""" + # Roll 20 times to ensure consistency + for _ in range(20): + results = dice_cog._roll_weighted_scout_dice("pitcher") + + # Should have 3 dice groups (1d6, 2d6, 1d20) + assert len(results) == 3 + + # First d6 should ALWAYS be 4-6 for pitcher + first_d6 = results[0]['rolls'][0] + assert 4 <= first_d6 <= 6, f"Pitcher first d6 was {first_d6}, expected 4-6" + + # Second roll (2d6) should be normal + assert results[1]['num_dice'] == 2 + assert results[1]['die_sides'] == 6 + assert all(1 <= roll <= 6 for roll in results[1]['rolls']) + + # Third roll (1d20) should be normal + assert results[2]['num_dice'] == 1 + assert results[2]['die_sides'] == 20 + assert 1 <= results[2]['rolls'][0] <= 20 + + @pytest.mark.asyncio + async def test_scout_command_batter(self, dice_cog, mock_interaction): + """Test scout slash command with batter card type.""" + # Mock a card_type choice + card_type_choice = MagicMock() + card_type_choice.value = 'batter' + card_type_choice.name = 'Batter' + + await dice_cog.scout_dice.callback(dice_cog, mock_interaction, card_type_choice) + + # Verify response was deferred + mock_interaction.response.defer.assert_called_once() + + # Verify followup was sent with embed + mock_interaction.followup.send.assert_called_once() + call_args = mock_interaction.followup.send.call_args + assert 'embed' in call_args.kwargs + + # Verify embed has the correct format + embed = call_args.kwargs['embed'] + assert isinstance(embed, discord.Embed) + assert embed.title == "Scouting roll for TestUser (Batter)" + assert len(embed.fields) == 1 + assert "Details:[1d6;2d6;1d20" in embed.fields[0].value + + @pytest.mark.asyncio + async def test_scout_command_pitcher(self, dice_cog, mock_interaction): + """Test scout slash command with pitcher card type.""" + # Mock a card_type choice + card_type_choice = MagicMock() + card_type_choice.value = 'pitcher' + card_type_choice.name = 'Pitcher' + + await dice_cog.scout_dice.callback(dice_cog, mock_interaction, card_type_choice) + + # Verify response was deferred + mock_interaction.response.defer.assert_called_once() + + # Verify followup was sent with embed + mock_interaction.followup.send.assert_called_once() + call_args = mock_interaction.followup.send.call_args + assert 'embed' in call_args.kwargs + + # Verify embed has the correct format + embed = call_args.kwargs['embed'] + assert isinstance(embed, discord.Embed) + assert embed.title == "Scouting roll for TestUser (Pitcher)" + assert len(embed.fields) == 1 + assert "Details:[1d6;2d6;1d20" in embed.fields[0].value \ No newline at end of file