← Chess results

Chess

Tiel Coder 35B A3B GGUF 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 39,783 bytes · SHA-256 bb607f864f67
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chess — vs Computer</title>
<style>
  :root{
    --bg:#1e1c1a; --panel:#2b2724; --panel2:#34302c;
    --light:#f0d9b5; --dark:#b58863;
    --accent:#d7a650; --text:#ece5d8; --muted:#a49b8c;
    --last:rgba(240,220,120,.42);
    --sel:rgba(60,120,220,.40);
    --check:rgba(220,40,40,.62);
    --dot:rgba(20,20,20,.38);
    --cap:rgba(200,40,40,.55);
  }
  *{box-sizing:border-box}
  html,body{margin:0;height:100%}
  body{
    font-family:"Segoe UI",system-ui,Arial,sans-serif;
    background:radial-gradient(circle at 50% 0%,#3a332c,#1b1917 70%);
    color:var(--text);
    display:flex; justify-content:center; padding:18px;
    -webkit-user-select:none; user-select:none;
  }
  .app{display:flex; gap:22px; align-items:flex-start;}
  h1{font-size:20px; margin:0 0 2px; letter-spacing:.5px;}
  .sub{color:var(--muted); font-size:12px; margin:0 0 14px;}
  .board-wrap{display:flex; align-items:stretch;}
  .rank-labels{display:flex; flex-direction:column; justify-content:space-between; margin-right:6px;}
  .rank-labels span{flex:1; display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:12px; width:16px;}
  .board{
    position:relative;
    width:min(74vmin,520px); height:min(74vmin,520px);
    display:grid; grid-template-columns:repeat(8,1fr); grid-template-rows:repeat(8,1fr);
    border:8px solid #4a3a28; border-radius:6px;
    box-shadow:0 10px 30px rgba(0,0,0,.55);
    flex:1;
  }
  .cell{
    position:relative;
    display:flex; align-items:center; justify-content:center;
    font-size:clamp(26px,7vmin,52px); line-height:1;
    cursor:pointer;
  }
  .cell.light{background:var(--light);}
  .cell.dark{background:var(--dark);}
  .cell .piece{pointer-events:none; text-shadow:0 1px 2px rgba(0,0,0,.35); transition:transform .05s;}
  .cell.light .piece{color:#ffffff;}
  .cell.dark .piece{color:#111111;}
  .cell.last{box-shadow:inset 0 0 0 40px var(--last);}
  .cell.sel{box-shadow:inset 0 0 0 40px var(--sel);}
  .cell.check{box-shadow:inset 0 0 0 40px var(--check), 0 0 14px 2px rgba(255,60,40,.8);}
  .cell.target::after{
    content:""; position:absolute; width:30%; height:30%; border-radius:50%;
    background:var(--dot); pointer-events:none;
  }
  .cell.cap::after{
    content:""; position:absolute; width:88%; height:88%; border-radius:50%;
    border:6px solid var(--cap); pointer-events:none;
  }
  .cell .coord{position:absolute; font-size:10px; font-weight:700; pointer-events:none; opacity:.8;}
  .cell .coord.tr{top:2px; right:3px;}
  .cell .coord.bl{bottom:2px; left:3px;}
  .cell.light .coord{color:#b58863;} .cell.dark .coord{color:#f0d9b5;}
  .file-labels{display:flex; margin-top:4px;}
  .file-labels span{flex:1; text-align:center; color:var(--muted); font-size:12px;}
  .promo-overlay{
    position:absolute; z-index:20; display:flex; flex-direction:column;
    background:rgba(30,25,20,.92); border-radius:6px;
    box-shadow:0 6px 18px rgba(0,0,0,.6); overflow:hidden;
  }
  .promo-btn{
    font-size:clamp(26px,6vmin,44px); border:none; cursor:pointer;
    background:var(--dark); color:#fff; width:64px; height:64px; padding:0;
  }
  .promo-btn:hover{background:var(--accent);}
  .ghost{
    position:fixed; z-index:100; pointer-events:none; font-size:52px;
    text-shadow:0 2px 6px rgba(0,0,0,.6); transform:translate(-50%,-50%);
  }
  .panel{
    width:300px; background:var(--panel); border-radius:10px; padding:16px;
    box-shadow:0 8px 24px rgba(0,0,0,.4);
  }
  .ctrl{display:flex; flex-wrap:wrap; gap:8px; margin-bottom:12px; align-items:center;}
  .ctrl label{font-size:12px; color:var(--muted);}
  .ctrl select,.ctrl button{
    background:var(--panel2); color:var(--text); border:1px solid #4a443c;
    border-radius:6px; padding:6px 9px; font-size:13px; cursor:pointer;
  }
  .ctrl button:hover{border-color:var(--accent);}
  .ctrl button.active{background:var(--accent); color:#222; border-color:var(--accent);}
  .turn{
    display:flex; align-items:center; gap:10px; padding:8px 10px;
    background:var(--panel2); border-radius:8px; margin-bottom:12px; font-size:14px;
  }
  .turn .dot{width:16px;height:16px;border-radius:50%;border:1px solid #000;}
  .banner{
    display:none; padding:10px; border-radius:8px; margin-bottom:12px;
    background:linear-gradient(90deg,#c0392b,#8e2a20); font-weight:600; text-align:center;
  }
  .banner.draw{background:linear-gradient(90deg,#7f6a3c,#5a4a2c);}
  .moves-box{background:var(--panel2); border-radius:8px; padding:8px; max-height:220px; overflow:auto; margin-bottom:12px;}
  .moves-row{display:flex; gap:6px; padding:3px 2px; border-bottom:1px solid #3a352f; font-size:13px;}
  .moves-row .num{color:var(--muted); width:26px; text-align:right;}
  .moves-row .mv{flex:1; cursor:pointer; padding:0 4px; border-radius:4px;}
  .moves-row .mv:hover{background:#403a32;}
  .moves-row .mv.current{background:var(--accent); color:#222;}
  .trays{display:flex; justify-content:space-between; gap:8px; margin-bottom:12px;}
  .tray{flex:1; background:var(--panel2); border-radius:8px; padding:6px 8px; min-height:34px;}
  .tray .lbl{font-size:11px; color:var(--muted); margin-bottom:2px;}
  .tray .pics{font-size:20px; line-height:1.2; min-height:22px; word-break:break-all;}
  .actions{display:flex; gap:8px;}
  .actions button{flex:1; background:var(--panel2); color:var(--text); border:1px solid #4a443c; border-radius:8px; padding:9px; cursor:pointer; font-size:13px;}
  .actions button:hover{border-color:var(--accent); color:#fff;}
  .hint{font-size:11px; color:var(--muted); margin-top:10px; line-height:1.4;}
  .thinking{color:var(--accent); font-weight:600;}
</style>
</head>
<body>
<div class="app">
  <div>
    <h1>Chess</h1>
    <p class="sub">vs Computer · full rules · click or drag</p>
    <div class="board-wrap">
      <div class="rank-labels" id="rankLabels"></div>
      <div class="board" id="board"></div>
    </div>
    <div class="file-labels" id="fileLabels"></div>
  </div>
  <div class="panel">
    <div class="ctrl">
      <label>Play as</label>
      <select id="mode">
        <option value="white">White</option>
        <option value="black">Black</option>
        <option value="both">Both (HvH)</option>
      </select>
      <label>Level</label>
      <select id="diff">
        <option value="1">Easy</option>
        <option value="2">Medium</option>
        <option value="3" selected>Hard</option>
      </select>
    </div>
    <div class="ctrl">
      <button id="muteBtn">🔊 Sound</button>
      <button id="flipBtn">⇅ Flip</button>
    </div>
    <div class="turn" id="turn"><span class="dot" id="turnDot"></span><span id="turnText">White to move</span></div>
    <div class="banner" id="banner"></div>
    <div class="trays">
      <div class="tray"><div class="lbl">Captured by White</div><div class="pics" id="capWhite"></div></div>
      <div class="tray"><div class="lbl">Captured by Black</div><div class="pics" id="capBlack"></div></div>
    </div>
    <div class="moves-box" id="movesBox"></div>
    <div class="actions">
      <button id="undoBtn">↶ Undo</button>
      <button id="newBtn">⟳ New Game</button>
    </div>
    <div class="hint">Click a piece to see legal moves (dots = move, rings = capture). Drag pieces to move. Debug via <code>window.chess</code>.</div>
  </div>
</div>
<script>
/* =========================================================================
   SECTION 1 — TUNABLE CONSTANTS
   ========================================================================= */
const WHITE = "w", BLACK = "b";
const PAWN="p", KNIGHT="n", BISHOP="b", ROOK="r", QUEEN="q", KING="k";
const MATE = 100000;

const MATERIAL = { p:100, n:320, b:330, r:500, q:900, k:20000 };

// Piece-square tables, indexed 0..63 from White's perspective (a1=0 ... h8=63).
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, 5,10,10, 5, 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,
     -40,-40,-40,-50,-50,-40,-40,-40,
     -40,-40,-40,-50,-50,-40,-40,-40,
     -50,-50,-50,-50,-50,-50,-50,-50,
     -40,-40,-40,-50,-50,-40,-40,-40,
     -40,-40,-40,-50,-50,-40,-40,-40,
     -30,-40,-40,-50,-50,-40,-40,-30,
     -40,-40,-40,-50,-50,-40,-40,-40 ]
};

const AI_DEPTH = 3;            // default difficulty (1/2/3)
const AI_TIME_LIMIT_MS = 1000; // hard cap so the UI never freezes
const INITIAL_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
const GLYPH = {
  w:{K:"♔",Q:"♕",R:"♖",B:"♗",N:"♘",P:"♙"},
  b:{K:"♚",Q:"♛",R:"♜",B:"♝",N:"♞",P:"♟"}
};
</script>

<script>
/* =========================================================================
   SECTION 2 — MOVE GENERATION (mailbox 0..63, a1=0 ... h8=63)
   ========================================================================= */
const FILES = ["a","b","c","d","e","f","g","h"];
const RANKS = ["1","2","3","4","5","6","7","8"];

function sq(f,r){ return (r*8)+f; }  // r=0 is rank1 (bottom), a1=0
function fileOf(s){ return s & 7; }
function rankOf(s){ return s >> 3; }
function onBoard(f,r){ return f>=0&&f<8&&r>=0&&r<8; }
function squareName(s){ return FILES[fileOf(s)]+RANKS[rankOf(s)]; }
function fileOfSq(c){ return FILES.indexOf(c); }
function rankOfSq(c){ return RANKS.indexOf(c); }
function nameToSquare(n){ return fileOfSq(n[0]) + rankOfSq(n[1]) * 8; }

// generate pseudo-legal moves for a single piece at `from`
function pieceMoves(state, from){
  const p = state.board[from];
  if(!p) return [];
  const moves = [];
  const color = p.c, type = p.t;
  const enemy = color===WHITE? BLACK: WHITE;
  const dir = color===WHITE ? 1 : -1;

  function add(to, flags){ moves.push({from,to,flags,pc:type}); }
  function trySlide(fdr,fdr2){
    let f=fileOf(from)+fdr, r=rankOf(from)+fdr2;
    while(onBoard(f,r)){
      const t=sq(f,r);
      if(state.board[t]){ if(state.board[t].c===enemy) add(t,"cp"); else add(t,"ob"); break; }
      add(t,"mv");
      f+=fdr; r+=fdr2;
    }
  }

  if(type===PAWN){
    const startRank = color===WHITE?1:6;
    const promoRank = color===WHITE?7:0;
    const one = sq(fileOf(from), rankOf(from)+dir);
    if(onBoard(fileOf(from), rankOf(from)+dir) && !state.board[one]){
      if(rankOf(one)===promoRank) add(one,"pr");
      else add(one,"mv");
      const two = sq(fileOf(from), rankOf(from)+2*dir);
      if(rankOf(from)===startRank){
        const t2=sq(fileOf(from),rankOf(from)+dir);
        if(!state.board[t2]) add(two,"dp");
      }
    }
    for(const df of [-1,1]){
      const f=fileOf(from)+df, r=rankOf(from)+dir;
      if(!onBoard(f,r)) continue;
      const t=sq(f,r);
      const tp = state.board[t];
      if(tp && tp.c===enemy){
        if(rankOf(t)===promoRank) add(t,"pr");
        else add(t,"cp");
      } else if(state.ep !== -1 && t===state.ep){
        add(t,"ep");
      }
    }
  } else if(type===KNIGHT){
    const deltas=[[-1,-2],[1,-2],[2,-1],[2,1],[1,2],[-1,2],[-2,-1],[-2,1]];
    for(const [df,dr] of deltas){
      const f=fileOf(from)+df, r=rankOf(from)+dr;
      if(!onBoard(f,r)) continue;
      const t=sq(f,r); const tp=state.board[t];
      if(tp){ if(tp.c===enemy) add(t,"cp"); else add(t,"ob"); }
      else add(t,"mv");
    }
  } else if(type===KING){
    for(const df of [-1,0,1]) for(const dr of [-1,0,1]){
      if(!df&&!dr) continue;
      const f=fileOf(from)+df, r=rankOf(from)+dr;
      if(!onBoard(f,r)) continue;
      const t=sq(f,r); const tp=state.board[t];
      if(tp){ if(tp.c===enemy) add(t,"cp"); else add(t,"ob"); }
      else add(t,"mv");
    }
  } else {
    const dirs = type===BISHOP?[[1,1],[1,-1],[-1,1],[-1,-1]]:
                 type===ROOK?[[0,1],[0,-1],[1,0],[-1,0]]:
                 [[0,1],[0,-1],[1,0],[-1,0],[1,1],[1,-1],[-1,1],[-1,-1]];
    for(const [df,dr] of dirs) trySlide(df,dr);
  }
  return moves;
}

function kingPos(state,color){
  for(let s=0;s<64;s++){ const p=state.board[s]; if(p&&p.t===KING&&p.c===color) return s; }
  return -1;
}

function isAttacked(state, square, byColor){
  const dir = byColor===WHITE?1:-1;
  const rf = rankOf(square)-dir;
  for(const df of [-1,1]){
    const f=fileOf(square)+df, r=rf;
    if(onBoard(f,r)){
      const p=state.board[sq(f,r)];
      if(p&&p.c===byColor&&p.t===PAWN) return true;
    }
  }
  const knightDeltas=[[-1,-2],[1,-2],[2,-1],[2,1],[1,2],[-1,2],[-2,-1],[-2,1]];
  for(const [df,dr] of knightDeltas){
    const f=fileOf(square)+df, r=rankOf(square)+dr;
    if(onBoard(f,r)){ const p=state.board[sq(f,r)]; if(p&&p.c===byColor&&p.t===KNIGHT) return true; }
  }
  for(const df of [-1,0,1]) for(const dr of [-1,0,1]){
    if(!df&&!dr) continue;
    const f=fileOf(square)+df, r=rankOf(square)+dr;
    if(onBoard(f,r)){ const p=state.board[sq(f,r)]; if(p&&p.c===byColor&&p.t===KING) return true; }
  }
  const bishopDirs=[[1,1],[1,-1],[-1,1],[-1,-1]];
  const rookDirs=[[0,1],[0,-1],[1,0],[-1,0]];
  for(const [df,dr] of bishopDirs){
    let f=fileOf(square)+df, r=rankOf(square)+dr;
    while(onBoard(f,r)){
      const p=state.board[sq(f,r)];
      if(p){ if(p.c===byColor&&(p.t===BISHOP||p.t===QUEEN)) return true; break; }
      f+=df; r+=dr;
    }
  }
  for(const [df,dr] of rookDirs){
    let f=fileOf(square)+df, r=rankOf(square)+dr;
    while(onBoard(f,r)){
      const p=state.board[sq(f,r)];
      if(p){ if(p.c===byColor&&(p.t===ROOK||p.t===QUEEN)) return true; break; }
      f+=df; r+=dr;
    }
  }
  return false;
}

function inCheck(state,color){ return isAttacked(state,kingPos(state,color), color===WHITE?BLACK:WHITE); }

function applyMove(state, move){
  const ns = JSON.parse(JSON.stringify(state));
  const {from,to,flags} = move;
  const piece = ns.board[from];
  if(piece.t===KING){
    if(piece.c===WHITE){ ns.castle.K=false; ns.castle.Q=false; }
    else { ns.castle.k=false; ns.castle.q=false; }
  }
  if(piece.t===ROOK){
    if(from===sq(0,0)) ns.castle.Q=false;
    if(from===sq(7,0)) ns.castle.K=false;
    if(from===sq(0,7)) ns.castle.k=false;
    if(from===sq(7,7)) ns.castle.q=false;
  }
  if(flags==="cp"||flags==="ob"){
    const capPiece = ns.board[to];
    if(capPiece && capPiece.t===ROOK){
      if(to===sq(0,0)) ns.castle.Q=false;
      if(to===sq(7,0)) ns.castle.K=false;
      if(to===sq(0,7)) ns.castle.k=false;
      if(to===sq(7,7)) ns.castle.q=false;
    }
  }
  ns.board[from] = null;
  ns.board[to] = piece;
  if(flags==="pr"){
    if(move.promo){ ns.board[to]={t:move.promo,c:piece.c}; }
    else { ns.board[to]={t:QUEEN,c:piece.c}; }
  }
  if(flags==="ck"){ ns.board[sq(5,0)]=ns.board[sq(7,0)]; ns.board[sq(7,0)]=null; }
  else if(flags==="cq"){ ns.board[sq(3,0)]=ns.board[sq(0,0)]; ns.board[sq(0,0)]=null; }
  if(flags==="ep"){
    const capSq = sq(fileOf(to), rankOf(from));
    ns.board[capSq] = null;
  }
  ns.ep = -1;
  if(flags==="dp"){
    ns.ep = sq(fileOf(from), (rankOf(from)+rankOf(to))/2);
  }
  ns.turn = ns.turn===WHITE?BLACK:WHITE;
  if(ns.turn===WHITE) ns.fullmove++;
  ns.halfmove = (piece.t===PAWN||flags==="cp"||flags==="ep")?0:ns.halfmove+1;
  return ns;
}

function legalMovesFrom(state, from){
  const piece = state.board[from];
  if(!piece || piece.c!==state.turn) return [];
  const results = [];
  const pseudo = pieceMoves(state, from);
  for(const m of pseudo){
    if(m.flags==="ob") continue;
    const ns = applyMove(state, m);
    if(!inCheck(ns, piece.c)) results.push(m);
  }
  return results;
}

function legalMoves(state){
  const results = [];
  const color = state.turn;
  for(let s=0;s<64;s++){
    const p = state.board[s];
    if(!p || p.c!==color) continue;
    if(p.t===KING){
      const enemy = color===WHITE?BLACK:WHITE;
      const oppKing = kingPos(state, enemy);
      if(color===WHITE && !inCheck(state, WHITE)){
        if(state.castle.K && s===sq(4,0) && s===oppKing){
          const rook=state.board[sq(7,0)];
          if(rook && rook.t===ROOK){
            const f1=sq(5,0),g1=sq(6,0);
            if(!state.board[f1] && !state.board[g1]
               && !isAttacked(state,f1,enemy) && !isAttacked(state,g1,enemy)){
              results.push({from:s,to:g1,flags:"ck",pc:KING});
            }
          }
        }
        if(state.castle.Q && s===sq(4,0) && s===oppKing){
          const rook=state.board[sq(0,0)];
          if(rook && rook.t===ROOK){
            const d1=sq(3,0),c1=sq(2,0),b1=sq(1,0);
            if(!state.board[d1] && !state.board[c1] && !state.board[b1]
               && !isAttacked(state,d1,enemy) && !isAttacked(state,c1,enemy)
               && !isAttacked(state,sq(4,0),enemy)){
              results.push({from:s,to:sq(3,0),flags:"cq",pc:KING});
            }
          }
        }
      } else if(color===BLACK && !inCheck(state, BLACK)){
        if(state.castle.k && s===sq(4,7) && s===oppKing){
          const rook=state.board[sq(7,7)];
          if(rook && rook.t===ROOK){
            const f8=sq(5,7),g8=sq(6,7);
            if(!state.board[f8] && !state.board[g8]
               && !isAttacked(state,f8,enemy) && !isAttacked(state,g8,enemy)){
              results.push({from:s,to:g8,flags:"ck",pc:KING});
            }
          }
        }
        if(state.castle.q && s===sq(4,7) && s===oppKing){
          const rook=state.board[sq(0,7)];
          if(rook && rook.t===ROOK){
            const d8=sq(3,7),c8=sq(2,7),b8=sq(1,7);
            if(!state.board[d8] && !state.board[c8] && !state.board[b8]
               && !isAttacked(state,d8,enemy) && !isAttacked(state,c8,enemy)
               && !isAttacked(state,sq(4,7),enemy)){
              results.push({from:s,to:c8,flags:"cq",pc:KING});
            }
          }
        }
      }
      const pseudo = pieceMoves(state, s);
      for(const m of pseudo){
        if(m.flags==="ob") continue;
        const ns = applyMove(state, m);
        if(!inCheck(ns, color)) results.push(m);
      }
    } else {
      results.push(...legalMovesFrom(state, s));
    }
  }
  return results;
}
</script>

<script>
/* =========================================================================
   SECTION 3 — GAME OBJECT (state, FEN, SAN, history, persistence)
   ========================================================================= */
function createGame(){
  return {
    board: [], turn: WHITE, castle:{K:false,Q:false,k:false,q:false},
    ep:-1, fullmove:1, halfmove:0, history:[], gameOver:false, result:null
  };
}
function parseFen(fen){
  const parts = fen.split(" ");
  const state = createGame();
  const rows = parts[0].split("/");
  for(let r=0;r<8;r++){
    let f=0;
    for(const ch of rows[r]){
      if(/\d/.test(ch)){ f += parseInt(ch,10); }
      else {
        const t = ch.toLowerCase();
        const c = ch===ch.toUpperCase()? WHITE: BLACK;
        state.board[sq(f,7-r)] = {t,c};
        f++;
      }
    }
  }
  state.turn = parts[1]===WHITE?WHITE:BLACK;
  const c = parts[2];
  state.castle = { K:c.includes("K"), Q:c.includes("Q"), k:c.includes("k"), q:c.includes("q") };
  state.ep = parts[3]==="-"?-1:(fileOfSq(parts[3][0])+rankOfSq(parts[3][1]));
  state.halfmove = parseInt(parts[4]||"0",10);
  state.fullmove = parseInt(parts[5]||"1",10);
  return state;
}
function gameToFen(state){
  let fen="";
  for(let r=7;r>=0;r--){
    let empty=0;
    for(let f=0;f<8;f++){
      const p=state.board[sq(f,r)];
      if(p){
        if(empty){ fen+=empty; empty=0; }
        const g = p.t.toUpperCase();
        fen += p.c===WHITE? g : g.toLowerCase();
      } else empty++;
    }
    if(empty) fen+=empty;
    if(r>0) fen+="/";
  }
  fen += " "+state.turn+" ";
  const c = (state.castle.K?"K":"")+(state.castle.Q?"Q":"")+(state.castle.k?"k":"")+(state.castle.q?"q":"");
  fen += (c||"-")+" ";
  fen += (state.ep===-1?"-":squareName(state.ep))+" ";
  fen += state.halfmove+" "+state.fullmove;
  return fen;
}

let game = createGame();
function resetGame(){
  const s=parseFen(INITIAL_FEN);
  game.board=s.board; game.turn=s.turn; game.castle=s.castle;
  game.ep=s.ep; game.halfmove=s.halfmove; game.fullmove=s.fullmove;
  game.history=[]; game.gameOver=false; game.result=null;
  scratch=parseFen(INITIAL_FEN);
}

function makeMove(move){
  if(game.gameOver) return null;
  const legal = legalMoves(game);
  const found = legal.find(m => m.from===move.from && m.to===move.to &&
    (!move.promo || !m.promo || m.promo===move.promo) &&
    m.flags===move.flags);
  if(!found) return null;
  const san = moveToSAN(game, found);
  const captured = found.flags==="ep" ? {t:PAWN, c:game.turn===WHITE?BLACK:WHITE}
                 : (found.flags==="cp" ? game.board[found.to] : null);
  const ns = applyMove(game, found);
  ns.history.push({ state: gameToFen(game), san, captured, from:found.from, to:found.to });
  game = ns;
  if(legalMoves(game).length===0){
    game.gameOver = true;
    if(inCheck(game, game.turn)){
      const winner = game.turn===WHITE? BLACK: WHITE;
      game.result = (winner===WHITE?"White":"Black")+" wins by checkmate";
    } else {
      game.result = "Draw — stalemate";
    }
  } else {
    if(isInsufficientMaterial(game)) game.result = "Draw — insufficient material";
    else if(game.halfmove>=100) game.result = "Draw — 50-move rule";
  }
  return found;
}
function moveToSAN(state, move){
  if(move.flags==="ck"||move.flags==="cq") return move.flags==="ck"?"O-O":"O-O-O";
  const piece = state.board[move.from];
  let san="";
  if(piece.t!==PAWN){
    san += piece.t.toUpperCase();
    const others = legalMoves(state).filter(m=>m.pc===move.pc && m.from!==move.from && m.to===move.to);
    if(others.length){
      const sameFile = others.some(m=>fileOf(m.from)===fileOf(move.from));
      const sameRank = others.some(m=>rankOf(m.from)===rankOf(move.from));
      if(!sameFile) san += FILES[fileOf(move.from)];
      else if(!sameRank) san += RANKS[rankOf(move.from)];
    }
  }
  let dest = squareName(move.to);
  const isCapture = move.flags==="cp" || move.flags==="ep";
  if(move.flags==="cp"||move.flags==="ep") san += squareName(move.from)[0];
  san += isCapture? "x":""+dest;
  if(move.flags==="pr"){ san += "="+move.promo.toUpperCase(); }
  const ns = applyMove(state, move);
  if(inCheck(ns, ns.turn)) san += legalMoves(ns).length===0? "#":"+";
  return san;
}
function isInsufficientMaterial(state){
  const pieces = [];
  for(let s=0;s<64;s++){ const p=state.board[s]; if(p) pieces.push({t:p.t,c:p.c}); }
  if(pieces.length===2) return true;
  if(pieces.length===3){
    if(pieces.some(p=>p.t==="n"||p.t==="b")) return true;
  }
  return false;
}
</script>

<script>
/* =========================================================================
   SECTION 4 — AI (minimax + alpha-beta, quiescence-lite, move ordering)
   ========================================================================= */
function evaluate(state){
  let white=0, black=0;
  for(let s=0;s<64;s++){
    const p=state.board[s];
    if(!p) continue;
    const pst = PST[p.t][s];
    if(p.c===WHITE) white += MATERIAL[p.t]+pst;
    else black += MATERIAL[p.t]+pst;
  }
  return state.turn===WHITE ? (white-black) : (black-white);
}

function orderMoves(moves, state){
  return moves.map((m,i)=>{
    let score=i;
    const vp = state.board[m.to];
    if(m.flags==="cp"||m.flags==="ep") score += 1000 + (MATERIAL[vp&&vp.t]||0)*10 - MATERIAL[state.board[m.from].t];
    if(m.flags==="pr") score += 900;
    return {m, score, i:Math.random()*1000};
  }).sort((a,b)=> (b.score*1000+b.i) - (a.score*1000+a.i)).map(x=>x.m);
}

function search(state, depth, alpha, beta, useTime, deadline){
  const moves = orderMoves(legalMoves(state), state);
  if(moves.length===0){
    if(inCheck(state, state.turn)) return -MATE + (AI_DEPTH-depth);
    return 0;
  }
  if(depth===0) return evaluate(state);
  let best=-Infinity, bestMove=null;
  for(const m of moves){
    const ns = applyMove(state, m);
    const val = search(ns, depth-1, -beta, -alpha, useTime, deadline);
    if(val===null){ bestMove=m; break; }
    if(val>best) best=val;
    if(best>alpha) alpha=best;
    if(alpha>=beta) break;
  }
  return bestMove ? bestMove : best;
}

function aiMove(depth){
  const moves = legalMoves(game);
  if(moves.length===0) return null;
  const useTime = depth>=2;
  const deadline = performance.now() + AI_TIME_LIMIT_MS;
  let bestMove=null; let bestVal=-Infinity;
  let alpha=-Infinity, beta=Infinity;
  const ordered = orderMoves(moves, game);
  for(const m of ordered){
    const ns = applyMove(game, m);
    const val = search(ns, depth-1, -beta, -alpha, useTime, deadline);
    if(val===null) break;
    if(val>bestVal){ bestVal=val; bestMove=m; }
    if(bestVal>alpha) alpha=bestVal;
    if(alpha>=beta) break;
  }
  return bestMove || ordered[0];
}
</script>

<script>
/* =========================================================================
   SECTION 5 — UI RENDERING + INTERACTION
   ========================================================================= */
const ui = {
  board: document.getElementById("board"),
  rankLabels: document.getElementById("rankLabels"),
  fileLabels: document.getElementById("fileLabels"),
  movesBox: document.getElementById("movesBox"),
  turnText: document.getElementById("turnText"),
  turnDot: document.getElementById("turnDot"),
  banner: document.getElementById("banner"),
  capWhite: document.getElementById("capWhite"),
  capBlack: document.getElementById("capBlack"),
  mode: document.getElementById("mode"),
  diff: document.getElementById("diff"),
  muteBtn: document.getElementById("muteBtn"),
  flipBtn: document.getElementById("flipBtn"),
  undoBtn: document.getElementById("undoBtn"),
  newBtn: document.getElementById("newBtn")
};
let orientation = "white";
let selectedSquare = null;
let legalTargets = [];
let soundMuted = false;
let audioCtx = null;

function beep(freq, dur, type="sine", vol=0.15){
  if(soundMuted) return;
  try{
    if(!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
    const o=audioCtx.createOscillator(), g=audioCtx.createGain();
    o.type=type; o.frequency.value=freq; o.connect(g); g.connect(audioCtx.destination);
    g.gain.setValueAtTime(vol, audioCtx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime+dur);
    o.start(); o.stop(audioCtx.currentTime+dur);
  }catch(e){}
}
function sndMove(){ beep(220,0.09,"triangle",0.2); }
function sndCapture(){ beep(140,0.12,"square",0.22); }
function sndCheck(){ beep(660,0.18,"sawtooth",0.2); }

function renderBoard(){
  ui.board.innerHTML="";
  const flipped = orientation==="black";
  const rankOrder = flipped?[7,6,5,4,3,2,1,0]:[0,1,2,3,4,5,6,7];
  const fileOrder = flipped?[7,6,5,4,3,2,1,0]:[0,1,2,3,4,5,6,7];
  ui.rankLabels.innerHTML="";
  rankOrder.forEach((r)=>{ const s=document.createElement("span"); s.textContent=RANKS[7-r]; ui.rankLabels.appendChild(s); });
  ui.fileLabels.innerHTML="";
  fileOrder.forEach(f=>{ const s=document.createElement("span"); s.textContent=FILES[f]; ui.fileLabels.appendChild(s); });
  const lastMove = game.history.length? game.history[game.history.length-1]:null;
  const inCheckNow = !game.gameOver && inCheck(game, game.turn);
  const kingSq = inCheckNow? kingPos(game, game.turn):-1;
  for(const r of [...rankOrder].reverse()){
    for(const f of fileOrder){
      const s=sq(f,r);
      const cell=document.createElement("div");
      cell.className="cell "+((f+r)%2===0?"light":"dark");
      cell.dataset.square=s;
      if(lastMove && (s===lastMove.from||s===lastMove.to)) cell.classList.add("last");
      if(s===kingSq) cell.classList.add("check");
      if(selectedSquare===s) cell.classList.add("sel");
      const tgt = legalTargets.find(m=>m.to===s);
      if(tgt){ cell.classList.add(tgt.flags==="cp"||tgt.flags==="ep"?"cap":"target"); }
      const p=game.board[s];
      if(p){
        const pe=document.createElement("div");
        pe.className="piece";
        pe.textContent=GLYPH[p.c][p.t.toUpperCase()];
        pe.draggable=true;
        cell.appendChild(pe);
      }
      if(f===0){ const c=document.createElement("div"); c.className="coord tr"; c.textContent=RANKS[r]; cell.appendChild(c); }
      if(r===0){ const c=document.createElement("div"); c.className="coord bl"; c.textContent=FILES[f]; cell.appendChild(c); }
      cell.addEventListener("click", ()=>onCellClick(s));
      cell.addEventListener("dragstart", (e)=>onDragStart(e,s));
      cell.addEventListener("dragend", (e)=>onDragEnd(e));
      ui.board.appendChild(cell);
    }
  }
  ui.board.addEventListener("dragover", e=>e.preventDefault());
  ui.board.addEventListener("drop", e=>onDrop(e));
}
</script>

<script>
/* =========================================================================
   SECTION 6 — UI EVENTS (click, drag, controls, game flow)
   ========================================================================= */
function onCellClick(s){
  if(game.gameOver) return;
  if(itIsComputerTurn()) return;
  const p=game.board[s];
  if(selectedSquare!==null){
    const mv = legalTargets.find(m=>m.to===s);
    if(mv){ doPlayerMove(mv); return; }
  }
  if(p && p.c===game.turn){
    selectedSquare=s;
    legalTargets = legalMovesFrom(game, s);
  } else {
    selectedSquare=null; legalTargets=[];
  }
  renderBoard();
}
function doPlayerMove(mv){
  if(mv.flags==="pr" && !mv.promo){
    showPromotion(game.turn, (promo)=>{ mv.promo=promo; finalizeMove(mv); });
    return;
  }
  finalizeMove(mv);
}
function finalizeMove(mv){
  const legal = legalMoves(game);
  const found = legal.find(m=>m.from===mv.from&&m.to===mv.to&&m.flags===mv.flags&&
    (!mv.promo||!m.promo||mv.promo===m.promo));
  if(!found) return;
  makeMove(found);
  if(found.flags==="cp"||found.flags==="ep") sndCapture();
  else sndMove();
  if(game.gameOver && inCheck(game, game.turn)) sndCheck();
  selectedSquare=null; legalTargets=[];
  renderBoard(); renderMoves(); renderTrays(); renderTurn(); checkGameOver();
  scheduleAi();
}
function itIsComputerTurn(){
  const mode=ui.mode.value;
  if(mode==="both") return false;
  return game.turn=== (mode==="black"? WHITE:BLACK);
}
function scheduleAi(){
  if(game.gameOver) return;
  if(itIsComputerTurn()){
    ui.turnText.textContent="AI thinking..."; ui.turnText.classList.add("thinking");
    setTimeout(()=>{
      const depth=parseInt(ui.diff.value,10);
      const mv=aiMove(depth);
      if(mv && !game.gameOver){
        if(mv.flags==="pr") mv.promo="q";
        makeMove(mv);
        if(mv.flags==="cp"||mv.flags==="ep") sndCapture();
        else sndMove();
        if(game.gameOver && inCheck(game, game.turn)) sndCheck();
        selectedSquare=null; legalTargets=[];
        renderBoard(); renderMoves(); renderTrays(); renderTurn(); checkGameOver();
      }
    }, 120);
  }
}
function checkGameOver(){
  if(game.gameOver){
    ui.banner.style.display="block";
    ui.banner.textContent=game.result;
    ui.banner.classList.toggle("draw", game.result.startsWith("Draw"));
  }
}
</script>

<script>
/* =========================================================================
   SECTION 7 — PROMOTION, MOVES LIST, TRAYS, PERSISTENCE, CONTROLS
   ========================================================================= */
function showPromotion(color, cb){
  const promoTarget = legalTargets.find(m=>m.flags==="pr");
  let targetCell=null;
  if(promoTarget) targetCell=ui.board.querySelector('[data-square="'+promoTarget.to+'"]');
  if(!targetCell) targetCell=ui.board.querySelector('[data-square="'+kingPos(game,color)+'"]');
  const overlay=document.createElement("div");
  overlay.className="promo-overlay";
  const rect=targetCell.getBoundingClientRect();
  overlay.style.left=(rect.left+rect.width/2-32)+"px";
  overlay.style.top=(rect.top+rect.height/2-32)+"px";
  ["Q","R","B","N"].forEach(t=>{
    const b=document.createElement("button");
    b.className="promo-btn";
    b.textContent=GLYPH[color][t];
    b.onclick=()=>{ overlay.remove(); cb(t.toLowerCase()); };
    overlay.appendChild(b);
  });
  document.body.appendChild(overlay);
}
function renderMoves(){
  ui.movesBox.innerHTML="";
  const hist=game.history;
  for(let i=0;i<hist.length;i+=2){
    const row=document.createElement("div"); row.className="moves-row";
    const num=document.createElement("span"); num.className="num"; num.textContent=(i/2+1)+".";
    const white=document.createElement("span"); white.className="mv"; white.textContent=hist[i].san;
    const black=document.createElement("span"); black.className="mv"; black.textContent=hist[i+1]?hist[i+1].san:"";
    row.appendChild(num); row.appendChild(white); row.appendChild(black);
    ui.movesBox.appendChild(row);
  }
  ui.movesBox.scrollTop=ui.movesBox.scrollHeight;
}
function renderTrays(){
  let wPics="", bPics="";
  for(const h of game.history){
    if(h.captured){
      const g=GLYPH[h.captured.c][h.captured.t.toUpperCase()];
      if(h.captured.c===WHITE) bPics+=g; else wPics+=g;
    }
  }
  ui.capWhite.textContent=wPics;
  ui.capBlack.textContent=bPics;
}
function renderTurn(){
  ui.turnText.textContent = game.turn===WHITE?"White to move":"Black to move";
  ui.turnText.classList.remove("thinking");
  ui.turnDot.style.background = game.turn===WHITE?"#fff":"#111";
  ui.turnDot.style.border = "1px solid #000";
}
const SAVE_KEY="chess_state_v1";
function saveState(){
  const data={ fen:gameToFen(game), mode:ui.mode.value, diff:ui.diff.value, muted:soundMuted, orient:orientation };
  try{ localStorage.setItem(SAVE_KEY, JSON.stringify(data)); }catch(e){}
}
function loadState(){
  let raw=null;
  try{ raw=localStorage.getItem(SAVE_KEY); }catch(e){ return false; }
  if(!raw) return false;
  try{
    const data=JSON.parse(raw);
    const parsed=parseFen(data.fen);
    game.board=parsed.board; game.turn=parsed.turn; game.castle=parsed.castle;
    game.ep=parsed.ep; game.halfmove=parsed.halfmove; game.fullmove=parsed.fullmove;
    game.history=[]; game.gameOver=false; game.result=null;
    rebuildHistory();
    if(data.mode){ ui.mode.value=data.mode; }
    if(data.diff){ ui.diff.value=data.diff; }
    if(typeof data.muted==="boolean") soundMuted=data.muted;
    if(data.orient) orientation=data.orient;
    return true;
  }catch(e){ return false; }
}
function rebuildHistory(){
  game.history.forEach((h,idx)=>{
    const nextFen=h.state;
    const next=parseFen(nextFen);
    const moves=legalMoves(scratch);
    let found=null;
    for(const m of moves){
      const ns=applyMove(scratch,m);
      if(gameToFen(ns)===nextFen){ found=m; break; }
    }
    if(found){
      const san=moveToSAN(scratch, found);
      const captured = found.flags==="cp"?scratch.board[found.to]: (found.flags==="ep"?{t:PAWN,c:scratch.turn===WHITE?BLACK:WHITE}:null);
      game.history[idx]={state:nextFen,san,captured,from:found.from,to:found.to};
      scratch.board=next.board; scratch.turn=next.turn; scratch.castle=next.castle;
      scratch.ep=next.ep; scratch.halfmove=next.halfmove; scratch.fullmove=next.fullmove;
    }
  });
}
let scratch = parseFen(INITIAL_FEN);
ui.muteBtn.addEventListener("click",()=>{ soundMuted=!soundMuted; ui.muteBtn.textContent=soundMuted?"🔇 Muted":"🔊 Sound"; });
ui.flipBtn.addEventListener("click",()=>{ orientation=orientation==="white"?"black":"white"; renderBoard(); });
ui.newBtn.addEventListener("click",()=>{
  const s=parseFen(INITIAL_FEN);
  game.board=s.board; game.turn=s.turn; game.castle=s.castle;
  game.ep=s.ep; game.halfmove=s.halfmove; game.fullmove=s.fullmove;
  game.history=[]; game.gameOver=false; game.result=null;
  scratch=parseFen(INITIAL_FEN);
  selectedSquare=null; legalTargets=[];
  ui.banner.style.display="none";
  renderBoard(); renderMoves(); renderTrays(); renderTurn();
  saveState();
});
ui.undoBtn.addEventListener("click",()=>{
  if(game.history.length===0) return;
  if(game.history.length>=2 && game.turn===WHITE && ui.mode.value!=="black"){
    undoOne(); undoOne();
  } else if(game.history.length>=1){
    undoOne();
  }
  game.gameOver=false; game.result=null; ui.banner.style.display="none";
  selectedSquare=null; legalTargets=[];
  renderBoard(); renderMoves(); renderTrays(); renderTurn(); checkGameOver();
});
function undoOne(){
  if(game.history.length===0) return;
  const last=game.history.pop();
  game=parseFen(last.state);
}
setInterval(()=>{ try{ saveState(); }catch(e){} }, 1500);
window.chess = {
  get game(){ return game; }, legalMoves: ()=>legalMoves(game), move:(s)=>{
    if(!s) return false;
    const mv=parseMove(s);
    if(mv){ makeMove(mv); return true;} return false;
  },
  fen:()=>gameToFen(game), reset:resetGame, loadFen:(f)=>{ const p=parseFen(f); game.board=p.board; game.turn=p.turn; game.castle=p.castle; game.ep=p.ep; game.halfmove=p.halfmove; game.fullmove=p.fullmove; game.history=[]; game.gameOver=false; game.result=null; },
  debug:true
};
function parseMove(s){
  const from=nameToSquare(s.slice(0,2));
  const to=nameToSquare(s.slice(2,4));
  const promo = s.length>4 ? s[4] : null;
  const legal=legalMoves(game);
  const found=legal.find(m=>m.from===from&&m.to===to&&(!promo||!m.promo||m.promo===promo));
  if(found && promo) found.promo=promo.toLowerCase();
  return found;
}
let draggedSquare=null;
function onDragStart(e,s){
  if(game.gameOver||itIsComputerTurn()) return;
  const p=game.board[s];
  if(!p||p.c!==game.turn) return;
  draggedSquare=s;
  selectedSquare=s;
  legalTargets=legalMovesFrom(game,s);
  renderBoard();
  const ghost=document.createElement("div"); ghost.className="ghost";
  ghost.textContent=GLYPH[p.c][p.t];
  ghost.style.left=e.clientX+"px"; ghost.style.top=e.clientY+"px";
  document.body.appendChild(ghost); e.dataTransfer.setDragImage(ghost,26,26);
  e.dataTransfer.effectAllowed="move";
}
function onDragEnd(e){ document.querySelectorAll(".ghost").forEach(g=>g.remove()); draggedSquare=null; }
function onDrop(e){
  if(draggedSquare===null) return;
  const el=document.elementFromPoint(e.clientX,e.clientY);
  const cell=el?el.closest(".cell"):null;
  if(cell){
    const to=parseInt(cell.dataset.square,10);
    const mv=legalTargets.find(m=>m.to===to);
    if(mv){ doPlayerMove(mv); }
  }
  document.querySelectorAll(".ghost").forEach(g=>g.remove());
  draggedSquare=null; legalTargets=[];
}
resetGame();
renderBoard(); renderMoves(); renderTrays(); renderTurn();
if(loadState()){ renderBoard(); renderMoves(); renderTrays(); renderTurn(); checkGameOver(); }
</script>

</body>
</html>
<!-- agent-meta {"model":"peculiar-ragdoll/Tiel-Coder-35B-A3B-GGUF-MTP","provider":"brigid","persona":"full","sessionId":"802eb2c9-be5e-4444-b2f9-1e85089c163e","tokensIn":26150048,"tokensOut":126602,"tokensTotal":26276650,"cost":0,"turns":212,"toolCalls":211,"failedToolCalls":3,"timestamp":"2026-09-04T17:55:14.724Z"} -->