UNPKG

hathora-et-labora-game

Version:

Plays Uwe Rosenberg's Ora et Labora for the Hathora engine. It reduces a list of moves into a board game state.

135 lines 6.88 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { assoc, equals, findIndex, lensProp, map, pipe, reduce, remove, set } from 'ramda'; import { match } from 'ts-pattern'; import { nextFrame4Long } from './frame/nextFrame4Long'; import { nextFrame3Long } from './frame/nextFrame3Long'; import { nextFrameSolo } from './frame/nextFrameSolo'; import { nextFrame3Short } from './frame/nextFrame3Short'; import { nextFrame4Short } from './frame/nextFrame4Short'; import { nextFrame2Long } from './frame/nextFrame2Long'; import { nextFrame2Short } from './frame/nextFrame2Short'; import { NextUseClergy, } from '../types'; import { LANDSCAPES, findBuildingWithoutOffset, getAdjacentOffsets, occupiedBuildingsForPlayers } from './landscape'; import { isSettlement } from './buildings'; export const withFrame = (func) => (state) => { if (state === undefined) return state; const frame = func(state.frame); if (frame === undefined) return undefined; return Object.assign(Object.assign({}, state), { frame }); }; export const addBonusAction = (command) => withFrame((frame) => (Object.assign(Object.assign({}, frame), { bonusActions: [...frame.bonusActions, command] }))); const runProgression = (withFlow) => (state) => { var _a; const nextFrameIndex = state === null || state === void 0 ? void 0 : state.frame.next; if (nextFrameIndex === undefined) return undefined; const _b = (_a = withFlow[nextFrameIndex]) !== null && _a !== void 0 ? _a : {}, { upkeep = [] } = _b, frameUpdates = __rest(_b, ["upkeep"]); return pipe( // first, with all of the properties on the new frame, overlay them on the current frame withFrame((frame) => { var _a; const newFrame = Object.assign(Object.assign(Object.assign({}, frame), { activePlayerIndex: (_a = frameUpdates.currentPlayerIndex) !== null && _a !== void 0 ? _a : frame === null || frame === void 0 ? void 0 : frame.activePlayerIndex, mainActionUsed: false, bonusActions: [], canBuyLandscape: true, unusableBuildings: [], usableBuildings: [], nextUse: NextUseClergy.Any, neutralBuildingPhase: false }), frameUpdates); return newFrame; }), // and then if there are any upkeep reducer functions on the frame, run them (state) => { return upkeep.reduce((accum, f) => f(accum), state); })(state); }; export const pickFrameFlow = (config) => match(config) .with({ players: 3, length: 'long' }, () => nextFrame3Long) .with({ players: 4, length: 'long' }, () => nextFrame4Long) .with({ players: 3, length: 'short' }, () => nextFrame3Short) .with({ players: 4, length: 'short' }, () => nextFrame4Short) .with({ players: 2, length: 'long' }, () => nextFrame2Long) .with({ players: 2, length: 'short' }, () => nextFrame2Short) .with({ players: 1 }, () => nextFrameSolo) .exhaustive(); export const nextFrame = (state) => { if (state === undefined) return undefined; if (state.config === undefined) return undefined; return runProgression(pickFrameFlow(state.config))(state); }; export const revertActivePlayerToCurrent = withFrame((frame) => (Object.assign(Object.assign({}, frame), { activePlayerIndex: frame.currentPlayerIndex }))); const consumeCommandFromBonus = (command) => (frame) => { if (frame === undefined) return undefined; const bonusIndex = findIndex(equals(command))(frame.bonusActions); if (bonusIndex === -1) return undefined; const newValue = remove(bonusIndex, 1, frame.bonusActions); return set(lensProp('bonusActions'), newValue, frame); }; export const onlyViaBonusActions = (command) => withFrame(consumeCommandFromBonus(command)); export const oncePerFrame = (command) => withFrame((frame) => { // first try to remove the proposed command from bonusActions const bonusFrame = consumeCommandFromBonus(command)(frame); if (bonusFrame !== undefined) return bonusFrame; // then if it couldn't be, consume the main action, if available if (frame.mainActionUsed === false) return Object.assign(Object.assign({}, frame), { mainActionUsed: true }); return undefined; }); export const clearNeutralBuildingPhase = withFrame(assoc('neutralBuildingPhase', false)); export const setFrameToAllowFreeUsage = (building) => withFrame((frame) => (Object.assign(Object.assign({}, frame), { usableBuildings: building, nextUse: NextUseClergy.Free }))); export const disableFurtherUsage = (building) => withFrame((frame) => frame && Object.assign(Object.assign({}, frame), { unusableBuildings: [...frame.unusableBuildings, building] })); const whichIndexHasBuilding = (building) => (landscapes) => { for (let i = 0; i < landscapes.length; i++) { const location = findBuildingWithoutOffset(building)(landscapes[i]); if (location) return [i, ...location]; } return undefined; }; export const allowFreeUsageToNeighborsOf = (building) => (state) => { if (state === undefined) return state; const location = whichIndexHasBuilding(building)(state.players.map((p) => p.landscape)); if (location === undefined) return state; const [player, row, col] = location; return setFrameToAllowFreeUsage(reduce((accum, curr) => { var _a, _b; const [p, r, c] = curr; const landStack = (_b = (_a = state.players[p].landscape) === null || _a === void 0 ? void 0 : _a[r]) === null || _b === void 0 ? void 0 : _b[c]; if (landStack === undefined) return accum; const [_, building, clergy] = landStack; if (building === undefined) return accum; if (clergy !== undefined) return accum; if (isSettlement(building)) return accum; if (LANDSCAPES.includes(building)) return accum; accum.push(building); return accum; }, [], map(([rowMod, colMod]) => [player, row + rowMod, col + colMod], getAdjacentOffsets(col - 2))))(state); }; export const allOccupiedBuildingsUsable = (state) => { if (state === undefined) return state; return setFrameToAllowFreeUsage(occupiedBuildingsForPlayers(state.players))(state); }; export const checkNotBonusRound = withFrame((frame) => { if ((frame === null || frame === void 0 ? void 0 : frame.bonusRoundPlacement) === true) return undefined; return frame; }); //# sourceMappingURL=frame.js.map