clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / src / app / turns.js
turns.js 3.1K
1/**2 * Coordinates a settled engine action with presentation and shell effects.3 *4 * The engine remains the source of truth. This adapter owns the ordering that5 * must stay consistent for movement, waits, undo, and restart: reconcile old6 * tweens, invoke the action, consume the event, then refresh the shell.7 */8export function createTurnActions(options = {}) {9 const {10 getGame,11 getStage,12 getJuice,13 getNow = () => performance.now(),14 accepted = result => typeof result === 'object' && result !== null ? result.accepted : result,15 setFeedback = () => {},16 clearFeedback = () => setFeedback(''),17 updateUI = () => {},18 render = () => {},19 paintBoardFrame = () => {},20 animateBoard = () => {},21 playSound = () => {},22 playTurnSounds = () => {},23 announceTurn = () => {},24 onWin = () => {},25 onRestart = () => {},26 onUndo = () => {},27 } = options;2829 function execute(action) {30 const game = getGame();31 if (!game || game.getState().won) return null;32 const now = getNow();33 const juice = getJuice();34 juice.syncState(game.getState(), now);35 juice.beginInput(now);36 const result = action(game);37 juice.consume(result, now, game.getState());38 if (accepted(result)) {39 clearFeedback();40 playTurnSounds(result);41 announceTurn(result);42 if (game.getState().won) onWin(now, result);43 updateUI();44 render();45 paintBoardFrame(now);46 animateBoard();47 } else if (result?.blocked) {48 playSound(result.blocked.reason === 'bounds' ? 'blockedBounds' : 'blockedStop');49 setFeedback('BLOCKED / try another sentence');50 paintBoardFrame(now);51 animateBoard(180);52 }53 return result;54 }5556 function move(dx, dy) {57 return execute(game => game.step(dx, dy));58 }5960 function wait() {61 return typeof getGame()?.wait === 'function' ? execute(game => game.wait()) : null;62 }6364 function restart() {65 const game = getGame();66 if (!game) return null;67 const now = getNow();68 const juice = getJuice();69 juice.syncState(game.getState(), now);70 const result = game.reset();71 juice.consume(result, now, game.getState());72 juice.syncState(game.getState(), now);73 clearFeedback();74 playSound('restart');75 announceTurn(result);76 updateUI();77 render();78 const stage = getStage();79 if (stage?.scene !== 'proof') stage?.show('proof');80 onRestart(now, result);81 paintBoardFrame(now);82 animateBoard(300);83 return result;84 }8586 function undo() {87 const game = getGame();88 if (!game) return null;89 const now = getNow();90 const juice = getJuice();91 juice.syncState(game.getState(), now);92 const result = game.undo();93 juice.consume(result, now, game.getState());94 if (accepted(result)) {95 onUndo();96 clearFeedback();97 playSound('undo');98 announceTurn(result);99 updateUI();100 render();101 const stage = getStage();102 if (stage?.scene !== 'proof') stage?.show('proof');103 paintBoardFrame(now);104 animateBoard(300);105 } else playSound('undoEmpty');106 return result;107 }108109 return Object.freeze({ execute, move, wait, restart, undo });110}111112export default createTurnActions;