Chess
Tiel Coder 35B A3B GGUF MTP · 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 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"} -->