A hand forced by amnesia or paralysis still casts what it completes
The default cast was chosen only when a gesture was clicked, so a hand bound to repeat its last gesture finished its spell and let it pass. Defaults are now chosen at the start of every turn. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0036eaf625
commit
527820ee95
@@ -228,9 +228,11 @@ export class Duel {
|
|||||||
/** Opponent gestures to light up in the ledger. */
|
/** Opponent gestures to light up in the ledger. */
|
||||||
highlight = $state<Highlight | null>(null);
|
highlight = $state<Highlight | null>(null);
|
||||||
|
|
||||||
constructor() {
|
constructor(initial?: GameState) {
|
||||||
const saved = typeof localStorage === 'undefined' ? null : loadSaved();
|
const saved = typeof localStorage === 'undefined' ? null : loadSaved();
|
||||||
if (saved) {
|
if (initial) {
|
||||||
|
this.state = initial;
|
||||||
|
} else if (saved) {
|
||||||
this.state = saved.state;
|
this.state = saved.state;
|
||||||
this.pins = saved.pins ?? { left: null, right: null };
|
this.pins = saved.pins ?? { left: null, right: null };
|
||||||
this.sets = saved.sets ?? blankSets();
|
this.sets = saved.sets ?? blankSets();
|
||||||
@@ -238,6 +240,16 @@ export class Duel {
|
|||||||
this.monsterOrders = saved.monsterOrders ?? {};
|
this.monsterOrders = saved.monsterOrders ?? {};
|
||||||
this.release = saved.release ?? '';
|
this.release = saved.release ?? '';
|
||||||
}
|
}
|
||||||
|
this.syncAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose each hand's default cast from its gestures. A hand bound by amnesia or
|
||||||
|
* paralysis can finish a spell without the player touching it, so this must run
|
||||||
|
* at the start of every turn, not only after a click.
|
||||||
|
*/
|
||||||
|
syncAll(): void {
|
||||||
|
for (const s of SET_NAMES) for (const h of HANDS) this.syncSpell(s, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rename the player for this duel and every duel after it. An empty name goes back to a drawn one. */
|
/** Rename the player for this duel and every duel after it. An empty name goes back to a drawn one. */
|
||||||
@@ -631,6 +643,7 @@ export class Duel {
|
|||||||
|
|
||||||
private resetDrafts(): void {
|
private resetDrafts(): void {
|
||||||
this.sets = blankSets();
|
this.sets = blankSets();
|
||||||
|
this.syncAll();
|
||||||
this.stabTarget = FOE;
|
this.stabTarget = FOE;
|
||||||
// Monsters keep attacking what they attacked last, until told otherwise or the target is gone.
|
// Monsters keep attacking what they attacked last, until told otherwise or the target is gone.
|
||||||
const orders: Record<string, TargetId> = {};
|
const orders: Record<string, TargetId> = {};
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { Duel } from './duel.svelte';
|
||||||
|
import { resolveTurn } from './resolve';
|
||||||
|
import { createGame, type GameState, type TurnInput } from './state';
|
||||||
|
import type { Gesture } from './spells';
|
||||||
|
|
||||||
|
const noop = (): TurnInput => ({ left: '-', right: '-', casts: [] });
|
||||||
|
|
||||||
|
/** A is one gesture from a lightning bolt and has just been struck by amnesia. */
|
||||||
|
function amnesiaBeforeLightning(): GameState {
|
||||||
|
let s = createGame({ A: 'You', B: 'Foe' }, 1);
|
||||||
|
const left = 'DFFD';
|
||||||
|
const foe = '-DPP';
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
s = resolveTurn(s, {
|
||||||
|
A: { ...noop(), left: left[i] as Gesture },
|
||||||
|
B: { ...noop(), left: foe[i] as Gesture, casts: i === 3 ? [{ hand: 'left', spellId: 'amnesia' }] : [] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect(s.wizards.A.constraints.amnesia).toBe(true);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('the duel store', () => {
|
||||||
|
it('casts the spell a forced repeat completes, without a click', () => {
|
||||||
|
const duel = new Duel(amnesiaBeforeLightning());
|
||||||
|
expect(duel.forced.left).toBe('D');
|
||||||
|
expect(duel.completions.main.left.map((c) => c.spell.id)).toContain('lightning_bolt');
|
||||||
|
expect(duel.sets.main.left.spellId).toBe('lightning_bolt');
|
||||||
|
expect(duel.ready).toBe(true);
|
||||||
|
duel.reveal();
|
||||||
|
expect(duel.foe.hp).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('chooses a default cast at the start of the next turn as well', () => {
|
||||||
|
let s = createGame({ A: 'You', B: 'Foe' }, 1);
|
||||||
|
// A's left hand made D-F-F-D; B's amnesia lands as A makes the fourth gesture.
|
||||||
|
const left = 'DFFD';
|
||||||
|
const foe = '-DPP';
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
s = resolveTurn(s, { A: { ...noop(), left: left[i] as Gesture }, B: { ...noop(), left: foe[i] as Gesture } });
|
||||||
|
}
|
||||||
|
const duel = new Duel(s);
|
||||||
|
duel.choose('main', 'left', 'D');
|
||||||
|
duel.choose('main', 'right', '-');
|
||||||
|
// The bot is replaced by a scripted foe for this test: resolve directly, then hand the store the result.
|
||||||
|
const next = resolveTurn(s, { A: { ...noop(), left: 'D' }, B: { ...noop(), left: 'P', casts: [{ hand: 'left', spellId: 'amnesia' }] } });
|
||||||
|
const fresh = new Duel(next);
|
||||||
|
expect(fresh.sets.main.left.spellId).toBe('lightning_bolt');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user