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:

  1. Discover Hidden Rules: Detect special rules like fewerPiecesContinue by finding patterns in turn sequences

  2. Learn Position Values: Build a data-driven position value matrix instead of using predefined weights

  3. Create Opening Books: Learn effective opening sequences based on actual gameplay results

  4. Identify Winning Strategies: Analyze which strategies perform best against different opponents

  5. 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.