From f61201144f895227834111ec1c4a7b2b6dbad8df Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Fri, 12 Jun 2026 11:32:15 -0700 Subject: [PATCH] Dead Pi-Rat reroll rework --- TODO.md | 10 +- .../components/CharacterCreationPhase.svelte | 126 +------ .../src/components/FaceCardAssigner.svelte | 79 +++++ frontend/src/components/RecruitPhase.svelte | 309 ++++++++++++++++++ .../src/components/SceneSetupPhase.svelte | 14 +- frontend/src/components/UpkeepPhase.svelte | 121 ++----- .../components/scene/CharacterSheet.svelte | 9 +- frontend/src/pages/Dashboard.svelte | 3 + src/pirats/crud_base.py | 31 +- src/pirats/crud_challenge.py | 6 +- src/pirats/crud_character.py | 245 +++++++++----- src/pirats/crud_scene.py | 18 +- src/pirats/crud_upkeep.py | 59 ++-- .../71c6d3e50ba3_recruit_creation_columns.py | 34 ++ src/pirats/models.py | 6 +- src/pirats/routes_character.py | 54 ++- src/pirats/routes_scene.py | 1 + tests/test_game.py | 243 ++++++++------ 18 files changed, 908 insertions(+), 460 deletions(-) create mode 100644 frontend/src/components/FaceCardAssigner.svelte create mode 100644 frontend/src/components/RecruitPhase.svelte create mode 100644 src/pirats/migrations/versions/71c6d3e50ba3_recruit_creation_columns.py diff --git a/TODO.md b/TODO.md index 8c06dcd..dd7e8e6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,5 @@ A roughly triaged list of features and improvements needed for the app, in descending order of criticality. -- [ ] **Dead Pi-Rat re-roll flow.** I suspect this is badly broken, as it was implemented in a rush by an older model. In particular, other players will need to contribute likes/hates and techniques, which I believe are not currently implemented. This flow should also be used for new players who join after initial character creation, and should take place as a between-scenes phase after Deep Upkeep. - - [ ] **Dev Mode.** Move the dev mode toggle in character creation to the hamburger mode (and leave it around for all phases in case we want to add other dev mode features). It's now only visible to admin player, but affects all players. For non-admin players, the "suggest" buttons in the character creator are hidden unless dev mode is on. For the admin player, they also get the "auto-assign everything" button in the hamburger menu, renamed to "Skip Character Creation", which fully automates any incomplete parts of character creation for all players and moves on to the pre-scene phase. - [ ] **Technique swapping.** During character creation, players can offer to swap techniques with another player, as long as the technique they're offering wasn't created by the player they're offering to swap with. The swap offer should contain no information other than that a swap has been offered. This requires tracking who created each technique, which we should be doing anyway since it's illegal for a player to wind up with their own techniques and I'm not sure the current shuffler cares about that. I want two options here: First option, players start with all of their own techniques and the game can't proceed to technique assignment to cards until they've successfully swapped them all away and everyone has clicked a "ready" button. Second, a button in the hamburger menu that the Admin player can click to fully auto-assign techniques, which randomly distributes them to eligible players and automatically assigns them to J/Q/K. This button should only appear during technique distribution. It does not require dev mode, just admin privileges. @@ -15,3 +13,11 @@ A roughly triaged list of features and improvements needed for the app, in desce - [ ] **Example Play formatting.** The Example Play section of the Rules page could use a bit more formatting for legibility. Make diagrams of the card state at each step rather than just a text listing. - [ ] **Suggestions.** Take a pass through all of the suggestions in suggestions.js, rewrite ones that are stiff, awkward, or don't fit the theme/setting as described in the rulebook. Move the suggestion pool from suggestions.js into a backend API endpoint. + +- [ ] **Defensive coding.** While we trust our players, this app is exposed on the open internet. I'd like to take basic precautions like making sure we're safe against SQL injection, doing some basic sanitizing of free inputs from players, and making sure that every game action API call is specific and constrained enough that it can't just do arbitrary state updates. + +- [ ] **Player minimum.** You need at least three players to start a game. + +- [ ] **Player kicking.** Admins should be able to kick players out of the game so that it doesn't get stuck if a player vanishes. + +- [ ] **Dead Pi-Rat rank voting streamlined**. Don't allow voting for dead Pi-Rats. If a player's only vote options are dead, then they skip voting entirely. diff --git a/frontend/src/components/CharacterCreationPhase.svelte b/frontend/src/components/CharacterCreationPhase.svelte index f328619..0164269 100644 --- a/frontend/src/components/CharacterCreationPhase.svelte +++ b/frontend/src/components/CharacterCreationPhase.svelte @@ -3,6 +3,7 @@ import { apiRequest } from '../lib/api'; import { getSuggestion, getTechniqueSuggestion, getTechniqueSuggestions } from '../lib/suggestions'; import { displayName } from '../lib/cards'; + import FaceCardAssigner from './FaceCardAssigner.svelte'; export let state; @@ -352,130 +353,7 @@
{faceError}
{/if} -
-

Available Swapped Techniques

-

Drag a technique onto a card slot below to assign it.

