fertig-classic-games/public/src/games/wordladder/WordLadderLogic.js

73 lines
2.4 KiB
JavaScript

// 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;
}