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] 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] 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
|
- [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
|
## Unknown - need to clarify what these mean
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
<!-- PvP: defender picks a card -->
|
<!-- PvP: defender picks a card -->
|
||||||
{#if myPvpDefense && myPvpDefense.id === ch.id}
|
{#if myPvpDefense && myPvpDefense.id === ch.id}
|
||||||
<div style="margin-top: 0.75rem;">
|
<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">
|
<div class="challenge-actions">
|
||||||
{#each hand.filter(c => !isJoker(c)) as card}
|
{#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>
|
<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}
|
{#if canDuel}
|
||||||
<div class="sheet-group margin-top">
|
<div class="sheet-group margin-top">
|
||||||
<h4>⚔️ Duel {crewLabel(target, state.game.captain_player_id)}</h4>
|
<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;">
|
<select class="select-field" bind:value={pvpCard} style="width: 100%; margin-bottom: 0.5rem;">
|
||||||
<option value="">Throw which card?</option>
|
<option value="">Throw which card?</option>
|
||||||
{#each hand.filter(c => !isJoker(c)) as card}
|
{#each hand.filter(c => !isJoker(c)) as card}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
// - Add a CHANGELOG entry only when a commit changes something players can
|
// - Add a CHANGELOG entry only when a commit changes something players can
|
||||||
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
|
// 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, ...] }.
|
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
|
||||||
export const CHANGELOG = [
|
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: 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: 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.'] },
|
{ 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
|
assert rat.completed_personal_1
|
||||||
finally:
|
finally:
|
||||||
app.dependency_overrides.clear()
|
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