-
- {#each swappedTechniques as tech} -
e.dataTransfer.setData('text/plain', tech)} - class="draggable-tech-chip {tech === jackTech || tech === queenTech || tech === kingTech ? 'assigned-hidden' : ''}" - > - ⋮⋮ {tech} -
- {/each} -
-
- - -
- - -
-
{ - const tech = e.dataTransfer.getData('text/plain'); - if (tech) { - if (queenTech === tech) queenTech = ''; - if (kingTech === tech) kingTech = ''; - jackTech = tech; - } - }}> -
J
-
- J -
-
-
Jack
-
Drop Technique Here
-
- {#if jackTech} -
- {jackTech} - jackTech = ''}>× -
- {/if} -
-
- -
-
- - -
-
{ - const tech = e.dataTransfer.getData('text/plain'); - if (tech) { - if (jackTech === tech) jackTech = ''; - if (kingTech === tech) kingTech = ''; - queenTech = tech; - } - }}> -
Q
-
- Q -
-
-
Queen
-
Drop Technique Here
-
- {#if queenTech} -
- {queenTech} - queenTech = ''}>× -
- {/if} -
-
- -
-
- - -
-
{ - const tech = e.dataTransfer.getData('text/plain'); - if (tech) { - if (jackTech === tech) jackTech = ''; - if (queenTech === tech) queenTech = ''; - kingTech = tech; - } - }}> -
K
-
- K -
-
-
King
-
Drop Technique Here
-
- {#if kingTech} -
- {kingTech} - kingTech = ''}>× -
- {/if} -
-
- -
-
-
+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ + + {/if} + + +
+

II. Crew Delegations {likeDone && hateDone ? '✅' : '❌'}

+

Crewmates decide what their rats like and hate about your new recruit.

+
+
+

What does another rat LIKE about you?

+
+ {me.other_like ? `"${me.other_like}"` : '(Waiting for answer...)'} +
+
— {getPlayerName(me.other_like_from_player_id)}
+
+
+

What does another rat HATE about you?

+
+ {me.other_hate ? `"${me.other_hate}"` : '(Waiting for answer...)'} +
+
— {getPlayerName(me.other_hate_from_player_id)}
+
+
+
+ +
+

III. Your New Secret Techniques {techsDone ? '✅' : '❌'}

+ {#if !techsDone} +

Your crewmates are writing 3 fresh techniques for you:

+ + {:else} +
+

Drag and drop your new techniques onto your Face Cards:

+ {#if finalizeError} +
{finalizeError}
+ {/if} + +
+ + {#if !basicComplete || !likeDone || !hateDone} +

Finish your Rat Records and wait for your Like/Hate answers before joining.

+ {/if} +
+
+ {/if} +
+ {/if} + + +
+

{iAmRecruit ? 'IV.' : 'I.'} Your Inbox (Help the Recruits)

+

Answer questions and write Secret Techniques for the fresh recruits. Be creative and have fun with it!

+
+ {#each myTasks as task (task.recruit.id)} + {#if task.like} +
+ +
+ + + +
+
+ {/if} + {#if task.hate} +
+ +
+ + + +
+
+ {/if} + {#each task.techSlots as slot} +
+ + {#if techErrors[`${task.recruit.id}:${slot.slot}`]} +
{techErrors[`${task.recruit.id}:${slot.slot}`]}
+ {/if} +
+ + + +
+
+ {/each} + {/each} + {#if myTasks.length === 0} +

+ {#if iAmRecruit} + No tasks — your crewmates are hard at work on your new rat. + {:else} + Nothing left to write. Waiting for the recruits to finish their sheets... + {/if} +

+ {/if} +
+
+ + +
+

{iAmRecruit ? 'V.' : 'II.'} Recruit Progress

+
+
    + {#each recruits as p} +
  • + {displayName(p)} + {recruitProgress(p)} +
  • + {/each} + {#if recruits.length === 0} +
  • All recruits are aboard! Heading to the next scene...
  • + {/if} +
+
+
+ + diff --git a/frontend/src/components/SceneSetupPhase.svelte b/frontend/src/components/SceneSetupPhase.svelte index 185965f..1248bc7 100644 --- a/frontend/src/components/SceneSetupPhase.svelte +++ b/frontend/src/components/SceneSetupPhase.svelte @@ -41,7 +41,7 @@ } } - $: allRolesAssigned = state.players.every(p => p.role !== null); + $: allRolesAssigned = state.players.every(p => p.role !== null || p.needs_reroll); $: deepPlayer = state.players.find(p => p.role === 'deep'); $: piratPlayer = state.players.find(p => p.role === 'pirat'); $: canStart = allRolesAssigned && deepPlayer && piratPlayer && (state.player.is_creator || state.player.role === 'deep'); @@ -67,7 +67,13 @@

Select Your Role

- + + {#if state.player.needs_reroll} +
+

🐀 You don't have a Pi-Rat yet! You'll spectate this scene and create your recruit with the crew between scenes.

+
+ {/if} +
- {#if !allowedRoles.includes('pirat')} + {#if !allowedRoles.includes('pirat') && !state.player.needs_reroll} {#if state.game.current_scene_number === 1}

You start at Rank 3, so you must play The Deep for the first scene!

{:else}

You are the only eligible player for The Deep this scene!

{/if} {/if} - {#if !allowedRoles.includes('deep')} + {#if !allowedRoles.includes('deep') && !state.player.needs_reroll} {#if state.game.current_scene_number === 1}

You start at Rank 1, so you must play your Pi-Rat for the first scene!

{:else} diff --git a/frontend/src/components/UpkeepPhase.svelte b/frontend/src/components/UpkeepPhase.svelte index 0dfb433..ef893ec 100644 --- a/frontend/src/components/UpkeepPhase.svelte +++ b/frontend/src/components/UpkeepPhase.svelte @@ -1,6 +1,5 @@