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.

173 lines 8.41 kB
import { always, equals, filter, findIndex, head, map, pipe, range, reject, remove, toString, view, } from 'ramda'; import { P, match } from 'ts-pattern'; import { costMoney } from '../board/resource'; import { activeLens, subtractCoins, withActivePlayer } from '../board/player'; import { LandEnum, GameCommandEnum } from '../types'; const checkCanGetLandscape = (state) => { if (state === undefined) return undefined; if (state.frame.currentPlayerIndex !== state.frame.activePlayerIndex) return undefined; if (state.frame.canBuyLandscape) return state; if (state.frame.bonusActions.includes(GameCommandEnum.BUY_PLOT)) return state; return undefined; }; const checkForConnection = (y, side) => withActivePlayer((player) => { const { landscape, landscapeOffset } = player; if (match(side) .with('COAST', () => { var _a, _b, _c, _d; return ((_a = landscape[y + landscapeOffset - 1]) === null || _a === void 0 ? void 0 : _a[1][0]) === undefined && // above ((_b = landscape[y + landscapeOffset]) === null || _b === void 0 ? void 0 : _b[3][0]) === undefined && // upper-right ((_c = landscape[y + landscapeOffset + 1]) === null || _c === void 0 ? void 0 : _c[3][0]) === undefined && // lower-right ((_d = landscape[y + landscapeOffset + 2]) === null || _d === void 0 ? void 0 : _d[1][0]) === undefined; } // below ) .with('MOUNTAIN', () => { var _a, _b, _c, _d; return ((_a = landscape[y + landscapeOffset - 1]) === null || _a === void 0 ? void 0 : _a[7][0]) === undefined && // above ((_b = landscape[y + landscapeOffset]) === null || _b === void 0 ? void 0 : _b[6][0]) === undefined && // upper-left ((_c = landscape[y + landscapeOffset + 1]) === null || _c === void 0 ? void 0 : _c[6][0]) === undefined && // lower-left ((_d = landscape[y + landscapeOffset + 2]) === null || _d === void 0 ? void 0 : _d[7][0]) === undefined; } // below ) .exhaustive()) return undefined; return player; }); const checkForOverlap = (y, side) => withActivePlayer((player) => { const { landscape, landscapeOffset } = player; if (match(side) .with('COAST', () => { var _a, _b; return ((_a = landscape[y + landscapeOffset]) === null || _a === void 0 ? void 0 : _a[0][0]) !== undefined || ((_b = landscape[y + landscapeOffset + 1]) === null || _b === void 0 ? void 0 : _b[1][0]) !== undefined; }) .with('MOUNTAIN', () => { var _a, _b; return ((_a = landscape[y + landscapeOffset]) === null || _a === void 0 ? void 0 : _a[7][0]) !== undefined || ((_b = landscape[y + landscapeOffset + 1]) === null || _b === void 0 ? void 0 : _b[7][0]) !== undefined; }) .exhaustive()) return undefined; return player; }); const checkCanAfford = (state) => { var _a, _b, _c; if ((_b = (_a = state === null || state === void 0 ? void 0 : state.frame) === null || _a === void 0 ? void 0 : _a.bonusActions) === null || _b === void 0 ? void 0 : _b.includes(GameCommandEnum.BUY_PLOT)) return state; const cost = (_c = state === null || state === void 0 ? void 0 : state.plotPurchasePrices[0]) !== null && _c !== void 0 ? _c : 999; return withActivePlayer((player) => { if (costMoney(player) < cost) return undefined; return player; })(state); }; const payForPlot = (state) => { var _a; if (state === undefined) return undefined; if ((_a = state.frame.bonusActions) === null || _a === void 0 ? void 0 : _a.includes(GameCommandEnum.BUY_PLOT)) return state; const cost = state.plotPurchasePrices[0]; return withActivePlayer(subtractCoins(cost))(state); }; const removePlotFromPool = (state) => { var _a; if (state === undefined) return undefined; if ((_a = state.frame.bonusActions) === null || _a === void 0 ? void 0 : _a.includes(GameCommandEnum.BUY_PLOT)) return state; return Object.assign(Object.assign({}, state), { plotPurchasePrices: state.plotPurchasePrices.slice(1) }); }; const denyBuyingAnyMoreLandscape = (state) => { if (state === undefined) return undefined; const atIndex = findIndex(equals(GameCommandEnum.BUY_PLOT), state.frame.bonusActions); return Object.assign(Object.assign({}, state), { frame: Object.assign(Object.assign({}, state.frame), (atIndex !== -1 ? { // leave canBuyLandscape as it was bonusActions: remove(atIndex, 1, state.frame.bonusActions), } : { // otherwise set canBuyLandscape to false canBuyLandscape: false, })) }); }; const newPlot = (side) => match(side) .with('COAST', () => [ [[LandEnum.Water], [LandEnum.Coast]], [[LandEnum.Water], [LandEnum.Coast]], ]) .with('MOUNTAIN', () => [ [[LandEnum.Hillside], [LandEnum.Mountain]], [[LandEnum.Hillside], [LandEnum.BelowMountain]], ]) .exhaustive(); const NEW_ROW = [[], [], [], [], [], [], [], [], []]; const expandLandscape = (y) => withActivePlayer((player) => { const landscape = [...player.landscape]; let { landscapeOffset } = player; while (y + landscapeOffset < 0) { landscape.unshift(NEW_ROW); landscapeOffset++; } while (y + landscapeOffset + 2 > landscape.length) { landscape.push(NEW_ROW); } return Object.assign(Object.assign({}, player), { landscape, landscapeOffset }); }); const addNewPlot = (y, side) => withActivePlayer((player) => { const newTiles = newPlot(side); const rowsBefore = player.landscape.slice(0, y + player.landscapeOffset); const rowsAfter = player.landscape.slice(y + player.landscapeOffset + 2); const newRows = match(side) .with('COAST', () => { var _a, _b; return [ [...newTiles[0], ...((_a = player.landscape[y + player.landscapeOffset]) !== null && _a !== void 0 ? _a : []).slice(2)], [...newTiles[1], ...((_b = player.landscape[y + player.landscapeOffset + 1]) !== null && _b !== void 0 ? _b : []).slice(2)], ]; }) .with('MOUNTAIN', () => { var _a, _b; return [ [...((_a = player.landscape[y + player.landscapeOffset]) !== null && _a !== void 0 ? _a : []).slice(0, 7), ...newTiles[0]], [...((_b = player.landscape[y + player.landscapeOffset + 1]) !== null && _b !== void 0 ? _b : []).slice(0, 7), ...newTiles[1]], ]; }) .exhaustive(); const landscape = [...rowsBefore, ...newRows, ...rowsAfter]; return Object.assign(Object.assign({}, player), { landscape }); }); export const buyPlot = ({ side, y }) => pipe( // checkCanGetLandscape, checkForOverlap(y, side), checkForConnection(y, side), checkCanAfford, payForPlot, removePlotFromPool, denyBuyingAnyMoreLandscape, expandLandscape(y), addNewPlot(y, side)); export const complete = (state) => (partial) => { const player = view(activeLens(state), state); return match(partial) .with([], () => { var _a; if (state.frame.bonusActions.includes(GameCommandEnum.BUY_PLOT) && head(state.plotPurchasePrices)) return [GameCommandEnum.BUY_PLOT]; if (checkCanGetLandscape(state) === undefined) return []; const playerWealth = costMoney(player); const nextPlotCost = (_a = head(state.plotPurchasePrices)) !== null && _a !== void 0 ? _a : Infinity; if (playerWealth < nextPlotCost) return []; return [GameCommandEnum.BUY_PLOT]; }) .with([GameCommandEnum.BUY_PLOT], () => map(toString, filter((y) => (!!checkForConnection(y, 'COAST')(state) && !!checkForOverlap(y, 'COAST')(state)) || (!!checkForConnection(y, 'MOUNTAIN')(state) && !!checkForOverlap(y, 'MOUNTAIN')(state)), range(-2 - player.landscapeOffset, 1 + player.landscape.length - player.landscapeOffset)))) .with([GameCommandEnum.BUY_PLOT, P._], ([, yRaw]) => { const y = Number.parseInt(yRaw, 10); return reject((side) => !checkForOverlap(y, side)(state) || !checkForConnection(y, side)(state))(['COAST', 'MOUNTAIN']); }) .with([GameCommandEnum.BUY_PLOT, P._, P._], always([''])) .otherwise(always([])); }; //# sourceMappingURL=buyPlot.js.map