← Chess results

Chess

Qwen 3.6 27B MTP GGUF IQ4 NL · 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 34,815 bytes · SHA-256 4b6354a7c427
<!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>
:root {
  --board-light: #f0d9b5; --board-dark: #b58863;
  --highlight-from: #aad751; --highlight-to: #7bb83c;
  --sq-size: min(8vw, 72px);
  --panel-bg: #1e1e2e; --panel-text: #d4d4d8;
  --accent: #6ab04c; --accent-hover: #7ec95a;
  --btn-bg: #2a2a3e; --btn-hover: #3a3a52;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Segoe UI','Helvetica Neue',Arial,sans-serif; background:#0f0f1a; color:var(--panel-text); min-height:100vh; display:flex; justify-content:center; align-items:flex-start; padding:16px; }
#app { display:flex; gap:24px; align-items:flex-start; flex-wrap:wrap; justify-content:center; max-width:1100px; }
#board-wrapper { position:relative; flex-shrink:0; }
#board-label-top,#board-label-bottom { text-align:center; font-size:13px; color:#777; padding:3px 0; }
#board { display:grid; grid-template-columns:repeat(8,var(--sq-size)); grid-template-rows:repeat(8,var(--sq-size)); border:3px solid #4a2a0a; border-radius:4px; box-shadow:0 8px 32px rgba(0,0,0,0.6); position:relative; user-select:none; }
.square { width:var(--sq-size); height:var(--sq-size); display:flex; align-items:center; justify-content:center; font-size:calc(var(--sq-size)*0.75); cursor:pointer; position:relative; }
.square.light { background:var(--board-light); }
.square.dark { background:var(--board-dark); }
.square.selected { background:#bbcc33 !important; }
.square.move-from { background:#cdcc33 !important; }
.square.move-to { background:#b4aa33 !important; }
.square.check { background:radial-gradient(ellipse at center,rgba(255,0,0,0.8) 0%,rgba(255,0,0,0.3) 40%,transparent 70%) !important; }
.square .pw { color:#fff; text-shadow:0 0 2px rgba(0,0,0,0.7),0 1px 3px rgba(0,0,0,0.5); filter:drop-shadow(0 1px 2px rgba(0,0,0,0.4)); }
.square .pb { color:#1a1a1a; text-shadow:0 0 2px rgba(0,0,0,0.3); }
.square .dot { position:absolute; width:30%; height:30%; border-radius:50%; background:rgba(30,30,30,0.25); pointer-events:none; }
.square .ring { position:absolute; width:85%; height:85%; border-radius:50%; border:calc(var(--sq-size)*0.07) solid rgba(30,30,30,0.22); pointer-events:none; }
.square .coord { position:absolute; font-size:calc(var(--sq-size)*0.17); font-weight:700; pointer-events:none; }
.square .coord.fl { bottom:1px; right:3px; }
.square .coord.rl { top:1px; left:3px; }
.square.light .coord { color:var(--board-dark); }
.square.dark .coord { color:var(--board-light); }
#drag-ghost { position:fixed; pointer-events:none; font-size:calc(var(--sq-size)*0.82); z-index:9999; filter:drop-shadow(3px 5px 8px rgba(0,0,0,0.6)); display:none; }
#promo-overlay { display:none; position:absolute; top:0;left:0;right:0;bottom:0; z-index:100; background:rgba(0,0,0,0.25); justify-content:center; align-items:center; border-radius:4px; }
#promo-overlay.active { display:flex; }
#promo-picker { display:flex; background:#f0d9b5; border-radius:6px; overflow:hidden; border:2px solid #888; box-shadow:0 4px 20px rgba(0,0,0,0.5); }
#promo-picker .pb2 { width:var(--sq-size); height:var(--sq-size); display:flex; align-items:center; justify-content:center; font-size:calc(var(--sq-size)*0.75); cursor:pointer; }
#promo-picker .pb2:hover { background:#bbcc33; }
#promo-picker .pb2+.pb2 { border-left:1px solid #ccc; }
#panel { width:280px; background:var(--panel-bg); border-radius:10px; padding:14px; box-shadow:0 4px 24px rgba(0,0,0,0.5); display:flex; flex-direction:column; gap:10px; max-height:calc(8*var(--sq-size)+60px); }
.section { background:rgba(255,255,255,0.04); border-radius:8px; padding:10px; }
.section h3 { font-size:11px; text-transform:uppercase; letter-spacing:1px; color:#888; margin-bottom:5px; }
#turn-indicator { font-size:15px; font-weight:700; text-align:center; padding:10px; border-radius:6px; transition:all 0.2s; }
#turn-indicator.w { background:#f0d9b5; color:#222; }
#turn-indicator.b { background:#2a2a2a; color:#f0d9b5; }
.captured-row { font-size:20px; line-height:1.5; min-height:26px; word-break:break-all; }
#move-list { max-height:180px; overflow-y:auto; font-family:'Courier New',monospace; font-size:13px; line-height:1.7; }
#move-list::-webkit-scrollbar { width:5px; }
#move-list::-webkit-scrollbar-thumb { background:#555; border-radius:3px; }
.move-row { display:flex; gap:4px; }
.move-num { color:#666; min-width:26px; text-align:right; }
.move-w,.move-b { min-width:56px; }
.ctrl-row { display:flex; flex-wrap:wrap; gap:5px; }
button { padding:7px 12px; border:none; border-radius:6px; background:var(--btn-bg); color:var(--panel-text); font-size:12px; cursor:pointer; font-weight:500; }
button:hover { background:var(--btn-hover); }
button.primary { background:var(--accent); color:#fff; }
button.primary:hover { background:var(--accent-hover); }
select { padding:5px 8px; border:1px solid #555; border-radius:6px; background:var(--btn-bg); color:var(--panel-text); font-size:12px; cursor:pointer; }
.color-pick { display:flex; gap:5px; }
.color-pick button { flex:1; font-size:16px; padding:7px; }
.color-pick button.active { outline:2px solid var(--accent); }
#game-over-banner { display:none; text-align:center; padding:10px; border-radius:8px; font-weight:700; font-size:14px; background:rgba(106,176,76,0.15); border:1px solid var(--accent); }
#game-over-banner.active { display:block; }
@media(max-width:800px){ #app{flex-direction:column;align-items:center} #panel{width:100%;max-width:600px;max-height:none} :root{--sq-size:min(11vw,64px)} }
</style>
</head>
<body>
<div id="app">
  <div>
    <div id="board-label-top"></div>
    <div id="board-wrapper">
      <div id="board"></div>
      <div id="promo-overlay"><div id="promo-picker"></div></div>
    </div>
    <div id="board-label-bottom"></div>
  </div>
  <div id="panel">
    <div id="turn-indicator" class="w">⬜ White to move</div>
    <div id="game-over-banner"></div>
    <div class="section"><h3>Captured by White</h3><div id="capt-w" class="captured-row"></div><h3 style="margin-top:6px">Captured by Black</h3><div id="capt-b" class="captured-row"></div></div>
    <div class="section"><h3>Moves</h3><div id="move-list"></div></div>
    <div class="section"><h3>Difficulty</h3><select id="depth-select"><option value="1">Depth 1 (Easy)</option><option value="2">Depth 2 (Medium)</option><option value="3" selected>Depth 3 (Hard)</option></select></div>
    <div class="section"><h3>Play As</h3><div class="color-pick"><button id="pick-w" class="active" onclick="pickColor(0)">♔ White</button><button id="pick-b" onclick="pickColor(1)">♚ Black</button><button id="pick-x" onclick="pickColor(2)">✦ Both</button></div></div>
    <div class="section ctrl-row"><button class="primary" onclick="newGame()">New Game</button><button onclick="undoMove()">↩ Undo</button><button onclick="flipBoard()">🔄 Flip</button><button id="mute-btn" onclick="toggleMute()">🔊 Sound</button></div>
  </div>
</div>
<div id="drag-ghost"></div>
<script>
/* ============================================================
   CONSTANTS
   ============================================================ */
const C = {pawn:100,knight:320,bishop:330,rook:500,queen:900,king:20000,defaultDepth:3,maxDepth:3,qDepth:4,vol:0.3};

/* ============================================================
   0x88 BOARD: sq(rank, file), rank 0-7, file 0-7
   rank 0 = a1-h1 (white home), rank 7 = a8-h8 (black home)
   Valid squares: (s & 0x88) === 0
   ============================================================ */
const E=0, WP=1,WN=2,WB=3,WR=4,WQ=5,WK=6, BP=7,BN=8,BB=9,BR=10,BQ=11,BK=12;
const PN={[WP]:'P',[WN]:'N',[WB]:'B',[WR]:'R',[WQ]:'Q',[WK]:'K',[BP]:'p',[BN]:'n',[BB]:'b',[BR]:'r',[BQ]:'q',[BK]:'k'};
const PU={[WK]:'♔',[WQ]:'♕',[WR]:'♖',[WB]:'♗',[WN]:'♘',[WP]:'♙',[BK]:'♚',[BQ]:'♛',[BR]:'♜',[BB]:'♝',[BN]:'♞',[BP]:'♟'};
const PT=[0,1,2,3,4,5,6,1,2,3,4,5,6];
const PC=[0,0,0,0,0,0,0,1,1,1,1,1,1,1]; // 0=white,1=black

const sq=(r,f)=>(r<<4)|f;
const sr=s=>s>>4;
const sf=s=>s&0xf;
const onB=s=>s<128&&!(s&0x88);
const a2s=a=>sq(a.charCodeAt(1)-49,a.charCodeAt(0)-97);
const s2a=s=>'abcdefgh'[sf(s)]+(sr(s)+1);

/* ============================================================
   SOUND
   ============================================================ */
let actx=null, muted=false;
function initA(){if(!actx)actx=new(window.AudioContext||window.webkitAudioContext)();}
function snd(t){
  if(muted||!actx)return;
  if(actx.state==='suspended')actx.resume();
  const T=actx.currentTime,v=C.vol;
  const mk=(arr,m,d)=>{arr.forEach(([f,delay])=>{
    const o=actx.createOscillator(),g=actx.createGain();
    o.connect(g);g.connect(actx.destination);
    o.frequency.setValueAtTime(f,T+delay);
    g.gain.setValueAtTime(v*m,T+delay);
    g.gain.exponentialRampToValueAtTime(0.001,T+delay+d);
    o.start(T+delay);o.stop(T+delay+d);
  });};
  if(t==='move')mk([[300,0]],0.4,0.08);
  else if(t==='cap')mk([[400,0]],0.5,0.12);
  else if(t==='chk')mk([[600,0],[800,0.07]],0.35,0.08);
  else if(t==='over')mk([[400,0],[500,0.12],[600,0.24],[800,0.36]],0.3,0.15);
}

/* ============================================================
   PIECE-SQUARE TABLES (white perspective, sq index)
   ============================================================ */
const PST={};
PST[1]=[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];
PST[2]=[-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];
PST[3]=[-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];
PST[4]=[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];
PST[5]=[-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];
PST[6]={m:[-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],e:[-50,-40,-30,-20,-20,-30,-40,-50,-30,-20,-10,0,0,-10,-20,-30,-30,-10,20,30,30,20,-10,-30,-30,-10,30,40,40,30,-10,-30,-30,-10,30,40,40,30,-10,-30,-30,-10,20,30,30,20,-10,-30,-30,-30,0,0,0,0,-30,-30,-50,-30,-30,-30,-30,-30,-30,-50]};
const flip=s=>s^0x38;

/* ============================================================
   INITIAL POSITION
   ============================================================ */
function initPos(){
  const b=new Array(128).fill(E);
  for(let f=0;f<8;f++){
    b[sq(0,f)]=[WR,WN,WB,WQ,WK,WB,WN,WR][f];
    b[sq(1,f)]=WP;
    b[sq(6,f)]=BP;
    b[sq(7,f)]=[BR,BN,BB,BQ,BK,BB,BN,BR][f];
  }
  return b;
}

/* ============================================================
   GAME CLASS
   ============================================================ */
class Game{
  constructor(){this.reset();}
  reset(){
    this.board=initPos(); this.turn=0;
    this.C={wK:true,wQ:true,bK:true,bQ:true};
    this.ep=-1; this.hmc=0; this.fm=1;
    this.hist=[]; this.cap=[[],[]];
    this.over=false; this.res=null;
    this.pCol=0;
  }

  /* Check if square s is attacked by color `att` on board `b` */
  attacked(s,att,b){
    b=b||this.board;
    // Pawn attacks
    const pwn=att===0?WP:BP;
    if(att===0){ // White pawns attack upward; to attack s, look below s
      for(const d of[-1,1]){
        const t=s-16+d; // one rank down, d file offset
        if(onB(t)&&Math.abs(sf(t)-sf(s))<=1&&b[t]===pwn) return true;
      }
    }else{ // Black pawns attack downward; look above s
      for(const d of[-1,1]){
        const t=s+16+d;
        if(onB(t)&&Math.abs(sf(t)-sf(s))<=1&&b[t]===pwn) return true;
      }
    }
    // Knight
    const knt=att===0?WN:BN;
    for(const d of[-33,-31,-18,-14,14,18,31,33]){
      const t=s+d;
      if(!onB(t)||Math.abs(sf(t)-sf(s))>2) continue;
      if(b[t]===knt) return true;
    }
    // King
    const kng=att===0?WK:BK;
    for(const d of[-17,-16,-15,-1,1,15,16,17]){
      const t=s+d;
      if(!onB(t)||Math.abs(sf(t)-sf(s))>1) continue;
      if(b[t]===kng) return true;
    }
    // Sliding: bishop/queen on diagonals
    const bsh=att===0?WB:BB, qn=att===0?WQ:BQ;
    for(const d of[-17,-15,15,17]){
      for(let i=1;i<8;i++){
        const t=s+d*i;
        if(!onB(t)||Math.abs(sf(t)-sf(s))>i) break;
        if(b[t]!==E){if(b[t]===bsh||b[t]===qn) return true; break;}
      }
    }
    // Sliding: rook/queen on straights
    const rok=att===0?WR:BR;
    for(const d of[-16,16,-1,1]){
      for(let i=1;i<8;i++){
        const t=s+d*i;
        if(!onB(t)||Math.abs(sf(t)-sf(s))>i) break;
        if(b[t]!==E){if(b[t]===rok||b[t]===qn) return true; break;}
      }
    }
    return false;
  }

  inCheck(c,b){const k=this.fndK(c,b||this.board);return k>=0&&this.attacked(k,1-c,b||this.board);}
  fndK(c,b){const k=c===0?WK:BK;for(let s=0;s<128;s++)if(!(s&0x88)&&b[s]===k)return s;return -1;}

  pseudoLegal(c){
    c=c!==undefined?c:this.turn;
    const mv=[],b=this.board,em=1-c;
    for(let f=0;f<128;f++){
      if(f&0x88)continue;
      const p=b[f]; if(p===E||PC[p]!==c)continue;
      const t=PT[p];

      if(t===1){ // PAWN
        const dir=c===0?16:-16, sR=c===0?1:6, pR=c===0?7:0;
        const fw=f+dir;
        if(onB(fw)&&b[fw]===E){
          if(sr(fw)===pR) for(const pr of[5,4,3,2]) mv.push({f,to:fw,pr,type:'promo'});
          else mv.push({f,to:fw,type:'n'});
          if(sr(f)===sR){const fw2=f+dir*2;if(onB(fw2)&&b[fw2]===E) mv.push({f,to:fw2,type:'dbl'});}
        }
        for(const df of[-1,1]){
          const cp=fw+df;
          if(!onB(cp)||Math.abs(sf(cp)-sf(fw))>1)continue;
          if(b[cp]===E||PC[b[cp]]!==em)continue;
          if(sr(cp)===pR) for(const pr of[5,4,3,2]) mv.push({f,to:cp,pr,type:'promo'});
          else mv.push({f,to:cp,type:'cap'});
        }
        if(this.ep>=0){
          const ep=this.ep;
          for(const df of[-1,1]){
            const tp=fw+df;
            if(tp===ep&&onB(ep)) mv.push({f,to:ep,type:'ep'});
          }
        }
      }
      else if(t===2){ // KNIGHT
        for(const d of[-33,-31,-18,-14,14,18,31,33]){
          const to=f+d;
          if(!onB(to)||Math.abs(sf(to)-sf(f))>2)continue;
          if(b[to]!==E&&PC[b[to]]===c)continue;
          mv.push({f,to,type:b[to]===E?'n':'cap'});
        }
      }
      else if(t>=3&&t<=5){ // BISHOP/ROOK/QUEEN
        const dirs=t===3?[-17,-15,15,17]:t===4?[-16,16,-1,1]:[-17,-15,15,17,-16,16,-1,1];
        for(const d of dirs){
          for(let i=1;i<8;i++){
            const to=f+d*i;
            if(!onB(to)||Math.abs(sf(to)-sf(f))>i)break;
            if(b[to]===E) mv.push({f,to,type:'n'});
            else{if(PC[b[to]]===em) mv.push({f,to,type:'cap'}); break;}
          }
        }
      }
      else if(t===6){ // KING
        for(const d of[-17,-16,-15,-1,1,15,16,17]){
          const to=f+d;
          if(!onB(to)||Math.abs(sf(to)-sf(f))>1)continue;
          if(b[to]!==E&&PC[b[to]]===c)continue;
          mv.push({f,to,type:b[to]===E?'n':'cap'});
        }
        // Castling
        if(c===0){
          if(this.C.wK&&b[6]===E&&b[5]===E&&!this.attacked(5,1,b)&&!this.attacked(6,1,b)) mv.push({f:4,to:6,type:'k'});
          if(this.C.wQ&&b[3]===E&&b[2]===E&&b[1]===E&&!this.attacked(3,1,b)&&!this.attacked(2,1,b)) mv.push({f:4,to:2,type:'q'});
        }else{
          if(this.C.bK&&b[sq(7,6)]===E&&b[sq(7,5)]===E&&!this.attacked(sq(7,5),0,b)&&!this.attacked(sq(7,6),0,b)) mv.push({f:sq(7,4),to:sq(7,6),type:'k'});
          if(this.C.bQ&&b[sq(7,3)]===E&&b[sq(7,2)]===E&&b[sq(7,1)]===E&&!this.attacked(sq(7,3),0,b)&&!this.attacked(sq(7,2),0,b)) mv.push({f:sq(7,4),to:sq(7,2),type:'q'});
        }
      }
    }
    return mv;
  }

  doMove(m,b){
    b=b||this.board;
    const p=b[m.f],col=PC[p],typ=PT[p],cap=b[m.to];
    const st={f:m.f,to:m.to,p,cap,ep:this.ep,C:{...this.C},hmc:this.hmc,epP:-1,rF:-1,rT:-1};
    b[m.to]=p; b[m.f]=E;
    if(m.pr) b[m.to]=col===0?m.pr:m.pr+6;
    if(m.type==='ep'){const cp=col===0?m.to+16:m.to-16;st.epP=b[cp];b[cp]=E;}
    if(m.type==='k'){if(col===0){b[sq(0,7)]=E;b[sq(0,5)]=WR;st.rF=sq(0,7);st.rT=sq(0,5);}else{b[sq(7,7)]=E;b[sq(7,5)]=BR;st.rF=sq(7,7);st.rT=sq(7,5);}}
    if(m.type==='q'){if(col===0){b[sq(0,0)]=E;b[sq(0,3)]=WR;st.rF=sq(0,0);st.rT=sq(0,3);}else{b[sq(7,0)]=E;b[sq(7,3)]=BR;st.rF=sq(7,0);st.rT=sq(7,3);}}
    this.ep=m.type==='dbl'?(col===0?m.f+16:m.f-16):-1;
    if(p===WK){this.C.wK=false;this.C.wQ=false;}
    if(p===BK){this.C.bK=false;this.C.bQ=false;}
    if(m.f===sq(0,0)||m.to===sq(0,0)) this.C.wQ=false;
    if(m.f===sq(0,7)||m.to===sq(0,7)) this.C.wK=false;
    if(m.f===sq(7,0)||m.to===sq(7,0)) this.C.bQ=false;
    if(m.f===sq(7,7)||m.to===sq(7,7)) this.C.bK=false;
    this.hmc=(typ===1||st.cap||st.epP)?0:this.hmc+1;
    return st;
  }

  undoM(m,st,b){
    b=b||this.board;
    b[st.f]=st.p; b[st.to]=st.cap;
    if(st.epP>=0){const col=PC[st.p];b[col===0?st.to+16:st.to-16]=st.epP;}
    if(st.rF>=0){b[st.rF]=b[st.rT];b[st.rT]=E;}
    this.ep=st.ep;this.C={...st.C};this.hmc=st.hmc;
  }

  legal(c){
    c=c!==undefined?c:this.turn;
    const ps=this.pseudoLegal(c),leg=[];
    for(const m of ps){
      const b=this.board.slice();
      const st=this.doMove(m,b);
      if(!this.inCheck(c,b)) leg.push(m);
      this.undoM(m,st,b);
    }
    return leg;
  }

  toAlg(m){
    const b=this.board,p=b[m.f],typ=PT[p],col=PC[p];
    const isCap=b[m.to]!==E||m.type==='ep';
    const toS=s2a(m.to);
    if(m.type==='k') return 'O-O';
    if(m.type==='q') return 'O-O-O';
    let n='';
    if(typ===1){
      n=isCap?'abcdefgh'[sf(m.f)]+'x'+toS:toS;
      if(m.pr) n+='='+'QRBN'[m.pr-2];
    }else{
      n='NRQBNQ'[typ];
      const am=this.amb(m,typ,col);
      if(am) n+=am;
      if(isCap) n+='x';
      n+=toS;
    }
    // Check/mate
    const b2=this.board.slice();
    const st=this.doMove(m,b2);
    const opp=1-col;
    const tmp=new Game();tmp.board=b2;tmp.turn=opp;tmp.C=this.C;tmp.ep=this.ep;
    const ol=tmp.legal(opp);
    const ic=this.inCheck(opp,b2);
    if(ic&&!ol.length) n+='#';
    else if(ic) n+='+';
    this.undoM(m,st,b2);
    if(m.type==='ep') n+=' e.p.';
    return n;
  }

  amb(m,typ,col){
    const cands=[];
    for(let s=0;s<128;s++){
      if(s&0x88||s===m.f)continue;
      const p=this.board[s];
      if(PC[p]!==col||PT[p]!==typ)continue;
      const o={f:s,to:m.to,type:'n'};
      const b=this.board.slice();
      const st=this.doMove(o,b);
      if(!this.inCheck(col,b)) cands.push(s);
      this.undoM(o,st,b);
    }
    if(!cands.length) return '';
    const sF=cands.some(c=>sf(c)===sf(m.f));
    const sR=cands.some(c=>sr(c)===sr(m.f));
    if(!sF) return 'abcdefgh'[sf(m.f)];
    if(!sR) return (sr(m.f)+1).toString();
    return s2a(m.f);
  }

  execute(m){
    const p=this.board[m.f],typ=PT[p],col=PC[p];
    const nt=this.toAlg(m);
    const cp=m.type==='ep'?(col===0?BP:WP):this.board[m.to];
    if(cp!==E) this.cap[col].push(cp);
    const st=this.doMove(m);
    this.hist.push({f:m.f,to:m.to,p,cap:cp,nt,type:m.type,pr:m.pr,st});
    if(this.turn===1) this.fm++;
    this.turn=1-this.turn;
    this.chkEnd();
  }

  chkEnd(){
    const l=this.legal(this.turn);
    if(!l.length){this.over=true;this.res=this.inCheck(this.turn)?'checkmate':'stalemate';return;}
    if(this.insuffMat()){this.over=true;this.res='insufficient';return;}
    if(this.hmc>=100){this.over=true;this.res='fifty-move';return;}
  }

  insuffMat(){
    const ps=[];
    for(let s=0;s<128;s++){if(s&0x88)continue;if(this.board[s]!==E)ps.push(this.board[s]);}
    if(ps.length===2) return true;
    if(ps.length===3){const nk=ps.find(p=>PT[p]!==6);if(nk&&(PT[nk]===2||PT[nk]===3)) return true;}
    return false;
  }

  undo(){
    for(let i=0;i<2;i++){
      if(!this.hist.length) return false;
      const r=this.hist.pop();
      const m={f:r.f,to:r.to,type:r.type,pr:r.pr};
      this.undoM(m,r.st);
      if(r.cap!==E) this.cap[PC[r.p]].pop();
    }
    this.over=false;this.res=null;
    return true;
  }

  fen(){
    let f='';
    for(let r=7;r>=0;r--){let e=0;for(let c=0;c<8;c++){const p=this.board[sq(r,c)];if(p===E)e++;else{if(e){f+=e;e=0;}f+=PN[p];}}if(e)f+=e;if(r>0)f+='/';}
    f+=' '+(this.turn===0?'w':'b');
    let ck='';if(this.C.wK)ck+='K';if(this.C.wQ)ck+='Q';if(this.C.bK)ck+='k';if(this.C.bQ)ck+='q';
    f+=' '+(ck||'-')+' '+(this.ep>=0?s2a(this.ep):'-')+' '+this.hmc+' '+this.fm;
    return f;
  }

  parseFEN(str){
    const p=str.split(' ');
    this.board=new Array(128).fill(E);
    const rows=p[0].split('/');
    for(let rank=0;rank<8;rank++){let file=0;for(const ch of rows[rank]){if(ch>='1'&&ch<='8')file+=parseInt(ch);else{this.board[sq(7-rank,file)]=parseInt(Object.keys(PN).find(k=>PN[parseInt(k)]===ch));file++;}}}
    this.turn=p[1]==='w'?0:1;
    this.C={wK:false,wQ:false,bK:false,bQ:false};
    for(const ch of p[2]){if(ch==='K')this.C.wK=true;if(ch==='Q')this.C.wQ=true;if(ch==='k')this.C.bK=true;if(ch==='q')this.C.bQ=true;}
    this.ep=p[3]==='-'?-1:a2s(p[3]);
    if(p.length>=5)this.hmc=parseInt(p[4]);
    if(p.length>=6)this.fm=parseInt(p[5]);
    this.over=false;this.res=null;
  }
}

/* ============================================================
   AI ENGINE
   ============================================================ */
function evalPos(g){
  let score=0,totalMat=0;
  const b=g.board;
  for(let s=0;s<128;s++){if(s&0x88)continue;const p=b[s];if(p===E)continue;const t=PT[p];if(t!==6)totalMat+=[0,C.pawn,C.knight,C.bishop,C.rook,C.queen,0][t];}
  for(let s=0;s<128;s++){
    if(s&0x88)continue;
    const p=b[s];if(p===E)continue;
    const col=PC[s>6?1:0]!==undefined?PC[p]:0;
    const typ=PT[p],mat=[0,C.pawn,C.knight,C.bishop,C.rook,C.queen,C.king][typ];
    let pst=0;
    const idx=col===0?s:flip(s);
    if(typ<6) pst=PST[typ][idx];
    else pst=totalMat<2600?PST[6].e[idx]:PST[6].m[idx];
    score+=col===0?mat+pst:-(mat+pst);
  }
  const mob=g.pseudoLegal(g.turn).length-g.pseudoLegal(1-g.turn).length;
  score+=g.turn===0?mob*3:-mob*3;
  return score;
}

function ordMoves(mv,b){
  return mv.slice().sort((a,ob)=>{
    let sa=0,sb=0;
    if(a.type==='cap'||a.type==='ep') sa=10*PT[b[a.to]]-PT[b[a.f]];
    if(ob.type==='cap'||ob.type==='ep') sb=10*PT[b[ob.to]]-PT[b[ob.f]];
    if(a.pr) sa+=80+a.pr;
    if(ob.pr) sb+=80+ob.pr;
    return sb-sa;
  });
}

function quiesce(g,al,beta,d){
  const sp=evalPos(g);
  if(d===0)return sp;
  if(sp>=beta)return beta;
  if(sp>al)al=sp;
  const caps=g.legal(g.turn).filter(m=>m.type==='cap'||m.type==='ep'||m.pr);
  const ordered=ordMoves(caps,g.board);
  for(const m of ordered){
    const b=g.board.slice();
    const st=g.doMove(m,b);
    g.turn=1-g.turn;
    const sc=-quiesce(g,-beta,-al,d-1);
    g.turn=1-g.turn;
    g.undoM(m,st,b);
    if(sc>=beta)return beta;
    if(sc>al)al=sc;
  }
  return al;
}

function minimax(g,dep,al,beta,max){
  if(dep===0) return quiesce(g,al,beta,C.qDepth);
  if(g.over){if(g.res==='checkmate')return max?-99999+(C.maxDepth-dep):99999-(C.maxDepth-dep);return 0;}
  const mv=g.legal(g.turn);
  if(!mv.length){if(g.inCheck(g.turn))return max?-99999+(C.maxDepth-dep):99999-(C.maxDepth-dep);return 0;}
  const ordered=ordMoves(mv,g.board);
  if(max){
    let best=-Infinity;
    for(const m of ordered){
      const b=g.board.slice();const st=g.doMove(m,b);g.turn=1-g.turn;
      const s=minimax(g,dep-1,al,beta,false);
      g.turn=1-g.turn;g.undoM(m,st,b);
      best=Math.max(best,s);al=Math.max(al,s);
      if(beta<=al)break;
    }
    return best;
  }else{
    let best=Infinity;
    for(const m of ordered){
      const b=g.board.slice();const st=g.doMove(m,b);g.turn=1-g.turn;
      const s=minimax(g,dep-1,al,beta,true);
      g.turn=1-g.turn;g.undoM(m,st,b);
      best=Math.min(best,s);beta=Math.min(beta,s);
      if(beta<=al)break;
    }
    return best;
  }
}

function bestMv(g,dep){
  const mv=g.legal(g.turn);
  if(!mv.length)return null;
  const ordered=ordMoves(mv,g.board);
  let bm=ordered[0],bs=g.turn===0?-Infinity:Infinity;
  for(const m of ordered){
    const b=g.board.slice();const st=g.doMove(m,b);g.turn=1-g.turn;
    const s=minimax(g,dep-1,-Infinity,Infinity,g.turn===0);
    g.turn=1-g.turn;g.undoM(m,st,b);
    if(g.turn===0){if(s>bs){bs=s;bm=m;}}else{if(s<bs){bs=s;bm=m;}}
  }
  return bm;
}

/* ============================================================
   UI
   ============================================================ */
const game=new Game();
let sel=-1,selMv=[],flipped=false;
let dragging=false,dragS=-1,dragP=null;
let pendPromo=null,aiThink=false;

function buildBoard(){
  const el=document.getElementById('board');el.innerHTML='';
  for(let dr=0;dr<8;dr++){
    for(let df=0;df<8;df++){
      const rank=flipped?dr:7-dr,file=flipped?7-df:df;
      const s=sq(rank,file),light=(rank+file)%2===0;
      const div=document.createElement('div');
      div.className='square '+(light?'light':'dark');div.dataset.s=s;
      const p=game.board[s];
      if(p!==E){const sp=document.createElement('span');sp.className=PC[p]===0?'pw':'pb';sp.textContent=PU[p];div.appendChild(sp);}
      if(df===0){const l=document.createElement('span');l.className='coord rl';l.textContent=rank+1;div.appendChild(l);}
      if(dr===7){const l=document.createElement('span');l.className='coord fl';l.textContent='abcdefgh'[file];div.appendChild(l);}
      div.addEventListener('mousedown',e=>onMD(e,s));
      div.addEventListener('touchstart',e=>onTS(e,s),{passive:false});
      el.appendChild(div);
    }
  }
}

function highlight(){
  document.querySelectorAll('.square').forEach(div=>{
    const s=parseInt(div.dataset.s);
    div.classList.remove('selected','move-from','move-to','check');
    div.querySelectorAll('.dot,.ring').forEach(d=>d.remove());
    const last=game.hist.length?game.hist[game.hist.length-1]:null;
    if(last){if(s===last.f)div.classList.add('move-from');if(s===last.to)div.classList.add('move-to');}
    if(s===sel)div.classList.add('selected');
    for(const m of selMv){
      if(m.to===s){
        if(game.board[s]!==E||m.type==='ep'){const r=document.createElement('div');r.className='ring';div.appendChild(r);}
        else{const d=document.createElement('div');d.className='dot';div.appendChild(d);}
      }
    }
    if(s===game.fndK(game.turn,game.board)&&game.inCheck(game.turn))div.classList.add('check');
  });
}

function render(){buildBoard();highlight();rCap();rMov();rTurn();rOver();saveState();}
function rCap(){const s=a=>a.slice().sort((x,y)=>PT[y]-PT[x]);document.getElementById('capt-w').textContent=s(game.cap[0]).map(p=>PU[p]).join(' ');document.getElementById('capt-b').textContent=s(game.cap[1]).map(p=>PU[p]).join(' ');}
function rMov(){const el=document.getElementById('move-list');el.innerHTML='';for(let i=0;i<game.hist.length;i+=2){const row=document.createElement('div');row.className='move-row';const num=document.createElement('span');num.className='move-num';num.textContent=(Math.floor(i/2)+1)+'.';row.appendChild(num);const w=document.createElement('span');w.className='move-w';w.textContent=game.hist[i].nt;row.appendChild(w);if(i+1<game.hist.length){const b=document.createElement('span');b.className='move-b';b.textContent=game.hist[i+1].nt;row.appendChild(b);}el.appendChild(row);}el.scrollTop=el.scrollHeight;}
function rTurn(){const el=document.getElementById('turn-indicator');if(game.over){el.textContent='Game Over';el.className='';return;}el.textContent=game.turn===0?'⬜ White to move':'⬛ Black to move';el.className=game.turn===0?'w':'b';}
function rOver(){const b=document.getElementById('game-over-banner');if(!game.over){b.classList.remove('active');return;}b.classList.add('active');let t='';if(game.res==='checkmate'){t=`Checkmate! ${game.turn===0?'Black':'White'} wins!`;snd('over');}else if(game.res==='stalemate'){t='Stalemate! Draw.';snd('over');}else if(game.res==='insufficient')t='Draw — insufficient material.';else if(game.res==='fifty-move')t='Draw — 50-move rule.';b.textContent=t;}

/* Mouse/Touch */
function onMD(e,s){
  if(game.over||aiThink)return;
  if(game.pCol!==2&&PC[game.board[s]]!==game.pCol)return;
  if(game.pCol!==2&&PC[game.board[s]]!==game.turn)return;
  e.preventDefault();
  dragS=s;dragP=game.board[s];dragging=false;
  const gh=document.getElementById('drag-ghost');
  if(dragP!==E){gh.textContent=PU[dragP];gh.style.display='block';}
  const onM=ev=>{dragging=true;gh.style.left=(ev.clientX-22)+'px';gh.style.top=(ev.clientY-22)+'px';};
  onM(e);
  const onUp=ev=>{
    document.removeEventListener('mousemove',onM);document.removeEventListener('mouseup',onUp);gh.style.display='none';
    if(!dragging){handleClick(s);}
    else{
      const br=document.getElementById('board').getBoundingClientRect(),spx=br.width/8;
      let df=Math.max(0,Math.min(7,Math.floor((ev.clientX-br.left)/spx)));
      let dr=Math.max(0,Math.min(7,Math.floor((ev.clientY-br.top)/spx)));
      tryMove(dragS,sq(flipped?dr:7-dr,flipped?7-df:df));
    }
  };
  document.addEventListener('mousemove',onM);document.addEventListener('mouseup',onUp);
}

let tDrag=false,tS=-1;
function onTS(e,s){
  if(game.over||aiThink)return;
  if(game.pCol!==2&&PC[game.board[s]]!==game.pCol)return;
  e.preventDefault();tS=s;tDrag=false;dragS=s;dragP=game.board[s];
  const gh=document.getElementById('drag-ghost');
  if(dragP!==E){gh.textContent=PU[dragP];gh.style.display='block';}
}
document.addEventListener('touchmove',e=>{if(!dragP)return;e.preventDefault();tDrag=true;const gh=document.getElementById('drag-ghost'),t=e.touches[0];gh.style.left=(t.clientX-22)+'px';gh.style.top=(t.clientY-22)+'px';},{passive:false});
document.addEventListener('touchend',e=>{
  document.getElementById('drag-ghost').style.display='none';
  if(!tDrag&&tS>=0)handleClick(tS);
  else if(tDrag&&dragS>=0){
    const t=e.changedTouches[0],br=document.getElementById('board').getBoundingClientRect(),spx=br.width/8;
    let df=Math.max(0,Math.min(7,Math.floor((t.clientX-br.left)/spx)));
    let dr=Math.max(0,Math.min(7,Math.floor((t.clientY-br.top)/spx)));
    tryMove(dragS,sq(flipped?dr:7-dr,flipped?7-df:df));
  }
  tS=-1;tDrag=false;dragS=-1;dragP=null;
},{passive:false});

function handleClick(s){
  if(game.over||aiThink||pendPromo)return;
  const p=game.board[s];
  if(sel>=0){const m=selMv.find(x=>x.to===s);if(m){execPlayer(m);return;}}
  if(p!==E&&PC[p]===game.turn){
    if(game.pCol!==2&&PC[p]!==game.pCol)return;
    sel=s;selMv=game.legal(game.turn).filter(m=>m.f===s);render();return;
  }
  sel=-1;selMv=[];render();
}
function tryMove(from,to){const m=game.legal(game.turn).find(x=>x.f===from&&x.to===to);if(m)execPlayer(m);else{sel=-1;selMv=[];render();}}
function execPlayer(m){
  const p=game.board[m.f],typ=PT[p],pR=PC[p]===0?7:0;
  if(typ===1&&sr(m.to)===pR){
    const promos=game.legal(game.turn).filter(x=>x.f===m.f&&x.to===m.to&&x.pr);
    if(promos.length){showPromo(promos);return;}
  }
  sel=-1;selMv=[];game.execute(m);pSnd(m);render();
  if(!game.over&&!aiThink&&game.pCol!==2)schedAI();
}
function showPromo(promos){
  pendPromo=promos;const ov=document.getElementById('promo-overlay'),pk=document.getElementById('promo-picker');pk.innerHTML='';
  const col=PC[game.board[promos[0].f]],pcs=col===0?[WQ,WR,WB,WN]:[BQ,BR,BB,BN];
  for(const p of pcs){
    const btn=document.createElement('div');btn.className='pb2';
    const sp=document.createElement('span');sp.className=col===0?'pw':'pb';sp.textContent=PU[p];btn.appendChild(sp);
    const pt=PT[p];
    btn.addEventListener('click',()=>{
      const m=promos.find(x=>x.pr===pt);
      if(m){pendPromo=null;ov.classList.remove('active');sel=-1;selMv=[];game.execute(m);pSnd(m);render();if(!game.over&&!aiThink&&game.pCol!==2)schedAI();}
    });
    pk.appendChild(btn);
  }
  ov.classList.add('active');
}
function pSnd(m){if(m.type==='cap'||m.type==='ep')snd('cap');else snd('move');if(game.inCheck(game.turn))snd('chk');}
function schedAI(){if(game.turn===game.pCol)return;aiThink=true;render();setTimeout(()=>{const d=parseInt(document.getElementById('depth-select').value);const m=bestMv(game,d);if(m){game.execute(m);pSnd(m);}aiThink=false;render();},50);}

function newGame(){game.reset();game.pCol=game.pCol;sel=-1;selMv=[];pendPromo=null;aiThink=false;document.getElementById('promo-overlay').classList.remove('active');render();if(game.pCol===1)schedAI();}
function undoMove(){if(aiThink)return;game.undo();sel=-1;selMv=[];render();}
function flipBoard(){flipped=!flipped;render();}
function toggleMute(){muted=!muted;document.getElementById('mute-btn').textContent=muted?'🔇 Muted':'🔊 Sound';}
function pickColor(c){game.pCol=c;document.getElementById('pick-w').classList.toggle('active',c===0);document.getElementById('pick-b').classList.toggle('active',c===1);document.getElementById('pick-x').classList.toggle('active',c===2);newGame();}

function saveState(){try{localStorage.setItem('chess',JSON.stringify({fen:game.fen(),hist:game.hist,cap:game.cap,over:game.over,res:game.res,pc:game.pCol,flip:flipped,depth:document.getElementById('depth-select').value}));}catch(e){}}
function loadState(){try{const r=localStorage.getItem('chess');if(!r)return false;const s=JSON.parse(r);game.parseFEN(s.fen);game.hist=s.hist||[];game.cap=s.cap||[[],[]];game.over=s.over||false;game.res=s.res||null;game.pCol=s.pc!==undefined?s.pc:0;flipped=s.flip||false;if(s.depth)document.getElementById('depth-select').value=s.depth;return true;}catch(e){return false;}}

/* Debug */
window.game=game;
window.debug={fen:()=>game.fen(),move:a=>{if(a.length>=4){const f=a2s(a.substring(0,2)),t=a2s(a.substring(2,4));const m=game.legal(game.turn).find(x=>x.f===f&&x.to===t);if(m){game.execute(m);return{ok:true,nt:game.hist[game.hist.length-1].nt};}}return{ok:false,error:'illegal'};},legalMoves:()=>game.legal(game.turn).map(m=>({from:s2a(m.f),to:s2a(m.to),type:m.type,pr:m.pr||null})),eval:()=>evalPos(game),findBestMove:d=>bestMv(game,d||3)};
window.Game=Game;window.bestMv=bestMv;window.evalPos=evalPos;

function init(){
  initA();
  if(!loadState())newGame();
  else{sel=-1;selMv=[];render();}
  document.getElementById('pick-w').classList.toggle('active',game.pCol===0);
  document.getElementById('pick-b').classList.toggle('active',game.pCol===1);
  document.getElementById('pick-x').classList.toggle('active',game.pCol===2);
  document.addEventListener('click',()=>initA(),{once:true});
}
init();
</script>
</body>
</html>