Using Game Logs for World Model Learning
The Othello Arena provides game logging capabilities that can be extremely valuable for building a “world model” - an understanding of how the game behaves under different conditions. By analyzing past games, you can discover patterns, validate rules, and improve your strategy.
Collecting Game Logs
1. Play Multiple Games with Different Strategies
First, collect a diverse set of game data:
// Play games using different built-in strategies
// For example, Random vs Greedy, Corners vs Positional, etc.
// Play at least 5-10 games to get meaningful data
2. Save Game Logs
After playing several games:
// Click the "Save Log" button in the UI
// or use the console to access logs:
// Get logs from GameLogger
const gameData = window.gameLogger.previousGames;
console.log(`Collected ${gameData.length} games`);
// Export to JSON (for advanced analysis in external tools)
const jsonData = JSON.stringify(gameData);
console.log(jsonData);
// You can copy this JSON or use the browser's save functionality
// to download it to a file
const blob = new Blob([jsonData], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'othello_games.json';
a.click();
Analyzing Game Logs
1. Basic Game Statistics Analysis
// Analyze collected games
function analyzeGameStats() {
const games = window.gameLogger.previousGames;
if (!games || games.length === 0) {
console.log("No game data available");
return;
}
console.log(`Analyzing ${games.length} games...`);
// Win statistics by player
let blackWins = 0;
let whiteWins = 0;
let ties = 0;
// Move statistics
const moveCountsByGame = [];
const capturesByGame = [];
const averageCapturesPerMove = [];
games.forEach((game, index) => {
console.log(`Game ${index+1}: ${game.metadata.blackStrategy} vs ${game.metadata.whiteStrategy}`);
// Determine winner
const blackScore = game.metadata.blackScore;
const whiteScore = game.metadata.whiteScore;
if (blackScore > whiteScore) {
blackWins++;
console.log(`Black won: ${blackScore}-${whiteScore}`);
} else if (whiteScore > blackScore) {
whiteWins++;
console.log(`White won: ${whiteScore}-${blackScore}`);
} else {
ties++;
console.log(`Tie: ${blackScore}-${whiteScore}`);
}
// Move statistics
if (game.moves) {
moveCountsByGame.push(game.moves.length);
// Track captures
const gameCaptureData = game.moves.filter(m => m.capturedCount).map(m => m.capturedCount);
const totalCaptures = gameCaptureData.reduce((sum, count) => sum + count, 0);
capturesByGame.push(totalCaptures);
if (gameCaptureData.length > 0) {
averageCapturesPerMove.push(totalCaptures / gameCaptureData.length);
}
}
});
// Summary statistics
console.log("\nGame Statistics Summary:");
console.log(`Total Games: ${games.length}`);
console.log(`Black Wins: ${blackWins} (${(blackWins/games.length*100).toFixed(1)}%)`);
console.log(`White Wins: ${whiteWins} (${(whiteWins/games.length*100).toFixed(1)}%)`);
console.log(`Ties: ${ties} (${(ties/games.length*100).toFixed(1)}%)`);
if (moveCountsByGame.length > 0) {
const avgMoves = moveCountsByGame.reduce((sum, count) => sum + count, 0) / moveCountsByGame.length;
console.log(`\nAverage moves per game: ${avgMoves.toFixed(1)}`);
console.log(`Min moves: ${Math.min(...moveCountsByGame)}, Max moves: ${Math.max(...moveCountsByGame)}`);
}
if (averageCapturesPerMove.length > 0) {
const overallAvgCaptures = averageCapturesPerMove.reduce((sum, avg) => sum + avg, 0) / averageCapturesPerMove.length;
console.log(`Average captures per move: ${overallAvgCaptures.toFixed(2)}`);
}
return {
games: games.length,
blackWins,
whiteWins,
ties,
moveStats: {
avgMoves: moveCountsByGame.length > 0 ?
moveCountsByGame.reduce((sum, count) => sum + count, 0) / moveCountsByGame.length : 0,
avgCaptures: averageCapturesPerMove.length > 0 ?
averageCapturesPerMove.reduce((sum, avg) => sum + avg, 0) / averageCapturesPerMove.length : 0
}
};
}
// Run analysis
const gameStats = analyzeGameStats();
2. Detecting Game Rules from Logs
// Analyze game logs to understand special rules
function detectSpecialRules() {
const games = window.gameLogger.previousGames;
if (!games || games.length === 0) {
console.log("No game data available");
return;
}
console.log("Analyzing game moves to detect special rules...");
// Check for continuous turns (fewerPiecesContinue rule)
let consecutiveTurnEvidence = 0;
let totalTurnTransitions = 0;
// Check for occlusion behavior
let occlusionJumpEvidence = 0;
let potentialOcclusionSituations = 0;
games.forEach(game => {
if (!game.moves || game.moves.length < 2) return;
// Track player transitions
for (let i = 1; i < game.moves.length; i++) {
const previousPlayer = game.moves[i-1].player;
const currentPlayer = game.moves[i].player;
totalTurnTransitions++;
// Check for same player playing twice in a row
if (previousPlayer === currentPlayer) {
consecutiveTurnEvidence++;
console.log(`Detected same player (${previousPlayer}) playing consecutive moves at moves ${i-1}-${i}`);
}
// Advanced: Check board states for occlusion jumping
// This requires more complex analysis of board states between moves
// We'd need to check if a move resulted in capturing pieces across blocked cells
}
});
// Probability estimation based on observations
const consecutiveTurnProbability = totalTurnTransitions > 0 ?
consecutiveTurnEvidence / totalTurnTransitions : 0;
console.log("\nRule Detection Results:");
console.log(`Consecutive turns by same player: ${consecutiveTurnEvidence} instances out of ${totalTurnTransitions} transitions`);
console.log(`Probability of fewerPiecesContinue rule: ${(consecutiveTurnProbability*100).toFixed(2)}%`);
if (consecutiveTurnProbability > 0.05) {
console.log("Evidence suggests the fewerPiecesContinue rule is active");
} else {
console.log("Little evidence for fewerPiecesContinue rule");
}
// Occlusion rule would require more specific board state analysis
console.log("\nOcclusion rule detection requires more detailed board state analysis");
return {
fewerPiecesContinue: {
evidence: consecutiveTurnEvidence,
probability: consecutiveTurnProbability
},
occlusionJumping: {
evidence: occlusionJumpEvidence,
probability: potentialOcclusionSituations > 0 ?
occlusionJumpEvidence / potentialOcclusionSituations : 0
}
};
}
// Run rule detection
const ruleDetection = detectSpecialRules();
3. Analyzing Position Value from Game Results
You can use game logs to learn which positions are most valuable:
// Learn position values from successful games
function analyzePositionValues() {
const games = window.gameLogger.previousGames;
if (!games || games.length === 0) {
console.log("No game data available");
return;
}
// Create a value map for each position on the board
const positionValues = {};
const defaultBoardSize = 8; // Adjust if needed
// Initialize position map
for (let r = 0; r < defaultBoardSize; r++) {
for (let c = 0; c < defaultBoardSize; c++) {
const key = `${r},${c}`;
positionValues[key] = {
blackMoves: 0,
whiteMoves: 0,
blackWins: 0,
whiteWins: 0,
totalGames: 0
};
}
}
// Analyze each game
games.forEach(game => {
if (!game.moves) return;
const blackWon = game.metadata && game.metadata.blackScore > game.metadata.whiteScore;
const whiteWon = game.metadata && game.metadata.whiteScore > game.metadata.blackScore;
// Count move frequencies by position and winning moves
game.moves.forEach(move => {
if (!move.position) return;
const key = `${move.position.row},${move.position.col}`;
if (move.player === 1) { // Black
positionValues[key].blackMoves++;
if (blackWon) {
positionValues[key].blackWins++;
}
} else if (move.player === 2) { // White
positionValues[key].whiteMoves++;
if (whiteWon) {
positionValues[key].whiteWins++;
}
}
positionValues[key].totalGames++;
});
});
// Calculate win rates for each position
for (const key in positionValues) {
const pos = positionValues[key];
pos.blackWinRate = pos.blackMoves > 0 ? pos.blackWins / pos.blackMoves : 0;
pos.whiteWinRate = pos.whiteMoves > 0 ? pos.whiteWins / pos.whiteMoves : 0;
pos.overallWinRate = (pos.blackWins + pos.whiteWins) /
Math.max(1, pos.blackMoves + pos.whiteMoves);
// Calculate position value score (accounting for sample size)
const sampleSizeBonus = Math.min(1, (pos.blackMoves + pos.whiteMoves) / 10);
pos.valueScore = pos.overallWinRate * sampleSizeBonus * 100;
}
// Create a visual representation of position values
const visualValueMap = Array(defaultBoardSize).fill().map(() => Array(defaultBoardSize).fill(0));
for (let r = 0; r < defaultBoardSize; r++) {
for (let c = 0; c < defaultBoardSize; c++) {
const key = `${r},${c}`;
visualValueMap[r][c] = positionValues[key].valueScore.toFixed(1);
}
}
console.log("Position Value Map (higher is better):");
console.table(visualValueMap);
// Find most valuable positions
const valuablePositions = Object.entries(positionValues)
.map(([key, data]) => ({
position: key,
valueScore: data.valueScore,
winRate: data.overallWinRate,
moveCount: data.blackMoves + data.whiteMoves
}))
.filter(pos => pos.moveCount > 0)
.sort((a, b) => b.valueScore - a.valueScore);
console.log("Top 10 Most Valuable Positions:");
console.table(valuablePositions.slice(0, 10));
return {
positionValues,
visualValueMap,
topPositions: valuablePositions.slice(0, 10)
};
}
// Run position value analysis
const positionAnalysis = analyzePositionValues();
4. Building an Opening Book from Data
You can use game logs to create an opening book:
// Generate an opening book from successful games
function generateOpeningBook() {
const games = window.gameLogger.previousGames;
if (!games || games.length === 0) {
console.log("No game data available");
return;
}
// Track opening sequences (first 4-8 moves) that led to wins
const openingSequences = {};
games.forEach(game => {
if (!game.moves || game.moves.length < 4) return;
// Determine if black or white won
const blackWon = game.metadata && game.metadata.blackScore > game.metadata.whiteScore;
const whiteWon = game.metadata && game.metadata.whiteScore > game.metadata.blackScore;
if (!blackWon && !whiteWon) return; // Skip ties
// Get the opening sequence (first 6 moves or all if less than 6)
const openingMoves = game.moves.slice(0, Math.min(6, game.moves.length));
// Create a key representing this sequence
const sequenceKey = openingMoves
.map(m => `${m.player}:${m.position.row},${m.position.col}`)
.join('|');
// Record this sequence
if (!openingSequences[sequenceKey]) {
openingSequences[sequenceKey] = {
moves: openingMoves,
blackWins: 0,
whiteWins: 0,
totalGames: 0
};
}
openingSequences[sequenceKey].totalGames++;
if (blackWon) openingSequences[sequenceKey].blackWins++;
if (whiteWon) openingSequences[sequenceKey].whiteWins++;
});
// Calculate win rates for each sequence
for (const key in openingSequences) {
const seq = openingSequences[key];
seq.blackWinRate = seq.blackWins / seq.totalGames;
seq.whiteWinRate = seq.whiteWins / seq.totalGames;
seq.successRate = (seq.blackWins + seq.whiteWins) / seq.totalGames;
}
// Sort openings by success rate
const sortedOpenings = Object.values(openingSequences)
.filter(seq => seq.totalGames >= 2) // At least 2 games with this opening
.sort((a, b) => b.successRate - a.successRate);
console.log("Top Opening Sequences:");
sortedOpenings.slice(0, 5).forEach((seq, index) => {
console.log(`Opening #${index+1} (Win rate: ${(seq.successRate*100).toFixed(1)}%, Games: ${seq.totalGames}):`);
seq.moves.forEach(move => {
console.log(` ${move.player === 1 ? "Black" : "White"}: (${move.position.row}, ${move.position.col})`);
});
console.log("---");
});
// Format opening book for use in analyzeStage function
const formalizedOpeningBook = sortedOpenings.slice(0, 10).map(seq => {
// Extract just the first 2-3 moves to keep the book manageable
const initialMoves = seq.moves.slice(0, 3);
return {
moves: initialMoves.map(m => ({
player: m.player,
row: m.position.row,
col: m.position.col
})),
winRate: seq.successRate,
games: seq.totalGames
};
});
console.log("Opening book generated with", formalizedOpeningBook.length, "sequences");
console.log("Example usage in analyzeStage:");
console.log(`
function createOpeningBook() {
return ${JSON.stringify(formalizedOpeningBook, null, 2)};
}
`);
return {
raw: openingSequences,
sorted: sortedOpenings,
formalized: formalizedOpeningBook
};
}
// Generate opening book
const openingBook = generateOpeningBook();
Integrating Analysis with Your Intelligent System
Here’s how to use the game log analysis in your analyzeStage
function:
function analyzeStage(stageConfig, initialBoard, validMoves, api) {
console.log("Analyzing stage:", stageConfig.name);
// Load pre-computed position values from game analysis
const positionValues = [
[120, -20, 20, 5, 5, 20, -20, 120],
[-20, -40, -5, -5, -5, -5, -40, -20],
[20, -5, 15, 3, 3, 15, -5, 20],
[5, -5, 3, 3, 3, 3, -5, 5],
[5, -5, 3, 3, 3, 3, -5, 5],
[20, -5, 15, 3, 3, 15, -5, 20],
[-20, -40, -5, -5, -5, -5, -40, -20],
[120, -20, 20, 5, 5, 20, -20, 120]
];
// Use your pre-generated opening book
const openingBook = [
{
"moves": [
{"player": 1, "row": 2, "col": 4},
{"player": 2, "row": 2, "col": 3},
{"player": 1, "row": 1, "col": 3}
],
"winRate": 0.85,
"games": 7
},
// Add more opening sequences from your analysis
];
// Return the strategy function with learned data
return function(board, player, validMoves, makeMove) {
if (validMoves.length === 0) return null;
// Check if we're in the opening phase
if (countPieces(board) <= 12) {
// Try to match current board state with our opening book
const bookMove = findOpeningBookMove(board, player, validMoves, openingBook);
if (bookMove) return bookMove;
}
// Otherwise use our learned position values
let bestMove = null;
let bestScore = -Infinity;
for (const move of validMoves) {
const score = positionValues[move.row][move.col];
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove;
};
// Count total pieces on board
function countPieces(board) {
let count = 0;
for (let r = 0; r < board.length; r++) {
for (let c = 0; c < board[r].length; c++) {
if (board[r][c] === 1 || board[r][c] === 2) count++;
}
}
return count;
}
// Find a move from our opening book
function findOpeningBookMove(board, player, validMoves, openingBook) {
// Implementation would try to match current board state with opening book positions
// This is a complex task that would need to account for board state matching
// and determining which move in the sequence to play next
// ...
return null; // Return a move if found in opening book
}
}
Benefits of Game Log Analysis
By analyzing game logs:
-
Discover Hidden Rules: Detect special rules like
fewerPiecesContinue
by finding patterns in turn sequences -
Learn Position Values: Build a data-driven position value matrix instead of using predefined weights
-
Create Opening Books: Learn effective opening sequences based on actual gameplay results
-
Identify Winning Strategies: Analyze which strategies perform best against different opponents
-
Improve Your AI: Use all these insights to build a stronger, more adaptive intelligent system
Game log analysis is a powerful approach to understanding the “world model” of Othello without relying solely on predefined rules or strategies.