← Chess results

Chess

Qwen 3.6 27b MTP · full

Initial view of Chess

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 72,540 bytes · SHA-256 ff7835480d3e
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chess — AI Opponent</title>
<style>
/* ============================================================
   CONSTANTS & THEME
   ============================================================ */
:root {
  --sq-light: #f0d9b5;
  --sq-dark: #b58863;
  --sq-highlight: rgba(255,255,100,0.45);
  --sq-lastmove: rgba(155,199,0,0.41);
  --sq-check: radial-gradient(circle, #ff0000 0%, transparent 70%);
  --dot-color: rgba(0,0,0,0.25);
  --ring-color: rgba(0,0,0,0.3);
  --panel-bg: #2b2b2b;
  --panel-text: #e0d8c8;
  --accent: #769656;
  --board-border: #4a3728;
}

* { margin:0; padding:0; box-sizing:border-box; }
body {
  font-family: 'Segoe UI', system-ui, sans-serif;
  background: #1a1a1a;
  color: var(--panel-text);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  min-height: 100vh;
  padding: 20px;
}

#app {
  display: flex;
  gap: 24px;
  max-width: 1100px;
  width: 100%;
  align-items: flex-start;
}

/* Board area */
#board-container {
  position: relative;
  flex-shrink: 0;
}

#board {
  display: grid;
  grid-template-columns: repeat(8, var(--sq-size, 72px));
  grid-template-rows: repeat(8, var(--sq-size, 72px));
  border: 4px solid var(--board-border);
  border-radius: 4px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  user-select: none;
}

.square {
  width: var(--sq-size, 72px);
  height: var(--sq-size, 72px);
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: calc(var(--sq-size, 72px) * 0.75);
  cursor: pointer;
  position: relative;
}

.square.light { background: var(--sq-light); }
.square.dark { background: var(--sq-dark); }
.square.last-move { background: var(--sq-lastmove) !important; }
.square.selected { background: var(--sq-highlight) !important; }
.square.check-glow::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--sq-check);
  pointer-events: none;
}

/* Legal move indicators */
.legal-dot::before {
  content: '';
  position: absolute;
  width: 28%;
  height: 28%;
  border-radius: 50%;
  background: var(--dot-color);
  pointer-events: none;
}

.legal-ring::after {
  content: '';
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  border: calc(var(--sq-size,72px)*0.08) solid var(--ring-color);
  pointer-events: none;
}

/* Piece styling */
.piece {
  z-index: 2;
  line-height: 1;
  cursor: grab;
  transition: transform 0.05s;
  filter: drop-shadow(1px 1px 1px rgba(0,0,0,0.3));
}

.piece.white-piece { color: #fff; text-shadow: 0 0 2px #000, 0 0 2px #000; }
.piece.black-piece { color: #222; text-shadow: 0 0 1px rgba(255,255,255,0.3); }

/* Dragging */
.piece.dragging { opacity: 0.4; cursor: grabbing; }
#drag-ghost {
  position: fixed;
  pointer-events: none;
  z-index: 1000;
  font-size: calc(var(--sq-size,72px)*0.8);
  filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.5));
}

/* Coordinates */
.coord {
  position: absolute;
  font-size: 10px;
  font-weight: bold;
  pointer-events: none;
  opacity: 0.7;
}
.coord-file { bottom: 2px; right: 4px; }
.coord-rank { top: 2px; left: 4px; }
.square.light .coord { color: var(--sq-dark); }
.square.dark .coord { color: var(--sq-light); }

/* Side panel */
#panel {
  flex: 1;
  min-width: 260px;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.panel-section {
  background: var(--panel-bg);
  border-radius: 8px;
  padding: 14px;
}

.panel-title {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #999;
  margin-bottom: 8px;
}

