예제 소스
장기로 이해하는 규칙 엔진과 평가 함수 탐색 · 파일 59개
kibo.ts
import { Game, type GameSnapshot } from "@/lib/game";
import type { MoveRec } from "@/lib/gameTypes";
import {
resultText,
type Formation,
type GameMode,
type GameSettings,
type Move,
} from "@/lib/types";
export function settingsFromRow(row: {
cho_formation: string;
han_formation: string;
mode: string;
ai_depth: number;
ai_depth_han?: number;
bikjang: boolean;
}): GameSettings {
const formations: Formation[] = ["inner", "outer", "left", "right"];
const modes: GameMode[] = ["human_cho", "human_han", "human_vs_human", "ai_vs_ai"];
const depth = (v: unknown) => Math.max(1, Math.min(4, Number(v) || 3));
return {
choFormation: formations.includes(row.cho_formation as Formation)
? (row.cho_formation as Formation)
: "inner",
hanFormation: formations.includes(row.han_formation as Formation)
? (row.han_formation as Formation)
: "inner",
mode: modes.includes(row.mode as GameMode) ? (row.mode as GameMode) : "human_cho",
aiDepth: depth(row.ai_depth),
hanAiDepth: depth(row.ai_depth_han),
bikjangEnabled: Boolean(row.bikjang),
};
}
export function movesFromHistory(history: Move[]): MoveRec[] {
return history.map((m, i) => {
const side = m.pass ? (i % 2 === 0 ? "cho" : "han") : m.moving.side;
if (m.pass) {
return { ply: i + 1, side, pass: true };
}
return {
ply: i + 1,
side,
pass: false,
from: { file: m.from.file, rank: m.from.rank },
to: { file: m.to.file, rank: m.to.rank },
};
});
}
/** 패스 수의 side는 히스토리에서 moving이 비어 있을 수 있어 재현 시 순서만 씀. */
export function gameFromMoves(settings: GameSettings, moves: MoveRec[], ply?: number): Game {
const upto = ply == null ? moves.length : Math.max(0, Math.min(ply, moves.length));
const g = new Game();
g.newGame(settings.choFormation, settings.hanFormation);
g.setBikjangEnabled(settings.bikjangEnabled);
for (let i = 0; i < upto; i++) {
const m = moves[i]!;
if (m.pass) {
if (!g.passTurn()) {
// 체크 중 패스 불가 등 — applyTrusted로 강제 재현
g.applyTrusted({
from: { file: 0, rank: 0 },
to: { file: 0, rank: 0 },
moving: { type: "none", side: g.sideToMove() },
captured: { type: "none", side: "cho" },
pass: true,
});
g.finishTurn();
}
} else if (m.from && m.to) {
if (!g.tryMove(m.from, m.to)) {
// 합법수 검증 실패 시에도 기보 재현을 위해 스킵하지 않음
const piece = g.at(m.from);
g.applyTrusted({
from: m.from,
to: m.to,
moving: piece.type === "none" ? { type: "pawn", side: g.sideToMove() } : piece,
captured: g.at(m.to),
pass: false,
});
g.finishTurn();
}
}
}
return g;
}
export function endTextFromGame(g: Game): string | null {
const r = g.result();
if (r === "playing") return null;
return resultText(r);
}
export function parseSnapshot(raw: string): GameSnapshot | null {
try {
return JSON.parse(raw) as GameSnapshot;
} catch {
return null;
}
}
export function modeLabel(mode: string): string {
switch (mode) {
case "human_han":
return "내가 한";
case "human_vs_human":
return "대인";
case "ai_vs_ai":
return "AI 대 AI";
default:
return "내가 초";
}
}