Chess
lfm2 24b a2b · basic

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 26,044 bytes · SHA-256 35426824624c
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chess Game with AI</title>
<style>
body { font-family: Arial, sans-serif; background: #222; color: #eee; margin: 0; }
.container { display: flex; height: 100vh; }
.board { position: relative; width: 240px; height: 240px; margin: 20px; }
.board div { width: 30px; height: 30px; border: 1px solid #333; }
.board .square { display: inline-block; text-align: center; vertical-align: middle; }
.board .white { background: #f0d9b5; }
.board .black { background: #b58863; }
.board .white .square { background: #f5e6d3; }
.board .black .square { background: #d2b48c; }
.board .selected { outline: 2px solid #0ff; }
.board .highlight { background: #ffeb3b; }
.board .king { background: #d32f2f; }
.board .queen { background: #e91e63; }
.board .rook { background: #9c27b0; }
.board .bishop { background: #ff9800; }
.board .knight { background: #f57f17; }
.board .promo { background: #fff3e0; }
.board .promo .square { border-color: #ff9800; }
.board .side { position: absolute; top: 10px; right: 10px; }
.side button { margin: 5px; padding: 8px 16px; }
#moveList { background: #333; padding: 10px; width: 200px; overflow-y: auto; }
#promotionOverlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; display: none; }
.piece { font-size: 2.5em; cursor: pointer; }
.piece.selected { color: #fff; }
.piece.capture { color: #ff9800; }
.piece.promotion { color: #9c27b0; }
#lastMove { background: #ffeb3b; }
#check { background: #ffeb3b; }
#gameOver { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 2em; color: #f00; }
</style>
</head>
<body>
<div class="container">
<div class="board" id="board"></div>
<div id="side">
<div class="side">Turn: <span id="turn">White</span></div>
<div id="moveList"></div>
<div id="promotionOverlay">
<input type="text" id="promotionSquare" placeholder="e.g. e8" style="width:80px;"/>
<select id="promotionType">
<option value="queen">Queen</option>
<option value="rook">Rook</option>
<option value="bishop">Bishop</option>
<option value="knight">Knight</option>
</select>
<button id="promote">Promote</button>
</div>
<div id="controls">
<button id="flip">Flip</button>
<button id="undo">Undo</button>
<button id="newGame">New Game</button>
<label>AI Difficulty:
<select id="difficulty">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</label>
<button id="mute">Mute</button>
</div>
<div id="gameOver"></div>
<div id="debug"></div>
</div>
<script>
/* === CONSTANTS === */
const COLORS = {
white: '#f0d9b5',
black: '#b58863',
square: (i) => i % 2 ? '#b58863' : '#f0d9b5',
king: '#d32f2f',
queen: '#e91e63',
rook: '#9c27b0',
bishop: '#ff9800',
knight: '#f57f17',
promo: '#fff3e0'
};
const EVAL = {
pawn: 10, knight: 3, bishop: 3, rook: 5, queen: 9, king: 0,
promotion: {queen:10, rook:5, bishop:3, knight:3}
};
const DEFAULTS = {
castling: {w:true, b:true, wk:true, bk:true},
enPassant: null,
moveList: [],
lastMove: null,
turn: 'white',
gameOver: false,
mute: false
};
let state = {...DEFAULTS, board: [], fen:null, moveList:[], castlingRights:null, enPassant:null, moveIndex:0};
let aiDepth=1;
let aiMoveHistory=[];
let lastMoveSquare=null;
let moveHistory=[];
let gameOverFlag=false;
/* === UTILS === */
function fenString(board) {
let f = '';
for(let r=0;r<8;r++) {
for(let c=0;c<8;c++) {
let piece = board[r][c];
if(piece) {
let color = piece.color==='white'?'w':'b';
f += color==='w'?piece.symbol:piece.symbol;
} else {
f += '.'
}
}
f += '\n';
}
return f;
}
function parseFen(fen) {
let lines = fen.split('\n');
let board=[];
for(let r=0;r<8;r++) {
let row=[];
let cells = fen[r].split(' ');
for(let c=0;c<8;c++) {
let cell = cells[c] || '.';
if(cell==='.') row.push(null);
else {
let piece = {symbol:cell, color:'white'};
if(cell.match(/[KQRBN]/i)) piece.color=cell.toUpperCase()==='W'?'white':'black';
row.push(piece);
}
}
board.push(row);
}
return {board, fen:fen, turn:'white'};
}
function getSquare(r,c){return r*8+c;}
/* === BOARD RENDERING === */
function renderBoard() {
const boardEl = document.getElementById('board');
boardEl.innerHTML='';
let fenObj = state.fen;
let board = fenObj.board;
for(let r=0;r<8;r++) {
for(let c=0;c<8;c++) {
let sq = document.createElement('div');
sq.className = 'square '+ (board[r][c]?board[r][c].color==='white'?'white':'black') + ' '+ (board[r][c]?.symbol||'');
if(board[r][c] && board[r][c].color==='white') sq.classList.add('white');
if(board[r][c] && board[r][c].color==='black') sq.classList.add('black');
if(board[r][c] && board[r][c].symbol) sq.classList.add('piece', board[r][c].symbol);
if(board[r][c] && board[r][c].color==='white' && board[r][c].king) sq.classList.add('king');
if(board[r][c] && board[r][c].color==='white' && board[r][c].queen) sq.classList.add('queen');
if(board[r][c] && board[r][c].color==='white' && board[r][c].rook) sq.classList.add('rook');
if(board[r][c] && board[r][c].color==='white' && board[r][c].bishop) sq.classList.add('bishop');
if(board[r][c] && board[r][c].color==='white' && board[r][c].knight) sq.classList.add('knight');
if(board[r][c] && board[r][c].color==='white' && board[r][c].promotion) sq.classList.add('promo', state.board[r][c].promotion);
// Highlight moves
let legalMoves = getLegalMoves(r,c,board[r][c]?board[r][c].color:'');
if(state.turn==='white' && board[r][c]?.color==='white' && legalMoves.includes(`${getSquare(r,c)}`)) sq.classList.add('highlight');
// Capture ring
let isCapture = state.moveList.some(m=>m.startsWith(`${getSquare(r,c)}`) && m.endsWith('e.p.'));
if(isCapture) sq.classList.add('capture');
// Last move highlight
if(state.lastMove===`${getSquare(r,c)}`) sq.classList.add('last-move');
// Check/checkmate
if(state.turn==='white' && board[r][c]?.color==='white') {
let inCheck = isInCheck(board, r,c, board[r][c]?board[r][c].color:'white');
if(inCheck) sq.classList.add('check');
}
boardEl.appendChild(sq);
}
}
// Highlight all legal moves
for(let m of state.moveList) {
let [from, to] = m.split(' ');
let frm = getSquare(from[1],from[2]), toT = getSquare(to[1],to[2]);
if(frm && toT && board[frm.row][frm.col]) {
let sq = document.querySelector(`#board .square[data-row="${frm.row}"][data-col="${frm.col}"]`);
if(sq) sq.classList.add('highlight');
}
}
// Last move
if(state.lastMove) {
let [from, to] = state.lastMove.split(' ');
let frm = getSquare(from[1],from[2]), toT = getSquare(to[1],to[2]);
if(frm && toT && board[frm.row][frm.col]) {
let sq = document.querySelector(`#board .square[data-row="${frm.row}"][data-col="${frm.col}"]`);
if(sq) sq.classList.add('last-move');
}
}
// Turn
document.getElementById('turn').textContent = state.turn;
// Game over
let goBtn = document.getElementById('gameOver');
if(gameOverFlag) {
goBtn.textContent = state.gameOver ? 'Restart' : '';
goBtn.style.color='#fff';
goBtn.style.background='#ff9800';
} else {
goBtn.textContent = '';
goBtn.style.color='#eee';
goBtn.style.background='#333';
}
// Debug
let debug = document.getElementById('debug');
debug.innerHTML=`<pre>${JSON.stringify(state,null,2)}</pre>`;
}
/* === MOVEMENT LOGIC === */
function getLegalMoves(r,c,color) {
// Only pieces of this color
let piece = state.board[r][c];
if(!piece) return [];
let moves=[];
// Castling
if(color==='white' && state.castlingRights.w && piece.type==='king' && piece.color==='white') {
// kingside
if(isCastleAllowed(r,c,'k') && isNotInCheck(state.board, r,c, piece.color)) {
let trg = getSquare(r,c+2);
if(validMove([r,c], trg, piece.color, state.board, color, false)) moves.push(`O-O`);
}
// queenside
if(isCastleAllowed(r,c,'K') && isNotInCheck(state.board, r,c, piece.color) && !piece.king.color) {
let trg = getSquare(r,c-2);
if(validMove([r,c], trg, piece.color, state.board, color, false)) moves.push(`O-O`);
}
}
// Regular move generation
for(let dr=0;dr<8;dr++) {
for(let dc=0;dc<8;dc++) {
if(dr===0&&dc===0) continue;
let nr=r+dr, nc=c+dc;
if(nr<0||nr>7||nc<0||nc>7) continue;
let target = state.board[nr][nc];
// Skip own pieces
if(target && target.color===color) continue;
// Capture
if(target) {
if(!isInCheck(state.board, nr,nc, target.color)) moves.push(`${getSquare(r,c)} ${getSquare(nr,nc)}`);
continue;
}
// Regular move
if(validMove([r,c], [nr,nc], piece.color, state.board, color, false, true)) {
moves.push(`${getSquare(r,c)} ${getSquare(nr,nc)}`);
}
}
}
return moves;
}
function isCastleAllowed(r,c,side,color) {
if(side==='k' && color==='white') return state.castlingRights.w && r===4 && c===7;
if(side==='k' && color==='black') return state.castlingRights.b && r===3 && c===7;
if(side==='K' && color==='white') return state.castlingRights.w && r===0 && c===4;
if(side==='K' && color==='black') return state.castlingRights.b && r===7 && c===4;
return false;
}
function validMove(from,to,color,board, turn, captures, isCastle=false) {
let frm = from, to = to;
// bounds
if(!board[to.row]||!board[to.row][to.col]) return false;
let piece = board[to.row][to.col];
// same color move not allowed
if(piece && piece.color===color) return false;
// capture only if target is enemy or empty for non-capture
if(piece) {
if(piece.color!==color && !captures) return false;
}
// check move
let newBoard = board.map(row=>row.slice());
let movedPiece = newBoard[from.row][from.col];
newBoard[from.row][from.col]=null;
if(movedPiece) newBoard[to.row][to.col]=movedPiece;
// check if in check
let inCheck = isInCheck(newBoard, to.row, to.col, piece?.color===color?piece.color:'');
if(inCheck) return false;
// promotion
if(piece && piece.type==='pawn' && (to.row===0||to.row===7)) {
let promo = state.promotion || 'queen';
newBoard[to.row][to.col] = {...movedPiece, color:promo, promotion:promo};
}
// en passant
if(captures && piece?.type==='pawn' && ((to.row-(from.row))===1||(to.row-(from.row))===-1) && board[to.row][to.col]==null) && state.enPassant===getSquare(from.row,from.col)) {
// en passant capture
let dir = (to.row-from.row);
if(dir===1) { // white pawn to black's 7th
let target = newBoard[to.row][to.col];
if(target && target.color!==color && target.type==='pawn' && target.color===(color==='white'?'black':'white')) {
newBoard[to.row][to.col]=target;
newBoard[from.row][from.col]=null;
}
} else if(dir===-1) { // black pawn to white's 7th
let target = newBoard[to.row][to.col];
if(target && target.color!==color && target.type==='pawn' && target.color===(color==='white'?'black':'white')) {
newBoard[to.row][to.col]=target;
newBoard[from.row][from.col]=null;
}
}
}
// check for check
let inCheck = isInCheck(newBoard, to.row, to.col, piece?.color===color?piece.color:'');
if(inCheck) return false;
// move history
state.moveList.push(`${getSquare(from.row,from.col)} ${getSquare(to.row,to.col)}`);
// update castling rights
if(isCastle && !isInCheck(newBoard, to.row, to.col, piece?.color===color?piece.color:'white')) {
state.castlingRights[side] = false;
}
// move list
state.moveList.push(`${getSquare(from.row,from.col)} ${getSquare(to.row,to.col)}`);
// update last move
state.lastMove = `${getSquare(from.row,from.col)} ${getSquare(to.row,to.col)}`;
// turn
state.turn = turn;
// save state for undo
state.moveIndex++;
return true;
}
function isInCheck(board, r,c, color) {
// Simulate move and check if king in check
let temp = board.map(row=>row.slice());
let piece = temp[r][c];
let dir = (r===0?7:r===7?0:r);
let oppColor = color==='white'?'black':'white';
// Find king
let kR,kC;
for(let i=0;i<8;i++) {
for(let j=0;j<8;j++) {
if(temp[i][j] && temp[i][j].color===oppColor && temp[i][j].type==='king') {kR=i;kC=j; break;}
}
}
// Check all enemy pieces
for(let i=0;i<8;i++) {
for(let j=0;j<8;j++) {
let p = temp[i][j];
if(p && p.color===oppColor && piece && piece.color===oppColor) {
// move check
if(piece.type==='king' && p.row===kR && p.col===kC) continue;
let dr = i-kR, dc = j-kC;
if(dr===0||dc===0||Math.abs(dr)===Math.abs(dc))) return true;
// queen
if(p.type==='queen' && (Math.abs(dr)===Math.abs(dc)||dr===0||dc===0)) return true;
// rook
if((dr===0||dc===0)&&Math.min(Math.abs(dr),Math.abs(dc))<8) return true;
// bishop
if(Math.abs(dr)==Math.abs(dc)) return true;
// knight
if(Math.abs(dr)===1&&Math.abs(dc)===2||Math.abs(dr)===2&&Math.abs(dc)===1) return true;
}
}
}
return false;
}
function getSquare(r,c){return r*8+c;}
/* === AI === */
async function aiMove() {
if(state.gameOver) return;
// Save state for undo
let savedState = JSON.parse(JSON.stringify(state));
// Move history
state.moveHistory = state.moveList.slice();
state.moveIndex=0;
// Undo last move if any
if(state.moveIndex>0) {
state.moveIndex--;
undoMove();
}
// Get legal moves
let legal = getLegalMoves(...state.moveList[state.moveIndex-1].split(' ').map(s=>parseSquare(s)));
if(!legal.length) return; // no move
// Choose difficulty
let depth=aiDepth;
// Minimax with alpha-beta, iterative deepening
let bestMove = null, bestScore=-Infinity, lastMove=null;
for(let d=1;d<=depth;d++) {
let score = minimax(parseSquare(legal[0]), legal, d, -Infinity, Infinity, false, legal.length, state.turn, state.board, state.moveList, state.moveIndex, state.gameOver, state.enPassant, state.castlingRights, state.turn==='white');
if(score>bestScore) {
bestScore=score; bestMove=legal[0]; lastMove=bestMove;
}
}
// Make move
if(bestMove) {
let [fr, to] = bestMove.split(' ').map(s=>parseSquare(s));
validMove([fr.row,fr.col], [to.ray(),to.2], state.board[fr.row][fr.col].color==='white'?true:'black',state.board, state.turn, false, state.moveList, state.moveIndex, state.gameOver, state.enPassant, state.castlingRights, state.turn==='white');
// Update turn
state.turn = state.turn==='white'? 'black':'white';
// Save move history
state.moveList.push(bestMove);
state.moveIndex++;
renderBoard();
if(state.turn==='white') document.getElementById('turn').textContent='White';
else document.getElementById('turn').textContent='Black';
}
}
function minimax(fr, to, depth, alpha, beta, isMax, moves, turn, board, history, moveIdx, gameOver, enPassant, castlingRights, playerTurn) {
if(moves.length===0 || depth===0 || gameOver) return (isMax?0:-beta:+beta);
if(depth===1) return evaluate(board, turn);
// Capture moves first
let capMoves = moves.filter(m=>m.startsWith('e.p.'));
if(capMoves.length>0) {
for(let m of capMoves) {
let [f,t]=m.split(' ').map(s=>parseSquare(s));
let saved=state.board[f.row][f.col]; board[f.row][f.col]=null; validMove(f,t,board[f.row][f.col].color===playerTurn?true:'',board, playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights); if(minimax(to.row,to.2,depth-1,alpha,beta,!isMax, moves.filter(x=>x!=m), turn, board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white'))<=alpha) return alpha;
if(minimax(to.row,to.2,depth-1,alpha,beta,!isMax, moves.filter(x=>x!=m), playerTurn==='white'?t:'b',board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white'))>=beta) return beta;
}
}
// Castling
if(castlingRights[playerTurn==='white'?'w':'b'] && isCastleAllowed(fr.row,fr.col, playerTurn==='white'? 'w':'b')) {
let dir = (playerTurn==='white'?1:-1);
let newF = {...board[fr.row]};
let newK = {...board[fr.0]};
// kingside
if(dir===1) {
let kR=fr.row, kC=fr.col+2;
if(to.row===kR && to.col===kC) {
// check
if(!isInCheck(board, kR,kC, board[kR][kC].color)) {
validMove(fr, newF, board[fr.row], board[fr.0], playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights);
// update king/castling
board[kR][kC]=board[fr.row][fr.col];
board[fr.row][fr.col]=null;
if(isCastleW(board, kR,kC)) castlingRights[playerTurn==='white'?'w':'b']=false;
// move king
board[kR][kC]=board[fr.row][fr.col];
board[fr.row][fr.col]=null;
if(isCastleW(board, kR,kC)) castlingRights[playerTurn==='white'?'w':'b']=false;
// update
state.moveList.push(`O-O ${to}`);
// check turn
playerTurn = playerTurn==='white'? 'black':'white';
state.turn=playerTurn;
return evaluate(board, playerTurn, depth-1, alpha, beta, false, moves.filter(x=>x!='O-O'), playerTurn, board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white');
}
}
}
// queenside
if(dir===-1) {
let kR=fr.row, kC=fr.col-2;
if(to.row===kR && to.col===kC) {
if(!isInCheck(board, kR,kC, board[kR][kC].color)) {
validMove(fr, newF, board[fr.row], board[fr.0], playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights);
board[kR][kC]=board[fr.row][fr.col];
board[fr.row][fr.col]=null;
if(isCastleW(board, kR,kC)) castlingRights[playerTurn==='white'?'w':'b']=false;
board[kR][kC]=board[fr.row][fr.col];
board[fr.row][fr.col]=null;
if(isCastleW(board, kR,kC)) castlingRights[playerTurn==='white'?'w':'b']=false;
playerTurn = playerTurn==='white'? 'black':'white';
state.turn=playerTurn;
return evaluate(board, playerTurn, depth-1, alpha, beta, false, moves.filter(x=>x!='O-O'), playerTurn, board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white');
}
}
}
}
// Promotion
if(board[to.row][to.col] && board[to.row][to.col].type==='pawn') {
let promo = state.promotion || 'queen';
validMove(fr, newF, board[fr.row], board[fr.0], playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights);
// update
board[to.row][to.col]=promo=='queen'? {...board[to.row][to.col], type:'queen', promotion:'queen'} : {...board[to.row][to.col], type:promo, promotion:promo};
// check
if(!isInCheck(board, to.row,to.col, board[to.row][to.col].color===playerTurn?true:'black')) {
return evaluate(board, playerTurn, depth-1, alpha, beta, false, moves.filter(x=>x!='e.p.'), playerTurn==='white'?true:'black',board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white');
}
}
// Regular
let best = isMax ? -Infinity : Infinity;
for(let m of moves) {
let [f,t]=m.split(' ').map(s=>parseSquare(s));
let saved=state.board[f.row][f.col]; state.board[f.row][f.col]=null; validMove(f,t,board[f.row],board[f.0],playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights);
let v=minimax(to.row,to.2, depth-1, alpha, beta, !isMax, moves.filter(x=>x!=m), playerTurn==='white'?t:'b',board, history, moveIdx+1, gameOver, enPassant, castlingRights, playerTurn==='white');
if(isMax) best=Math.max(best,v); else best=Math.min(best,v);
state.board[f.row][f.col]=saved;
if(isMax && v<=alpha) break;
if(!isMax && v>=beta) break;
}
// Undo
for(let m of moves) {
let [f,t]=m.split(' ').map(s=>parseSquare(s));
state.board[f.row][f.col]=saved;
validMove(f,t,board[f.row],board[f.0],playerTurn, false, history, moveIdx, gameOver, enPassant, castlingRights);
}
return best;
}
function evaluate(board, player, depth) {
// Material
let total=0;
for(let r=0;r<8;r++) {
for(let c=0;c<8;c++) {
let p=board[r][c];
if(!p) continue;
let val = 0;
switch(p.type) {
case 'pawn': val=p.color==='white'?10: -10; break;
case 'knight': val=3; break;
case 'bishop': val=3; break;
case 'rook': val=5; break;
case 'queen': val=9; break;
case 'king': val=0; break;
}
if(p.promotion) {
let tp = p.promotion;
switch(tp) {
case 'queen': val+=8; break;
case 'rook': val+=5; break;
case 'bishop': val+=3; break;
case 'knight': val+=3; break;
}
}
total += p.color==='white'?val: -val;
}
}
// Check/checkmate
let inCheck = isInCheck(board, board.findIndex(r=>r[player==='white'?0:7][player==='white'?7:0])[0], player==='white'?1:0, board, player==='white'?true:'false');
if(inCheck) return -10000;
// Stalemate
let movesLeft = countMoves(board, player==='white'?true:'black');
if(movesLeft===0) return 0;
// Draws
if((player==='white'&&board[7][0]&&board[7][0].color==='white')||(player==='black'&&board[0][7]&&board[0][7].color==='black')) return 50000;
if(moves===[]) return 0;
// Win
if(inCheck && !isCheckmate(board, player==='white'?1:0)) return 10000;
if(isCheckmate(board, player==='white'?1:0)) return -10000;
return total + (depth*10);
}
function isCheckmate(board, color) {
// Simulate all moves
let moves = getLegalMoves(...board.findIndex(r=>r[color==='white'?0:7][color==='white'?7:0])[0], board.findIndex(r=>r[color==='white'?0:7][color==='white'?7:0])[1]);
for(m of moves) {
let [f,t]=m.split(' ').map(s=>parseSquare(s));
let saved=board[f.row][f.col]; board[f.row][f.col]=null; validMove(f,t,board[f.row],board[f.0],color==='white'?true:'black',false,[]);
if(!isInCheck(board, t.row,t.col,board[t.0][t.1])) return false;
// undo
board[f.row][f.col]=saved;
validMove(f,t,board[f.row],board[f.0],color==='white'?true:'black',false,[]);
}
return true;
}
function countMoves(board, color) {
let c=0; for(let r=0;r<8;r++) for(let c=0;c<8;c++) if(board[r][c]&&board[r][c].color==color) c++; return c;
}
/* === UI === */
document.getElementById('flip').addEventListener('click',()=>{state.gameOver=false; renderBoard();});
document.getElementById('undo').addEventListener('click',()=>undoMove());
document.getElementById('newGame').addEventListener('click',()=>newGame());
document.getElementById('mute').addEventListener('click',()=>{
state.mute=!state.mute;
document.getElementById('mute').style.color=state.mute? '#fff':'#000';
});
document.getElementById('difficulty').addEventListener('change',e=>aiDepth=+e.target.value);
document.getElementById('moveList').innerHTML=state.moveList.join('<br>');
document.getElementById('promotionOverlay').innerHTML=`<input id="promotionSquare" placeholder="e8" style="width:80px;margin-right:5px;"/>
<select id="promotionType">
<option value="queen">Queen</option>
<option value="rook">Rook</option>
<option value="bishop">Bishop</name>`;
document.getElementById('promotionOverlay').style.display='none';
document.getElementById('promotionOverlay').style.position='absolute';
let moveInput=null,promptSquare=null;
document.getElementById('moveList').addEventListener('click',e=>{
let txt=e.target.textContent.trim();
if(!txt) return;
let m = txt.split(' ');
if(m.length!=2) return;
let [f,t]=m;
let r=f[1],c=f[2];
if(r<0||r>7||c<0||c>7) return;
let sq = getSquare(r,c);
if(!state.moveList.includes(`${f} ${t}`)) return;
// legal?
if(state.moveList.includes(`${f} ${t}`)) {
// move
let ok=validMove([r,c],[r,c+parseInt(t[1])], state.board[r][c]?.color==='white'?true:'black',state.
<!-- agent-meta {"model":"liquid/lfm2-24b-a2b","provider":"lmstudio","persona":"basic","sessionId":"3c9a2999-1b58-445e-a1c3-80b25778362e","tokensIn":1222,"tokensOut":8394,"tokensTotal":9616,"cost":0,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-01T04:48:22.705Z"} -->