Spaces:
Sleeping
Sleeping
| ; | |
| /** | |
| * Trigo Evaluation Agent - AI agent using value prediction for position evaluation | |
| * | |
| * Uses evaluation mode ONNX model to predict game outcomes. | |
| * The model takes a TGN sequence and returns a value in [-1, 1]: | |
| * - Positive values favor White (second player) | |
| * - Negative values favor Black (first player) | |
| * - Values near ±1 indicate strong advantage | |
| * - Values near 0 indicate balanced position | |
| */ | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.TrigoEvaluationAgent = void 0; | |
| const game_1 = require("./trigo/game"); | |
| const ab0yz_1 = require("./trigo/ab0yz"); | |
| class TrigoEvaluationAgent { | |
| constructor(inferencer) { | |
| this.START_TOKEN = 1; | |
| this.END_TOKEN = 2; | |
| this.VALUE_TOKEN = 3; | |
| this.inferencer = inferencer; | |
| } | |
| /** | |
| * Check if agent is initialized (checks if inferencer has a session) | |
| */ | |
| isInitialized() { | |
| return this.inferencer !== null && this.inferencer.isReady(); | |
| } | |
| /** | |
| * Convert Stone type to player string | |
| */ | |
| stoneToPlayer(stone) { | |
| if (stone === game_1.StoneType.BLACK) | |
| return "black"; | |
| if (stone === game_1.StoneType.WHITE) | |
| return "white"; | |
| throw new Error(`Invalid stone type: ${stone}`); | |
| } | |
| /** | |
| * Encode a position to TGN notation | |
| */ | |
| positionToTGN(pos, shape) { | |
| const posArray = [pos.x, pos.y, pos.z]; | |
| const shapeArray = [shape.x, shape.y, shape.z]; | |
| return (0, ab0yz_1.encodeAb0yz)(posArray, shapeArray); | |
| } | |
| /** | |
| * Convert string to byte tokens (ASCII encoding) | |
| */ | |
| stringToTokens(str) { | |
| const tokens = []; | |
| for (let i = 0; i < str.length; i++) { | |
| const charCode = str.charCodeAt(i); | |
| // Only accept valid ASCII tokens (0-127) | |
| if (charCode < 128) { | |
| tokens.push(charCode); | |
| } | |
| } | |
| return tokens; | |
| } | |
| /** | |
| * Tokenize TGN text with special tokens | |
| * Adds START and END tokens for proper sequence formatting | |
| */ | |
| tokenizeTGN(tgnText, maxLength = 256) { | |
| // Convert TGN to tokens | |
| const contentTokens = this.stringToTokens(tgnText); | |
| // Add special tokens: START + content + END | |
| const tokens = [this.START_TOKEN, ...contentTokens, this.END_TOKEN]; | |
| // Truncate if needed (preserve END token) | |
| if (tokens.length > maxLength) { | |
| return [...tokens.slice(0, maxLength - 1), this.END_TOKEN]; | |
| } | |
| // Pad with PAD tokens (0) to fixed length | |
| while (tokens.length < maxLength) { | |
| tokens.push(0); // PAD_TOKEN | |
| } | |
| return tokens; | |
| } | |
| /** | |
| * Evaluate a position by predicting the game outcome value | |
| * @param game - The game to evaluate | |
| * @returns Position evaluation with value and interpretation | |
| */ | |
| async evaluatePosition(game) { | |
| if (!this.isInitialized()) { | |
| throw new Error("Agent not initialized. Inferencer must have a session."); | |
| } | |
| // Get current TGN | |
| const tgn = game.toTGN().trim(); | |
| // Tokenize | |
| const config = this.inferencer.getConfig(); | |
| const tokens = this.tokenizeTGN(tgn, config.seqLen); | |
| // Run value prediction inference | |
| const value = await this.inferencer.runValuePrediction(tokens); | |
| return { | |
| value, | |
| interpretation: this.interpretValue(value) | |
| }; | |
| } | |
| /** | |
| * Interpret a position value to human-readable text | |
| */ | |
| interpretValue(value) { | |
| if (value > 0.5) { | |
| return `Strong advantage for White (+${value.toFixed(3)})`; | |
| } | |
| else if (value > 0.1) { | |
| return `Slight advantage for White (+${value.toFixed(3)})`; | |
| } | |
| else if (value > -0.1) { | |
| return `Balanced position (${value.toFixed(3)})`; | |
| } | |
| else if (value > -0.5) { | |
| return `Slight advantage for Black (${value.toFixed(3)})`; | |
| } | |
| else { | |
| return `Strong advantage for Black (${value.toFixed(3)})`; | |
| } | |
| } | |
| /** | |
| * Evaluate all valid moves and return them sorted by value | |
| * @param game - Current game state | |
| * @returns Array of moves with their position values | |
| */ | |
| async evaluateMoves(game) { | |
| if (!this.isInitialized()) { | |
| throw new Error("Agent not initialized"); | |
| } | |
| const currentPlayer = this.stoneToPlayer(game.getCurrentPlayer()); | |
| // Get all valid moves | |
| const validMoves = game.validMovePositions().map((pos) => ({ | |
| x: pos.x, | |
| y: pos.y, | |
| z: pos.z, | |
| player: currentPlayer | |
| })); | |
| validMoves.push({ player: currentPlayer, isPass: true }); // Add pass move | |
| if (validMoves.length === 0) { | |
| return []; | |
| } | |
| console.log(`[TrigoEvaluationAgent] Evaluating ${validMoves.length} moves...`); | |
| // Evaluate each move by simulating it and evaluating the resulting position | |
| const valuedMoves = []; | |
| for (const move of validMoves) { | |
| // Clone game and apply move | |
| const testGame = game.clone(); | |
| let success; | |
| if (move.isPass) { | |
| success = testGame.pass(); | |
| } | |
| else if (move.x !== undefined && move.y !== undefined && move.z !== undefined) { | |
| success = testGame.drop({ x: move.x, y: move.y, z: move.z }); | |
| } | |
| else { | |
| continue; // Invalid move format | |
| } | |
| if (!success) { | |
| continue; // Move failed | |
| } | |
| // Evaluate the resulting position | |
| const evaluation = await this.evaluatePosition(testGame); | |
| // Get move notation | |
| const notation = move.isPass | |
| ? "P" | |
| : this.positionToTGN({ x: move.x, y: move.y, z: move.z }, testGame.getShape()); | |
| // Model returns white-positive values (positive = white advantage) | |
| // Adjust to current player perspective: positive = advantage for current player | |
| const adjustedValue = currentPlayer === "white" ? evaluation.value : -evaluation.value; | |
| valuedMoves.push({ | |
| move, | |
| value: adjustedValue, | |
| notation | |
| }); | |
| } | |
| // Sort by value (highest first - best for current player) | |
| valuedMoves.sort((a, b) => b.value - a.value); | |
| return valuedMoves; | |
| } | |
| /** | |
| * Select the best move based on position evaluation | |
| * @param game - Current game state | |
| * @returns Best move or null if no moves available | |
| */ | |
| async selectBestMove(game) { | |
| const valuedMoves = await this.evaluateMoves(game); | |
| if (valuedMoves.length === 0) { | |
| return null; | |
| } | |
| console.log(`[TrigoEvaluationAgent] Best move: ${valuedMoves[0].notation} (value: ${valuedMoves[0].value.toFixed(4)})`); | |
| console.log(`[TrigoEvaluationAgent] Top 3 moves:`); | |
| for (let i = 0; i < Math.min(3, valuedMoves.length); i++) { | |
| console.log(` ${i + 1}. ${valuedMoves[i].notation}: ${valuedMoves[i].value.toFixed(4)}`); | |
| } | |
| return valuedMoves[0].move; | |
| } | |
| /** | |
| * Get detailed evaluation report for current position | |
| */ | |
| async getEvaluationReport(game) { | |
| const currentEval = await this.evaluatePosition(game); | |
| const valuedMoves = await this.evaluateMoves(game); | |
| return { | |
| currentValue: currentEval, | |
| topMoves: valuedMoves.slice(0, 5), // Top 5 moves | |
| moveCount: valuedMoves.length | |
| }; | |
| } | |
| /** | |
| * Clean up resources | |
| */ | |
| destroy() { | |
| // Agent doesn't own the inferencer, so just clear reference | |
| console.log("[TrigoEvaluationAgent] Destroyed"); | |
| } | |
| } | |
| exports.TrigoEvaluationAgent = TrigoEvaluationAgent; | |
| //# sourceMappingURL=trigoEvaluationAgent.js.map |