- 分享
新版
- @ 2026-10-4 13:08:01
const openingBook = { "e2e4":["e7e5","c7c5","e7e6","c7c6"], "d2d4":["d7d5","g8f6","e7e6"], "c2c4":["e7e5","d7d5"], "e2e4e7e5":["g1f3","b1c3"], "e2e4c7c5":["g1f3"] };
const startBoard = [ ['r','n','b','q','k','b','n','r'], ['p','p','p','p','p','p','p','p'], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['','','','','','','',''], ['P','P','P','P','P','P','P','P'], ['R','N','B','Q','K','B','N','R'], ];
let board = null; let turnWhite = true; let selected = null; let pgnMoves = []; let enPassantTarget = null; let castlingRights = {K:true,Q:true,k:true,q:true}; let gameOver = false; let aiDepth = 3; let playVsAI = true; let promoPending = null;
const info = document.getElementById('info'); const promoModal = document.getElementById('promoModal'); const $pieceLayer = document.getElementById('pieceLayer'); const$animLayer = document.getElementById('animLayer');
document.getElementById('resetBtn').onclick = resetGame; document.getElementById('exportPgnBtn').onclick = exportPGN; document.getElementById('modeBtn').onclick = ()=>{ playVsAI = !playVsAI; alert(playVsAI ? "人机对战:你白方,电脑黑方" : "双人对战"); render(); };
document.querySelectorAll('.promoPanel').forEach(b=>{ b.onclick = ()=>{ const pc = b.dataset.p; const {sr,sc,tr,tc,w} = promoPending; board[tr][tc] = w ? pc : pc.toLowerCase(); promoPending = null; $promoModal.style.display='none'; finishMove(sr,sc,tr,tc); } })
function isWhite(p){ return p && p === p.toUpperCase(); } function isBlack(p){ return p && p === p.toLowerCase(); } function copyBoard(b){ return JSON.parse(JSON.stringify(b)); }
function resetGame(){ board = copyBoard(startBoard); turnWhite = true; selected = null; pgnMoves = []; enPassantTarget = null; castlingRights = {K:true,Q:true,k:true,q:true}; gameOver = false; promoPending=null; render(); }
function findKing(b, white){ for(let r=0;r<8;r++)for(let c=0;c<8;c++){ const p = b[r][c]; if(p && p === (white?'K':'k')) return {r,c}; } return null; }
function isSquareAttacked(b, r, c, byWhite){ for(let i=0;i<8;i++)for(let j=0;j<8;j++){ const p = b[i][j]; if(!p) continue; if(byWhite ? !isWhite(p) : isWhite(p)) continue; const moves = getRawMoves(b,i,j); if(moves.some(([tr,tc])=>tr=r&&tc=c)) return true; } return false; }
function getRawMoves(b, r, c){ const p = b[r][c]; if(!p) return []; const moves = []; const w = isWhite(p); const type = p.toLowerCase(); function canGo(tr,tc){ if(tr<0||tr>=8||tc<0||tc>=8) return false; const t = b[tr][tc]; if(!t) return true; if(w?isWhite(t):isBlack(t)) return false; return true; } function addLine(dr,dc){ let tr=r+dr,tc=c+dc; while(tr>=0&&tr<8&&tc>=0&&tc<8){ const t = b[tr][tc]; if(!t){ moves.push([tr,tc]); } else{ if(w?isBlack(t):isWhite(t)) moves.push([tr,tc]); break; } tr+=dr;tc+=dc; } } if(type==='p'){ const dir = w ? -1 : 1; const startR = w?6:1; if(b[r+dir]?.[c]=''){ moves.push([r+dir,c]); if(r=startR && b[r+2dir][c]==='') moves.push([r+2dir,c]); } for(const dc of [-1,1]){ const tr=r+dir; const tc=c+dc; if(tr>=0&&tr<8&&tc>=0&&tc<8){ const t = b[tr][tc]; if(t && (w?isBlack(t):isWhite(t))) moves.push([tr,tc]); } } }else if(type==='n'){ const dirs=[[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]]; for(const [dr,dc] of dirs){ const tr=r+dr; const tc=c+dc; if(canGo(tr,tc)) moves.push([tr,tc]); } }else if(type==='b') addLine(-1,-1),addLine(-1,1),addLine(1,-1),addLine(1,1); else if(type==='r') addLine(-1,0),addLine(1,0),addLine(0,-1),addLine(0,1); else if(type==='q'){ addLine(-1,-1),addLine(-1,1),addLine(1,-1),addLine(1,1); addLine(-1,0),addLine(1,0),addLine(0,-1),addLine(0,1); }else if(type==='k'){ const dirs=[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]; for(const [dr,dc] of dirs){ const tr=r+dr; const tc=c+dc; if(canGo(tr,tc)) moves.push([tr,tc]); } } return moves; }
function getLegalMoves(b, r, c){ const p = b[r][c]; const w = isWhite(p); let raw = getRawMoves(b,r,c); const legal = []; for(const [tr,tc] of raw){ const sim = copyBoard(b); sim[tr][tc] = sim[r][c]; sim[r][c] = ''; const kingPos = findKing(sim,w); if(!isSquareAttacked(sim, kingPos.r, kingPos.c, !w)){ legal.push([tr,tc]); } } if(p.toLowerCase()='k' && r === (w?7:0)){ const kr = w ? 'K':'k'; const qr = w ? 'Q':'q'; if(castlingRights[kr]){ if(b[r][5]='' && b[r][6]=''){ if(!isSquareAttacked(b,r,4,!w) && !isSquareAttacked(b,r,5,!w) && !isSquareAttacked(b,r,6,!w)){ legal.push([r,6]); } } } if(castlingRights[qr]){ if(b[r][1]='' && b[r][2]='' && b[r][3]=''){ if(!isSquareAttacked(b,r,4,!w) && !isSquareAttacked(b,r,3,!w) && !isSquareAttacked(b,r,2,!w)){ legal.push([r,2]); } } } } if(p.toLowerCase()==='p' && enPassantTarget){ const epR = enPassantTarget.r; const epC = enPassantTarget.c; const dir = w ? -1 : 1; if(r+dir === epR && Math.abs(c - epC) === 1){ legal.push([epR, epC]); } } return legal; }
function getAllLegalMoves(isWhiteTurn, boardState=board){ const moves = []; for(let r=0;r<8;r++)for(let c=0;c<8;c++){ const p = boardState[r][c]; if(!p) continue; if(isWhiteTurn ? isWhite(p) : isBlack(p)){ const lm = getLegalMoves(boardState, r,c); for(const m of lm) moves.push({from:[r,c],to:m}); } } return moves; }
function inCheck(whiteTurn,b=board){ const kp = findKing(b, whiteTurn); return isSquareAttacked(b, kp.r, kp.c, !whiteTurn); }
// ==================== 增强评估函数 ==================== function evaluateBoard(b){ let score = 0; let whitePawns = Array(8).fill(0); let blackPawns = Array(8).fill(0); let endgame = true; let whitePieces=0,blackPieces=0;
for(let r=0;r<8;r++){
for(let c=0;c<8;c++){
const p = b[r][c];
if(!p)continue;
const tp = p.toLowerCase();
const w = isWhite(p);
let val = pieceValue[tp];
let posScore;
if(w){
posScore = posTable[tp][r][c];
}else{
posScore = posTable[tp][7-r][c];
val = -val; posScore = -posScore;
}
score += val + posScore;
if(tp!=='p'&&tp!=='k'){
endgame=false;
w?whitePieces++:blackPieces++;
}
if(tp==='p'){
if(w) whitePawns[c]++;
else blackPawns[c]++;
}
//中心格子d4 d5 e4 e5
if((c===3||c===4)&&(r===3||r===4)){
score += w ? 12 : -12;
}else if((c===2||c===5)&&(r===2||r===5)){
score += w ? 6 : -6;
}
}
}
//兵型惩罚:叠兵、孤立兵
for(let c=0;c<8;c++){
if(whitePawns[c]>1) score -= 20*(whitePawns[c]-1);
if(blackPawns[c]>1) score +=20*(blackPawns[c]-1);
if(whitePawns[c]>0){
if(whitePawns[c-1]===0&&whitePawns[c+1]===0) score -=15;
}
if(blackPawns[c]>0){
if(blackPawns[c-1]===0&&blackPawns[c+1]===0) score +=15;
}
}
//通路兵加分
for(let c=0;c<8;c++){
if(whitePawns[c]>0 && blackPawns[c]===0){
score += 25;
}
if(blackPawns[c]>0 && whitePawns[c]===0){
score -=25;
}
}
//残局王要主动
if(endgame){
const wk = findKing(b,true);
const bk = findKing(b,false);
const wkd = Math.abs(3.5-wk.r)+Math.abs(3.5-wk.c);
const bkd = Math.abs(3.5-bk.r)+Math.abs(3.5-bk.c);
score -= wkd*6;
score += bkd*6;
}
//将军惩罚
if(inCheck(true,b)) score-=70;
if(inCheck(false,b)) score+=70;
return score;
}
//Minimax αβ剪枝 function minimax(b, depth, alpha, beta, isMaxPlayer){ if(depth=0){ return evaluateBoard(b); } const moves = getAllLegalMoves(isMaxPlayer,b); if(moves.length=0){ if(inCheck(isMaxPlayer,b)){ return isMaxPlayer ? -10000 : 10000; }else{ return 0; } }
if(isMaxPlayer){
let bestVal = -Infinity;
for(const m of moves){
const sim = copyBoard(b);
sim[m.to[0]][m.to[1]] = sim[m.from[0]][m.from[1]];
sim[m.from[0]][m.from[1]] = '';
const val = minimax(sim, depth-1, alpha, beta, false);
bestVal = Math.max(bestVal,val);
alpha = Math.max(alpha,val);
if(beta <= alpha) break;
}
return bestVal;
}else{
let bestVal = Infinity;
for(const m of moves){
const sim = copyBoard(b);
sim[m.to[0]][m.to[1]] = sim[m.from[0]][m.from[1]];
sim[m.from[0]][m.from[1]] = '';
const val = minimax(sim, depth-1, alpha, beta, true);
bestVal = Math.min(bestVal,val);
beta = Math.min(beta,val);
if(beta <= alpha) break;
}
return bestVal;
}
}
function moveToStr(moveObj){
const f = String.fromCharCode(97+moveObj.from[1]);
const fr = 8-moveObj.from[0];
const t = String.fromCharCode(97+moveObj.to[1]);
const tr = 8-moveObj.to[0];
return ${f}${fr}${t}${tr};
}
async function aiMove(){ if(gameOver || turnWhite) return; const moves = getAllLegalMoves(false); if(moves.length===0) return;
let bestMove = null;
let bestScore = Infinity;
//开局库优先
if(pgnMoves.length <=2){
let lastStr = null;
if(pgnMoves.length>=1){
lastStr = pgnMoves[0];
}
if(lastStr && openingBook[lastStr]){
for(const resp of openingBook[lastStr]){
const found = moves.find(m=>moveToStr(m)===resp);
if(found){
bestMove = found;
break;
}
}
}
}
if(!bestMove){
//遍历全部走法
for(const m of moves){
const sim = copyBoard(board);
sim[m.to[0]][m.to[1]] = sim[m.from[0]][m.from[1]];
sim[m.from[0]][m.from[1]] = '';
const sc = minimax(sim, aiDepth-1, -Infinity, Infinity, true);
if(sc < bestScore){
bestScore = sc;
bestMove = m;
}
}
}
if(bestMove){
await executeMove(bestMove.from[0],bestMove.from[1],bestMove.to[0],bestMove.to[1]);
}
}
function updateGameStatus(){ const allMoves = getAllLegalMoves(turnWhite); const check = inCheck(turnWhite); if(allMoves.length === 0){ gameOver = true; if(check){ $info.textContent = turnWhite ? "黑方将死白方!" : "白方将死黑方!"; }else{$info.textContent = "逼和(无子可动)平局"; } }else{ if(check){ $info.textContent = turnWhite ? "白方回合,将军!" : "黑方回合,将军!"; }else{$info.textContent = turnWhite ? "白方回合" : "黑方回合"; } } }
function colToFile(c){ return String.fromCharCode(97 + c); } function moveToPGN(fromR,fromC,toR,toC,prom){ const piece = board[fromR][fromC]; const type = piece.toLowerCase(); const f = colToFile(fromC); const r = 8-fromR; const tf = colToFile(toC); const tr = 8-toR; let notation = ''; if(type === 'p'){ if(board[toR][toC] || (enPassantTarget && toR=enPassantTarget.r && toC=enPassantTarget.c)) notation += f; notation += tf + tr; if(prom) notation += '=' + prom.toUpperCase(); }else{ notation += piece.toUpperCase(); notation += tf+tr; } return notation; }
async function executeMove(sr,sc,tr,tc){ const piece = board[sr][sc]; const w = isWhite(piece); const type = piece.toLowerCase();
const animPiece = document.createElement("div");
animPiece.className = "piece";
animPiece.innerText = pieceChar[piece];
$animLayer.appendChild(animPiece);
animPiece.style.transform = `translate(${sc*64}px,${sr*64}px)`;
animPiece.offsetHeight;
animPiece.style.transform = `translate(${tc*64}px,${tr*64}px)`;
await sleep(300);
//王车易位
if(type === 'k' && Math.abs(sc-tc) ===2){
if(tc ===6){
board[sr][5] = board[sr][7];
board[sr][7] = '';
}else if(tc===2){
board[sr][3] = board[sr][0];
board[sr][0] = '';
}
castlingRights[w?'K':'k'] = false;
castlingRights[w?'Q':'q'] = false;
}
//吃过路兵
if(type==='p' && enPassantTarget && tr === enPassantTarget.r && tc === enPassantTarget.c){
const capR = w ? tr+1 : tr-1;
board[capR][tc] = '';
}
//兵升变
if(type==='p' && (tr===0||tr===7)){
promoPending = {sr,sc,tr,tc,w};
$promoModal.style.display='flex';
animPiece.remove();
board[sr][sc] = '';
render();
return;
}
board[tr][tc] = piece;
board[sr][sc] = '';
animPiece.remove();
finishMove(sr,sc,tr,tc);
}
function finishMove(sr,sc,tr,tc){ const pc = board[tr][tc]; const type = pc.toLowerCase(); const w = isWhite(pc); enPassantTarget = null; if(type==='p' && Math.abs(sr-tr)===2){ enPassantTarget = {r:(sr+tr)/2, c:sc}; } if(type === 'k') castlingRights[w?'K':'k'] = castlingRights[w?'Q':'q'] = false; if(type === 'r'){ if(sr === 7 && sc === 0) castlingRights.Q = false; if(sr ===7 && sc ===7) castlingRights.K = false; if(sr ===0 && sc ===0) castlingRights.q = false; if(sr ===0 && sc ===7) castlingRights.k = false; } const pgnStr = moveToPGN(sr,sc,tr,tc, board[tr][tc]); pgnMoves.push(pgnStr); turnWhite = !turnWhite; selected = null; render(); if(!gameOver && playVsAI && !turnWhite){ setTimeout(aiMove,400); } }
function sleep(ms){ return new Promise(resolve=>setTimeout(resolve,ms)); }
function clickSquare(r,c){ if(gameOver) return; if(playVsAI && !turnWhite) return; if(selected){ const sr = selected.r, sc = selected.c; const leg = getLegalMoves(board, sr, sc); const ok = leg.some(([tr,tc])=>tr=r&&tc=c); if(ok){ executeMove(sr,sc,r,c); return; } const p = board[r][c]; if(p && (turnWhite?isWhite(p):isBlack(p))){ selected = {r,c}; render(); return; } selected = null; render(); return; } const p = board[r][c]; if(p && (turnWhite?isWhite(p):isBlack(p))){ selected = {r,c}; render(); } }
function render(){ pieceLayer.innerHTML = ''; let possibleMoves = []; if(selected){ possibleMoves = getLegalMoves(board, selected.r, selected.c); } for(let r=0;r<8;r++){ for(let c=0;c<8;c++){ const sq = document.createElement('div'); const light = (r+c)%2=0; sq.className = 'square ' + (light?'light':'dark'); const isPossible = possibleMoves.some(([tr,tc])=>tr=r&&tc===c); if(isPossible){ if(board[r][c]) sq.classList.add('possible-capture'); else sq.classList.add('possible'); } sq.onclick = ()=>clickSquare(r,c); $board.appendChild(sq); const pc = board[r][c]; if(pc){ const piece = document.createElement("div"); piece.className = "piece"; piece.innerText = pieceChar[pc]; piece.style.transform = `translate(${c*64}px,pieceLayer.appendChild(piece); } } } updateGameStatus(); $pgnBox.value = pgnMoves.join(' '); }
function exportPGN(){ alert(pgnMoves.join(' ')); }
resetGame();