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.

126 lines (122 loc) 4.68 kB
import { match } from 'ts-pattern' import { always, head, map, pipe, sum } from 'ramda' import { pickFrameFlow } from './board/frame' import { Flower, GameCommandEnum, GameStatePlaying, OrdinalFrame, Controls, Tableau, Score, SettlementRound, RondelToken, GameStatusEnum, } from './types' import { completeBuild, completeCommit, completeConvert, completeCutPeat, completeFellTrees, completeSettle, completeUse, completeWorkContract, completeWithLaybrother, completeWithPrior, completeBuyPlot, completeBuyDistrict, } from './commands' import { costPoints } from './board/resource' import { allBuildingPoints, allDwellingPoints } from './board/landscape' import { introduceBuildings, roundBuildings } from './board/buildings' import { introduceGrapeToken, introduceStoneToken } from './board/rondel' const computeFlow = (state: GameStatePlaying) => { const frameFlow = pickFrameFlow(state.config) const flow: Flower[] = [] let limit = 200 let frameIndex let playerIndex = state.frame.activePlayerIndex let { frame }: { frame: OrdinalFrame } = state let { round } = frame let lastSeenSettlementRound = SettlementRound.S do { round = frame.round ?? round playerIndex = frame.currentPlayerIndex ?? playerIndex const settle = !!frame.bonusActions?.includes(GameCommandEnum.SETTLE) || !!frame.neutralBuildingPhase lastSeenSettlementRound = frame.settlementRound ?? lastSeenSettlementRound flow.push({ round, player: state?.players?.[playerIndex]?.color, settle, bonus: !!frame.bonusRoundPlacement, introduced: [ ...(frame.upkeep?.includes(introduceBuildings) ? roundBuildings(state.config, lastSeenSettlementRound) : []), ...(frame.upkeep?.includes(introduceGrapeToken) ? ['grape' as RondelToken] : []), ...(frame.upkeep?.includes(introduceStoneToken) ? ['stone' as RondelToken] : []), ], }) frameIndex = frame.next frame = frameFlow[frame.next] } while (frame && frameIndex < frame.next && limit--) return flow } export const control = (state: GameStatePlaying, partial: string[], player?: number): Controls => { const completion = match(head(partial)) .with(undefined, () => state.status === GameStatusEnum.FINISHED ? [] : [ ...completeUse(state)([]), ...completeBuild(state)([]), ...completeCutPeat(state)([]), ...completeFellTrees(state)([]), ...completeWorkContract(state)([]), ...completeBuyPlot(state)([]), ...completeBuyDistrict(state)([]), ...completeConvert(state)([]), ...completeSettle(state)([]), ...completeWithLaybrother(state)([]), ...completeWithPrior(state)([]), ...completeCommit(state)([]), ] ) // this can be prettier with https://github.com/gvergnaud/ts-pattern/pull/139 // git blame this line and see how it used to be, but i really want something like // .with([GameCommandEnum.Use, P.rest], complete.USE(state)) .with(GameCommandEnum.USE, () => completeUse(state)(partial)) .with(GameCommandEnum.BUILD, () => completeBuild(state)(partial)) .with(GameCommandEnum.CUT_PEAT, () => completeCutPeat(state)(partial)) .with(GameCommandEnum.FELL_TREES, () => completeFellTrees(state)(partial)) .with(GameCommandEnum.WORK_CONTRACT, () => completeWorkContract(state)(partial)) .with(GameCommandEnum.BUY_PLOT, () => completeBuyPlot(state)(partial)) .with(GameCommandEnum.BUY_DISTRICT, () => completeBuyDistrict(state)(partial)) .with(GameCommandEnum.CONVERT, () => completeConvert(state)(partial)) .with(GameCommandEnum.SETTLE, () => completeSettle(state)(partial)) .with(GameCommandEnum.WITH_LAYBROTHER, () => completeWithLaybrother(state)(partial)) .with(GameCommandEnum.WITH_PRIOR, () => completeWithPrior(state)(partial)) .with(GameCommandEnum.COMMIT, () => completeCommit(state)(partial)) .otherwise(always([])) const score = map( pipe( (player: Tableau): Score => ({ goods: costPoints(player) + 30 * player.wonders, economic: allBuildingPoints(player.landscape), settlements: allDwellingPoints(player.landscape), total: -Infinity, }), (score: Score): Score => ({ ...score, total: sum([...score.settlements, score.goods, score.economic]), }) ), state.players.slice(0, state.config.players) // this will remove neutral player ) return { active: player === state.frame.activePlayerIndex, flow: computeFlow(state), partial, completion, score, } }