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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 3x 60x 3x 3x 57x 57x 7x 7x 7x 49x 14x 14x 68x 1x 1x 1x 67x 20x 20x 47x 10x 17x 10x 37x 34x 37x 14x | import { tCommon as translate } from "@multi-game-engines/i18n-common";
import { EngineError,
EngineErrorCode,
ProtocolValidator,
Move,
createMove,
IBaseSearchOptions,
IBaseSearchInfo,
IBaseSearchResult,
createI18nKey } from "@multi-game-engines/core";
/** 麻雀の指し手(打牌、副露等) */
export type MahjongMove = Move<"MahjongMove">;
/**
* 麻雀の探索オプション。
*/
export interface IMahjongSearchOptions extends IBaseSearchOptions {
board: Record<string, unknown> | unknown[];
[key: string]: unknown;
}
/**
* 麻雀の探索状況。
*/
export interface IMahjongSearchInfo extends IBaseSearchInfo {
raw: string;
thinking: string;
evaluations?:
| {
move: MahjongMove;
ev: number;
prob?: number | undefined;
}[]
| undefined;
[key: string]: unknown;
}
/**
* 麻雀の探索結果。
*/
export interface IMahjongSearchResult extends IBaseSearchResult {
raw: string;
bestMove: MahjongMove | null;
[key: string]: unknown;
}
/**
* 麻雀の指し手形式(例: 1m, tsumo, riichi 等)を検証する正規表現。
*/
export const MAHJONG_MOVE_REGEX =
/^([1-9][mpsz]|tsumo|ron|riichi|chi|pon|kan|kakan|nuki|none)$/;
/**
* 文字列を MahjongMove へ変換し、厳密に検証します。
*/
export function createMahjongMove(move: string): MahjongMove {
if (typeof move !== "string" || move.trim().length === 0) {
const i18nKey = createI18nKey("engine.errors.invalidMahjongMove");
throw new EngineError({
code: EngineErrorCode.VALIDATION_ERROR,
message: translate(i18nKey),
i18nKey,
});
}
ProtocolValidator.assertNoInjection(move, "MahjongMove");
if (!MAHJONG_MOVE_REGEX.test(move)) {
const i18nKey = createI18nKey("engine.errors.invalidMahjongMove");
const i18nParams = { move };
throw new EngineError({
code: EngineErrorCode.VALIDATION_ERROR,
message: translate(i18nKey, i18nParams),
i18nKey,
i18nParams,
});
}
return createMove<"MahjongMove">(move);
}
/**
* 麻雀盤面データ(JSON構造)のバリデータ。
*/
export function validateMahjongBoard(board: unknown): void {
const MAX_DEPTH = 10;
const validateValue = (
value: unknown,
path: string = "board",
depth: number = 0,
): void => {
if (depth > MAX_DEPTH) {
const i18nKey = createI18nKey("engine.errors.nestedTooDeep");
const i18nParams = { path };
throw new EngineError({
code: EngineErrorCode.VALIDATION_ERROR,
message: translate(i18nKey, i18nParams),
i18nKey,
i18nParams,
});
}
if (typeof value === "string") {
ProtocolValidator.assertNoInjection(
value,
`mahjong board data: ${path}`,
true,
);
return;
}
if (Array.isArray(value)) {
value.forEach((v, i) => {
validateValue(v, `${path}[${i}]`, depth + 1);
});
return;
}
if (value && typeof value === "object") {
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
validateValue(v, `${path}.${k}`, depth + 1);
}
}
};
validateValue(board);
}
|