Files
pirats/frontend/src/components/SceneSetupPhase.svelte
Tim McCarthy 121f2f7498 Player minimum of 3 to start a game or scene
Enforced server-side in lobby/start and confirm_scene_setup via
crud_base.has_min_players; Dev Mode bypasses it so local single-player
testing still works. Lobby and scene-setup UIs show a player-count
message instead of the start button when below the minimum.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:16:49 -07:00

154 lines
6.7 KiB
Svelte

<script>
import { onMount } from 'svelte';
import { apiRequest } from '../lib/api';
import { displayName } from '../lib/cards';
export let state;
let settingRole = false;
let starting = false;
let error = '';
let allowedRoles = ['pirat', 'deep'];
onMount(async () => {
try {
const res = await apiRequest(`/game/${state.game.id}/player/${state.player.id}/allowed-roles`, 'GET');
allowedRoles = res.allowed_roles;
} catch(e) {
console.error("Failed to load allowed roles", e);
}
});
async function setRole(role) {
settingRole = true;
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/set-role`, 'POST', { role });
} catch(e) {
console.error(e);
} finally {
settingRole = false;
}
}
async function startScene() {
starting = true;
error = '';
try {
await apiRequest(`/game/${state.game.id}/scene/start`, 'POST');
} catch(e) {
error = e.message;
starting = false;
}
}
$: 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');
$: enoughPlayers = state.players.length >= 3 || state.game.dev_mode;
$: canStart = enoughPlayers && allRolesAssigned && deepPlayer && piratPlayer && (state.player.is_admin || state.player.role === 'deep');
</script>
<div class="scene-setup-view text-center">
<h2>Scene Setup (Scene #{state.game.current_scene_number})</h2>
<p class="description">Select your role for the upcoming scene. There must be at least one Pi-Rat and one Deep player. If you played the Deep in the last scene, you must play a Pi-Rat this scene!</p>
{#if state.game.current_scene_number === 1}
<div class="notice-banner accent">
<h4>🚢 First Scene Framing</h4>
<p class="info-text" style="margin: 0; font-size: 0.95rem;">
The first scene must establish that the Pi-Rats are <strong>on a ship they can steal</strong> (or have an immediate opportunity to steal one). <strong>Don't start far from water!</strong>
{#if state.player.role === 'deep'}
<br/><span class="text-accent">You're playing the Deep — once the scene starts, describe where the crew is and what ship is ripe for the taking before calling any Challenges.</span>
{/if}
</p>
</div>
{/if}
<div class="setup-grid">
<!-- Role Selection Card -->
<div class="card glass-panel role-selection-card">
<h3>Select Your Role</h3>
{#if state.player.needs_reroll}
<div class="notice-banner accent">
<p class="info-text" style="margin: 0;">🐀 You don't have a Pi-Rat yet! You'll spectate this scene and create your recruit with the crew between scenes.</p>
</div>
{/if}
<div class="role-buttons">
<button on:click={() => setRole('pirat')}
disabled={settingRole || !allowedRoles.includes('pirat')}
class="btn btn-large role-btn pirat-btn {state.player.role === 'pirat' ? 'active' : ''}">
🐀 Play Pi-Rat
</button>
<button on:click={() => setRole('deep')}
disabled={settingRole || !allowedRoles.includes('deep')}
class="btn btn-large role-btn deep-btn {state.player.role === 'deep' ? 'active' : ''}">
🌊 Play the Deep
</button>
</div>
<div class="role-restrictions mt-4">
{#if !allowedRoles.includes('pirat') && !state.player.needs_reroll}
{#if state.game.current_scene_number === 1}
<p class="text-danger text-sm italic">You start at Rank 3, so you must play The Deep for the first scene!</p>
{:else}
<p class="text-danger text-sm italic">You are the only eligible player for The Deep this scene!</p>
{/if}
{/if}
{#if !allowedRoles.includes('deep') && !state.player.needs_reroll}
{#if state.game.current_scene_number === 1}
<p class="text-danger text-sm italic">You start at Rank 1, so you must play your Pi-Rat for the first scene!</p>
{:else}
<p class="text-danger text-sm italic">You played The Deep last scene, so you must play a Pi-Rat!</p>
{/if}
{/if}
</div>
</div>
<!-- Live Roster Card -->
<div class="card glass-panel roster-card">
<h3>Live Roster Selection</h3>
<div class="roster-list" id="scene-roster-list">
{#each state.players as p}
<div class="roster-item">
<span class="player-name">{displayName(p)} <span class="text-sm text-muted">(Rank {p.rank})</span></span>
{#if p.role === 'pirat'}
<span class="role-badge pirat">Pi-Rat</span>
{:else if p.role === 'deep'}
<span class="role-badge deep">The Deep</span>
{:else}
<span class="role-badge unassigned">Choosing...</span>
{/if}
</div>
{/each}
</div>
</div>
</div>
<div class="action-box margin-top">
{#if error}
<div class="alert alert-danger">{error}</div>
{/if}
{#if canStart}
<button on:click={startScene}
disabled={starting}
class="btn btn-primary btn-large glow-effect">
{starting ? 'Starting Scene...' : 'Confirm Roles & Shuffle Deck'}
</button>
{:else if !enoughPlayers}
<p class="text-danger italic mt-4">You need at least 3 players to start a scene — share the join link!</p>
{:else if !allRolesAssigned}
<p class="text-muted italic mt-4">Waiting for all players to choose a role...</p>
{:else if !deepPlayer}
<p class="text-danger italic mt-4">Someone must play as The Deep!</p>
{:else if !piratPlayer}
<p class="text-danger italic mt-4">Someone must play as a Pi-Rat!</p>
{:else}
<p class="text-muted italic mt-4">Waiting for The Deep or Creator to start the scene...</p>
{/if}
</div>
</div>