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