Files
wechat-minigame/js/gameplay/session/index.js
2026-03-29 00:49:03 +08:00

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