Chess
Qwen 3.6 27B MTP GGUF IQ4 NL · full

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>