All files JanggiParser.ts

97.87% Statements 46/47
77.41% Branches 24/31
100% Functions 7/7
100% Lines 41/41

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 { IJanggiSearchOptions,
  IJanggiSearchInfo,
  IJanggiSearchResult,
  createJanggiMove, } from "@multi-game-engines/domain-janggi";
 
export class JanggiParser implements IProtocolParser<
  IJanggiSearchOptions,
  IJanggiSearchInfo,
  IJanggiSearchResult
> {
  createSearchCommand(options: IJanggiSearchOptions): MiddlewareCommand {
    ProtocolValidator.assertNoInjection(options, "JanggiSearchOptions", true);
    const commands: string[] = [];
    if (options.position) {
      commands.push(`position ${options.position}`);
    }
    commands.push("go");
    return commands;
  }
 
  createStopCommand(): MiddlewareCommand {
    return "stop";
  }
 
  createOptionCommand(name: string, value: unknown): MiddlewareCommand {
    ProtocolValidator.assertNoInjection({ name, value }, "JanggiOption", true);
    return `setoption name ${name} value ${value}`;
  }
 
  parseInfo(
    data: string | Record<string, unknown>,
    positionId?: PositionId,
  ): IJanggiSearchInfo | null {
    if (typeof data !== "string") return null;
    if (!data.startsWith("info ")) return null;
 
    const info: IJanggiSearchInfo = { 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, "janggi"),
            };
          }
          break;
        }
        case "pv": {
          const pvMoves = parts.slice(i + 1).map((m) => {
            try {
              return createJanggiMove(m);
            } catch {
              return null;
            }
          });
          info.pv = pvMoves.filter((m) => m !== null);
          i = parts.length; // PV is always last
          break;
        }
      }
    }
    return info;
  }
 
  parseResult(
    data: string | Record<string, unknown>,
  ): IJanggiSearchResult | null {
    if (typeof data !== "string") return null;
    if (data.startsWith("bestmove ")) {
      const parts = data.split(/\s+/);
      return {
        bestMove: parts[1] ? createJanggiMove(parts[1]) : null,
        raw: data,
      };
    }
    return null;
  }
}