/* Turn indicator */
#turn-indicator {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
}
.turn-dot {
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 2px solid #555;
}
.turn-dot.white { background: #fff; }
.turn-dot.black { background: #333; }

/* Captured pieces */
.captured-row {
  display: flex;
  flex-wrap: wrap;
  min-height: 28px;
  font-size: 20px;
  gap: 1px;
}

/* Move list */
#move-list-container {
  max-height: 300px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: #555 transparent;
}
#move-list {
  display: grid;
  grid-template-columns: 32px 1fr 1fr;
  gap: 0 6px;
  font-family: 'Courier New', monospace;
  font-size: 14px;
}
.move-num { color: #777; text-align: right; }
.move-w, .move-b { padding: 2px 4px; border-radius: 3px; cursor: default; }
.move-w:hover, .move-b:hover { background: rgba(255,255,255,0.1); }

/* Controls */
#controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.btn {
  padding: 8px 14px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  transition: background 0.2s, transform 0.1s;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn-primary { background: var(--accent); color: #fff; }
.btn-secondary { background: #444; color: #ccc; }
.btn-danger { background: #c0392b; color: #fff; }

select, input[type=range] {
  background: #444;
  color: #ddd;
  border: 1px solid #555;
  padding: 6px 8px;
  border-radius: 4px;
  font-size: 13px;
}

.control-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.control-label { font-size: 12px; color: #999; min-width: 70px; }

/* Promotion overlay */
#promotion-overlay {
  display: none;
  position: absolute;
  z-index: 100;
  background: rgba(0,0,0,0.85);
  border-radius: 8px;
  padding: 6px;
  gap: 4px;
}
#promotion-overlay.active { display: flex; }

.promo-piece {
  width: calc(var(--sq-size,72px)*0.7);
  height: calc(var(--sq-size,72px)*0.7);
  font-size: calc(var(--sq-size,72px)*0.65);
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  border-radius: 4px;
  transition: background 0.15s;
}
.promo-piece:hover { background: rgba(255,255,255,0.3); }

/* Game over banner */
#game-over-banner {
  display: none;
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0,0,0,0.92);
  color: #fff;
  padding: 24px 36px;
  border-radius: 12px;
  text-align: center;
  z-index: 200;
  box-shadow: 0 8px 40px rgba(0,0,0,0.7);
}
#game-over-banner.active { display: block; }
#game-over-banner h2 { margin-bottom: 6px; font-size: 22px; }
#game-over-banner p { color: #aaa; font-size: 14px; }

/* Responsive */
@media (max-width: 800px) {
  #app { flex-direction: column; align-items: center; }
  :root { --sq-size: min(11vw, 64px); }
  #panel { max-width: 95vw; width: 100%; }
}
</style>
</head>
<body>

<div id="app">
  <div id="board-container">
    <div id="board"></div>
    <div id="promotion-overlay"></div>
    <div id="game-over-banner">
      <h2 id="go-title"></h2>
      <p id="go-subtitle"></p>
      <br>
      <button class="btn btn-primary" onclick="newGame()">New Game</button>
    </div>
  </div>

  <div id="panel">
    <div class="panel-section">
      <div id="turn-indicator">
        <span class="turn-dot white"></span>
        <span id="turn-text">White to move</span>
      </div>
    </div>

    <div class="panel-section">
      <div class="panel-title">Captured</div>
      <div style="margin-bottom:4px"><small>Captured by White:</small></div>
      <div id="captured-by-white" class="captured-row"></div>
      <div style="margin-top:6px;margin-bottom:4px"><small>Captured by Black:</small></div>
      <div id="captured-by-black" class="captured-row"></div>
    </div>

    <div class="panel-section">
      <div class="panel-title">Moves</div>
      <div id="move-list-container">
        <div id="move-list"></div>
      </div>
    </div>

    <div class="panel-section" id="controls">
      <button class="btn btn-primary" onclick="newGame()">New Game</button>
      <button class="btn btn-secondary" onclick="undoMove()" id="undo-btn">Undo</button>
      <button class="btn btn-secondary" onclick="flipBoard()">Flip Board</button>

      <div class="control-row">
        <span class="control-label">Difficulty:</span>
        <select id="depth-select">
          <option value="1">Easy (Depth 1)</option>
          <option value="2" selected>Medium (Depth 2)</option>
          <option value="3">Hard (Depth 3)</option>
        </select>
      </div>

      <div class="control-row">
        <span class="control-label">Play as:</span>
        <select id="side-select">
          <option value="white" selected>White</option>
          <option value="black">Black</option>
          <option value="both">Both (Analysis)</option>
        </select>
      </div>

      <div class="control-row">
        <span class="control-label">Sound:</span>
        <button class="btn btn-secondary" id="mute-btn" onclick="toggleMute()">🔊 On</button>
      </div>
    </div>
  </div>
</div>

<div id="drag-ghost"></div>

<script>
/* ================================================================
   SECTION 1: CONSTANTS & CONFIGURATION
   ================================================================ */
const CFG = {
  // Board colors (CSS vars override these)
  sqLight: '#f0d9b5',
  sqDark: '#b58863',
  // AI search depths available
  aiDepths: [1, 2, 3],
  // Piece values for evaluation
  pieceValues: { P: 100, N: 320, B: 330, R: 500, Q: 900, K: 20000 },
  // Sound enabled by default
  soundEnabled: true,
};

// Unicode chess pieces (by piece value index: 0=empty, 1-6=white, 7-12=black)
const PIECE_CHAR = [
  '', '\u2654','\u2655','\u2656','\u2657','\u2658','\u2659', // white K,Q,R,B,N,P — wait, wrong order
];
// Correct: WP=1,WQ=5,WR=4,WB=3,WN=2,WK=6  → indices by piece value
const PIECE_SYMBOL = [
  '', '\u2659','\u2658','\u2657','\u2656','\u2655','\u2654', // white P,N,B,R,Q,K
  '', '\u265F','\u265E','\u265D','\u265C','\u265B','\u265A', // black p,n,b,r,q,k
];

// Legacy map for compatibility
const PIECE_UNICODE = {
  'K': '\u2654', 'Q': '\u2655', 'R': '\u2656', 'B': '\u2657', 'N': '\u2658', 'P': '\u2659',
  'k': '\u265A', 'q': '\u265B', 'r': '\u265C', 'b': '\u265D', 'n': '\u265E', 'p': '\u265F',
};

// Piece-square tables (from white's perspective; mirrored for black)
const PST = {
  P: [
    0,  0,  0,  0,  0,  0,  0,  0,
    50, 50, 50, 50, 50, 50, 50, 50,
    10, 10, 20, 30, 30, 20, 10, 10,
    5,  5, 10, 25, 25, 10,  5,  5,
    0,  0,  0, 20, 20,  0,  0,  0,
    5, -5,-10,  0,  0,-10, -5,  5,
    5, 10, 10,-20,-20, 10, 10,  5,
    0,  0,  0,  0,  0,  0,  0,  0,
  ],
  N: [
    -50,-40,-30,-30,-30,-30,-40,-50,
    -40,-20,  0,  0,  0,  0,-20,-40,
    -30,  0, 10, 15, 15, 10,  0,-30,
    -30,  5, 15, 20, 20, 15,  5,-30,
    -30,  0, 15, 20, 20, 15,  0,-30,
    -30,  5, 10, 15, 15, 10,  5,-30,
    -40,-20,  0,  5,  5,  0,-20,-40,
    -50,-40,-30,-30,-30,-30,-40,-50,
  ],
  B: [
    -20,-10,-10,-10,-10,-10,-10,-20,
    -10,  0,  0,  0,  0,  0,  0,-10,
    -10,  0, 10, 10, 10, 10,  0,-10,
    -10,  5,  5, 10, 10,  5,  5,-10,
    -10,  0, 10, 10, 10, 10,  0,-10,
    -10, 10, 10, 10, 10, 10, 10,-10,
    -10,  5,  0,  0,  0,  0,  5,-10,
    -20,-10,-10,-10,-10,-10,-10,-20,
  ],
  R: [
    0,  0,  0,  0,  0,  0,  0,  0,
    5, 10, 10, 10, 10, 10, 10,  5,
   -5,  0,  0,  0,  0,  0,  0, -5,
   -5,  0,  0,  0,  0,  0,  0, -5,
   -5,  0,  0,  0,  0,  0,  0, -5,
   -5,  0,  0,  0,  0,  0,  0, -5,
   -5,  0,  0,  0,  0,  0,  0, -5,
    0,  0,  0,  5,  5,  0,  0,  0,
  ],
  Q: [
   -20,-10,-10, -5, -5,-10,-10,-20,
   -10,  0,  0,  0,  0,  0,  0,-10,
   -10,  0,  5,  5,  5,  5,  0,-10,
    -5,  0,  5,  5,  5,  5,  0, -5,
     0,  0,  5,  5,  5,  5,  0, -5,
   -10,  5,  5,  5,  5,  5,  0,-10,
   -10,  0,  5,  0,  0,  0,  0,-10,
   -20,-10,-10, -5, -5,-10,-10,-20,
  ],
  K: [
    -30,-40,-40,-50,-50,-40,-40,-30,
    -30,-40,-40,-50,-50,-40,-40,-30,
    -30,-40,-40,-50,-50,-40,-40,-30,
    -30,-40,-40,-50,-50,-40,-40,-30,
    -20,-30,-30,-40,-40,-30,-30,-20,
    -10,-20,-20,-20,-20,-20,-20,-10,
     20, 20,  0,  0,  0,  0, 20, 20,
     20, 30, 10,  0,  0, 10, 30, 20,
  ],
};

/* ================================================================
   SECTION 2: BOARD REPRESENTATION (0x88)
   ================================================================ */
// 0x88 board: 128-element array. Square = row*16+col. Valid if sq&8===0.
const EMPTY = 0;
const WP=1, WN=2, WB=3, WR=4, WQ=5, WK=6;
const BP=7, BN=8, BB=9, BR=10, BQ=11, BK=12;

// Piece type codes (for move generation)
const PIECE_TYPE = { P:1, N:2, B:3, R:4, Q:5, K:6 };

function pieceColor(p) { return p >= 1 && p <= 6 ? 'w' : p >= 7 && p <= 12 ? 'b' : null; }
function isWhite(p) { return p >= 1 && p <= 6; }
function isBlack(p) { return p >= 7 && p <= 12; }
function pieceType(p) { return p <= 6 ? p : p - 6; }

// Direction offsets for sliding and stepping pieces (from white perspective)
const NORTH = -16, SOUTH = 16, WEST = -1, EAST = 1;
const KNIGHT_DIRS = [-33, -31, 31, 33, -18, -14, 14, 18]; // ±2ranks±1file, ±1rank±2files in 0x88
const KING_DIRS = [NORTH, SOUTH, WEST, EAST, NORTH+WEST, NORTH+EAST, SOUTH+WEST, SOUTH+EAST];
const PAWN_CAPTURE_DIRS_W = [NORTH+WEST, NORTH+EAST]; // white captures northward (toward row 0)

// Wait — let me reconsider. In standard chess notation, White starts at ranks 7-8 (rows 6-7 in 0-indexed).
// Let's use: row 0 = rank 1 (white home), row 7 = rank 8 (black home).
// So white pawns move toward higher rows (SOUTH = +16), black pawns toward lower rows (NORTH = -16).

const WP_MOVE_DIRS = [SOUTH]; // white pawns go south (+16)
const BP_MOVE_DIRS = [NORTH]; // black pawns go north (-16)
const PAWN_CAPTURE_W = [SOUTH+WEST, SOUTH+EAST];
const PAWN_CAPTURE_B = [NORTH+WEST, NORTH+EAST];

function sqToFile(sq) { return sq & 7; }
function sqToRank(sq) { return sq >> 4; }
function isOffBoard(sq) { return sq < 0 || sq >= 128 || (sq & 8) !== 0; }

// Algebraic notation helpers
const FILES = 'abcdefgh';
const RANKS = '12345678';
function sqToAlgebraic(sq) { return FILES[sqToFile(sq)] + RANKS[sqToRank(sq)]; }
function algebraicToSq(s) { return (s.charCodeAt(1)-49)*16 + (s.charCodeAt(0)-97); }

/* ================================================================
   SECTION 3: GAME STATE
   ================================================================ */
class GameState {
  constructor() {
    this.board = new Array(128).fill(EMPTY);
    this.turn = 'w'; // 'w' or 'b'
    this.castlingRights = { K: true, Q: true, k: true, q: true };
    this.enPassantSq = -1; // 0x88 square index, or -1
    this.halfMoveClock = 0;
    this.fullMoveNumber = 1;
    this.moveHistory = []; // array of { from, to, piece, captured, promotion, castling, ep }
    this.capturedPieces = []; // for display: [{color:'w'|'b', type}]
    this.gameOver = false;
    this.result = null; // '1-0','0-1','½-½' or null
    this.resultReason = '';
    this.initBoard();
  }

  initBoard() {
    const backRow = [WR, WN, WB, WQ, WK, WB, WN, WR];
    for (let c = 0; c < 8; c++) {
      this.board[c] = backRow[c]; // row 0 = rank 1
      this.board[7*16+c] = BP; // row 7 = rank 8... wait
    }
    // Actually let me reconsider the layout. Standard: White pieces on ranks 1,2 (rows 0,1). Black on ranks 7,8 (rows 6,7).
    // Row 0 = rank 1, Row 7 = rank 8.
    const whiteBack = [WR, WN, WB, WQ, WK, WB, WN, WR];
    const blackBack = [BR, BN, BB, BQ, BK, BB, BN, BR];
    for (let c = 0; c < 8; c++) {
      this.board[0*16+c] = whiteBack[c];   // rank 1
      this.board[1*16+c] = WP;              // rank 2 (white pawns)
      this.board[6*16+c] = BP;              // rank 7 (black pawns)
      this.board[7*16+c] = blackBack[c];   // rank 8
    }
  }

  clone() {
    const g = new GameState();
    g.board = [...this.board];
    g.turn = this.turn;
    g.castlingRights = {...this.castlingRights};
    g.enPassantSq = this.enPassantSq;
    g.halfMoveClock = this.halfMoveClock;
    g.fullMoveNumber = this.fullMoveNumber;
    g.moveHistory = [...this.moveHistory];
    g.capturedPieces = [...this.capturedPieces];
    g.gameOver = this.gameOver;
    g.result = this.result;
    g.resultReason = this.resultReason;
    return g;
  }

  // Make a move on the board (assumes legal)
  makeMove(from, to, promotion='Q') {
    const piece = this.board[from];
    const captured = this.board[to];
    const color = pieceColor(piece);
    const type = pieceType(piece);

    let isCastling = false;
    let isEP = false;
    let epCapturedSq = -1;
    let promoPiece = null;

    // En passant capture
    if (type === PIECE_TYPE.P && to === this.enPassantSq) {
      const capturedPawnSq = color === 'w' ? to - 16 : to + 16;
      isEP = true;
      epCapturedSq = capturedPawnSq;
      // Record the captured pawn
      this.capturedPieces.push({ color: pieceColor(this.board[capturedPawnSq]), type: PIECE_TYPE.P });
    }

    // Castling
    if (type === PIECE_TYPE.K && Math.abs(to - from) === 2) {
      isCastling = true;
      const dir = to > from ? 1 : -1;
      const rookFrom = from + dir * 3;
      const rookTo = from + dir;
      this.board[rookTo] = this.board[rookFrom];
      this.board[rookFrom] = EMPTY;
    }

    // Record capture (non-EP)
    if (captured !== EMPTY && !isEP) {
      this.capturedPieces.push({ color: pieceColor(captured), type: pieceType(captured) });
    }

    // Move the piece
    this.board[to] = piece;
    this.board[from] = EMPTY;

    // Promotion
    if (type === PIECE_TYPE.P && (sqToRank(to) === 0 || sqToRank(to) === 7)) {
      const promoMap = { 'Q': color==='w'?WQ:BQ, 'R': color==='w'?WR:BR, 'B': color==='w'?WB:BB, 'N': color==='w'?WN:BN };
      this.board[to] = promoMap[promotion];
      promoPiece = promotion;
    }

    // Update en passant square
    if (type === PIECE_TYPE.P && Math.abs(sqToRank(to) - sqToRank(from)) === 2) {
      this.enPassantSq = color === 'w' ? from + 16 : from - 16;
    } else {
      this.enPassantSq = -1;
    }

    // Update castling rights
    if (piece === WK) { this.castlingRights.K = false; this.castlingRights.Q = false; }
    if (piece === BK) { this.castlingRights.k = false; this.castlingRights.q = false; }
    if (to === 0*16+7 || from === 0*16+7) this.castlingRights.K = false; // h1
    if (to === 0*16+0 || from === 0*16+0) this.castlingRights.Q = false; // a1
    if (to === 7*16+7 || from === 7*16+7) this.castlingRights.k = false; // h8
    if (to === 7*16+0 || from === 7*16+0) this.castlingRights.q = false; // a8

    // Half-move clock
    if (type === PIECE_TYPE.P || captured !== EMPTY || isEP) {
      this.halfMoveClock = 0;
    } else {
      this.halfMoveClock++;
    }

    // Full move number increments after black moves
    if (this.turn === 'b') this.fullMoveNumber++;

    // Record move for undo
    const moveRecord = {
      from, to, piece, captured: captured || (isEP ? this.board[epCapturedSq] : EMPTY),
      promotion: promoPiece, castling: isCastling, ep: isEP, epCapturedSq,
      prevEnPassant: this.enPassantSq === -1 && !((type===PIECE_TYPE.P&&Math.abs(sqToRank(to)-sqToRank(from))===2) ? true : false) ? -1 : (color==='w'&&type===PIECE_TYPE.P&&Math.abs(sqToRank(to)-sqToRank(from))===2 ? from+16 : type===PIECE_TYPE.P&&Math.abs(sqToRank(to)-sqToRank(from))===2 ? from-16 : -1),
      prevCastling: {...this.castlingRights}, // We'll fix this below
    };

    // Actually, let me store the state before the move for proper undo
    // Store previous en passant and castling rights BEFORE they were updated
    // Hmm, I already modified them. Let me restructure.
  }

  // Better approach: makeMove returns info needed for unmake
  makeMove2(from, to, promotion='Q') {
    const piece = this.board[from];
    const captured = this.board[to];
    const color = pieceColor(piece);
    const type = pieceType(piece);

    let isCastling = false;
    let isEP = false;
    let epCapturedSq = -1;
    let promoPiece = null;
    let rookFrom = -1, rookTo = -1;

    // Save state for undo
    const undoInfo = {
      from, to, piece, captured, isCastling: false, isEP: false,
      epCapturedSq: -1, promoPiece: null, rookFrom: -1, rookTo: -1,
      prevEnPassant: this.enPassantSq,
      prevCastlingRights: {...this.castlingRights},
      prevHalfMoveClock: this.halfMoveClock,
    };

    // En passant capture
    if (type === PIECE_TYPE.P && to === this.enPassantSq) {
      isEP = true;
      epCapturedSq = color === 'w' ? to - 16 : to + 16;
      const epPiece = this.board[epCapturedSq];
      undoInfo.captured = epPiece; // override captured with the EP pawn
      this.board[epCapturedSq] = EMPTY;
      this.capturedPieces.push({ color: pieceColor(epPiece), type: PIECE_TYPE.P });
    }

    // Castling
    if (type === PIECE_TYPE.K && Math.abs(to - from) === 2) {
      isCastling = true;
      const dir = to > from ? 1 : -1;
      rookFrom = from + dir * 3;
      rookTo = from + dir;
      this.board[rookTo] = this.board[rookFrom];
      this.board[rookFrom] = EMPTY;
    }

    // Record capture (non-EP)
    if (captured !== EMPTY && !isEP) {
      this.capturedPieces.push({ color: pieceColor(captured), type: pieceType(captured) });
    }

    // Move the piece
    this.board[to] = piece;
    this.board[from] = EMPTY;

    // Promotion
    if (type === PIECE_TYPE.P && (sqToRank(to) === 0 || sqToRank(to) === 7)) {
      const promoMap = { 'Q': color==='w'?WQ:BQ, 'R': color==='w'?WR:BR, 'B': color==='w'?WB:BB, 'N': color==='w'?WN:BN };
      this.board[to] = promoMap[promotion];
      promoPiece = promotion;
    }

    // Update en passant square
    if (type === PIECE_TYPE.P && Math.abs(sqToRank(to) - sqToRank(from)) === 2) {
      this.enPassantSq = color === 'w' ? from + 16 : from - 16;
    } else {
      this.enPassantSq = -1;
    }

    // Update castling rights
    if (piece === WK) { this.castlingRights.K = false; this.castlingRights.Q = false; }
    if (piece === BK) { this.castlingRights.k = false; this.castlingRights.q = false; }
    if (to === 0*16+7 || from === 0*16+7) this.castlingRights.K = false;
    if (to === 0*16+0 || from === 0*16+0) this.castlingRights.Q = false;
    if (to === 7*16+7 || from === 7*16+7) this.castlingRights.k = false;
    if (to === 7*16+0 || from === 7*16+0) this.castlingRights.q = false;

    // Half-move clock
    if (type === PIECE_TYPE.P || captured !== EMPTY || isEP) {
      this.halfMoveClock = 0;
    } else {
      this.halfMoveClock++;
    }

    // Full move number
    if (this.turn === 'b') this.fullMoveNumber++;

    // Switch turn
    this.turn = this.turn === 'w' ? 'b' : 'w';

    undoInfo.isCastling = isCastling;
    undoInfo.isEP = isEP;
    undoInfo.epCapturedSq = epCapturedSq;
    undoInfo.promoPiece = promoPiece;
    undoInfo.rookFrom = rookFrom;
    undoInfo.rookTo = rookTo;

    return undoInfo;
  }

  unmakeMove(undoInfo) {
    // Restore piece positions
    this.board[undoInfo.from] = undoInfo.piece;
    this.board[undoInfo.to] = EMPTY;

    // Restore captured piece
    if (undoInfo.isEP) {
      this.board[undoInfo.epCapturedSq] = undoInfo.captured;
      this.capturedPieces.pop();
    } else if (undoInfo.captured !== EMPTY) {
      this.capturedPieces.pop();
    }

    // Restore castling rook
    if (undoInfo.isCastling) {
      this.board[undoInfo.rookFrom] = this.board[undoInfo.rookTo];
      this.board[undoInfo.rookTo] = EMPTY;
    }

    // Undo promotion
    if (undoInfo.promoPiece) {
      const color = pieceColor(undoInfo.piece);
      this.board[undoInfo.to] = color === 'w' ? WP : BP;
    }

    // Restore state
    this.enPassantSq = undoInfo.prevEnPassant;
    this.castlingRights = undoInfo.prevCastlingRights;
    this.halfMoveClock = undoInfo.prevHalfMoveClock;
    if (this.turn === 'w') this.fullMoveNumber--;
    this.turn = this.turn === 'w' ? 'b' : 'w';

    // Remove from move history
    this.moveHistory.pop();
  }

  // Generate all pseudo-legal moves, then filter for legality
  generateMoves(color) {
    const moves = [];
    color = color || this.turn;
    for (let sq = 0; sq < 128; sq++) {
      if ((sq & 8) !== 0) continue; // off board
      const piece = this.board[sq];
      if (piece === EMPTY || pieceColor(piece) !== color) continue;
      const type = pieceType(piece);

      switch(type) {
        case PIECE_TYPE.P: this.genPawnMoves(sq, color, moves); break;
        case PIECE_TYPE.N: this.genKnightMoves(sq, color, moves); break;
        case PIECE_TYPE.B: this.genBishopMoves(sq, color, moves); break;
        case PIECE_TYPE.R: this.genRookMoves(sq, color, moves); break;
        case PIECE_TYPE.Q: this.genQueenMoves(sq, color, moves); break;
        case PIECE_TYPE.K: this.genKingMoves(sq, color, moves); break;
      }
    }
    return moves;
  }

  genPawnMoves(sq, color, moves) {
    const dir = color === 'w' ? 16 : -16; // white goes toward higher rank (south), black toward lower (north)
    const startRank = color === 'w' ? 1 : 6;
    const promoRank = color === 'w' ? 7 : 0;
    const captureDirs = color === 'w' ? [16-1, 16+1] : [-16-1, -16+1];

    // Single push
    let to = sq + dir;
    if (!isOffBoard(to) && this.board[to] === EMPTY) {
      moves.push({ from: sq, to });
      // Double push
      if (sqToRank(sq) === startRank) {
        const to2 = sq + 2*dir;
        if (this.board[to2] === EMPTY) {
          moves.push({ from: sq, to: to2 });
        }
      }
    }

    // Captures
    for (const cd of captureDirs) {
      const capTo = sq + cd;
      if (isOffBoard(capTo)) continue;
      const target = this.board[capTo];
      if (target !== EMPTY && pieceColor(target) !== color) {
        // Regular capture
        if (sqToRank(capTo) === promoRank) {
          for (const p of ['Q','R','B','N']) moves.push({ from: sq, to: capTo, promotion: p });
        } else {
          moves.push({ from: sq, to: capTo });
        }
      }
      // En passant — for black pawns the EP landing is one more step toward white's side
      const epTarget = color === 'w' ? capTo : capTo - 16;
      if (!isOffBoard(epTarget) && epTarget === this.enPassantSq) {
        moves.push({ from: sq, to: this.enPassantSq, ep: true });
      }
    }
  }

  genKnightMoves(sq, color, moves) {
    for (const d of KNIGHT_DIRS) {
      const to = sq + d;
      if (isOffBoard(to)) continue;
      const target = this.board[to];
      if (target === EMPTY || pieceColor(target) !== color) {
        moves.push({ from: sq, to });
      }
    }
  }

  genBishopMoves(sq, color, moves) {
    this.genSlidingMoves(sq, color, moves, [16-1, 16+1, -16-1, -16+1]);
  }

  genRookMoves(sq, color, moves) {
    this.genSlidingMoves(sq, color, moves, [16, -16, 1, -1]);
  }

  genQueenMoves(sq, color, moves) {
    this.genSlidingMoves(sq, color, moves, [16, -16, 1, -1, 16-1, 16+1, -16-1, -16+1]);
  }

  genSlidingMoves(sq, color, moves, dirs) {
    for (const d of dirs) {
      let to = sq + d;
      while (!isOffBoard(to)) {
        const target = this.board[to];
        if (target === EMPTY) {
          moves.push({ from: sq, to });
        } else {
          if (pieceColor(target) !== color) moves.push({ from: sq, to });
          break;
        }
        to += d;
      }
    }
  }

  genKingMoves(sq, color, moves) {
    for (const d of KING_DIRS) {
      const to = sq + d;
      if (isOffBoard(to)) continue;
      const target = this.board[to];
      if (target === EMPTY || pieceColor(target) !== color) {
        moves.push({ from: sq, to });
      }
    }

    // Castling
    const enemy = color === 'w' ? 'b' : 'w';
    if (!this.isSquareAttacked(sq, enemy)) {
      // Kingside
      const ksRight = color === 'w' ? this.castlingRights.K : this.castlingRights.k;
      if (ksRight) {
        const rFrom = color === 'w' ? 0*16+7 : 7*16+7;
        const rTo = color === 'w' ? 0*16+5 : 7*16+5;
        const kTo = color === 'w' ? 0*16+6 : 7*16+6;
        if (this.board[rFrom] !== EMPTY && this.board[rTo] === EMPTY && this.board[kTo] === EMPTY) {
          if (!this.isSquareAttacked(rTo, enemy) && !this.isSquareAttacked(kTo, enemy)) {
            moves.push({ from: sq, to: kTo });
          }
        }
      }
      // Queenside
      const qsRight = color === 'w' ? this.castlingRights.Q : this.castlingRights.q;
      if (qsRight) {
        const rFrom = color === 'w' ? 0*16+0 : 7*16+0;
        const rTo = color === 'w' ? 0*16+3 : 7*16+3;
        const kTo = color === 'w' ? 0*16+2 : 7*16+2;
        if (this.board[rFrom] !== EMPTY && this.board[rTo] === EMPTY && this.board[kTo] === EMPTY && this.board[color==='w'?0*16+1:7*16+1] === EMPTY) {
          if (!this.isSquareAttacked(rTo, enemy) && !this.isSquareAttacked(kTo, enemy)) {
            moves.push({ from: sq, to: kTo });
          }
        }
      }
    }
  }

  // Check if a square is attacked by the given color
  isSquareAttacked(sq, byColor) {
    // Pawn attacks
    const pawnDir = byColor === 'w' ? -16 : 16; // white pawns attack from below (lower rank toward higher)
    // Wait: if we're checking if sq is attacked BY white, white pawns are at lower ranks attacking upward.
    // White pawn at rank r attacks squares at rank r+1. So to check if sq is attacked by white pawns,
    // look for white pawns at sq - 16 ± 1 (one rank below).
    const pawnAttackFrom = byColor === 'w' ? [sq - 16 - 1, sq - 16 + 1] : [sq + 16 - 1, sq + 16 + 1];
    for (const paf of pawnAttackFrom) {
      if (!isOffBoard(paf)) {
        const p = this.board[paf];
        if (p !== EMPTY && pieceColor(p) === byColor && pieceType(p) === PIECE_TYPE.P) return true;
      }
    }

    // Knight attacks
    for (const d of KNIGHT_DIRS) {
      const to = sq + d;
      if (!isOffBoard(to)) {
        const p = this.board[to];
        if (p !== EMPTY && pieceColor(p) === byColor && pieceType(p) === PIECE_TYPE.N) return true;
      }
    }

    // King attacks
    for (const d of KING_DIRS) {
      const to = sq + d;
      if (!isOffBoard(to)) {
        const p = this.board[to];
        if (p !== EMPTY && pieceColor(p) === byColor && pieceType(p) === PIECE_TYPE.K) return true;
      }
    }

    // Sliding pieces: bishop/queen diagonals, rook/queen straights
    const diagDirs = [16-1, 16+1, -16-1, -16+1];
    const straightDirs = [16, -16, 1, -1];

    for (const d of diagDirs) {
      let to = sq + d;
      while (!isOffBoard(to)) {
        const p = this.board[to];
        if (p !== EMPTY) {
          if (pieceColor(p) === byColor && (pieceType(p) === PIECE_TYPE.B || pieceType(p) === PIECE_TYPE.Q)) return true;
          break;
        }
        to += d;
      }
    }

    for (const d of straightDirs) {
      let to = sq + d;
      while (!isOffBoard(to)) {
        const p = this.board[to];
        if (p !== EMPTY) {
          if (pieceColor(p) === byColor && (pieceType(p) === PIECE_TYPE.R || pieceType(p) === PIECE_TYPE.Q)) return true;
          break;
        }
        to += d;
      }
    }

    return false;
  }

  // Find king position for a color
  findKing(color) {
    const target = color === 'w' ? WK : BK;
    for (let sq = 0; sq < 128; sq++) {
      if ((sq & 8) === 0 && this.board[sq] === target) return sq;
    }
    return -1;
  }

  // Is the given color in check?
  isInCheck(color) {
    const kingSq = this.findKing(color);
    if (kingSq === -1) return false;
    const enemy = color === 'w' ? 'b' : 'w';
    return this.isSquareAttacked(kingSq, enemy);
  }

  // Generate legal moves only
  generateLegalMoves(color) {
    color = color || this.turn;
    const pseudoMoves = this.generateMoves(color);
    const legal = [];

    for (const move of pseudoMoves) {
      if (this.isMoveLegal2(move, color)) {
        legal.push(move);
      }
    }
    return legal;
  }

  // Check if a specific move is legal (doesn't leave own king in check)
  isMoveLegal(move, color) {
    const piece = this.board[move.from];
    const captured = this.board[move.to];
    const type = pieceType(piece);
    const enemy = color === 'w' ? 'b' : 'w';

    // Make the move temporarily
    let epCapturedSq = -1;
    let rookFrom = -1, rookTo = -1;

    if (type === PIECE_TYPE.P && move.to === this.enPassantSq) {
      epCapturedSq = color === 'w' ? move.to - 16 : move.to + 16;
      const epPiece = this.board[epCapturedSq];
      this.board[epCapturedSq] = EMPTY;
    }

    if (type === PIECE_TYPE.K && Math.abs(move.to - move.from) === 2) {
      const dir = move.to > move.from ? 1 : -1;
      rookFrom = move.from + dir * 3;
      rookTo = move.from + dir;
      this.board[rookTo] = this.board[rookFrom];
      this.board[rookFrom] = EMPTY;
    }

    this.board[move.to] = piece;
    this.board[move.from] = EMPTY;

    const legal = !this.isSquareAttacked(this.findKing(color), enemy);

    // Undo
    this.board[move.from] = piece;
    this.board[move.to] = captured;
    if (epCapturedSq !== -1) {
      this.board[epCapturedSq] = this.board[epCapturedSq]; // already restored by setting move.to
      // Actually we need to restore the EP pawn. Let me fix:
      // The EP pawn was at epCapturedSq, we set it to EMPTY. We need to put it back.
      // But captured holds the piece that was at move.to (which was EMPTY for EP).
      // So we need a different approach.
    }
    if (rookFrom !== -1) {
      this.board[rookFrom] = this.board[rookTo];
      this.board[rookTo] = EMPTY;
    }

    return legal;
  }

  // Better isMoveLegal that properly handles undo
  isMoveLegal2(move, color) {
    const piece = this.board[move.from];
    const captured = this.board[move.to];
    const type = pieceType(piece);
    const enemy = color === 'w' ? 'b' : 'w';

    let epCapturedSq = -1;
    let rookFrom = -1, rookTo = -1;
    let epPiece = EMPTY;

    // En passant capture
    if (type === PIECE_TYPE.P && move.to === this.enPassantSq) {
      epCapturedSq = color === 'w' ? move.to - 16 : move.to + 16;
      epPiece = this.board[epCapturedSq];
      this.board[epCapturedSq] = EMPTY;
    }

    // Castling rook move
    if (type === PIECE_TYPE.K && Math.abs(move.to - move.from) === 2) {
      const dir = move.to > move.from ? 1 : -1;
      rookFrom = move.from + dir * 3;
      rookTo = move.from + dir;
      this.board[rookTo] = this.board[rookFrom];
      this.board[rookFrom] = EMPTY;
    }

    // Move piece
    this.board[move.to] = piece;
    this.board[move.from] = EMPTY;

    const kingSq = this.findKing(color);
    const legal = !this.isSquareAttacked(kingSq, enemy);

    // Undo
    this.board[move.from] = piece;
    this.board[move.to] = captured;
    if (epCapturedSq !== -1) {
      this.board[epCapturedSq] = epPiece;
    }
    if (rookFrom !== -1) {
      this.board[rookFrom] = this.board[rookTo];
      this.board[rookTo] = EMPTY;
    }

    return legal;
  }

  // Check game state: checkmate, stalemate, draws
  getGameState() {
    const legalMoves = this.generateLegalMoves(this.turn);
    if (legalMoves.length === 0) {
      if (this.isInCheck(this.turn)) {
        return 'checkmate';
      } else {
        return 'stalemate';
      }
    }

    // Insufficient material
    if (this.isInsufficientMaterial()) return 'insufficient_material';

    // 50-move rule
    if (this.halfMoveClock >= 100) return 'fifty_move_rule';

    return 'ongoing';
  }

  isInsufficientMaterial() {
    const pieces = [];
    for (let sq = 0; sq < 128; sq++) {
      if ((sq & 8) === 0 && this.board[sq] !== EMPTY) {
        pieces.push({ color: pieceColor(this.board[sq]), type: pieceType(this.board[sq]) });
      }
    }

    if (pieces.length === 2) return true; // K vs K

    // K+B vs K or K+N vs K
    if (pieces.length === 3) {
      const nonKing = pieces.find(p => p.type !== PIECE_TYPE.K);
      if (nonKing && (nonKing.type === PIECE_TYPE.B || nonKing.type === PIECE_TYPE.N)) return true;
    }

    // K+B vs K+B with same-colored bishops
    if (pieces.length === 4) {
      const bishops = pieces.filter(p => p.type === PIECE_TYPE.B);
      if (bishops.length === 2) {
        // Check if bishops are on the same color squares
        const b1Sq = this.findPieceByType('w', PIECE_TYPE.B);
        const b2Sq = this.findPieceByType('b', PIECE_TYPE.B);
        if (b1Sq !== -1 && b2Sq !== -1) {
          const sameColor = ((sqToFile(b1Sq) + sqToRank(b1Sq)) % 2) === ((sqToFile(b2Sq) + sqToRank(b2Sq)) % 2);
          if (sameColor) return true;
        }
      }
    }

    return false;
  }

  findPieceByType(color, type) {
    const target = color === 'w' ? type : type + 6;
    for (let sq = 0; sq < 128; sq++) {
      if ((sq & 8) === 0 && this.board[sq] === target) return sq;
    }
    return -1;
  }

  // Convert to FEN string
  fen() {
    let fen = '';
    for (let row = 7; row >= 0; row--) {
      let empty = 0;
      for (let col = 0; col < 8; col++) {
        const sq = row * 16 + col;
        if ((sq & 8) !== 0) continue;
        const p = this.board[sq];
        if (p === EMPTY) {
          empty++;
        } else {
          if (empty > 0) { fen += empty; empty = 0; }
          const typeChar = 'PNBRQK'[pieceType(p)-1];
          fen += isWhite(p) ? typeChar : typeChar.toLowerCase();
        }
      }
      if (empty > 0) fen += empty;
      if (row > 0) fen += '/';
    }

    fen += ' ' + this.turn;

    // Castling
    let castling = '';
    if (this.castlingRights.K) castling += 'K';
    if (this.castlingRights.Q) castling += 'Q';
    if (this.castlingRights.k) castling += 'k';
    if (this.castlingRights.q) castling += 'q';
    fen += ' ' + (castling || '-');

    // En passant
    fen += ' ' + (this.enPassantSq >= 0 ? sqToAlgebraic(this.enPassantSq) : '-');

    fen += ' ' + this.halfMoveClock;
    fen += ' ' + this.fullMoveNumber;

    return fen;
  }

  // Load from FEN
  loadFEN(fen) {
    const parts = fen.split(' ');
    const placement = parts[0];
    this.board = new Array(128).fill(EMPTY);

    const rows = placement.split('/');
    for (let row = 0; row < 8; row++) {
      let col = 0;
      for (const ch of rows[row]) {
        if (ch >= '1' && ch <= '8') {
          col += parseInt(ch);
        } else {
          const isUpper = ch === ch.toUpperCase();
          const typeIdx = 'PNBRQK'.indexOf(isUpper ? ch : ch.toUpperCase());
          const pieceVal = typeIdx + 1;
          const val = isUpper ? pieceVal : pieceVal + 6;
          this.board[(7-row)*16+col] = val;
          col++;
        }
      }
    }

    this.turn = parts[1];
    const castlingStr = parts[2];
    this.castlingRights = { K: false, Q: false, k: false, q: false };
    if (castlingStr !== '-') {
      if (castlingStr.includes('K')) this.castlingRights.K = true;
      if (castlingStr.includes('Q')) this.castlingRights.Q = true;
      if (castlingStr.includes('k')) this.castlingRights.k = true;
      if (castlingStr.includes('q')) this.castlingRights.q = true;
    }

    const epStr = parts[3];
    this.enPassantSq = epStr !== '-' ? algebraicToSq(epStr) : -1;

    this.halfMoveClock = parseInt(parts[4]) || 0;
    this.fullMoveNumber = parseInt(parts[5]) || 1;
    this.moveHistory = [];
    this.capturedPieces = [];
    this.gameOver = false;
    this.result = null;
    this.resultReason = '';
  }

  // Convert move to algebraic notation
  moveToAlgebraic(move) {
    const piece = this.board[move.from];
    const type = pieceType(piece);
    const color = pieceColor(piece);
    const captured = this.board[move.to] !== EMPTY || (type === PIECE_TYPE.P && move.ep);

    // Castling
    if (type === PIECE_TYPE.K && Math.abs(move.to - move.from) === 2) {
      return move.to > move.from ? 'O-O' : 'O-O-O';
    }

    let notation = '';

    // Piece letter (not for pawns)
    if (type !== PIECE_TYPE.P) {
      notation += 'PNBRQK'[type-1];

      // Disambiguation: check if another piece of same type can move to the same square
      const legalMoves = this.generateLegalMoves(color);
      const ambiguous = legalMoves.filter(m =>
        m.to === move.to &&
        this.board[m.from] !== EMPTY &&
        pieceType(this.board[m.from]) === type &&
        m.from !== move.from
      );

      if (ambiguous.length > 0) {
        const sameFile = ambiguous.some(m => sqToFile(m.from) === sqToFile(move.from));
        const sameRank = ambiguous.some(m => sqToRank(m.from) === sqToRank(move.from));
        if (!sameFile) {
          notation += FILES[sqToFile(move.from)];
        } else if (!sameRank) {
          notation += RANKS[sqToRank(move.from)];
        } else {
          notation += FILES[sqToFile(move.from)] + RANKS[sqToRank(move.from)];
        }
      }
    }

    // Capture indicator
    if (captured) {
      if (type === PIECE_TYPE.P) {
        notation += FILES[sqToFile(move.from)];
      }
      notation += 'x';
    }

    // Destination square
    notation += sqToAlgebraic(move.to);

    // Promotion
    if (move.promotion) {
      notation += '=' + move.promotion;
    }

    // En passant annotation
    if (type === PIECE_TYPE.P && move.ep) {
      notation += ' e.p.';
    }

    return notation.trim();
  }

  // Add check/mate symbols to algebraic notation
  moveToFullAlgebraic(move) {
    let notation = this.moveToAlgebraic(move);

    // Make the move temporarily to check for check/mate
    const undoInfo = this.makeMove2(move.from, move.to, move.promotion || 'Q');
    const enemyTurn = this.turn; // turn has switched
    const state = this.getGameState();

    if (state === 'checkmate') {
      notation += '#';
    } else if (this.isInCheck(enemyTurn)) {
      notation += '+';
    }

    this.unmakeMove(undoInfo);
    return notation;
  }
}

/* ================================================================
   SECTION 4: AI ENGINE — Minimax with Alpha-Beta Pruning
   ================================================================ */
class ChessAI {
  constructor(game) {
    this.game = game;
    this.nodesSearched = 0;
    this.maxNodes = 500000; // limit to keep responsive
  }

  evaluate() {
    let score = 0;
    for (let sq = 0; sq < 128; sq++) {
      if ((sq & 8) !== 0) continue;
      const piece = this.game.board[sq];
      if (piece === EMPTY) continue;

      const type = pieceType(piece);
      const color = pieceColor(piece);
      const val = CFG.pieceValues['PNBRQK'[type-1]];

      // Material
      score += isWhite(piece) ? val : -val;

      // Piece-square tables
      if (PST[type === 1 ? 'P' : type === 2 ? 'N' : type === 3 ? 'B' : type === 4 ? 'R' : type === 5 ? 'Q' : 'K']) {
        const table = PST['PNBRQK'[type-1]];
        let idx;
        if (isWhite(piece)) {
          idx = sq; // white perspective
        } else {
          // Mirror for black: flip rank and file
          const r = 7 - sqToRank(sq);
          const f = 7 - sqToFile(sq);
          idx = r * 8 + f;
        }
        score += isWhite(piece) ? table[idx] : -table[idx];
      }
    }

    // Mobility bonus (simplified)
    const wMoves = this.game.generateLegalMoves('w').length;
    const bMoves = this.game.generateLegalMoves('b').length;
    score += (wMoves - bMoves) * 3;

    return score; // positive = white advantage
  }

  search(depth, alpha, beta, maximizingPlayer) {
    if (++this.nodesSearched > this.maxNodes) return this.evaluate();
    if (depth === 0) return this.quiesce(alpha, beta, maximizingPlayer);

    const color = maximizingPlayer ? 'w' : 'b';
    const moves = this.game.generateLegalMoves(color);

    // Move ordering: captures first, then others
    this.orderMoves(moves);

    if (moves.length === 0) {
      if (this.game.isInCheck(color)) {
        return maximizingPlayer ? -99999 + (3 - depth) : 99999 - (3 - depth); // checkmate
      }
      return 0; // stalemate
    }

    if (maximizingPlayer) {
      let maxEval = -Infinity;
      for (const move of moves) {
        const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
        this.game.moveHistory.push({ from: move.from, to: move.to });
        const eval_ = this.search(depth - 1, alpha, beta, false);
        this.game.unmakeMove(undoInfo);

        maxEval = Math.max(maxEval, eval_);
        alpha = Math.max(alpha, eval_);
        if (beta <= alpha) break;
      }
      return maxEval;
    } else {
      let minEval = Infinity;
      for (const move of moves) {
        const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
        this.game.moveHistory.push({ from: move.from, to: move.to });
        const eval_ = this.search(depth - 1, alpha, beta, true);
        this.game.unmakeMove(undoInfo);

        minEval = Math.min(minEval, eval_);
        beta = Math.min(beta, eval_);
        if (beta <= alpha) break;
      }
      return minEval;
    }
  }

  // Quiescence search to avoid horizon effect
  quiesce(alpha, beta, maximizingPlayer) {
    const standPat = this.evaluate();

    if (maximizingPlayer) {
      if (standPat >= beta) return beta;
      if (alpha < standPat) alpha = standPat;

      const color = 'w';
      const moves = this.game.generateLegalMoves(color).filter(m => {
        // Only consider captures and promotions in quiescence
        const piece = this.game.board[m.from];
        return this.game.board[m.to] !== EMPTY || m.promotion || (pieceType(piece) === 1 && m.ep);
      });

      for (const move of moves) {
        const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
        this.game.moveHistory.push({ from: move.from, to: move.to });
        const eval_ = this.quiesce(alpha, beta, false);
        this.game.unmakeMove(undoInfo);

        if (eval_ >= beta) return beta;
        if (eval_ > alpha) alpha = eval_;
      }
      return alpha;
    } else {
      if (standPat <= alpha) return alpha;
      if (beta > standPat) beta = standPat;

      const color = 'b';
      const moves = this.game.generateLegalMoves(color).filter(m => {
        const piece = this.game.board[m.from];
        return this.game.board[m.to] !== EMPTY || m.promotion || (pieceType(piece) === 1 && m.ep);
      });

      for (const move of moves) {
        const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
        this.game.moveHistory.push({ from: move.from, to: move.to });
        const eval_ = this.quiesce(alpha, beta, true);
        this.game.unmakeMove(undoInfo);

        if (eval_ <= alpha) return alpha;
        if (eval_ < beta) beta = eval_;
      }
      return beta;
    }
  }

  orderMoves(moves) {
    // Simple MVV-LVA style ordering: captures of high-value pieces first
    moves.sort((a, b) => {
      const aCaptured = this.game.board[a.to];
      const bCaptured = this.game.board[b.to];
      const aVal = aCaptured !== EMPTY ? CFG.pieceValues['PNBRQK'[pieceType(aCaptured)-1]] : 0;
      const bVal = bCaptured !== EMPTY ? CFG.pieceValues['PNBRQK'[pieceType(bCaptured)-1]] : 0;

      // Promotion bonus
      if (a.promotion) aVal += 800;
      if (b.promotion) bVal += 800;

      return bVal - aVal;
    });
  }

  getBestMove(depth, color) {
    this.nodesSearched = 0;
    const moves = this.game.generateLegalMoves(color);
    if (moves.length === 0) return null;

    this.orderMoves(moves);

    const maximizingPlayer = color === 'w';
    let bestMove = moves[0];
    let bestEval = maximizingPlayer ? -Infinity : Infinity;

    for (const move of moves) {
      if (this.nodesSearched > this.maxNodes) break;

      const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
      this.game.moveHistory.push({ from: move.from, to: move.to });

      const eval_ = this.search(depth - 1, -Infinity, Infinity, !maximizingPlayer);

      this.game.unmakeMove(undoInfo);

      if (maximizingPlayer) {
        if (eval_ > bestEval) { bestEval = eval_; bestMove = move; }
      } else {
        if (eval_ < bestEval) { bestEval = eval_; bestMove = move; }
      }
    }

    return bestMove;
  }
}

/* ================================================================
   SECTION 5: SOUND ENGINE (Web Audio API)
   ================================================================ */
class SoundEngine {
  constructor() {
    this.ctx = null;
    this.enabled = true;
  }

  init() {
    if (!this.ctx) {
      try {
        this.ctx = new (window.AudioContext || window.webkitAudioContext)();
      } catch(e) {}
    }
  }

  playMove() {
    if (!this.enabled || !this.ctx) return;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.frequency.setValueAtTime(600, this.ctx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(300, this.ctx.currentTime + 0.08);
    gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.1);
    osc.start(this.ctx.currentTime);
    osc.stop(this.ctx.currentTime + 0.1);
  }

  playCapture() {
    if (!this.enabled || !this.ctx) return;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.type = 'sawtooth';
    osc.frequency.setValueAtTime(400, this.ctx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(150, this.ctx.currentTime + 0.12);
    gain.gain.setValueAtTime(0.12, this.ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
    osc.start(this.ctx.currentTime);
    osc.stop(this.ctx.currentTime + 0.15);
  }

  playCheck() {
    if (!this.enabled || !this.ctx) return;
    const osc = this.ctx.createOscillator();
    const gain = this.ctx.createGain();
    osc.connect(gain);
    gain.connect(this.ctx.destination);
    osc.type = 'square';
    osc.frequency.setValueAtTime(800, this.ctx.currentTime);
    osc.frequency.setValueAtTime(600, this.ctx.currentTime + 0.1);
    gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.25);
    osc.start(this.ctx.currentTime);
    osc.stop(this.ctx.currentTime + 0.25);
  }

  playGameOver() {
    if (!this.enabled || !this.ctx) return;
    [400, 500, 600].forEach((freq, i) => {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.frequency.setValueAtTime(freq, this.ctx.currentTime + i*0.15);
      gain.gain.setValueAtTime(0.12, this.ctx.currentTime + i*0.15);
      gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + i*0.15 + 0.3);
      osc.start(this.ctx.currentTime + i*0.15);
      osc.stop(this.ctx.currentTime + i*0.15 + 0.3);
    });
  }

  toggle() {
    this.enabled = !this.enabled;
    return this.enabled;
  }
}

/* ================================================================
   SECTION 6: UI RENDERER & INTERACTION
   ================================================================ */
class ChessUI {
  constructor(game, ai) {
    this.game = game;
    this.ai = ai;
    this.sound = new SoundEngine();
    this.boardEl = document.getElementById('board');
    this.promoOverlay = document.getElementById('promotion-overlay');
    this.dragGhost = document.getElementById('drag-ghost');

    this.selectedSquare = -1;
    this.legalMovesForSelected = [];
    this.flipped = false;
    this.lastMove = null; // {from, to}
    this.playerColor = 'w';
    this.analysisMode = false;
    this.aiThinking = false;

    this.buildBoard();
    this.render();
    this.setupInteraction();
  }

  buildBoard() {
    this.boardEl.innerHTML = '';
    for (let displayRow = 0; displayRow < 8; displayRow++) {
      for (let displayCol = 0; displayCol < 8; displayCol++) {
        const row = this.flipped ? 7 - displayRow : displayRow;
        const col = this.flipped ? 7 - displayCol : displayCol;
        const sq = row * 16 + col;

        const div = document.createElement('div');
        div.className = 'square';
        div.dataset.sq = sq;
        div.classList.add((row + col) % 2 === 0 ? 'light' : 'dark');

        // Coordinates
        if (displayCol === 0) {
          const rankLabel = document.createElement('span');
          rankLabel.className = 'coord coord-rank';
          rankLabel.textContent = RANKS[row];
          div.appendChild(rankLabel);
        }
        if (displayRow === 7) {
          const fileLabel = document.createElement('span');
          fileLabel.className = 'coord coord-file';
          fileLabel.textContent = FILES[col];
          div.appendChild(fileLabel);
        }

        this.boardEl.appendChild(div);
      }
    }
  }

  render() {
    this.buildBoard();

    const allSquares = this.boardEl.querySelectorAll('.square');
    const kingInCheckSq = this.game.isInCheck(this.game.turn) ? this.game.findKing(this.game.turn) : -1;

    for (const sqEl of allSquares) {
      const sq = parseInt(sqEl.dataset.sq);
      if ((sq & 8) !== 0) continue;

      const row = sqToRank(sq);
      const col = sqToFile(sq);
      const isLight = (row + col) % 2 === 0;
      sqEl.classList.add(isLight ? 'light' : 'dark');

      // Last move highlight
      if (this.lastMove && (sq === this.lastMove.from || sq === this.lastMove.to)) {
        sqEl.classList.add('last-move');
      }

      // Selected square
      if (sq === this.selectedSquare) {
        sqEl.classList.add('selected');
      }

      // Check glow on king
      if (sq === kingInCheckSq) {
        sqEl.classList.add('check-glow');
      }

      // Legal move indicators
      const legalMove = this.legalMovesForSelected.find(m => m.to === sq);
      if (legalMove) {
        if (this.game.board[sq] !== EMPTY) {
          sqEl.classList.add('legal-ring');
        } else {
          sqEl.classList.add('legal-dot');
        }
      }

      // Render piece
      const piece = this.game.board[sq];
      if (piece !== EMPTY) {
        const pieceSpan = document.createElement('span');
        pieceSpan.className = 'piece ' + (isWhite(piece) ? 'white-piece' : 'black-piece');
        pieceSpan.textContent = PIECE_SYMBOL[piece] || '?';
        sqEl.appendChild(pieceSpan);
      }

      // Coordinate labels
      const displayRow = this.flipped ? 7 - row : row;
      const displayCol = this.flipped ? 7 - col : col;
      if (displayCol === 0) {
        const rankLabel = document.createElement('span');
        rankLabel.className = 'coord coord-rank';
        rankLabel.textContent = RANKS[row];
        sqEl.appendChild(rankLabel);
      }
      if (displayRow === 7) {
        const fileLabel = document.createElement('span');
        fileLabel.className = 'coord coord-file';
        fileLabel.textContent = FILES[col];
        sqEl.appendChild(fileLabel);
      }
    }

    this.updatePanel();
    this.checkGameOver();
  }

  updatePanel() {
    // Turn indicator
    const turnDot = document.querySelector('.turn-dot');
    const turnText = document.getElementById('turn-text');
    if (this.game.turn === 'w') {
      turnDot.className = 'turn-dot white';
      turnText.textContent = this.game.isInCheck(this.game.turn) ? 'White in check!' : 'White to move';
    } else {
      turnDot.className = 'turn-dot black';
      turnText.textContent = this.game.isInCheck(this.game.turn) ? 'Black in check!' : 'Black to move';
    }

    // Captured pieces
    const capturedByWhite = document.getElementById('captured-by-white');
    const capturedByBlack = document.getElementById('captured-by-black');
    capturedByWhite.innerHTML = '';
    capturedByBlack.innerHTML = '';

    const pieceOrder = { Q: 0, R: 1, B: 2, N: 3, P: 4 };
    const sortedCaptures = [...this.game.capturedPieces].sort((a,b) => (pieceOrder[a.type]||5)-(pieceOrder[b.type]||5));

    for (const cp of sortedCaptures) {
      const span = document.createElement('span');
      // Captured by white means black pieces captured
      if (cp.color === 'b') {
        span.textContent = PIECE_UNICODE[Object.keys(PIECE_UNICODE).find(k => {
          const map = {'k':BK,'q':BQ,'r':BR,'b':BB,'n':BN,'p':BP};
          return map[k] === cp.type + 6;
        })];
        capturedByWhite.appendChild(span);
      } else {
        span.textContent = PIECE_UNICODE[Object.keys(PIECE_UNICODE).find(k => {
          const map = {'K':WK,'Q':WQ,'R':WR,'B':WB,'N':WN,'P':WP};
          return map[k] === cp.type;
        })];
        capturedByBlack.appendChild(span);
      }
    }

    // Move list
    this.renderMoveList();
  }

  renderMoveList() {
    const moveListEl = document.getElementById('move-list');
    moveListEl.innerHTML = '';

    for (let i = 0; i < this.game.moveHistory.length; i += 2) {
      const moveNum = Math.floor(i/2) + 1;
      const numSpan = document.createElement('span');
      numSpan.className = 'move-num';
      numSpan.textContent = moveNum + '.';

      const wMove = document.createElement('span');
      wMove.className = 'move-w';
      wMove.textContent = this.game.moveHistory[i]?.notation || '';

      const bMove = document.createElement('span');
      bMove.className = 'move-b';
      bMove.textContent = (i+1 < this.game.moveHistory.length) ? (this.game.moveHistory[i+1]?.notation || '') : '';

      moveListEl.appendChild(numSpan);
      moveListEl.appendChild(wMove);
      moveListEl.appendChild(bMove);
    }

    // Auto-scroll
    const container = document.getElementById('move-list-container');
    container.scrollTop = container.scrollHeight;
  }

  checkGameOver() {
    if (this.game.gameOver) return;

    const state = this.game.getGameState();
    if (state !== 'ongoing') {
      this.game.gameOver = true;
      this.sound.playGameOver();

      let title, subtitle;
      switch(state) {
        case 'checkmate':
          const winner = this.game.turn === 'w' ? 'Black' : 'White';
          this.game.result = this.game.turn === 'w' ? '0-1' : '1-0';
          title = `${winner} wins!`;
          subtitle = 'by checkmate';
          break;
        case 'stalemate':
          this.game.result = '½-½';
          title = 'Draw';
          subtitle = 'by stalemate';
          break;
        case 'insufficient_material':
          this.game.result = '½-½';
          title = 'Draw';
          subtitle = 'insufficient material';
          break;
        case 'fifty_move_rule':
          this.game.result = '½-½';
          title = 'Draw';
          subtitle = '50-move rule';
          break;
      }

      document.getElementById('go-title').textContent = title;
      document.getElementById('go-subtitle').textContent = subtitle;
      document.getElementById('game-over-banner').classList.add('active');

      this.saveState();
    }
  }

  setupInteraction() {
    let dragPiece = null;
    let dragFromSq = -1;
    let isDragging = false;

    const handleSquareClick = (sq) => {
      if ((sq & 8) !== 0 || this.game.gameOver || this.aiThinking) return;

      // Check if it's a legal move destination for selected piece
      const legalMove = this.legalMovesForSelected.find(m => m.to === sq);
      if (this.selectedSquare >= 0 && legalMove) {
        this.executePlayerMove(legalMove);
        return;
      }

      // Select a piece
      const piece = this.game.board[sq];
      if (piece !== EMPTY && pieceColor(piece) === this.playerColor && !this.analysisMode || this.analysisMode && piece !== EMPTY && pieceColor(piece) === this.game.turn) {
        this.selectedSquare = sq;
        this.legalMovesForSelected = this.game.generateLegalMoves(this.game.turn).filter(m => m.from === sq);
      } else if (this.analysisMode && piece !== EMPTY && pieceColor(piece) === this.game.turn) {
        this.selectedSquare = sq;
        this.legalMovesForSelected = this.game.generateLegalMoves(this.game.turn).filter(m => m.from === sq);
      } else {
        this.selectedSquare = -1;
        this.legalMovesForSelected = [];
      }

      this.render();
    };

    // Click handler
    this.boardEl.addEventListener('click', (e) => {
      const sqEl = e.target.closest('.square');
      if (!sqEl) return;
      const sq = parseInt(sqEl.dataset.sq);
      handleSquareClick(sq);
    });

    // Drag and drop
    this.boardEl.addEventListener('mousedown', (e) => {
      const sqEl = e.target.closest('.square');
      if (!sqEl) return;
      const sq = parseInt(sqEl.dataset.sq);
      if ((sq & 8) !== 0 || this.game.gameOver || this.aiThinking) return;

      const piece = this.game.board[sq];
      const canMove = (this.analysisMode ? pieceColor(piece) === this.game.turn : pieceColor(piece) === this.playerColor) && piece !== EMPTY;

      if (!canMove) return;

      dragFromSq = sq;
      isDragging = false; // will be set true on mousemove

      const ghost = document.getElementById('drag-ghost');
      const key = Object.keys(PIECE_UNICODE).find(k => {
        const map = {'K':WK,'Q':WQ,'R':WR,'B':WB,'N':WN,'P':WP,'k':BK,'q':BQ,'r':BR,'b':BB,'n':BN,'p':BP};
        return map[k] === piece;
      });
      ghost.textContent = PIECE_UNICODE[key];
      ghost.style.color = isWhite(piece) ? '#fff' : '#222';
      if (isWhite(piece)) {
        ghost.style.textShadow = '0 0 2px #000, 0 0 2px #000';
      } else {
        ghost.style.textShadow = '0 0 1px rgba(255,255,255,0.3)';
      }
      ghost.style.display = 'block';

      const moveGhost = (ev) => {
        ghost.style.left = (ev.clientX - 24) + 'px';
        ghost.style.top = (ev.clientY - 24) + 'px';
      };
      moveGhost(e);

      const onMouseMove = (ev) => {
        if (!isDragging) {
          isDragging = true;
          // Hide the piece on the board
          sqEl.querySelector('.piece')?.classList.add('dragging');
        }
        moveGhost(ev);
      };

      const onMouseUp = (ev) => {
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);
        ghost.style.display = 'none';

        if (!isDragging) return; // It was a click, not a drag — handled by click handler

        // Find the square under the mouse
        const targetEl = document.elementFromPoint(ev.clientX, ev.clientY)?.closest('.square');
        if (targetEl) {
          const toSq = parseInt(targetEl.dataset.sq);
          if ((toSq & 8) === 0 && toSq !== dragFromSq) {
            const legalMove = this.game.generateLegalMoves(this.analysisMode ? this.game.turn : this.playerColor).find(m => m.from === dragFromSq && m.to === toSq);
            if (legalMove) {
              this.executePlayerMove(legalMove);
              return;
            }
          }
        }

        // If no valid drop, re-render to restore piece appearance
        this.render();
      };

      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', onMouseUp);
    });

    // Touch support for mobile
    let touchStartSq = -1;
    this.boardEl.addEventListener('touchstart', (e) => {
      const touch = e.touches[0];
      const sqEl = document.elementFromPoint(touch.clientX, touch.clientY)?.closest('.square');
      if (!sqEl) return;
      touchStartSq = parseInt(sqEl.dataset.sq);
    }, { passive: true });

    this.boardEl.addEventListener('touchend', (e) => {
      if (touchStartSq >= 0) {
        handleSquareClick(touchStartSq);
        touchStartSq = -1;
      }
    }, { passive: true });
  }

  executePlayerMove(move) {
    this.sound.init(); // Initialize audio context on user gesture

    const notation = this.game.moveToFullAlgebraic(move);
    const undoInfo = this.game.makeMove2(move.from, move.to, move.promotion || 'Q');
    this.game.moveHistory.push({ ...undoInfo, notation });
    this.lastMove = { from: move.from, to: move.to };

    // Sound
    if (this.game.board[move.to] !== EMPTY || (pieceType(this.game.board[move.to]) === PIECE_TYPE.P && move.ep)) {
      this.sound.playCapture();
    } else {
      this.sound.playMove();
    }
    if (this.game.isInCheck(this.game.turn)) {
      this.sound.playCheck();
    }

    this.selectedSquare = -1;
    this.legalMovesForSelected = [];
    this.render();
    this.saveState();

    // Check game over before AI move
    if (this.game.getGameState() !== 'ongoing') return;

    // AI's turn
    if (!this.analysisMode && this.game.turn !== this.playerColor) {
      this.aiThinking = true;
      this.render();

      // Use requestAnimationFrame to allow UI to update before blocking
      requestAnimationFrame(() => {
        setTimeout(() => {
          const depth = parseInt(document.getElementById('depth-select').value);
          const aiMove = this.ai.getBestMove(depth, this.game.turn);
          if (aiMove) {
            const aiNotation = this.game.moveToFullAlgebraic(aiMove);
            const aiUndoInfo = this.game.makeMove2(aiMove.from, aiMove.to, aiMove.promotion || 'Q');
            this.game.moveHistory.push({ ...aiUndoInfo, notation: aiNotation });
            this.lastMove = { from: aiMove.from, to: aiMove.to };

            if (this.game.board[aiMove.to] !== EMPTY) {
              this.sound.playCapture();
            } else {
              this.sound.playMove();
            }
            if (this.game.isInCheck(this.game.turn)) {
              this.sound.playCheck();
            }
          }

          this.aiThinking = false;
          this.render();
          this.saveState();
        }, 50);
      });
    } else if (!this.analysisMode) {
      // Player plays both sides or it's player's turn already handled
    }
  }

  showPromotionPicker(from, to) {
    // This is called when a pawn reaches the promotion rank
    const sqEl = this.boardEl.querySelector(`[data-sq="${to}"]`);
    if (!sqEl) return;

    const rect = sqEl.getBoundingClientRect();
    const boardRect = this.boardEl.getBoundingClientRect();

    this.promoOverlay.style.left = (rect.left - boardRect.left + 4) + 'px';
    this.promoOverlay.style.top = (rect.top - boardRect.top - 4) + 'px';
    this.promoOverlay.innerHTML = '';

    const pieces = ['Q', 'R', 'B', 'N'];
    for (const p of pieces) {
      const btn = document.createElement('div');
      btn.className = 'promo-piece';
      const key = isWhite(this.game.board[from]) ? p : p.toLowerCase();
      btn.textContent = PIECE_UNICODE[key];
      btn.addEventListener('click', () => {
        this.promoOverlay.classList.remove('active');
        // Execute the move with promotion
        const legalMove = this.legalMovesForSelected.find(m => m.to === to && m.promotion === p);
        if (legalMove) {
          this.executePlayerMove(legalMove);
        }
      });
      this.promoOverlay.appendChild(btn);
    }

    this.promoOverlay.classList.add('active');
  }

  setPlayerColor(color, analysisMode = false) {
    this.playerColor = color;
    this.analysisMode = analysisMode;
    this.selectedSquare = -1;
    this.legalMovesForSelected = [];
    this.render();

    // If player chose black and it's white's turn (AI goes first), trigger AI move
    if (!analysisMode && color === 'b' && this.game.turn === 'w' && !this.game.gameOver) {
      this.aiThinking = true;
      requestAnimationFrame(() => {
        setTimeout(() => {
          const depth = parseInt(document.getElementById('depth-select').value);
          const aiMove = this.ai.getBestMove(depth, 'w');
          if (aiMove) {
            const notation = this.game.moveToFullAlgebraic(aiMove);
            const undoInfo = this.game.makeMove2(aiMove.from, aiMove.to, aiMove.promotion || 'Q');
            this.game.moveHistory.push({ ...undoInfo, notation });
            this.lastMove = { from: aiMove.from, to: aiMove.to };
            this.sound.playMove();
          }
          this.aiThinking = false;
          this.render();
          this.saveState();
        }, 50);
      });
    }
  }

  flipBoard() {
    this.flipped = !this.flipped;
    this.buildBoard();
    this.render();
  }

  undoMove() {
    if (this.game.moveHistory.length < 2 || this.aiThinking) return;

    // Undo AI move and player move (two moves back)
    const aiUndoInfo = this.game.moveHistory[this.game.moveHistory.length - 1];
    this.game.unmakeMove(aiUndoInfo);

    const playerUndoInfo = this.game.moveHistory[this.game.moveHistory.length - 1];
    this.game.unmakeMove(playerUndoInfo);

    // Update last move highlight
    if (this.game.moveHistory.length > 0) {
      const last = this.game.moveHistory[this.game.moveHistory.length - 1];
      this.lastMove = { from: last.from, to: last.to };
    } else {
      this.lastMove = null;
    }

    this.selectedSquare = -1;
    this.legalMovesForSelected = [];
    this.render();
    this.saveState();
  }

  saveState() {
    try {
      const state = {
        fen: this.game.fen(),
        moveHistory: this.game.moveHistory.map(m => ({ from: m.from, to: m.to, notation: m.notation })),
        lastMove: this.lastMove,
        capturedPieces: this.game.capturedPieces,
        gameOver: this.game.gameOver,
        result: this.game.result,
        resultReason: this.game.resultReason,
      };
      localStorage.setItem('chessGameState', JSON.stringify(state));
    } catch(e) {}
  }

  loadState() {
    try {
      const saved = localStorage.getItem('chessGameState');
      if (!saved) return false;
      const state = JSON.parse(saved);

      this.game.loadFEN(state.fen);
      this.game.moveHistory = state.moveHistory || [];
      this.lastMove = state.lastMove || null;
      this.game.capturedPieces = state.capturedPieces || [];
      this.game.gameOver = state.gameOver || false;
      this.game.result = state.result || null;

      if (this.game.gameOver) {
        document.getElementById('go-title').textContent = this.game.result === '1-0' ? 'White wins!' : this.game.result === '0-1' ? 'Black wins!' : 'Draw';
        document.getElementById('game-over-banner').classList.add('active');
      }

      return true;
    } catch(e) {
      return false;
    }
  }
}

/* ================================================================
   SECTION 7: MAIN INITIALIZATION & DEBUG INTERFACE
   ================================================================ */
let game, ai, ui;

function initGame() {
  game = new GameState();
  ai = new ChessAI(game);
  ui = new ChessUI(game, ai);

  // Try to load saved state
  if (!ui.loadState()) {
    const sideSelect = document.getElementById('side-select').value;
    if (sideSelect === 'both') {
      ui.setPlayerColor('w', true);
    } else {
      ui.setPlayerColor(sideSelect === 'black' ? 'b' : 'w');
    }
  } else {
    // Restore side selection from saved state
    const turn = game.turn;
    if (game.moveHistory.length > 0) {
      // Game was in progress, figure out player color from move count
      const lastMoveByWhite = game.turn === 'b'; // If it's black's turn, white just moved
      ui.setPlayerColor(lastMoveByWhite ? 'w' : 'b');
    }
  }

  // Side selector change handler
  document.getElementById('side-select').addEventListener('change', (e) => {
    const val = e.target.value;
    if (val === 'both') {
      ui.setPlayerColor('w', true);
    } else {
      newGame();
      setTimeout(() => ui.setPlayerColor(val === 'black' ? 'b' : 'w'), 100);
    }
  });

  // Expose debug interface
  window.game = game;
  window.ai = ai;
  window.ui = ui;
}

function newGame() {
  document.getElementById('game-over-banner').classList.remove('active');
  localStorage.removeItem('chessGameState');

  game = new GameState();
  ai = new ChessAI(game);
  ui = new ChessUI(game, ai);

  const sideSelect = document.getElementById('side-select').value;
  if (sideSelect === 'both') {
    ui.setPlayerColor('w', true);
  } else {
    ui.setPlayerColor(sideSelect === 'black' ? 'b' : 'w');
  }

  window.game = game;
  window.ai = ai;
  window.ui = ui;
}

function undoMove() {
  if (ui) ui.undoMove();
}

function flipBoard() {
  if (ui) ui.flipBoard();
}

function toggleMute() {
  if (!ui) return;
  const enabled = ui.sound.toggle();
  document.getElementById('mute-btn').textContent = enabled ? '🔊 On' : '🔇 Off';
}

// Debug helpers on window
window.debug = {
  legalMoves: (color) => game.generateLegalMoves(color || game.turn),
  move: (from, to, promotion) => {
    const moves = game.generateLegalMoves(game.turn);
    const m = typeof from === 'string' ?
      moves.find(mv => sqToAlgebraic(mv.from) === from && sqToAlgebraic(mv.to) === to) :
      moves.find(mv => mv.from === from && mv.to === to);
    if (!m) return { ok: false, reason: 'Illegal move' };
    const notation = game.moveToFullAlgebraic(m);
    const undoInfo = game.makeMove2(m.from, m.to, m.promotion || promotion || 'Q');
    game.moveHistory.push({ ...undoInfo, notation });
    if (ui) { ui.lastMove = { from: m.from, to: m.to }; ui.render(); }
    return { ok: true, notation, fen: game.fen() };
  },
  fen: () => game.fen(),
  evaluate: () => ai.evaluate(),
};

// Start the game
initGame();
</script>
</body>
</html>