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
JavaScript
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