SOURCE

console 命令行工具 X clear

                    
>
console
const SIZE = 3;
const MAX_PIECES = 4;
const EMPTY = 0;
const BLACK = 1;
const WHITE = 2;

let board = [];
let currentPlayer = BLACK;
let gameOver = false;
let blackCount = 0;
let whiteCount = 0;
let mode = "place";

let historyBoards = new Set();

const infoEl = document.getElementById("info");
const boardEl = document.getElementById("board");
const btnRestart = document.getElementById("btnRestart");
const btnPlace = document.getElementById("btnPlace");
const btnPush = document.getElementById("btnPush");

btnRestart.onclick = initGame;
btnPlace.onclick = function(){
    if(gameOver) return;
    if(blackCount<MAX_PIECES || whiteCount<MAX_PIECES){
        mode="place";
        updateUi();
    }
};
btnPush.onclick = function(){
    if(gameOver) return;
    if(blackCount>=MAX_PIECES && whiteCount>=MAX_PIECES){
        mode="push";
        updateUi();
    }
};

function getBoardSnapshot(){
    return board.flat().join(",");
}

// 深度拷贝棋盘,用于回滚
function cloneBoard(src){
    return src.map(row=>[...row]);
}

function initGame(){
    board = [];
    for(let i=0;i<SIZE;i++) board.push([EMPTY,EMPTY,EMPTY]);
    currentPlayer = BLACK;
    gameOver = false;
    blackCount = 0;
    whiteCount = 0;
    mode = "place";
    historyBoards.clear();
    historyBoards.add(getBoardSnapshot());
    renderBoard();
    updateUi();
}

function renderBoard(){
    boardEl.innerHTML = "";
    for(let r=0;r<3;r++){
        for(let c=0;c<3;c++){
            const div = document.createElement("div");
            div.className = "cell";
            const val = board[r][c];
            if(val === BLACK){ div.innerText = "●"; div.classList.add("black"); }
            if(val === WHITE){ div.innerText = "○"; div.classList.add("white"); }
            div.onclick = ()=>cellClick(r,c);
            boardEl.appendChild(div);
        }
    }
}

function updateUi(){
    if(gameOver) return;
    const playName = currentPlayer === BLACK ? "黑方 ●" : "白方 ○";
    const modeStr = mode === "place" ? "【落子模式】" : "【推挤模式】";
    infoEl.innerText = `${playName} ${modeStr}|黑${blackCount}/${MAX_PIECES} 白${whiteCount}/${MAX_PIECES}`;
}

function checkVictory(){
    if(blackCount <= 0) return WHITE;
    if(whiteCount <= 0) return BLACK;
    return null;
}

function placePiece(r,c){
    if(board[r][c] !== EMPTY) return;
    if(currentPlayer === BLACK && blackCount >= MAX_PIECES) return;
    if(currentPlayer === WHITE && whiteCount >= MAX_PIECES) return;

    board[r][c] = currentPlayer;
    if(currentPlayer === BLACK) blackCount++;
    else whiteCount++;

    renderBoard();
    currentPlayer = currentPlayer === BLACK ? WHITE : BLACK;

    if(blackCount >= MAX_PIECES && whiteCount >= MAX_PIECES){
        mode = "push";
    }
    historyBoards.add(getBoardSnapshot());
    updateUi();
}

