Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 3x 3x 3x 1x 2x 2x 1x 4x 4x 15x 14x 5x 5x 5x 21x 21x 21x 5x 5x 1x 1x 1x 1x 5x 5x 5x 5x 5x 4x 6x 6x 1x 6x 4x 4x 21x 11x 10x 1x 1x 9x | import { IProtocolParser, ScoreNormalizer, PositionId, MiddlewareCommand, ProtocolValidator } from "@multi-game-engines/core";
import { IXiangqiSearchOptions,
IXiangqiSearchInfo,
IXiangqiSearchResult,
createXiangqiMove, } from "@multi-game-engines/domain-xiangqi";
export class XiangqiParser implements IProtocolParser<
IXiangqiSearchOptions,
IXiangqiSearchInfo,
IXiangqiSearchResult
> {
createSearchCommand(options: IXiangqiSearchOptions): MiddlewareCommand {
ProtocolValidator.assertNoInjection(options, "XiangqiSearchOptions", true);
const commands: string[] = [];
if (options.xfen) {
commands.push(`position fen ${options.xfen}`);
}
commands.push("go");
return commands;
}
createStopCommand(): MiddlewareCommand {
return "stop";
}
createOptionCommand(name: string, value: unknown): MiddlewareCommand {
ProtocolValidator.assertNoInjection({ name, value }, "XiangqiOption", true);
return `setoption name ${name} value ${value}`;
}
parseInfo(
data: string | Record<string, unknown>,
positionId?: PositionId,
): IXiangqiSearchInfo | null {
if (typeof data !== "string") return null;
if (!data.startsWith("info ")) return null;
const info: IXiangqiSearchInfo = { raw: data, positionId };
const parts = data.split(/\s+/);
for (let i = 0; i < parts.length; i++) {
const token = parts[i];
Iif (!token) continue;
switch (token) {
case "depth":
info.depth = parseInt(parts[++i] || "0", 10);
break;
case "nodes":
info.nodes = parseInt(parts[++i] || "0", 10);
break;
case "nps":
info.nps = parseInt(parts[++i] || "0", 10);
break;
case "score": {
const type = parts[++i];
const val = parseInt(parts[++i] || "0", 10);
Eif (type === "cp" || type === "mate") {
info.score = {
unit: type,
[type]: val,
normalized: ScoreNormalizer.normalize(val, type, "xiangqi"),
};
}
break;
}
case "pv": {
const pvMoves = parts.slice(i + 1).map((m) => {
try {
return createXiangqiMove(m);
} catch {
return null;
}
});
info.pv = pvMoves.filter((m) => m !== null);
i = parts.length; // PV is always last in UCCI/UCI
break;
}
}
}
return info;
}
parseResult(
data: string | Record<string, unknown>,
): IXiangqiSearchResult | null {
if (typeof data !== "string") return null;
if (data.startsWith("bestmove ")) {
const parts = data.split(/\s+/);
return {
bestMove: parts[1] ? createXiangqiMove(parts[1]) : null,
raw: data,
};
}
return null;
}
}
|