195 lines
4.1 KiB
JavaScript
195 lines
4.1 KiB
JavaScript
import { generateBoard, getClickablePieces } from '../difficulty/index.js'
|
|
|
|
function cloneBoardState(boardState) {
|
|
return {
|
|
...boardState,
|
|
pieces: boardState.pieces.map((piece) => ({ ...piece })),
|
|
overlapGraph: Object.fromEntries(
|
|
Object.entries(boardState.overlapGraph ?? {}).map(([pieceId, blockedIds]) => [pieceId, [...blockedIds]]),
|
|
),
|
|
metrics: { ...(boardState.metrics ?? {}) },
|
|
}
|
|
}
|
|
|
|
function getPieceById(boardState, pieceId) {
|
|
return boardState.pieces.find((piece) => piece.id === pieceId) ?? null
|
|
}
|
|
|
|
function createSlotEntry(piece) {
|
|
return {
|
|
pieceId: piece.id,
|
|
elementId: piece.elementId,
|
|
}
|
|
}
|
|
|
|
function removeMatchedEntries(slot, elementId) {
|
|
let remaining = 3
|
|
|
|
return slot.filter((entry) => {
|
|
if (entry.elementId === elementId && remaining > 0) {
|
|
remaining -= 1
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
function getMatchingTriples(slot) {
|
|
const counts = slot.reduce((map, entry) => {
|
|
map.set(entry.elementId, (map.get(entry.elementId) ?? 0) + 1)
|
|
return map
|
|
}, new Map())
|
|
|
|
const matchedElementId = [...counts.entries()].find(([, count]) => count >= 3)?.[0] ?? null
|
|
|
|
return matchedElementId
|
|
}
|
|
|
|
function getStateSnapshot(state, boardState) {
|
|
return {
|
|
cityId: state.cityId,
|
|
levelId: state.levelId,
|
|
seed: state.seed,
|
|
status: state.status,
|
|
slot: [...state.slot],
|
|
bypass: [...state.bypass],
|
|
removedPieceIds: [...state.removedPieceIds],
|
|
boardState,
|
|
}
|
|
}
|
|
|
|
export function createGameSession({
|
|
cityId,
|
|
levelId,
|
|
seed,
|
|
contentSystem,
|
|
boardState,
|
|
restartFactory,
|
|
}) {
|
|
const resolvedBoard = boardState ? cloneBoardState(boardState) : generateBoard({ cityId, levelId, seed, contentSystem })
|
|
const state = {
|
|
cityId,
|
|
levelId,
|
|
seed: resolvedBoard.seed,
|
|
status: 'playing',
|
|
slot: [],
|
|
bypass: [],
|
|
removedPieceIds: [],
|
|
}
|
|
|
|
function getState() {
|
|
return getStateSnapshot(state, resolvedBoard)
|
|
}
|
|
|
|
function isBoardCleared() {
|
|
return resolvedBoard.pieces.every((piece) => piece.removed) && state.slot.length === 0
|
|
}
|
|
|
|
function getClickable() {
|
|
return getClickablePieces(resolvedBoard)
|
|
}
|
|
|
|
function pickPiece(pieceId) {
|
|
if (state.status !== 'playing') {
|
|
return {
|
|
status: state.status,
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
const piece = getPieceById(resolvedBoard, pieceId)
|
|
|
|
if (!piece || piece.removed) {
|
|
return {
|
|
status: 'invalid',
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
const clickableIds = new Set(getClickable().map((clickablePiece) => clickablePiece.id))
|
|
if (!clickableIds.has(pieceId)) {
|
|
return {
|
|
status: 'blocked',
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
piece.removed = true
|
|
state.slot.push(createSlotEntry(piece))
|
|
|
|
const matchedElementId = getMatchingTriples(state.slot)
|
|
if (matchedElementId) {
|
|
const matchedIds = state.slot
|
|
.filter((entry) => entry.elementId === matchedElementId)
|
|
.slice(0, 3)
|
|
.map((entry) => entry.pieceId)
|
|
|
|
state.slot = removeMatchedEntries(state.slot, matchedElementId)
|
|
state.removedPieceIds.push(...matchedIds)
|
|
|
|
if (isBoardCleared()) {
|
|
state.status = 'won'
|
|
return {
|
|
status: 'won',
|
|
matchedElementId,
|
|
matchedIds,
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: 'matched',
|
|
matchedElementId,
|
|
matchedIds,
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
if (state.slot.length >= 7) {
|
|
state.status = 'failed'
|
|
return {
|
|
status: 'failed',
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: 'picked',
|
|
state: getState(),
|
|
}
|
|
}
|
|
|
|
function restart(nextSeed = seed ?? resolvedBoard.seed) {
|
|
if (restartFactory) {
|
|
return restartFactory(nextSeed)
|
|
}
|
|
|
|
const freshSession = createGameSession({
|
|
cityId,
|
|
levelId,
|
|
seed: nextSeed,
|
|
contentSystem,
|
|
})
|
|
|
|
return freshSession
|
|
}
|
|
|
|
return {
|
|
cityId,
|
|
levelId,
|
|
seed: resolvedBoard.seed,
|
|
getBoardState() {
|
|
return resolvedBoard
|
|
},
|
|
getState,
|
|
getClickablePieces() {
|
|
return getClickable()
|
|
},
|
|
pickPiece,
|
|
restart,
|
|
}
|
|
}
|
|
|
|
export default createGameSession
|