function doAction(sr, sc, dr, dc){
    // 移动前完整保存现场,用于回滚
    const oldBoard = cloneBoard(board);
    const oldBlackCnt = blackCount;
    const oldWhiteCnt = whiteCount;

    const self = board[sr][sc];
    const tr = sr + dr;
    const tc = sc + dc;

    if(tr<0||tr>=SIZE||tc<0||tc>=SIZE){
        infoEl.innerText="⚠️不能移动到棋盘外面";
        return;
    }

    if(board[tr][tc] === EMPTY){
        board[tr][tc] = self;
        board[sr][sc] = EMPTY;
    }
    else if(board[tr][tc] !== self){
        const t2r = tr + dr;
        const t2c = tc + dc;
        if(t2r <0 || t2r >= SIZE || t2c <0 || t2c >= SIZE){
            const victim = board[tr][tc];
            board[tr][tc] = self;
            board[sr][sc] = EMPTY;
            if(victim === BLACK) blackCount--;
            if(victim === WHITE) whiteCount--;
        }else{
            if(board[t2r][t2c] !== EMPTY){
                infoEl.innerText="⚠️后方被挡住,推不动对方棋子";
                return;
            }
            board[t2r][t2c] = board[tr][tc];
            board[tr][tc] = self;
            board[sr][sc] = EMPTY;
        }
    }
    else{
        infoEl.innerText="⚠️不能移动到己方棋子位置";
        return;
    }

    renderBoard();

    let win = checkVictory();
    if(win){
        gameOver=true;
        infoEl.innerText = win===BLACK?"黑方 ● 胜利!":"白方 ○ 胜利!";
        return;
    }

    // 检测是否出现旧局面
    const snap = getBoardSnapshot();
    if(historyBoards.has(snap)){
        // 回滚到移动之前状态,回合不切换,游戏继续
        board = oldBoard;
        blackCount = oldBlackCnt;
        whiteCount = oldWhiteCnt;
        renderBoard();
        infoEl.innerText="⚠️出现重复局面,本次移动无效,请重新操作";
        return;
    }
    historyBoards.add(snap);

    currentPlayer = currentPlayer===BLACK?WHITE:BLACK;
    updateUi();
}

let selectR = -1;
let selectC = -1;

function cellClick(r,c){
    if(gameOver) return;

    if(mode === "place"){
        placePiece(r,c);
        selectR=-1;selectC=-1;
        return;
    }

    if(selectR === -1 && selectC === -1){
        if(board[r][c] !== currentPlayer){
            infoEl.innerText="⚠️请点击你自己的棋子";
            return;
        }
        selectR = r;
        selectC = c;
        infoEl.innerText="✅已选中,点击相邻格子:空格直接移动、敌方就推动";
    }else{
        const dr = r - selectR;
        const dc = c - selectC;
        if(Math.abs(dr)+Math.abs(dc) !== 1){
            selectR=-1;
            selectC=-1;
            updateUi();
            return;
        }
        doAction(selectR, selectC, dr, dc);
        selectR = -1;
        selectC = -1;
    }
}

initGame();
<h2>领地牢笼</h2>
<div id="info"></div>
<div id="board"></div>
<div>
<button id="btnRestart">重新开局</button>
<button id="btnPlace">落子模式</button>
<button id="btnPush">推挤模式</button>
</div>
<div class="desc">
落子:黑白轮流各下4子。下满4子后开始推挤。胜利:把对方棋子全部推出棋盘。
</div>
*{box-sizing:border-box;font-family:system-ui, sans-serif;}
body{background:#1a1a27;color:#eee;display:flex;flex-direction:column;align-items:center;padding:20px;}
#board{display:grid;grid-template-columns:repeat(3, 1fr);gap:6px;margin:16px 0;max-width:320px;width:100%;}
.cell{
  aspect-ratio:1/1;background:#2c2c44;border-radius:8px;
  display:flex;align-items:center;justify-content:center;
  font-size:40px;font-weight:bold;cursor:pointer;
  border:2px solid #444466;
}
.cell:hover{border-color:#7777aa;}
.cell.black{color:#111;background:#bbbbbb;}
.cell.white{color:#ffffff;background:#224488;}
#info{font-size:17px;min-height:45px;}
button{padding:8px 12px;font-size:15px;margin:4px;border:none;border-radius:6px;background:#4466bb;color:#fff;cursor:pointer;}
.desc{margin-top:14px;font-size:12px;color:#aaaaaa;max-width:400px;line-height:1.45;}