Verify and explain defender-only PvP redraws
This commit is contained in:
@@ -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 Obstacle’s 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 Obstacle’s 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}
|
||||
|
||||
@@ -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 Obstacle’s 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.'] },
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user