console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>⚔️魔改国际象棋 觉醒版(麒麟马修复+悔棋)</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%;font-family:system-ui,sans-serif;background:#0f1220;color:#fff;overflow-x:hidden}
#app{display:flex;flex-direction:column;align-items:center;padding:8px;min-height:100%}
.top-bar{width:min(94vw,420px);display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
h1{font-size:17px;background:linear-gradient(90deg,#ffd700,#ff8c00);-webkit-background-clip:text;color:transparent}
.info{font-size:13px;height:20px;color:#ffd060}
.chance-info{font-size:13px;color:#8cf;margin-bottom:6px}
.chessboard{width:min(92vw,380px);height:min(92vw,380px);display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);border:4px solid #8b6f47;border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,0.5);position:relative;touch-action:none;background:#8b6f47}
.cell{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:clamp(22px,8vw,42px);line-height:1;cursor:pointer;user-select:none;position:relative;overflow:hidden;transition:background .15s}
.light{background:#ead8b8}
.dark{background:#a67c52}
.cell:hover{filter:brightness(1.08)}
.selected{box-shadow:inset 0 0 0 4px #4285f4}
.mark{background:rgba(66,133,244,0.45)!important}
.capture{background:rgba(220,35,35,0.55)!important}
.check{box-shadow:inset 0 0 0 4px #ff2222}
.piece{position:absolute;pointer-events:none;text-shadow:0 2px 3px rgba(0,0,0,0.4);transition:transform 0.22s cubic-bezier(.34,1.56,.64,1)}
.piece.lift{transform:translateY(-10px) scale(1.12);z-index:20}
.piece.awake{filter:drop-shadow(0 0 7px #ffd700) drop-shadow(0 0 12px #ffaa00);background:linear-gradient(180deg,#fff2a8,#ffb700);-webkit-background-clip:text;color:transparent}
.phantom{opacity:0.4}
.frozen{filter:grayscale(1) brightness(0.6)}
.particle{position:fixed;width:8px;height:8px;border-radius:50%;pointer-events:none;animation:explode .6s ease-out forwards;z-index:999}
@keyframes explode{0%{transform:translate(0,0) scale(1);opacity:1}100%{transform:translate(var(--tx),var(--ty)) scale(0);opacity:0}}
.card-area{width:min(94vw,420px);margin-top:12px}
.card-title{font-size:13px;margin-bottom:4px;color:#aaa}
.card-row{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px}
.card{min-width:82px;padding:7px 5px;border:1px solid;border-radius:8px;font-size:11px;text-align:center;cursor:pointer;flex-shrink:0}
.card.white{background:#1a2a4a;border-color:#4a7fc9;color:#cfe2ff}
.card.black{background:#2a1a3a;border-color:#8b4ac9;color:#e2cfff}
.card.awake-card{background:linear-gradient(135deg,#3a2a10,#6b4a10);border-color:#ffd700;color:#ffe89a}
.card.disabled{opacity:.3;pointer-events:none}
.card.active{outline:2px solid #4cf;outline-offset:1px}
.tip{font-size:12px;color:#8cf;margin-top:6px;min-height:16px;text-align:center}
.ctrl{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
button{padding:7px 14px;border:none;border-radius:8px;background:linear-gradient(180deg,#3388dd,#2266bb);color:white;font-size:14px;box-shadow:0 2px 6px rgba(0,0,0,0.3)}
button:disabled{background:#444;opacity:0.4;cursor:not-allowed}
.btn-undo{background:linear-gradient(180deg,#dd8833,#bb6622);}
.skill-modal{position:fixed;inset:0;background:rgba(0,0,0,0.8);display:none;align-items:center;justify-content:center;z-index:1000;padding:20px}
.skill-box{width:min(88vw,360px);background:#1e2438;border-radius:12px;padding:18px;border:1px solid #445}
.skill-box h3{font-size:18px;margin-bottom:6px;color:#ffd700}
.skill-desc{font-size:14px;line-height:1.5;color:#ccc;margin-bottom:14px}
.skill-btns{display:flex;gap:10px;justify-content:flex-end}
.skill-btns button{padding:6px 14px;font-size:13px}
.btn-cancel{background:#555}
.promotion-modal{position:fixed;inset:0;background:rgba(0,0,0,.8);display:none;align-items:center;justify-content:center;gap:12px;z-index:1000}
.prom-piece{font-size:40px;padding:10px;background:#2a3048;border-radius:8px;cursor:pointer}
.gameover-modal{position:fixed;inset:0;background:rgba(0,0,0,0.85);display:none;align-items:center;justify-content:center;z-index:1100;padding:20px}
.gameover-box{width:min(80vw,320px);background:#181c2e;border-radius:14px;padding:24px;text-align:center;border:1px solid #ffc145}
.gameover-box h2{color:#ffd700;margin-bottom:12px}
.gameover-box p{color:#ddd;margin-bottom:20px;line-height:1.6}
.gameover-box button{padding:9px 20px;font-size:15px}
</style>
</head>
<body>
<div id="app">
<div class="top-bar">
<h1>⚔️ 魔改国际象棋 觉醒版</h1>
<div class="info" id="info">白方先行|第1轮</div>
</div>
<div class="chance-info" id="chanceInfo">🤍白抽奖次数:1|🖤黑抽奖次数:1</div>
<div class="chessboard" id="board"></div>
<div class="tip" id="tip">点击棋子移动,点击卡牌查看技能</div>
<div class="card-area">
<div class="card-title">🤍 白方手牌|牌堆剩余:<span id="whiteDeckCount">0</span></div>
<div class="card-row" id="whiteCards"></div>
<div class="card-title" style="margin-top:8px">🖤 黑方手牌|牌堆剩余:<span id="blackDeckCount">0</span></div>
<div class="card-row" id="blackCards"></div>
</div>
<div class="ctrl">
<button onclick="restart()">重新开局</button>
<button class="btn-undo" onclick="undoMove()" id="undoBtn">悔棋</button>
<button id="btnDrawW" onclick="drawFromDeck('w')">🤍 白方抽一张</button>
<button id="btnDrawB" onclick="drawFromDeck('b')">🖤 黑方抽一张</button>
<button onclick="cancelSkill()">取消选择</button>
</div>
</div>
<div class="skill-modal" id="skillModal">
<div class="skill-box">
<h3 id="modalSkillName">技能名称</h3>
<div class="skill-desc" id="modalSkillDesc">技能描述</div>
<div class="skill-btns">
<button class="btn-cancel" onclick="closeSkillModal()">取消</button>
<button onclick="confirmUseSkill()">使用技能</button>
</div>
</div>
</div>
<div class="promotion-modal" id="promModal">
<div class="prom-piece" data-p="Q">♕</div>
<div class="prom-piece" data-p="R">♖</div>
<div class="prom-piece" data-p="B">♗</div>
<div class="prom-piece" data-p="N">♘</div>
</div>
<div class="gameover-modal" id="gameOverModal">
<div class="gameover-box">
<h2 id="gameOverTitle">游戏结束</h2>
<p id="gameOverText"></p>
<button onclick="restart(); closeGameOverModal()">再来一局</button>
</div>
</div>
<script>
const pieceChar = {K:"♔", Q:"♕", R:"♖", B:"♗", N:"♘", P:"♙",k:"♚", q:"♛", r:"♜", b:"♝", n:"♞", p:"♟︎"};
const allSkillPool = [
{name:"冲锋", desc:"选择己方一个兵,向前直冲3格,不能越子、不能吃子", id:"charge", type:"self_pawn"},
{name:"幻影", desc:"选择己方一个棋子移动,移动后留下虚影,1回合内敌方无法吃掉它", id:"phantom", type:"self_move"},
{name:"护盾", desc:"己方王获得护盾,本回合内免疫所有将军", id:"shield", type:"instant"},
{name:"驰援", desc:"选择己方一个车,瞬移到任意一个己方兵的正后方一格", id:"reinforce", type:"self_rook"},
{name:"圣疗", desc:"复活一个本局被吃掉的己方兵,回到初始行的空格", id:"heal", type:"instant"},
{name:"御守", desc:"选中一个己方棋子,本回合不会被敌方技能指定", id:"guard", type:"self_one"},
{name:"疾行", desc:"选中己方一个非王棋子,本回合可以走两次", id:"rush", type:"self_one"},
{name:"启明", desc:"查看棋盘上所有敌方棋子合法走法,持续一轮", id:"reveal", type:"instant"},
{name:"引渡", desc:"将己方任意一个被冻结棋子解除冻结", id:"unfreeze", type:"self_one"},
{name:"诱捕", desc:"选择对方一个兵,冻结1回合无法移动", id:"trap", type:"enemy_pawn"},
{name:"暗影", desc:"激活后,本回合正常走棋后可额外再移动一次马", id:"shadow", type:"instant"},
{name:"反噬", desc:"激活后,本回合你吃掉对方棋子的位置,对方1回合内不能走回来", id:"reflect", type:"instant"},
{name:"毒刃", desc:"本回合移动的兵,落地后可毒杀斜向相邻1个敌方兵", id:"poison", type:"instant"},
{name:"裂地", desc:"己方所有在初始行的兵,全部向前前进1格(不能越子)", id:"quake", type:"instant"},
{name:"瞬移", desc:"选择己方一个象,瞬移到棋盘上任意同色空格", id:"blink", type:"self_bishop"},
{name:"黑雾", desc:"隐藏己方所有棋子的走法提示,敌方看不到你的可走格子", id:"fog", type:"instant"},
{name:"噬魂", desc:"吃掉敌方棋子时额外抽一张牌", id:"soul", type:"instant"},
{name:"地刺", desc:"在一个空格布置地刺,敌方棋子踩上去直接被消灭", id:"spike", type:"empty_cell"},
{name:"狂猎", desc:"选中己方马,这一轮可以跳过棋子直接跨越吃子", id:"hunt", type:"self_knight"},
{name:"夺势", desc:"强制交换双方一个非王棋子的位置,目标不能是王", id:"seize", type:"swap_piece"}
];
const awakeCards = [
{name:"貔貅·兵觉醒", desc:"兵觉醒:可以后退一格(后退不能吃子),保留向前斜向吃子", id:"awake_p", piece:"p", beast:"貔貅", isAwake:true},
{name:"麒麟·马觉醒", desc:"马觉醒:整条L路径上所有格子都能落脚,路径格子只能走空格;只有原版L终点可以吃子", id:"awake_n", piece:"n", beast:"麒麟", isAwake:true},
{name:"凤凰·象觉醒", desc:"象觉醒:走完正常斜线之后,可以额外选择直线一格移动", id:"awake_b", piece:"b", beast:"凤凰", isAwake:true},
{name:"穷奇·车觉醒", desc:"车觉醒:吃子后可额外再向前移动1格", id:"awake_r", piece:"r", beast:"穷奇", isAwake:true},
{name:"应龙·后觉醒", desc:"后觉醒:每轮免费瞬移一次,瞬移只能到空格,不能吃子", id:"awake_q", piece:"q", beast:"应龙", isAwake:true},
{name:"玄武·王觉醒", desc:"王觉醒:周围一圈己方棋子获得1次免死护盾", id:"awake_k", piece:"k", beast:"玄武", isAwake:true}
];
let whiteDeck = [],blackDeck = [],handWhite = [],handBlack = [];
let whiteChance = 1, blackChance = 1;
let board = [],turn = "w",round = 1,selected = null,legalMoves = [];
let castling = {wK:true,wQ:true,bK:true,bQ:true},enPassant = null,pendingSkill = null,activeSkill = null;
let promPending = null,phantomSet = new Set(),frozenSet = new Set(),guardSet = new Set();
let shieldSide = null,reflectBan = null,shadowExtra = false,poisonActive = false,awakeSet = new Set();
let spikeCells = new Set(),fogActive = false,revealActive = false,capturedWhitePawns = 0,capturedBlackPawns = 0,gameOver = false;
let yinglongTeleportReady = false;
let xuanwuShieldCells = new Set();
let phoenixExtraStep = null;
// ========== 悔棋历史栈 ==========
let historyStack = [];
function saveSnapshot(){
const snap = {
board: JSON.parse(JSON.stringify(board)),
turn, round,
castling: JSON.parse(JSON.stringify(castling)),
enPassant: enPassant ? {...enPassant} : null,
awakeSet: new Set(awakeSet),
spikeCells: new Set(spikeCells),
phantomSet: new Set(phantomSet),
frozenSet: new Set(frozenSet),
shieldSide, reflectBan, shadowExtra, poisonActive,
fogActive, revealActive,
yinglongTeleportReady,
xuanwuShieldCells: new Set(xuanwuShieldCells),
capturedWhitePawns, capturedBlackPawns
};
historyStack.push(snap);
}
function undoMove(){
if(historyStack.length === 0){
setTip("没有可以悔棋的步骤!");
return;
}
const last = historyStack.pop();
board = last.board;
turn = last.turn;
round = last.round;
castling = last.castling;
enPassant = last.enPassant;
awakeSet = last.awakeSet;
spikeCells = last.spikeCells;
phantomSet = last.phantomSet;
frozenSet = last.frozenSet;
shieldSide = last.shieldSide;
reflectBan = last.reflectBan;
shadowExtra = last.shadowExtra;
poisonActive = last.poisonActive;
fogActive = last.fogActive;
revealActive = last.revealActive;
yinglongTeleportReady = last.yinglongTeleportReady;
xuanwuShieldCells = last.xuanwuShieldCells;
capturedWhitePawns = last.capturedWhitePawns;
capturedBlackPawns = last.capturedBlackPawns;
selected = null;
legalMoves = [];
phoenixExtraStep = null;
activeSkill = null;
pendingSkill = null;
setTip("✅ 已悔棋!");
render();
}
function initBoard(){
const start = [["r","n","b","q","k","b","n","r"],["p","p","p","p","p","p","p","p"],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],[null,null,null,null,null,null,null,null],["P","P","P","P","P","P","P","P"],["R","N","B","Q","K","B","N","R"]];
board = JSON.parse(JSON.stringify(start));
capturedWhitePawns = 0; capturedBlackPawns = 0; gameOver = false;
yinglongTeleportReady = false;
xuanwuShieldCells.clear();
phoenixExtraStep = null;
historyStack = [];
}
function initDecks(){
const basePool = [...allSkillPool, ...awakeCards];
let poolW = JSON.parse(JSON.stringify(basePool));
shuffle(poolW);
whiteDeck = poolW.slice(0,16);
let poolB = JSON.parse(JSON.stringify(basePool));
shuffle(poolB);
blackDeck = poolB.slice(0,16);
}
function shuffle(arr){for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[arr[i],arr[j]]=[arr[j],arr[i]];}}
function drawFromDeck(side){
if(gameOver) return;
if(side === "w"){
if(whiteChance <= 0) { setTip("白方抽奖次数用完!"); return; }
if(whiteDeck.length <= 0){ setTip("白方牌堆空!"); return; }
whiteChance --;
const card = whiteDeck.pop(); handWhite.push(card);
setTip("🤍白方抽卡成功!");
}else{
if(blackChance <= 0) { setTip("黑方抽奖次数用完!"); return; }
if(blackDeck.length <= 0){ setTip("黑方牌堆空!"); return; }
blackChance --;
const card = blackDeck.pop(); handBlack.push(card);
setTip("🖤黑方抽卡成功!");
}
render();
}
function addChanceByLost(side, lostPieceChar){
const c = lostPieceChar.toLowerCase();
if(c === 'p') return;
if(c === 'q'){
if(side === 'w') whiteChance +=2;
else blackChance +=2;
}else{
if(side === 'w') whiteChance +=1;
else blackChance +=1;
}
}
function isWhite(p){return p && p === p.toUpperCase();}
function isBlack(p){return p && p === p.toLowerCase();}
function sameSide(a,b){if(!a||!b)return false;return isWhite(a)===isWhite(b);}
function findKing(side){const target=side==="w"?"K":"k";for(let r=0;r<8;r++)for(let c=0;c<8;c++)if(board[r][c]===target)return {r,c};return null;}
function checkGameState(){
const wKing=findKing("w"),bKing=findKing("b");
if(!wKing){showGameOver("🖤黑方胜利!","白方国王被击杀,游戏结束");return true;}
if(!bKing){showGameOver("🤍白方胜利!","黑方国王被击杀,游戏结束");return true;}
if(inCheck("w")&&!hasAnyLegalMoves("w")){showGameOver("🖤黑方胜利!","白方被将死!");return true;}
if(inCheck("b")&&!hasAnyLegalMoves("b")){showGameOver("🤍白方胜利!","黑方被将死!");return true;}
return false;
}
function hasAnyLegalMoves(side){
const targetChar=side==="w"?isWhite:isBlack;
for(let r=0;r<8;r++)for(let c=0;c<8;c++){const p=board[r][c];if(p&&targetChar(p)){const moves=legalMovesAt(r,c);if(moves.length>0)return true;}}
return false;
}
function showGameOver(title,text){gameOver=true;document.getElementById("gameOverTitle").innerText=title;document.getElementById("gameOverText").innerText=text;document.getElementById("gameOverModal").style.display="flex";}
function closeGameOverModal(){document.getElementById("gameOverModal").style.display="none";}
function rawMoves(r,c,attackOnly=false){
const p=board[r][c];if(!p)return [];const res=[];const white=isWhite(p);const type=p.toLowerCase();const isAwake=awakeSet.has(r+","+c);
const dirR=[[1,0],[-1,0],[0,1],[0,-1]],dirB=[[1,1],[1,-1],[-1,1],[-1,-1]],dirQ=dirR.concat(dirB),dirN=[[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2],[-1,-2]];
function slide(dirs){for(let [dr,dc] of dirs){let nr=r+dr,nc=c+dc;while(nr>=0&&nr<8&&nc>=0&&nc<8){if(!board[nr][nc]){res.push({r:nr,c:nc});}else{if(!sameSide(p,board[nr][nc]))res.push({r:nr,c:nc});break;}nr+=dr;nc+=dc;}}}
if(type==="r")slide(dirR);
if(type==="b")slide(dirB);
if(type==="q")slide(dirQ);
if(type==="n"){
if(isAwake){
for(let [dr,dc] of dirN){
let pathPoints = [];
if(Math.abs(dr) === 2 && Math.abs(dc) === 1){
pathPoints.push([r + (dr>0?1:-1), c]);
pathPoints.push([r + dr, c + dc]);
}else if(Math.abs(dr) ===1 && Math.abs(dc) ===2){
pathPoints.push([r, c + (dc>0?1:-1)]);
pathPoints.push([r + dr, c + dc]);
}
for(let idx=0; idx<pathPoints.length; idx++){
const [nr, nc] = pathPoints[idx];
if(nr<0 || nr>=8 || nc<0 || nc>=8) continue;
const isEndPoint = (idx === pathPoints.length - 1);
if(isEndPoint){
if(!sameSide(p, board[nr][nc])){
res.push({r:nr, c:nc, knightEnd:true});
}
}else{
if(!board[nr][nc]){
res.push({r:nr, c:nc, knightMid:true});
}
}
}
}
}else{
for(let [dr,dc] of dirN){let nr=r+dr,nc=c+dc;if(nr<0||nr>=8||nc<0||nc>=8)continue;if(!sameSide(p,board[nr][nc]))res.push({r:nr,c:nc});}
}
}
if(type==="k"){for(let [dr,dc] of dirQ){let nr=r+dr,nc=c+dc;if(nr<0||nr>=8||nc<0||nc>=8)continue;if(!sameSide(p,board[nr][nc]))res.push({r:nr,c:nc});}
if(!attackOnly){
if(white){
if(castling.wK&&!inCheck("w")&&!board[7][5]&&!board[7][6]&&!isAttacked(7,5,true)&&!isAttacked(7,6,true))res.push({r:7,c:6,castle:"wk"});
if(castling.wQ&&!inCheck("w")&&!board[7][1]&&!board[7][2]&&!board[7][3]&&!isAttacked(7,3,true)&&!isAttacked(7,2,true))res.push({r:7,c:2,castle:"wq"});
}else{
if(castling.bK&&!inCheck("b")&&!board[0][5]&&!board[0][6]&&!isAttacked(0,5,false)&&!isAttacked(0,6,false))res.push({r:0,c:6,castle:"bk"});
if(castling.bQ&&!inCheck("b")&&!board[0][1]&&!board[0][2]&&!board[0][3]&&!isAttacked(0,3,false)&&!isAttacked(0,2,false))res.push({r:0,c:2,castle:"bq"});
}
}
}
if(type==="p"){
const step=white?-1:1;const nr=r+step;
if(!attackOnly){
if(nr>=0&&nr<8&&!board[nr][c]){res.push({r:nr,c:c});}
const startRow=white?6:1;
if(r===startRow&&!board[r+step*2][c])res.push({r:r+step*2,c:c,double:true});
if(isAwake){
const backR = r-step;
if(backR >=0 && backR <8 && !board[backR][c]){
res.push({r:backR,c:c,pawnBack:true});
}
}
}
for(let dc of [-1,1]){
let nc=c+dc;if(nc<0||nc>=8)continue;
if(nr>=0&&nr<8&&board[nr][nc]&&!sameSide(p,board[nr][nc]))res.push({r:nr,c:nc});
if(!attackOnly&&enPassant&&enPassant.r===nr&&enPassant.c===nc)res.push({r:nr,c:nc,ep:true});
}
}
return res;
}
function isAttacked(r,c,byWhite){for(let i=0;i<8;i++)for(let j=0;j<8;j++){const p=board[i][j];if(!p)continue;if(byWhite&&!isWhite(p))continue;if(!byWhite&&!isBlack(p))continue;if(rawMoves(i,j,true).some(m=>m.r===r&&m.c===c))return true;}return false;}
function inCheck(side){if(shieldSide===side)return false;const k=findKing(side);if(!k)return false;return isAttacked(k.r,k.c,side==="b");}
function legalMovesAt(r,c){
if(phoenixExtraStep && phoenixExtraStep.r === r && phoenixExtraStep.c === c){
const out = [];
const dirR=[[1,0],[-1,0],[0,1],[0,-1]];
for(let [dr,dc] of dirR){
const nr=r+dr,nc=c+dc;
if(nr>=0&&nr<8&&nc>=0&&nc<8 && !board[nr][nc]){
out.push({r:nr,c:nc,phoenixAdd:true});
}
}
return out;
}
const raw=rawMoves(r,c);const out=[];const p=board[r][c];const side=isWhite(p)?"w":"b";
for(let m of raw){
if(reflectBan&&reflectBan.side!==side&&reflectBan.r===m.r&&reflectBan.c===m.c)continue;
if(board[m.r][m.c]&&phantomSet.has(m.r+","+m.c))continue;
const backup=JSON.parse(JSON.stringify(board));
board[m.r][m.c]=board[r][c];board[r][c]=null;
if(m.ep)board[r][m.c]=null;
if(m.castle){if(m.castle==="wk"){board[7][5]="R";board[7][7]=null;}if(m.castle==="wq"){board[7][3]="R";board[7][0]=null;}if(m.castle==="bk"){board[0][5]="r";board[0][7]=null;}if(m.castle==="bq"){board[0][3]="r";board[0][0]=null;}}
const bad=inCheck(side);board=backup;if(!bad)out.push(m);
}
if(p.toLowerCase()==="q" && awakeSet.has(r+","+c) && yinglongTeleportReady){
for(let i=0;i<8;i++)for(let j=0;j<8;j++){
if(!board[i][j]) out.push({r:i,c:j,yinglongTp:true});
}
}
return out;
}
function explode(x,y){for(let i=0;i<16;i++){const el=document.createElement("div");el.className="particle";const ang=Math.random()*Math.PI*2;const dist=25+Math.random()*45;el.style.setProperty("--tx",Math.cos(ang)*dist+"px");el.style.setProperty("--ty",Math.sin(ang)*dist+"px");el.style.left=x+"px";el.style.top=y+"px";const colors=["#ff4444","#ffdd44","#44ddff","#ff88ff","#ffaa00"];el.style.background=colors[Math.floor(Math.random()*colors.length)];document.body.appendChild(el);setTimeout(()=>el.remove(),600);}}
function openSkillModal(idx,side){if(gameOver)return;if(side!==turn){setTip("不是你的回合!");return;}const hand=side==="w"?handWhite:handBlack;pendingSkill={...hand[idx],side,idx};document.getElementById("modalSkillName").textContent=pendingSkill.name;document.getElementById("modalSkillDesc").textContent=pendingSkill.desc;document.getElementById("skillModal").style.display="flex";}
function closeSkillModal(){pendingSkill=null;document.getElementById("skillModal").style.display="none";}
function confirmUseSkill(){activeSkill=pendingSkill;closeSkillModal();selected=null;legalMoves=[];if(activeSkill.type==="instant"){executeInstantSkill();return;}
if(activeSkill.isAwake){
const side=activeSkill.side;
const isW=side==="w";
setTip(`觉醒:请选择己方${activeSkill.piece.toUpperCase()}棋子`);
render();
return;
}
const tips={self_pawn:"请选择己方的兵",self_move:"请选择己方棋子",self_rook:"请选择己方的车",self_bishop:"请选择己方的象",self_one:"请选择己方棋子",self_knight:"请选择己方的马",enemy_pawn:"请选择对方的兵",empty_cell:"选择一个空格放置地刺",swap_piece:"选择第一个目标棋子(非王)"};
setTip(tips[activeSkill.type]||"请选择目标");render();
}
function cancelSkill(){activeSkill=null;pendingSkill=null;selected=null;legalMoves=[];phoenixExtraStep=null;setTip("已取消");render();}
function executeInstantSkill(){
const side=activeSkill.side;const isW=side==="w";
if(activeSkill.id==="shield"){shieldSide=side;consumeSkill();setTip("王已获得护盾");render();return;}
if(activeSkill.id==="shadow"){shadowExtra=true;consumeSkill();setTip("暗影激活:走棋后可额外移动一次马");render();return;}
if(activeSkill.id==="reflect"){reflectBan={};consumeSkill();setTip("反噬已激活");render();return;}
if(activeSkill.id==="poison"){poisonActive=true;consumeSkill();setTip("毒刃已激活,本回合兵带毒");render();return;}
if(activeSkill.id==="quake"){const row=isW?6:1;const step=isW?-1:1;let moved=false;for(let c=0;c<8;c++){const p=board[row][c];if(p&&p.toLowerCase()==="p"&&sameSide(p,isW?"P":"p")){if(!board[row+step][c]){board[row+step][c]=p;board[row][c]=null;moved=true;}}}consumeSkill();setTip(moved?"裂地发动!":"没有可移动的兵");nextTurn();return;}
if(activeSkill.id==="heal"){if(isW&&capturedWhitePawns<=0){setTip("没有可复活的兵");return;}if(!isW&&capturedBlackPawns<=0){setTip("没有可复活的兵");return;}const row=isW?6:1;for(let c=0;c<8;c++){if(!board[row][c]){board[row][c]=isW?"P":"p";if(isW)capturedWhitePawns--;else capturedBlackPawns--;consumeSkill();setTip("圣疗成功,兵已复活");nextTurn();return;}}setTip("初始行已满,无法复活");return;}
if(activeSkill.id==="reveal"){revealActive=true;consumeSkill();setTip("启明生效,敌方走法全部显示");render();return;}
if(activeSkill.id==="fog"){fogActive=true;consumeSkill();setTip("黑雾生效,对手看不到你的走法");render();return;}
if(activeSkill.id==="soul"){consumeSkill();setTip("噬魂激活:本次吃子后自动摸一张牌");render();return;}
}
function useSkillOnCell(r,c){
if(!activeSkill||gameOver)return false;const side=activeSkill.side;const p=board[r][c];const isW=side==="w";const posKey=r+","+c;
if(activeSkill.isAwake){
const targetType=activeSkill.piece;
if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()!==targetType){setTip("请选择己方"+targetType.toUpperCase()+"棋子");return false;}
awakeSet.add(posKey);
consumeSkill();
setTip(`${activeSkill.beast}觉醒成功!`);
if(activeSkill.piece === "k"){
for(let dr=-1;dr<=1;dr++)for(let dc=-1;dc<=1;dc++){
const nr=r+dr,nc=c+dc;
if(nr>=0&&nr<8&&nc>=0&&nc<8) xuanwuShieldCells.add(nr+","+nc);
}
}
if(activeSkill.piece === "q"){
yinglongTeleportReady = true;
}
render();
return true;
}
if(activeSkill.id==="charge"){if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()!=="p"){setTip("请选择己方的兵");return false;}const tr=r-3;if(!isW) tr=r+3;
if(tr<0 || tr>=8){setTip("超出棋盘");return false;}
let blocked = false;
if(isW){for(let i=r-1;i>=tr;i--){if(board[i][c]){blocked=true;break;}}}
else {for(let i=r+1;i<=tr;i++){if(board[i][c]){blocked=true;break;}}}
if(blocked){setTip("路径被阻挡");return false;}
board[tr][c]=p;board[r][c]=null;consumeSkill();setTip("冲锋成功!");nextTurn();return true;
}
if(activeSkill.id==="phantom"){if(!p||!sameSide(p,isW?"P":"p")){setTip("请选择己方棋子");return false;}selected={r,c};legalMoves=legalMovesAt(r,c);setTip("选择移动目标,移动后留下虚影");render();return true;}
if(activeSkill.id==="reinforce"){if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()!=="r"){setTip("请选择己方车");return false;}let target=null;
if(isW){for(let cc=0;cc<8;cc++){if(board[r+1]&&board[r+1][cc]==="P"){target={r:r-1,c:cc};break;}}}
else{for(let cc=0;cc<8;cc++){if(board[r-1]&&board[r-1][cc]==="p"){target={r:r+1,c:cc};break;}}}
if(!target){setTip("附近没有己方兵");return false;}
board[target.r][target.c]=p;board[r][c]=null;consumeSkill();setTip("驰援到位!");nextTurn();return true;}
if(activeSkill.id==="trap"){if(!p||sameSide(p,isW?"P":"p")||p.toLowerCase()!=="p"){setTip("请选择对方的兵");return false;}frozenSet.add(posKey);consumeSkill();setTip("该兵已被冻结");render();return true;}
if(activeSkill.id==="blink"){if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()!=="b"){setTip("请选择己方象");return false;}selected={r,c};legalMoves=[];const color=(r+c)%2;for(let i=0;i<8;i++)for(let j=0;j<8;j++)if((i+j)%2===color&&!board[i][j])legalMoves.push({r:i,c:j});setTip("选择同色空格瞬移");render();return true;}
if(activeSkill.id==="guard"){if(!p||!sameSide(p,isW?"P":"p")){setTip("请选择己方棋子");return false;}guardSet.add(posKey);consumeSkill();setTip("御守加持,该棋子免疫敌方技能");render();return true;}
if(activeSkill.id==="unfreeze"){if(!p||!sameSide(p,isW?"P":"p")){setTip("请选择己方棋子");return false;}frozenSet.delete(posKey);consumeSkill();setTip("解除冻结!");render();return true;}
if(activeSkill.id==="rush"){if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()==="k"){setTip("不能选王");return false;}consumeSkill();setTip("疾行:该棋子本回合可以移动两次");render();return true;}
if(activeSkill.id==="hunt"){if(!p||!sameSide(p,isW?"P":"p")||p.toLowerCase()!=="n"){setTip("请选择己方马");return false;}consumeSkill();setTip("狂猎:这匹马可以跨子吃子");render();return true;}
if(activeSkill.id==="spike"){if(p){setTip("必须选空格!");return false;}spikeCells.add(posKey);consumeSkill();setTip("地刺布置完成!");render();return true;}
return false;
}
function consumeSkill(){const hand=activeSkill.side==="w"?handWhite:handBlack;hand.splice(activeSkill.idx,1);activeSkill=null;}
function onCellClick(r,c,dom){
if(gameOver||promPending)return;
if(activeSkill){if(useSkillOnCell(r,c))return;}
if(frozenSet.has(r+","+c)){setTip("该棋子被冻结");return;}
if(phoenixExtraStep){
const move=legalMoves.find(m=>m.r===r&&m.c===c);
if(move){
saveSnapshot();
const fr=phoenixExtraStep.r,fc=phoenixExtraStep.c;
const piece=board[fr][fc];
board[r][c]=piece;
board[fr][fc]=null;
awakeSet.delete(fr+","+fc);
awakeSet.add(r+","+c);
phoenixExtraStep=null;
setTip("凤凰象额外直线移动完成");
finishTurn();
return;
}else{
phoenixExtraStep=null;
setTip("放弃凤凰象额外移动");
finishTurn();
return;
}
}
if(selected){
const move=legalMoves.find(m=>m.r===r&&m.c===c);
if(move){
saveSnapshot();
const pieceDom=dom.querySelector(".piece");
if(pieceDom){
pieceDom.classList.add("lift");
setTimeout(()=>{pieceDom.classList.remove("lift");executeMove(selected.r,selected.c,r,c,move,dom);},210);
}else{
executeMove(selected.r,selected.c,r,c,move,dom);
}
return;
}
if(board[r][c]&&sameSide(board[selected.r][selected.c],board[r][c])){selected={r,c};legalMoves=legalMovesAt(r,c);render();return;}
selected=null;legalMoves=[];render();return;
}
if(board[r][c]){const owner=isWhite(board[r][c])?"w":"b";if(owner===turn){selected={r,c};legalMoves=legalMovesAt(r,c);}}
render();
}
function executeMove(fr,fc,tr,tc,move,dom){
const piece=board[fr][fc];
const captured=board[tr][tc];
const whiteTurn=isWhite(piece);
const tkey=tr+","+tc;
const fkey=fr+","+fc;
if(captured){
awakeSet.delete(tkey);
if(xuanwuShieldCells.has(tkey)){
setTip("玄武护盾抵挡本次击杀!");
xuanwuShieldCells.delete(tkey);
board[fr][fc]=null;
finishTurn();
return;
}
}
if(spikeCells.has(tkey)){
if(captured){
const rect=dom.getBoundingClientRect();explode(rect.left+rect.width/2,rect.top+rect.height/2);
addChanceByLost( isWhite(captured) ? "w" : "b", captured );
}
board[fr][fc]=null;
spikeCells.delete(tkey);
setTip("踩到地刺,棋子被消灭!");
finishTurn();
return;
}
if(captured){
const rect=dom.getBoundingClientRect();explode(rect.left+rect.width/2,rect.top+rect.height/2);
addChanceByLost( isWhite(captured) ? "w" : "b", captured );
if(captured.toLowerCase()==="p"){if(isWhite(captured))capturedWhitePawns++;else capturedBlackPawns++;}
if(activeSkill&&activeSkill.id==="soul")drawFromDeck(turn);
if(activeSkill&&activeSkill.id==="reflect")reflectBan={r:tr,c:tc,side:turn};
}
if(activeSkill&&activeSkill.id==="phantom"){phantomSet.add(tkey);consumeSkill();}
if(poisonActive&&piece.toLowerCase()==="p"){for(let dc=-1;dc<=1;dc++){const nc=tc+dc;if(nc>=0&&nc<8&&board[tr][nc]&&!sameSide(piece,board[tr][nc])&&board[tr][nc].toLowerCase()==="p"){board[tr][nc]=null;setTip("毒发!敌方兵被毒死");break;}}poisonActive=false;}
if(piece.toLowerCase()==="r" && captured && awakeSet.has(fkey)){
const dr=tr>fr?1:-1;
const nr=tr+dr;
if(nr>=0&&nr<8&&!board[nr][tc]){
board[nr][tc]=piece;
board[tr][tc]=null;
awakeSet.delete(fkey);
awakeSet.add(nr+","+tc);
tr = nr;
tkey = nr+","+tc;
}
}else{
if(awakeSet.has(fkey)){
awakeSet.delete(fkey);
awakeSet.add(tkey);
}
}
if(move.ep){board[fr][tc]=null;enPassant=null;}else enPassant=null;
if(move.castle){if(move.castle==="wk"){board[7][5]="R";board[7][7]=null;castling.wK=false;}if(move.castle==="wq"){board[7][3]="R";board[7][0]=null;castling.wQ=false;}if(move.castle==="bk"){board[0][5]="r";board[0][7]=null;castling.bK=false;}if(move.castle==="bq"){board[0][3]="r";board[0][0]=null;castling.bQ=false;}}
if(piece==="K"){castling.wK=false;castling.wQ=false;}if(piece==="k"){castling.bK=false;castling.bQ=false;}
if(piece==="R"&&fc===0)castling.wQ=false;if(piece==="R"&&fc===7)castling.wK=false;if(piece==="r"&&fc===0)castling.bQ=false;if(piece==="r"&&fc===7)castling.bK=false;
if(piece.toLowerCase()==="p"&&Math.abs(fr-tr)===2)enPassant={r:(fr+tr)/2,c:tc};
if(move.yinglongTp){
yinglongTeleportReady = false;
}
board[tr][tc]=piece;board[fr][fc]=null;
if(piece.toLowerCase()==="b" && awakeSet.has(tkey) && !move.phoenixAdd){
phoenixExtraStep = {r:tr,c:tc};
selected=null;legalMoves=legalMovesAt(tr,tc);
setTip("凤凰象!可以再走一格直线,点击格子移动,或者点取消结束");
render();
return;
}
if(piece==="P"&&tr===0){promPending={r:tr,c:tc,side:"w"};document.getElementById("promModal").style.display="flex";return;}
if(piece==="p"&&tr===7){promPending={r:tr,c:tc,side:"b"};document.getElementById("promModal").style.display="flex";return;}
finishTurn();
}
function finishTurn(){if(checkGameState())return;if(shadowExtra){shadowExtra=false;selected=null;legalMoves=[];setTip("暗影效果:请移动你的马");render();return;}nextTurn();}
function nextTurn(){
turn=turn==="w"?"b":"w";
if(turn==="w")round++;
phantomSet.clear();
frozenSet.clear();
shieldSide=null;
revealActive=false;
fogActive=false;
selected=null;
legalMoves=[];
activeSkill=null;
phoenixExtraStep=null;
yinglongTeleportReady = true;
setTip(inCheck(turn)?"⚠️ 将军!":"");
render();
}
document.querySelectorAll(".prom-piece").forEach(el=>{el.onclick=()=>{
saveSnapshot();
const ch=el.dataset.p;
board[promPending.r][promPending.c]=promPending.side==="w"?ch:ch.toLowerCase();
promPending=null;
document.getElementById("promModal").style.display="none";
if(checkGameState())return;
finishTurn();
};});
function setTip(txt){document.getElementById("tip").innerText=txt;}
function render(){
const boardEl=document.getElementById("board");boardEl.innerHTML="";const wKing=findKing("w"),bKing=findKing("b");
for(let r=0;r<8;r++)for(let c=0;c<8;c++){
const cell=document.createElement("div");cell.className="cell "+((r+c)%2===0?"light":"dark");const posKey=r+","+c;
if(spikeCells.has(posKey))cell.style.boxShadow="inset 0 0 0 3px #992222";
if(wKing&&r===wKing.r&&c===wKing.c&&inCheck("w"))cell.classList.add("check");
if(bKing&&r===bKing.r&&c===bKing.c&&inCheck("b"))cell.classList.add("check");
if(selected&&selected.r===r&&selected.c===c)cell.classList.add("selected");
const m=legalMoves.find(x=>x.r===r&&x.c===c);
if(m){let hideMoveHint=false;if(fogActive&&turn==="b"&&selected&&isBlack(board[selected.r][selected.c]))hideMoveHint=true;if(!hideMoveHint){if(board[r][c])cell.classList.add("capture");else cell.classList.add("mark");}}
const p=board[r][c];
if(p){const span=document.createElement("span");span.className="piece";span.innerText=pieceChar[p];if(awakeSet.has(posKey))span.classList.add("awake");if(phantomSet.has(posKey))span.classList.add("phantom");if(frozenSet.has(posKey))span.classList.add("frozen");cell.appendChild(span);}
cell.onclick=()=>onCellClick(r,c,cell);boardEl.appendChild(cell);
}
const wCardWrap=document.getElementById("whiteCards"),bCardWrap=document.getElementById("blackCards");
wCardWrap.innerHTML="";bCardWrap.innerHTML="";
handWhite.forEach((card,idx)=>{const div=document.createElement("div");div.className="card white "+(card.isAwake?"awake-card":"");div.innerText=card.name;div.onclick=()=>openSkillModal(idx,"w");wCardWrap.appendChild(div);})
handBlack.forEach((card,idx)=>{const div=document.createElement("div");div.className="card black "+(card.isAwake?"awake-card":"");div.innerText=card.name;div.onclick=()=>openSkillModal(idx,"b");bCardWrap.appendChild(div);})
document.getElementById("whiteDeckCount").innerText=whiteDeck.length;
document.getElementById("blackDeckCount").innerText=blackDeck.length;
document.getElementById("info").innerText=(turn==="w"?"🤍白方":"🖤黑方")+"回合|第"+round+"轮";
document.getElementById("chanceInfo").innerText=`🤍白抽奖次数:${whiteChance}|🖤黑抽奖次数:${blackChance}`;
document.getElementById("btnDrawW").disabled = whiteChance <= 0;
document.getElementById("btnDrawB").disabled = blackChance <= 0;
document.getElementById("undoBtn").disabled = historyStack.length === 0;
}
function restart(){
initBoard();
initDecks();
handWhite=[];handBlack=[];
whiteChance=1; blackChance=1;
selected=null;legalMoves=[];turn="w";round=1;
enPassant=null;phantomSet.clear();frozenSet.clear();awakeSet.clear();spikeCells.clear();
shieldSide=null;reflectBan=null;shadowExtra=false;poisonActive=false;fogActive=false;revealActive=false;
castling={wK:true,wQ:true,bK:true,bQ:true};
render();
}
initBoard();
initDecks();
render();
</script>
</body>
</html>