// Pure Word Ladder logic — no Phaser dependency. // // A ladder transforms START into TARGET by changing exactly one letter per rung, // where every rung is a real word of the same length. State tracks the rungs // played so far (rungs[0] is always the start word). export function createInitialState({ start, target, par, length }) { const S = String(start).toUpperCase(); return { start: S, target: String(target).toUpperCase(), par: par ?? null, length: length ?? S.length, rungs: [S], status: 'playing', // 'playing' | 'won' }; } export function lastRung(state) { return state.rungs[state.rungs.length - 1]; } // True when a and b are the same length and differ in exactly one position. export function isOneLetterDifferent(a, b) { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) diff++; if (diff > 1) return false; } return diff === 1; } // Index of the single differing position between two equal-length words, or -1. export function changedIndex(prev, word) { if (!prev || prev.length !== word.length) return -1; for (let i = 0; i < word.length; i++) { if (prev[i] !== word[i]) return i; } return -1; } // Attempt to append `word` as the next rung. Returns { ok, state } on success or // { ok: false, reason } with a short message suitable for display. export function tryAddRung(state, word, validSet) { if (state.status !== 'playing') return { ok: false, reason: 'Round over' }; const W = String(word).toUpperCase(); if (W.length !== state.length) return { ok: false, reason: 'Not enough letters' }; if (!isOneLetterDifferent(lastRung(state), W)) return { ok: false, reason: 'Change exactly one letter' }; if (!validSet.has(W)) return { ok: false, reason: 'Not in word list' }; if (state.rungs.includes(W)) return { ok: false, reason: 'Word already used' }; const rungs = [...state.rungs, W]; const status = W === state.target ? 'won' : 'playing'; return { ok: true, state: { ...state, rungs, status } }; } // Remove the most recent rung (never the start word). export function undoRung(state) { if (state.rungs.length <= 1) return state; return { ...state, rungs: state.rungs.slice(0, -1), status: 'playing' }; } export function isSolved(state) { return lastRung(state) === state.target; } // Steps taken so far (rungs beyond the start word). export function stepsTaken(state) { return state.rungs.length - 1; }