You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.7 KiB

import { randomInt } from 'node:crypto';
import { geese } from '../../shared/oca-board.mjs';
export { geese };
function advanceTurn(state, seat) {
let candidate = seat;
// Each skipped seat consumes one turn automatically; a full circuit may still have penalties.
for (let n = 0; n < state.positions.length * 4; n++) {
candidate = (candidate + 1) % state.positions.length;
if (state.withdrawn?.includes(candidate)) continue;
if (state.skips[candidate] > 0) {
state.skips[candidate]--;
continue;
}
state.turn = candidate;
return;
}
throw new Error('Estado de turnos no válido.');
}
export const oca = {
withdraw(state, seat) {
const next = structuredClone(state);
next.withdrawn = [...(next.withdrawn ?? []), seat];
next.positions[seat] = 0;
next.skips[seat] = 0;
if (next.turn === seat) advanceTurn(next, seat);
return next;
},
id: 'oca',
version: '1.0.0',
minPlayers: 2,
maxPlayers: 4,
create(players = 4) {
return {
positions: Array(players).fill(0),
skips: Array(players).fill(0),
turn: 0,
rolled: null,
lastPath: [],
lastPlayer: null,
note: 'Llegad al jardín de la casilla 63.',
};
},
legal(state, seat) {
return seat === state.turn ? [{ type: 'roll' }] : [];
},
apply(state, action, seat, rng = () => randomInt(1, 7)) {
if (seat !== state.turn || action.type !== 'roll')
throw new Error('No es tu turno.');
const die = rng();
if (!Number.isInteger(die) || die < 1 || die > 6)
throw new Error('Dado no válido.');
const next = structuredClone(state),
start = state.positions[seat];
let target = start + die,
repeat = false,
sound = 'roll';
next.rolled = die;
next.lastPlayer = seat;
next.lastEffect = null;
next.lastPath = Array.from({ length: die }, (_, i) =>
start + i + 1 > 63 ? 126 - start - i - 1 : start + i + 1,
);
if (target > 63) target = 126 - target;
let label = `Dado: ${die} · casilla ${target}`;
if (target === 63) {
next.positions[seat] = 63;
next.lastEffect = 'goal';
next.note = '¡Has llegado al jardín!';
return {
state: next,
label: `${label} · llega al jardín`,
sound,
result: { winner: seat, reason: 'llega exactamente a la casilla 63' },
};
}
if (geese.includes(target)) {
target = geese[geese.indexOf(target) + 1] ?? target;
repeat = true;
label += ` · de oca a oca: ${target}`;
} else if (target === 6 || target === 12) {
target = target === 6 ? 12 : 6;
repeat = true;
label += ` · puente a ${target}`;
} else if (target === 26 || target === 53) {
target = target === 26 ? 53 : 26;
repeat = true;
label += ` · de dado a dado: ${target}`;
} else if (target === 19) {
next.skips[seat] = 1;
label += ' · posada: descansa un turno';
} else if (target === 31) {
next.skips[seat] = 3;
label += ' · pozo: descansa tres turnos';
} else if (target === 42) {
target = 30;
label += ' · laberinto: vuelve a 30';
} else if (target === 52) {
next.skips[seat] = 2;
label += ' · cárcel: descansa dos turnos';
} else if (target === 58) {
target = 0;
label += ' · vuelve al inicio';
}
next.lastEffect = repeat
? 'bonus'
: next.skips[seat] > 0 || target < next.lastPath.at(-1)
? 'penalty'
: null;
next.positions[seat] = target;
next.lastPath.push(target);
next.note = label;
if (!repeat) advanceTurn(next, seat);
return { state: next, label, sound, result: null };
},
view(state) {
return { ...state, geese };
},
};

Powered by TurnKey Linux.