From 22880cc466e1a87de5f95060d5cc29aa485d9d54 Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Fri, 4 Sep 2026 19:33:39 -0700 Subject: [PATCH] Keep completed duel outcomes visible throughout the scene --- TODO.md | 2 +- frontend/src/components/ScenePhase.svelte | 5 ++--- .../components/scene/ChallengePanel.svelte | 20 +++++++++++++++++++ frontend/src/lib/changelog.js | 3 ++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 4384c1e..985e2a5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ ## Features - [x] Add an online indicator that displays if a player is connected -- [ ] PvP challenges should linger in the UI after completion so that players can see the result +- [x] PvP challenges should linger in the UI after completion so that players can see the result - [x] Add hat/gun icons next to player names on the player list to indicate if they're the captain and/or have a gat - [ ] Replace red/green borders of successful/failed cards with checks and crosses in the upper corner of the card. - [ ] Add a note taking area to store game state between sessions diff --git a/frontend/src/components/ScenePhase.svelte b/frontend/src/components/ScenePhase.svelte index 5d8081a..be06a64 100644 --- a/frontend/src/components/ScenePhase.svelte +++ b/frontend/src/components/ScenePhase.svelte @@ -15,9 +15,8 @@ $: playerTechs = { J: state.player.tech_jack, Q: state.player.tech_queen, K: state.player.tech_king }; $: isDeep = state.player.role === 'deep'; $: openChallenges = (state.challenges || []).filter(c => c.status === 'open'); - // The Challenge area is shown when something is happening there, or to the Deep - // (who calls Challenges and ends the scene from it). - $: showChallengeArea = isDeep || openChallenges.length > 0; + // Keep duel results visible to the whole table for the rest of the scene. + $: showChallengeArea = isDeep || openChallenges.length > 0 || (state.challenges || []).some(c => c.challenge_type === 'pvp' && ['succeeded', 'failed'].includes(c.status));
diff --git a/frontend/src/components/scene/ChallengePanel.svelte b/frontend/src/components/scene/ChallengePanel.svelte index 4709fb0..0f733c3 100644 --- a/frontend/src/components/scene/ChallengePanel.svelte +++ b/frontend/src/components/scene/ChallengePanel.svelte @@ -17,6 +17,7 @@ $: hand = state.player.hand_cards ? JSON.parse(state.player.hand_cards) : []; $: openChallenges = (state.challenges || []).filter(c => c.status === 'open'); + $: completedDuels = (state.challenges || []).filter(c => c.challenge_type === 'pvp' && ['succeeded', 'failed'].includes(c.status)); $: challengedObstacleIds = new Set(openChallenges.flatMap(c => JSON.parse(c.obstacle_ids || '[]'))); $: myTaxRequest = openChallenges.find(c => c.tax_state === 'requested' && c.tax_target_id === state.player.id) || null; $: myPvpDefense = openChallenges.find(c => c.challenge_type === 'pvp' && c.acting_player_id === state.player.id) || null; @@ -184,6 +185,25 @@
{/each} + +{#each completedDuels as ch (ch.id)} +
+

⚔️ Duel: {playerName(ch.challenger_player_id)} vs {playerName(ch.target_player_id)}

+

+ {ch.status === 'succeeded' ? '✓' : '✕'} + {playerName(ch.acting_player_id)} {ch.status === 'succeeded' ? 'won the defense' : 'lost the defense'}. + {playerName(ch.status === 'succeeded' ? ch.acting_player_id : ch.challenger_player_id)} wins the duel. +

+

Temporary Obstacle: {getCardDisplay(ch.temp_card)}

+ {#each JSON.parse(ch.plays || '[]') as play} +

+ Defense: {getCardDisplay(play.card)}. + {play.details} +

+ {/each} +
+{/each} + {#if isDeep && openChallenges.length === 0} diff --git a/frontend/src/lib/changelog.js b/frontend/src/lib/changelog.js index 45cb952..f1c5813 100644 --- a/frontend/src/lib/changelog.js +++ b/frontend/src/lib/changelog.js @@ -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 = 38; +export const VERSION = 39; // Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }. export const CHANGELOG = [ + { version: 39, date: '2026-09-04', changes: ['Completed duels stay visible for the rest of the scene, showing the winner, both cards, and how the defense resolved.'] }, { version: 38, date: '2026-09-04', changes: ['The crew roster shows who is online, with hat and gun icons for the Captain and Pi-Rats who have a Gat.'] }, { version: 37, date: '2026-09-04', changes: ['Color-match replacement cards arrive after the Deep resolves a Challenge. Each applied Obstacle accepts one card per Challenge, and each assistant can contribute one card.'] }, { 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.'] },