Verify and explain defender-only PvP redraws

This commit is contained in:
2026-09-04 19:27:25 -07:00
parent 2f8f67a24c
commit cf63ffed47
5 changed files with 25 additions and 4 deletions
+1 -1
View File
@@ -23,7 +23,7 @@
- [x] Check against the rules on pi-rat hand size and conditions for drawing more cards
- [x] Assisting other pi-rats should only be available if there are multiple obstacles in the current challenge
- [x] Checking off personal objectives (gat/name/death) should be handled by group vote rather than adjudication by the deep
- [ ] Double check if PvP obstacles should allow card re-draw
- [x] Double check if PvP obstacles should allow card re-draw — only the defender, on a suit-color match (including failure).
## Unknown - need to clarify what these mean
@@ -173,7 +173,7 @@
<!-- PvP: defender picks a card -->
{#if myPvpDefense && myPvpDefense.id === ch.id}
<div style="margin-top: 0.75rem;">
<p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Pick a card:</p>
<p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Match the Obstacles color to draw a replacement, even if you lose:</p>
<div class="challenge-actions">
{#each hand.filter(c => !isJoker(c)) as card}
<button class="btn btn-secondary" use:tooltip={{ html: cardTooltipHtml(card, $obstacleTable, true) }} on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
@@ -156,7 +156,7 @@
{#if canDuel}
<div class="sheet-group margin-top">
<h4>⚔️ Duel {crewLabel(target, state.game.captain_player_id)}</h4>
<p class="info-text" style="font-size: 0.85rem;">Challenge them (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards.</p>
<p class="info-text" style="font-size: 0.85rem;">Challenge them (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards. Only the defender draws a replacement, if their card matches the Obstacles color (even on failure).</p>
<select class="select-field" bind:value={pvpCard} style="width: 100%; margin-bottom: 0.5rem;">
<option value="">Throw which card?</option>
{#each hand.filter(c => !isJoker(c)) as card}
+2 -1
View File
@@ -6,10 +6,11 @@
// - Add a CHANGELOG entry only when a commit changes something players can
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
export const VERSION = 35;
export const VERSION = 36;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [
{ version: 36, date: '2026-09-04', changes: ['Duel instructions clarify that only the defender draws a replacement for matching the Obstacles color, even when the defense fails.'] },
{ version: 35, date: '2026-09-04', changes: ['Personal objectives now require a group vote. Propose the next objective from a character sheet, then vote Yes or No at the table. A majority approves; the Captain breaks tied votes.'] },
{ version: 34, date: '2026-09-04', changes: ['Assistance is available only when a Challenge has multiple active Obstacles. Gat and Name Tax takeovers still work against a single Obstacle.'] },
{ version: 33, date: '2026-09-04', changes: ['Hand sizes compare ranks across the crew, including Pi-Rats whose players are the Deep.', 'Discarded cards return to the deck at scene setup. Only the previous Deep can refresh their hand during upkeep, once per scene.'] },
+20
View File
@@ -2835,3 +2835,23 @@ def test_personal_objective_api_requires_vote(session):
assert rat.completed_personal_1
finally:
app.dependency_overrides.clear()
@pytest.mark.parametrize('defense, success, redraw', [
('9S', True, True), ('2S', False, True),
('9H', True, False), ('2H', False, False), ('QS', True, True),
])
def test_pvp_redraw_depends_on_color_not_success(session, defense, success, redraw):
game, deep, (attacker, defender) = make_scene_game(session, num_pirats=2)
attacker.hand_cards = '["7C"]'
defender.hand_cards = json.dumps([defense])
game.deck_cards = '["AD"]'
session.commit()
assert crud.create_pvp_challenge(session, game.id, attacker.id, defender.id, '7C')[0]
duel = game.challenges[0]
ok, _, result = crud.play_pvp_defense(session, duel.id, defender.id, defense)
assert ok and result['success'] == success
assert result['drew_card'] == ('AD' if redraw else None)
assert crud.get_player_hand(attacker) == []
assert crud.get_player_hand(defender) == (['AD'] if redraw else [])
assert not crud.play_pvp_defense(session, duel.id, defender.id, 'AD')[0]