Technique swapping implementation
This commit is contained in:
@@ -66,6 +66,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Technique swapping (swap_techniques phase)
|
||||
let offerTech = '', offerTargetId = '';
|
||||
let acceptChoice = {};
|
||||
let swapBusy = false;
|
||||
let swapError = '';
|
||||
|
||||
async function swapApi(path, body) {
|
||||
swapBusy = true;
|
||||
swapError = '';
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/${path}`, 'POST', body);
|
||||
} catch (e) {
|
||||
swapError = e.message;
|
||||
} finally {
|
||||
swapBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
function offerSwap() {
|
||||
swapApi('offer-swap', { target_player_id: offerTargetId, technique: offerTech });
|
||||
}
|
||||
function cancelOffer() {
|
||||
swapApi('cancel-swap-offer');
|
||||
}
|
||||
function respondOffer(offererId, accept) {
|
||||
swapApi('respond-swap-offer', {
|
||||
offerer_player_id: offererId,
|
||||
accept,
|
||||
technique: accept ? (acceptChoice[offererId] || '') : ''
|
||||
});
|
||||
}
|
||||
function setSwapReady(ready) {
|
||||
swapApi('swap-ready', { ready });
|
||||
}
|
||||
|
||||
// Face Techniques
|
||||
let jackTech = '', queenTech = '', kingTech = '';
|
||||
let assigningFace = false;
|
||||
@@ -103,9 +138,16 @@
|
||||
}
|
||||
|
||||
// Check completion status
|
||||
$: phase = state.game.phase;
|
||||
$: createdTechniques = state.player.created_techniques ? JSON.parse(state.player.created_techniques) : [];
|
||||
$: heldTechniques = state.player.held_techniques ? JSON.parse(state.player.held_techniques) : [];
|
||||
$: swappedTechniques = state.player.swapped_techniques ? JSON.parse(state.player.swapped_techniques) : [];
|
||||
|
||||
$: ownHeldCount = heldTechniques.filter(t => t.creator_id === state.player.id).length;
|
||||
$: incomingOffers = state.players.filter(p => p.id !== state.player.id && p.swap_offer_to_id === state.player.id);
|
||||
$: swapTargets = state.players.filter(p => p.id !== state.player.id && !p.needs_reroll);
|
||||
$: offerTechCreator = (heldTechniques.find(t => t.text === offerTech) || {}).creator_id;
|
||||
$: offerInvalid = !offerTech || !offerTargetId || offerTechCreator === offerTargetId;
|
||||
|
||||
$: basicComplete = !!(state.player.avatar_look && state.player.avatar_smell && state.player.first_words && state.player.good_at_math);
|
||||
$: delegateComplete = !!(state.player.other_like_from_player_id && state.player.other_hate_from_player_id);
|
||||
$: techniquesComplete = !!(state.player.tech_jack && state.player.tech_queen && state.player.tech_king);
|
||||
@@ -237,7 +279,9 @@
|
||||
<div class="card glass-panel section-techniques" id="techniques-card" style="grid-column: span 2;">
|
||||
<h3>IV. Secret Techniques {techniquesComplete ? '✅' : '❌'}</h3>
|
||||
|
||||
{#if createdTechniques.length === 0}
|
||||
{#if state.player.needs_reroll}
|
||||
<p class="text-muted italic">You're spectating for now — you'll create your Pi-Rat with the crew between scenes.</p>
|
||||
{:else if phase === 'character_creation' && createdTechniques.length === 0}
|
||||
<p class="section-desc">Create 3 techniques that you will swap with other players. Make them cool or ridiculous!</p>
|
||||
{#if techError}
|
||||
<div class="alert alert-danger">{techError}</div>
|
||||
@@ -266,42 +310,123 @@
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-full glow-effect" disabled={savingTechs}>Submit Techniques to Swap Pool</button>
|
||||
</form>
|
||||
{:else if createdTechniques.length === 3 && swappedTechniques.length < 3}
|
||||
{:else if phase === 'character_creation'}
|
||||
<div class="submitted-techniques text-center mt-4">
|
||||
<div class="tech-chip">#1: {createdTechniques[0]}</div>
|
||||
<div class="tech-chip">#2: {createdTechniques[1]}</div>
|
||||
<div class="tech-chip">#3: {createdTechniques[2]}</div>
|
||||
<p class="waiting-box margin-top flex justify-center mt-4">
|
||||
<span class="spinner-small"></span>
|
||||
Techniques submitted! Waiting for other players to submit so we can shuffle and swap them.
|
||||
Techniques submitted! Once everyone submits, you'll trade them all away in blind swaps.
|
||||
</p>
|
||||
</div>
|
||||
{:else if !techniquesComplete}
|
||||
{#if swappedTechniques.length === 3}
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<p>Drag and drop your assigned techniques onto your Face Cards:</p>
|
||||
|
||||
{#if faceError}
|
||||
<div class="alert alert-danger">{faceError}</div>
|
||||
{/if}
|
||||
{:else if phase === 'swap_techniques'}
|
||||
<p class="section-desc">
|
||||
Trade away all 3 techniques you created by offering blind swaps to your crewmates.
|
||||
They only learn that you offered a swap — never which technique is on the table!
|
||||
You can't offer a technique to the rat who wrote it.
|
||||
</p>
|
||||
{#if swapError}
|
||||
<div class="alert alert-danger">{swapError}</div>
|
||||
{/if}
|
||||
|
||||
<FaceCardAssigner techniques={swappedTechniques} bind:jack={jackTech} bind:queen={queenTech} bind:king={kingTech} />
|
||||
<h4>Your techniques in hand</h4>
|
||||
<div class="submitted-techniques mt-4">
|
||||
{#each heldTechniques as t}
|
||||
<div class="tech-chip">
|
||||
{t.text}
|
||||
{#if t.creator_id === state.player.id}
|
||||
<span class="text-warning"> — yours, swap it away!</span>
|
||||
{:else}
|
||||
<span class="text-success"> — from {getPlayerName(t.creator_id)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button
|
||||
class="btn btn-primary btn-large w-full"
|
||||
disabled={assigningFace || !jackTech || !queenTech || !kingTech}
|
||||
on:click={assignFaceTechniques}
|
||||
>
|
||||
{assigningFace ? 'Assigning...' : 'Assign to Cards'}
|
||||
</button>
|
||||
{#if state.player.swap_offer_to_id}
|
||||
<div class="delegation-box glass-panel mt-4">
|
||||
<p>
|
||||
You offered <strong>"{state.player.swap_offer_technique}"</strong> to
|
||||
{getPlayerName(state.player.swap_offer_to_id)}. Waiting for their answer...
|
||||
</p>
|
||||
<button class="btn btn-secondary mt-4" disabled={swapBusy} on:click={cancelOffer}>Withdraw Offer</button>
|
||||
</div>
|
||||
{:else if !state.player.is_ready}
|
||||
<div class="delegation-box glass-panel mt-4">
|
||||
<h4>Offer a swap</h4>
|
||||
<div class="input-row">
|
||||
<select class="input-field" bind:value={offerTech}>
|
||||
<option value="" disabled>Pick a technique to give...</option>
|
||||
{#each heldTechniques as t}
|
||||
<option value={t.text}>{t.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<select class="input-field" bind:value={offerTargetId}>
|
||||
<option value="" disabled>Pick a crewmate...</option>
|
||||
{#each swapTargets as p}
|
||||
<option value={p.id} disabled={offerTechCreator === p.id}>
|
||||
{displayName(p)}{offerTechCreator === p.id ? ' (wrote it!)' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="btn btn-primary" disabled={swapBusy || offerInvalid} on:click={offerSwap}>Offer Swap</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center italic text-muted">
|
||||
Waiting for all players to submit techniques so they can be shuffled...
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each incomingOffers as offerer (offerer.id)}
|
||||
<div class="delegation-box glass-panel mt-4">
|
||||
<h4>📨 {displayName(offerer)} offered you a mystery technique!</h4>
|
||||
<p class="text-sm text-muted">Choose one of your techniques to trade back (not one they wrote):</p>
|
||||
<div class="input-row">
|
||||
<select class="input-field" bind:value={acceptChoice[offerer.id]}>
|
||||
<option value="" disabled selected>Pick a technique to give back...</option>
|
||||
{#each heldTechniques as t}
|
||||
<option value={t.text} disabled={t.creator_id === offerer.id}>
|
||||
{t.text}{t.creator_id === offerer.id ? ' (they wrote it!)' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="btn btn-primary" disabled={swapBusy || !acceptChoice[offerer.id]} on:click={() => respondOffer(offerer.id, true)}>Accept</button>
|
||||
<button class="btn btn-secondary" disabled={swapBusy} on:click={() => respondOffer(offerer.id, false)}>Decline</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<div class="text-center mt-6">
|
||||
{#if state.player.is_ready}
|
||||
<p class="waiting-box flex justify-center">
|
||||
<span class="spinner-small"></span>
|
||||
Done swapping! Waiting for the rest of the crew...
|
||||
</p>
|
||||
<button class="btn btn-secondary mt-4" disabled={swapBusy} on:click={() => setSwapReady(false)}>Wait, I want to keep swapping</button>
|
||||
{:else if ownHeldCount > 0}
|
||||
<p class="text-muted italic">Swap away {ownHeldCount} more of your own technique{ownHeldCount === 1 ? '' : 's'} before you can ready up.</p>
|
||||
{:else}
|
||||
<button class="btn btn-primary btn-large" disabled={swapBusy || !!state.player.swap_offer_to_id} on:click={() => setSwapReady(true)}>I'm done swapping</button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if !techniquesComplete}
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<p>Drag and drop your assigned techniques onto your Face Cards:</p>
|
||||
|
||||
{#if faceError}
|
||||
<div class="alert alert-danger">{faceError}</div>
|
||||
{/if}
|
||||
|
||||
<FaceCardAssigner techniques={swappedTechniques} bind:jack={jackTech} bind:queen={queenTech} bind:king={kingTech} />
|
||||
|
||||
<div class="mt-6">
|
||||
<button
|
||||
class="btn btn-primary btn-large w-full"
|
||||
disabled={assigningFace || !jackTech || !queenTech || !kingTech}
|
||||
on:click={assignFaceTechniques}
|
||||
>
|
||||
{assigningFace ? 'Assigning...' : 'Assign to Cards'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="assigned-techs text-center mt-4">
|
||||
<div class="tech-assignment-pill"><span class="card-rank">J</span> {state.player.tech_jack}</div>
|
||||
@@ -325,10 +450,21 @@
|
||||
<li class="status-row {p.tech_jack ? 'is-ready' : ''}">
|
||||
<span>{displayName(p)}</span>
|
||||
<span class="text-sm">
|
||||
{#if p.tech_jack}
|
||||
{#if p.needs_reroll}
|
||||
<span class="text-muted">Spectating</span>
|
||||
{:else if phase === 'swap_techniques'}
|
||||
{#if p.is_ready}
|
||||
<span class="text-success">Done Swapping</span>
|
||||
{:else}
|
||||
{@const ownLeft = JSON.parse(p.held_techniques || '[]').filter(t => t.creator_id === p.id).length}
|
||||
<span class="text-warning">Swapping ({ownLeft} of their own left)</span>
|
||||
{/if}
|
||||
{:else if p.tech_jack}
|
||||
<span class="text-success">Ready</span>
|
||||
{:else if p.created_techniques && JSON.parse(p.created_techniques).length === 3}
|
||||
{:else if phase === 'assign_techniques'}
|
||||
<span class="text-warning">Assigning Face Cards</span>
|
||||
{:else if p.created_techniques && JSON.parse(p.created_techniques).length === 3}
|
||||
<span class="text-warning">Techniques Submitted</span>
|
||||
{:else}
|
||||
<span class="text-muted">Writing Sheet</span>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user