Limit assistance to challenges with multiple active obstacles
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
- [x] Check against rules on correct quantity of of obstacles and obstacle refresh rules
|
- [x] Check against rules on correct quantity of of obstacles and obstacle refresh rules
|
||||||
- [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
|
||||||
- [ ] 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
|
||||||
- [ ] Checking off personal objectives (gat/name/death) should be handled by group vote rather than adjudication by the deep
|
- [ ] 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
|
- [ ] Double check if PvP obstacles should allow card re-draw
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,9 @@
|
|||||||
Temporary Obstacle: <strong use:tooltip={{ html: cardTooltipHtml(ch.temp_card, $obstacleTable, true) }}>{getCardDisplay(ch.temp_card)}</strong> — {playerName(ch.acting_player_id)} must answer it!
|
Temporary Obstacle: <strong use:tooltip={{ html: cardTooltipHtml(ch.temp_card, $obstacleTable, true) }}>{getCardDisplay(ch.temp_card)}</strong> — {playerName(ch.acting_player_id)} must answer it!
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
|
<p class="info-text" style="margin-top: 0.5rem;">{state.obstacles.filter(o => chObstacleIds.includes(o.id)).length > 1
|
||||||
|
? 'Other Pi-Rats may assist. Assistants do not draw replacement cards.'
|
||||||
|
: 'Only the attempting Pi-Rat can play against this single Obstacle.'}</p>
|
||||||
<div class="challenge-obstacles">
|
<div class="challenge-obstacles">
|
||||||
{#each chObstacleIds as oid}
|
{#each chObstacleIds as oid}
|
||||||
{@const obs = state.obstacles.find(o => o.id === oid)}
|
{@const obs = state.obstacles.find(o => o.id === oid)}
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
$: success_count = column_cards.filter(c => c.success).length;
|
$: success_count = column_cards.filter(c => c.success).length;
|
||||||
$: is_completed = success_count >= state.players.length;
|
$: is_completed = success_count >= state.players.length;
|
||||||
$: in_challenge = openChallenges.some(c => JSON.parse(c.obstacle_ids || '[]').includes(obs.id));
|
$: in_challenge = openChallenges.some(c => JSON.parse(c.obstacle_ids || '[]').includes(obs.id));
|
||||||
|
$: challenge = openChallenges.find(c => JSON.parse(c.obstacle_ids || '[]').includes(obs.id));
|
||||||
|
$: canPlay = state.player.role === 'pirat' && !state.player.is_dead && !state.player.needs_reroll
|
||||||
|
&& challenge && challenge.tax_state !== 'requested'
|
||||||
|
&& (challenge.acting_player_id === state.player.id
|
||||||
|
|| state.obstacles.filter(o => JSON.parse(challenge.obstacle_ids || '[]').includes(o.id)).length > 1);
|
||||||
$: cardCodeLabel = `${cardValue(obs.original_card)}${SUIT_CHAR[obs.suit] || ''}`;
|
$: cardCodeLabel = `${cardValue(obs.original_card)}${SUIT_CHAR[obs.suit] || ''}`;
|
||||||
$: is_face_active = ['J', 'Q', 'K'].includes(active_card_code.slice(0, -1));
|
$: is_face_active = ['J', 'Q', 'K'].includes(active_card_code.slice(0, -1));
|
||||||
|
|
||||||
@@ -61,7 +66,7 @@
|
|||||||
const cardCode = e.dataTransfer.getData('text/plain');
|
const cardCode = e.dataTransfer.getData('text/plain');
|
||||||
if (cardCode) {
|
if (cardCode) {
|
||||||
if (isJoker(cardCode)) playJoker(cardCode);
|
if (isJoker(cardCode)) playJoker(cardCode);
|
||||||
else playCard(cardCode);
|
else if (canPlay) playCard(cardCode);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{#if error}
|
{#if error}
|
||||||
|
|||||||
@@ -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 = 33;
|
export const VERSION = 34;
|
||||||
|
|
||||||
// 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: 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.'] },
|
||||||
{ version: 32, date: '2026-09-04', changes: ['Obstacles are automatically discarded with their columns once they reach one success per player. Unfinished obstacles carry over between scenes.'] },
|
{ version: 32, date: '2026-09-04', changes: ['Obstacles are automatically discarded with their columns once they reach one success per player. Unfinished obstacles carry over between scenes.'] },
|
||||||
{ version: 31, date: '2026-09-04', changes: ['Change your rank-up vote until everyone has voted. The final vote now reveals the result and advances automatically, without a Ready button.', 'The vote result stays visible through upkeep and next-scene setup.'] },
|
{ version: 31, date: '2026-09-04', changes: ['Change your rank-up vote until everyone has voted. The final vote now reveals the result and advances automatically, without a Ready button.', 'The vote result stays visible through upkeep and next-scene setup.'] },
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ def play_challenge_card(
|
|||||||
if challenge.tax_state == "requested":
|
if challenge.tax_state == "requested":
|
||||||
return False, "A Gat/Name Tax is pending. Wait for an answer before playing cards.", {}
|
return False, "A Gat/Name Tax is pending. Wait for an answer before playing cards.", {}
|
||||||
|
|
||||||
|
if player.role != "pirat" or player.is_dead or player.needs_reroll:
|
||||||
|
return False, "Only participating Pi-Rats can play cards.", {}
|
||||||
|
active_ids = {o.id for o in game.obstacles} & set(json.loads(challenge.obstacle_ids))
|
||||||
|
if player.id != challenge.acting_player_id and len(active_ids) < 2:
|
||||||
|
return False, "Assistance requires multiple Obstacles in the current Challenge.", {}
|
||||||
|
|
||||||
# An Obstacle beaten as many times as there are players is spent
|
# An Obstacle beaten as many times as there are players is spent
|
||||||
played_list = json.loads(obstacle.played_cards)
|
played_list = json.loads(obstacle.played_cards)
|
||||||
current_successes = sum(1 for c in played_list if c.get("success") is True)
|
current_successes = sum(1 for c in played_list if c.get("success") is True)
|
||||||
|
|||||||
+17
-1
@@ -242,7 +242,7 @@ def test_assistant_does_not_draw(session):
|
|||||||
session.add_all([obs, p3])
|
session.add_all([obs, p3])
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id])
|
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [o.id for o in game.obstacles])
|
||||||
assert ok
|
assert ok
|
||||||
session.refresh(game)
|
session.refresh(game)
|
||||||
|
|
||||||
@@ -2726,3 +2726,19 @@ def test_deep_refresh_does_not_recycle_discards_or_allow_pirats(session):
|
|||||||
assert "KC" not in crud.get_game_deck(game)
|
assert "KC" not in crud.get_game_deck(game)
|
||||||
crud.confirm_deep_refresh(session, deep.id, ["AH"])
|
crud.confirm_deep_refresh(session, deep.id, ["AH"])
|
||||||
assert "AH" in crud.get_player_hand(deep)
|
assert "AH" in crud.get_player_hand(deep)
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_obstacle_rejects_assistance_but_allows_tax_actor(session):
|
||||||
|
game, deep, (rat, helper) = make_scene_game(session, num_pirats=2)
|
||||||
|
obs = game.obstacles[0]
|
||||||
|
helper.hand_cards = '["KH"]'
|
||||||
|
helper.completed_personal_1 = True
|
||||||
|
session.commit()
|
||||||
|
assert crud.create_challenge(session, game.id, deep.id, rat.id, [obs.id])[0]
|
||||||
|
ok, msg, _ = crud.play_challenge_card(session, helper.id, obs.id, "KH")
|
||||||
|
assert not ok and 'multiple' in msg
|
||||||
|
assert crud.get_player_hand(helper) == ["KH"]
|
||||||
|
challenge = game.challenges[0]
|
||||||
|
assert crud.request_tax(session, challenge.id, rat.id, helper.id)[0]
|
||||||
|
assert crud.respond_tax(session, challenge.id, helper.id, True)[0]
|
||||||
|
assert crud.play_challenge_card(session, helper.id, obs.id, "KH")[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user