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.
148 lines (138 loc) • 3.82 kB
text/typescript
import { createShuffle } from 'fast-shuffle'
import { createPcg32, randomInt } from 'fn-pcg'
import { pipe, range } from 'ramda'
import { nextFrame } from '../board/frame'
import { districtPrices, makeLandscape, plotPrices } from '../board/landscape'
import { clergyForColor } from '../board/player'
import {
GameCommandStartParams,
GameStatePlaying,
GameStateSetup,
GameStatusEnum,
NextUseClergy,
PlayerColor,
SettlementRound,
} from '../types'
// we could get more entropy with a second seed, but
// honestly this is fine for now.
const PCG_PERIOD = 42069
// this is a brute-forced seed given the above, which
// results in a noop shuffle for 2, 3, 4, or even 5 elements
const MAGIC_SEED = 2692
const neutralColor = (notThisColor: PlayerColor, neutralColorIndex: number) =>
[
// pick a color that the player isn't using
PlayerColor.Red,
PlayerColor.Blue,
PlayerColor.Green,
PlayerColor.White,
].filter((c) => c !== notThisColor)[neutralColorIndex]
export const start = (
state: GameStateSetup,
{ seed, colors }: GameCommandStartParams
): GameStatePlaying | undefined => {
if (state.rondel === undefined) return undefined
if (state.config === undefined) return undefined
if (colors.length < 1 || colors.length > 4) return undefined
const seedUsed = seed ?? MAGIC_SEED
let randGen = createPcg32({}, seedUsed, PCG_PERIOD)
const [playerOrderSeed, randGen1] = randomInt(0, 2 ** 32 - 1, randGen)
randGen = randGen1
const shuffledColors = createShuffle(playerOrderSeed, colors)
const playerIndexes = range(0, state.config.players)
const players = playerIndexes.map((i) => {
return {
landscapeOffset: 0,
wonders: 0,
peat: 0,
penny: 0,
clay: 0,
wood: 0,
grain: 0,
sheep: 0,
stone: 0,
flour: 0,
grape: 0,
nickel: 0,
malt: 0,
coal: 0,
book: 0,
ceramic: 0,
whiskey: 0,
straw: 0,
meat: 0,
ornament: 0,
bread: 0,
wine: 0,
beer: 0,
reliquary: 0,
color: shuffledColors[i],
landscape: makeLandscape(shuffledColors[i]),
clergy: clergyForColor(state.config)(shuffledColors[i]),
settlements: [],
}
})
// feels weird to do this here, but i think it's more difficult to pass along the
// neutral player's color IF we had it and generate it later from addNeutralPlayer
if (state.config.players === 1) {
if (seed === undefined) {
players.push({
...players[0],
color: shuffledColors[1],
landscape: [[]],
clergy: [],
})
} else {
const [neutralColorIndex, randGen2] = randomInt(0, 3, randGen)
randGen = randGen2
const color = neutralColor(players[0].color, neutralColorIndex)
players.push({
...players[0],
color,
landscape: [[]],
clergy: [],
})
}
}
const newState: GameStatePlaying = {
...state,
config: state.config,
randGen: randGen1,
status: GameStatusEnum.PLAYING,
players,
rondel: {
...state.rondel,
grain: 0,
peat: 0,
sheep: 0,
clay: 0,
coin: 0,
wood: 0,
joker: 0,
},
wonders: 8,
frame: {
next: 1,
round: 1,
startingPlayer: 0,
settlementRound: SettlementRound.S,
currentPlayerIndex: 0,
activePlayerIndex: 0,
neutralBuildingPhase: false,
bonusRoundPlacement: false,
mainActionUsed: false,
bonusActions: [],
canBuyLandscape: true,
unusableBuildings: [],
usableBuildings: [],
nextUse: NextUseClergy.Any,
},
buildings: [],
plotPurchasePrices: plotPrices(state.config),
districtPurchasePrices: districtPrices(state.config),
}
return pipe(
//
nextFrame
)(newState)
}