trigo / trigo-web /backend /dist /inc /trigoEvaluationAgent.js
k-l-lambda's picture
Deploy: fix build by keeping pre-built dist folder
63a8db2
"use strict";
/**
* 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