找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
查看: 429|回复: 34

[求助] 求热心网友,转运中国象棋专业版2.0 - html原码

[复制链接]
发表于 11 小时前 | 显示全部楼层 |阅读模式
本帖最后由 crtrccrtt 于 2026-3-19 11:13 编辑

求热心网友,转运中国象棋专业版2.0 - html原码

https://www.52pojie.cn/thread-2097870-1-1.html


五子棋专业版2.4 - 网页版



经使用,这个象棋,水平太差,不推荐使用。
发表于 11 小时前 | 显示全部楼层
期待热心网友...........
回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
我是热心网友

中国象棋专业版2.0.rar

6.85 KB, 下载次数: 65, 下载积分: 无忧币 -2

点评

AI执红,第一步居然是帅五进一  发表于 8 小时前
非常热  详情 回复 发表于 10 小时前
好人  发表于 11 小时前
很热  发表于 11 小时前

评分

参与人数 3无忧币 +15 收起 理由
cuicongyuan + 5
it323 + 5 赞一个!
yyz2191958 + 5

查看全部评分

回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>中国象棋专业版2.0</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
        }
        body {
            background: #1a1a1a;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: '楷体', 'KaiTi', '华文楷体', serif;
        }
        .game-container {
            background: transparent;
            padding: 20px;
            border-radius: 0;
            box-shadow: none;
            border: none;
            display: flex;
            gap: 40px;
            align-items: center;
        }
        /* 强制canvas显示尺寸与像素尺寸一致,防止浏览器缩放导致模糊 */
        canvas {
            display: block;
            width: 560px;
            height: 630px;
            border-radius: 18px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.3);
            background: #f0d9b5;
            cursor: pointer;
        }
        .right-panel {
            display: flex;
            flex-direction: column;
            gap: 15px;
            align-items: center;
            justify-content: center;
            min-width: 160px;
        }
        .turn-indicator {
            background: #5e3e1e;
            color: #f9eac3;
            padding: 8px 12px;
            border-radius: 50px;
            font-size: 22px;
            letter-spacing: 2px;
            box-shadow: inset 0 -3px 0 #2f1f0e, 0 6px 10px black;
            border-bottom: 2px solid #b5914c;
            width: 100%;
            text-align: center;
            margin-bottom: 5px;
        }
        .mode-selector, .difficulty-selector {
            background: #c29e5e;
            border-radius: 40px;
            padding: 8px 12px;
            font-size: 20px;
            font-weight: bold;
            color: #2b1a0a;
            border: 2px solid #efd39e;
            box-shadow: inset 0 -2px 0 #6f4f2e, 0 6px 8px black;
            cursor: pointer;
            outline: none;
            width: 100%;
            text-align: center;
        }
        .action-btn {
            background: #c29e5e;
            border: none;
            font-size: 22px;
            font-family: '楷体', serif;
            font-weight: bold;
            padding: 8px 0;
            border-radius: 40px;
            color: #2b1a0a;
            box-shadow: 0 8px 0 #6f4f2e, 0 10px 15px black;
            cursor: pointer;
            transition: 0.08s linear;
            border: 1px solid #efd39e;
            letter-spacing: 2px;
            width: 100%;
        }
        .action-btn:active {
            transform: translateY(7px);
            box-shadow: 0 2px 0 #5a3f22, 0 8px 12px black;
        }
    </style>
</head>
<body>
    <div class="game-container">
        <canvas id="chessCanvas" width="560" height="630"></canvas>
        <div class="right-panel">
            <div class="turn-indicator" id="turnText">红方走棋</div>
            <select id="modeSelect" class="mode-selector">
                <option value="human" selected>双人对战</option>
                <option value="ai-black">&#129302;AI执黑</option>
                <option value="ai-red">&#129302;AI执红</option>
            </select>
            <select id="difficultySelect" class="difficulty-selector">
                <option value="1">&#129302;初级1层</option>
                <option value="2" selected>&#129302;中级2层</option>
                <option value="3">&#129302;高级3层</option>
            </select>
            <button class="action-btn" id="undoBtn">悔棋</button>
            <button class="action-btn restart-btn" id="restartBtn">新局</button>
        </div>
    </div>

    <script>
        (function() {
            // ---------- 棋盘配置 ----------
            const canvas = document.getElementById('chessCanvas');
            const ctx = canvas.getContext('2d');
            const turnText = document.getElementById('turnText');
            const modeSelect = document.getElementById('modeSelect');
            const difficultySelect = document.getElementById('difficultySelect');
            const undoBtn = document.getElementById('undoBtn');
            const restartBtn = document.getElementById('restartBtn');

            const ROWS = 10;
            const COLS = 9;
            const GRID_SIZE = 56;
            const OFFSET_X = 56;
            const OFFSET_Y = 63;

            // ---------- 全局状态 ----------
            let board = [];
            let currentPlayer = 'r';
            let selectedPiece = null;
            let gameOver = false;
            let winner = null;               
            let history = [];

            // AI 相关
            let gameMode = 'human';
            let aiDifficulty = 2;
            let isAIPlaying = false;

            let killerMoves = [];
            const MAX_DEPTH = 10;
            function initKillers() {
                killerMoves = [];
                for (let d = 0; d <= MAX_DEPTH; d++) killerMoves[d] = [null, null];
            }
            initKillers();

            const pieceValue = {
                'k': 10000, 'a': 200, 'b': 200, 'n': 500, 'r': 1000, 'c': 800, 'p': 100
            };

            function initBoard() {
                board = Array(ROWS).fill().map(() => Array(COLS).fill(null));

                board[0][0] = 'br'; board[0][1] = 'bn'; board[0][2] = 'bb'; board[0][3] = 'ba'; board[0][4] = 'bk';
                board[0][5] = 'ba'; board[0][6] = 'bb'; board[0][7] = 'bn'; board[0][8] = 'br';
                board[2][1] = 'bc'; board[2][7] = 'bc';
                board[3][0] = 'bp'; board[3][2] = 'bp'; board[3][4] = 'bp'; board[3][6] = 'bp'; board[3][8] = 'bp';

                board[9][0] = 'rr'; board[9][1] = 'rn'; board[9][2] = 'rb'; board[9][3] = 'ra'; board[9][4] = 'rk';
                board[9][5] = 'ra'; board[9][6] = 'rb'; board[9][7] = 'rn'; board[9][8] = 'rr';
                board[7][1] = 'rc'; board[7][7] = 'rc';
                board[6][0] = 'rp'; board[6][2] = 'rp'; board[6][4] = 'rp'; board[6][6] = 'rp'; board[6][8] = 'rp';

                currentPlayer = 'r';
                selectedPiece = null;
                gameOver = false;
                winner = null;
                history = [];
                turnText.innerText = '红方走棋';
                initKillers();
            }

            // ---------- 绘制函数 (锐化处理) ----------
            function drawBoard() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                drawGrid();
                drawPieces();
                if (selectedPiece) {
                    const { row, col } = selectedPiece;
                    // 选中高亮:坐标对齐到半像素,阴影调小
                    ctx.save();
                    ctx.strokeStyle = '#f5e56b';
                    ctx.lineWidth = 3;
                    ctx.shadowBlur = 6;          // 减小阴影模糊
                    ctx.shadowColor = 'gold';
                    ctx.beginPath();
                    // 使用半像素坐标使圆弧边缘更清晰
                    ctx.arc(OFFSET_X + col * GRID_SIZE + 0.5, OFFSET_Y + row * GRID_SIZE + 0.5, 24, 0, 2 * Math.PI);
                    ctx.stroke();
                    ctx.restore();
                }
            }

            function drawGrid() {
                ctx.save();
                ctx.strokeStyle = '#6b4f32';
                ctx.lineWidth = 1;               // 线宽设为1,配合半像素坐标获得锐利直线

                // 绘制横线 (坐标+0.5使线条精确覆盖像素)
                for (let i = 0; i < ROWS; i++) {
                    ctx.beginPath();
                    ctx.moveTo(OFFSET_X + 0.5, OFFSET_Y + i * GRID_SIZE + 0.5);
                    ctx.lineTo(OFFSET_X + (COLS - 1) * GRID_SIZE + 0.5, OFFSET_Y + i * GRID_SIZE + 0.5);
                    ctx.stroke();
                }
                // 绘制竖线
                for (let i = 0; i < COLS; i++) {
                    ctx.beginPath();
                    ctx.moveTo(OFFSET_X + i * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
                    ctx.lineTo(OFFSET_X + i * GRID_SIZE + 0.5, OFFSET_Y + (ROWS - 1) * GRID_SIZE + 0.5);
                    ctx.stroke();
                }

                // 绘制楚河汉界 (半透明背景文字,稍微下移一点避开中线,保留原风格)
                ctx.font = '35px "楷体", "KaiTi"';
                ctx.fillStyle = '#4e2f15';
                ctx.globalAlpha = 0.3;
                const text = '楚 河         汉 界';
                const textWidth = ctx.measureText(text).width;
                const boardLeft = OFFSET_X;
                const boardRight = OFFSET_X + (COLS - 1) * GRID_SIZE;
                const centerX = (boardLeft + boardRight) / 2;
                const textX = centerX - textWidth / 2;
                // 保持文字整数坐标防止模糊 (半透明背景无需锐利)
                ctx.fillText(text, textX, OFFSET_Y + 5 * GRID_SIZE - 13);
                ctx.globalAlpha = 1;

                // 九宫对角线 (同样用半像素坐标)
                ctx.beginPath();
                ctx.moveTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
                ctx.lineTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 2 * GRID_SIZE + 0.5);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
                ctx.lineTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 2 * GRID_SIZE + 0.5);
                ctx.stroke();

                ctx.beginPath();
                ctx.moveTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 7 * GRID_SIZE + 0.5);
                ctx.lineTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 9 * GRID_SIZE + 0.5);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 7 * GRID_SIZE + 0.5);
                ctx.lineTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 9 * GRID_SIZE + 0.5);
                ctx.stroke();

                // 绘制炮/兵/卒标记点 (使用半像素坐标,使圆点清晰)
                ctx.fillStyle = '#6b4f32';
                ctx.shadowBlur = 0; // 标记点不要阴影
                for (let r of [2, 7]) {
                    for (let c of [1, 7]) {
                        ctx.beginPath();
                        ctx.arc(OFFSET_X + c * GRID_SIZE + 0.5, OFFSET_Y + r * GRID_SIZE + 0.5, 5, 0, 2 * Math.PI);
                        ctx.fill();
                    }
                }
                for (let r of [3, 6]) {
                    for (let c of [0, 2, 4, 6, 8]) {
                        ctx.beginPath();
                        ctx.arc(OFFSET_X + c * GRID_SIZE + 0.5, OFFSET_Y + r * GRID_SIZE + 0.5, 4, 0, 2 * Math.PI);
                        ctx.fill();
                    }
                }
                ctx.restore();
            }

            function drawPieces() {
                for (let r = 0; r < ROWS; r++) {
                    for (let c = 0; c < COLS; c++) {
                        const piece = board[r][c];
                        if (!piece) continue;
                        // 圆心坐标对齐半像素,使棋子边缘清晰
                        const x = OFFSET_X + c * GRID_SIZE + 0.5;
                        const y = OFFSET_Y + r * GRID_SIZE + 0.5;

                        ctx.save();
                        // 棋子阴影减弱
                        ctx.shadowColor = '#333';
                        ctx.shadowBlur = 4;        // 原为8,减半减少模糊
                        ctx.shadowOffsetY = 2;      // 原为3
                        ctx.beginPath();
                        ctx.arc(x, y, 24, 0, 2 * Math.PI);
                        ctx.fillStyle = piece[0] === 'r' ? '#c33' : '#222';
                        ctx.fill();
                        ctx.shadowBlur = 2;          // 描边阴影稍弱
                        ctx.strokeStyle = '#f1d28c';
                        ctx.lineWidth = 2;
                        ctx.stroke();

                        // 绘制文字前清除阴影,保证文字清晰
                        ctx.shadowBlur = 0;
                        ctx.shadowOffsetY = 0;
                        ctx.font = '35px "楷体", "KaiTi", "华文楷体", serif';
                        ctx.fillStyle = piece[0] === 'r' ? '#FFD966' : '#ddd';
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'middle';
                        // 微调偏移量,配合半像素圆心,文字仍保留轻微偏移以居中
                        ctx.fillText(pieceToChinese(piece), x - 1, y + 5);
                        ctx.restore();
                    }
                }
            }

            function pieceToChinese(p) {
                const map = {
                    'r': { 'k': '帅', 'a': '仕', 'b': '相', 'n': '马', 'r': '车', 'c': '炮', 'p': '兵' },
                    'b': { 'k': '将', 'a': '士', 'b': '象', 'n': '马', 'r': '车', 'c': '炮', 'p': '卒' }
                };
                return map[p[0]][p[1]] || '?';
            }

            // ---------- 规则核心函数 (保持不变,保证AI逻辑正确) ----------
            function getPieceRawMoves(boardState, row, col, piece) {
                const color = piece[0];
                const type = piece[1];
                const moves = [];

                if (type === 'k') {
                    const palaceRows = (color === 'r') ? [7,8,9] : [0,1,2];
                    const palaceCols = [3,4,5];
                    const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
                    for (let [dx,dy] of dirs) {
                        const nr = row+dx, nc = col+dy;
                        if (palaceRows.includes(nr) && palaceCols.includes(nc)) {
                            const target = boardState[nr]?.[nc];
                            if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                        }
                    }
                }
                else if (type === 'a') {
                    const palaceRows = (color === 'r') ? [7,8,9] : [0,1,2];
                    const palaceCols = [3,4,5];
                    const dirs = [[-1,-1],[-1,1],[1,-1],[1,1]];
                    for (let [dx,dy] of dirs) {
                        const nr = row+dx, nc = col+dy;
                        if (palaceRows.includes(nr) && palaceCols.includes(nc)) {
                            const target = boardState[nr]?.[nc];
                            if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                        }
                    }
                }
                else if (type === 'b') {
                    const dirs = [[-2,-2],[-2,2],[2,-2],[2,2]];
                    for (let [dx,dy] of dirs) {
                        const nr = row+dx, nc = col+dy;
                        if (nr<0 || nr>=ROWS || nc<0 || nc>=COLS) continue;
                        if (color === 'r' && nr < 5) continue;
                        if (color === 'b' && nr > 4) continue;
                        const eyeRow = row + dx/2, eyeCol = col + dy/2;
                        if (boardState[eyeRow][eyeCol] !== null) continue;
                        const target = boardState[nr][nc];
                        if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                    }
                }
                else if (type === 'n') {
                    const jumps = [[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]];
                    for (let [dx,dy] of jumps) {
                        const nr = row+dx, nc = col+dy;
                        if (nr<0 || nr>=ROWS || nc<0 || nc>=COLS) continue;
                        let blockRow = row, blockCol = col;
                        if (Math.abs(dx) === 2) { blockRow = row + (dx>0?1:-1); blockCol = col; }
                        else { blockRow = row; blockCol = col + (dy>0?1:-1); }
                        if (boardState[blockRow][blockCol] !== null) continue;
                        const target = boardState[nr][nc];
                        if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                    }
                }
                else if (type === 'r') {
                    const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
                    for (let [dx,dy] of dirs) {
                        let nr = row+dx, nc = col+dy;
                        while (nr>=0 && nr<ROWS && nc>=0 && nc<COLS) {
                            const target = boardState[nr][nc];
                            if (!target) moves.push({toX:nr, toY:nc});
                            else {
                                if (target[0] !== color) moves.push({toX:nr, toY:nc});
                                break;
                            }
                            nr += dx; nc += dy;
                        }
                    }
                }
                else if (type === 'c') {
                    const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
                    for (let [dx,dy] of dirs) {
                        let nr = row+dx, nc = col+dy;
                        let foundObstacle = false;
                        while (nr>=0 && nr<ROWS && nc>=0 && nc<COLS) {
                            const target = boardState[nr][nc];
                            if (!foundObstacle) {
                                if (!target) moves.push({toX:nr, toY:nc});
                                else foundObstacle = true;
                            } else {
                                if (target && target[0] !== color) {
                                    moves.push({toX:nr, toY:nc});
                                    break;
                                } else if (target) break;
                            }
                            nr += dx; nc += dy;
                        }
                    }
                }
                else if (type === 'p') {
                    const forward = (color === 'r') ? -1 : 1;
                    let nr = row + forward, nc = col;
                    if (nr>=0 && nr<ROWS) {
                        const target = boardState[nr][nc];
                        if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                    }
                    const crossed = (color === 'r') ? row <= 4 : row >= 5;
                    if (crossed) {
                        for (let dc of [-1,1]) {
                            nr = row; nc = col + dc;
                            if (nc>=0 && nc<COLS) {
                                const target = boardState[nr][nc];
                                if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
                            }
                        }
                    }
                }
                return moves;
            }

            function applyMove(boardState, fromX, fromY, toX, toY) {
                const newBoard = boardState.map(row => row.map(cell => cell));
                const piece = newBoard[fromX][fromY];
                newBoard[toX][toY] = piece;
                newBoard[fromX][fromY] = null;
                return newBoard;
            }

            function isKingInCheck(boardState, color) {
                let kingPos = null;
                for (let r=0; r<ROWS; r++) {
                    for (let c=0; c<COLS; c++) {
                        const p = boardState[r][c];
                        if (p && p[0]===color && p[1]==='k') kingPos = [r,c];
                    }
                }
                if (!kingPos) return false;
                const opponent = color==='r' ? 'b' : 'r';
                for (let r=0; r<ROWS; r++) {
                    for (let c=0; c<COLS; c++) {
                        const p = boardState[r][c];
                        if (p && p[0]===opponent) {
                            const rawMoves = getPieceRawMoves(boardState, r, c, p);
                            for (let m of rawMoves) {
                                if (m.toX === kingPos[0] && m.toY === kingPos[1]) return true;
                            }
                        }
                    }
                }
                return false;
            }

            function hasGeneralsFacing(boardState) {
                let redKing = null, blackKing = null;
                for (let r=0; r<ROWS; r++) {
                    for (let c=0; c<COLS; c++) {
                        const p = boardState[r][c];
                        if (p && p[1]==='k') {
                            if (p[0]==='r') redKing = [r,c];
                            else blackKing = [r,c];
                        }
                    }
                }
                if (!redKing || !blackKing) return false;
                if (redKing[1] !== blackKing[1]) return false;
                const col = redKing[1];
                const minRow = Math.min(redKing[0], blackKing[0]);
                const maxRow = Math.max(redKing[0], blackKing[0]);
                for (let r = minRow+1; r < maxRow; r++) {
                    if (boardState[r][col] !== null) return false;
                }
                return true;
            }

            function getAllValidMoves(boardState, color) {
                const moves = [];
                for (let r=0; r<ROWS; r++) {
                    for (let c=0; c<COLS; c++) {
                        const piece = boardState[r][c];
                        if (!piece || piece[0] !== color) continue;
                        const raw = getPieceRawMoves(boardState, r, c, piece);
                        for (let m of raw) {
                            const newBoard = applyMove(boardState, r, c, m.toX, m.toY);
                            if (isKingInCheck(newBoard, color)) continue;
                            if (hasGeneralsFacing(newBoard)) continue;
                            const pieceVal = pieceValue[piece[1]] || 0;
                            const target = boardState[m.toX][m.toY];
                            const targetVal = target ? (pieceValue[target[1]] || 0) : 0;
                            moves.push({
                                fromX: r, fromY: c,
                                toX: m.toX, toY: m.toY,
                                pieceValue: pieceVal, targetValue: targetVal
                            });
                        }
                    }
                }
                return moves;
            }

            function evaluateBoard(boardState, perspective) {
                let score = 0;
                for (let r=0; r<ROWS; r++) {
                    for (let c=0; c<COLS; c++) {
                        const p = boardState[r][c];
                        if (!p) continue;
                        const val = pieceValue[p[1]] || 0;
                        if (p[0] === perspective) score += val;
                        else score -= val;
                    }
                }
                return score;
            }

            function alphaBeta(boardState, depth, alpha, beta, maximizing, aiColor, currentDepth) {
                const currentColor = maximizing ? aiColor : (aiColor==='r' ? 'b' : 'r');
                const moves = getAllValidMoves(boardState, currentColor);
                if (moves.length === 0) {
                    return maximizing ? -Infinity : Infinity;
                }
                if (depth === 0) return evaluateBoard(boardState, aiColor);

                const killers = killerMoves[currentDepth] || [null, null];
                moves.sort((a,b) => {
                    const aKiller = (killers[0] && a.fromX===killers[0].fromX && a.fromY===killers[0].fromY && a.toX===killers[0].toX && a.toY===killers[0].toY) ||
                                    (killers[1] && a.fromX===killers[1].fromX && a.fromY===killers[1].fromY && a.toX===killers[1].toX && a.toY===killers[1].toY);
                    const bKiller = (killers[0] && b.fromX===killers[0].fromX && b.fromY===killers[0].fromY && b.toX===killers[0].toX && b.toY===killers[0].toY) ||
                                    (killers[1] && b.fromX===killers[1].fromX && b.fromY===killers[1].fromY && b.toX===killers[1].toX && b.toY===killers[1].toY);
                    if (aKiller && !bKiller) return -1;
                    if (!aKiller && bKiller) return 1;
                    if (a.targetValue !== b.targetValue) return b.targetValue - a.targetValue;
                    return b.pieceValue - a.pieceValue;
                });

                if (maximizing) {
                    let maxEval = -Infinity;
                    for (let move of moves) {
                        const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
                        const evalScore = alphaBeta(newBoard, depth-1, alpha, beta, false, aiColor, currentDepth+1);
                        maxEval = Math.max(maxEval, evalScore);
                        alpha = Math.max(alpha, evalScore);
                        if (beta <= alpha) {
                            if (move.targetValue === 0) {
                                if (!killerMoves[currentDepth]) killerMoves[currentDepth] = [null, null];
                                const k = killerMoves[currentDepth];
                                if (!k[0]) k[0] = move;
                                else if (!k[1] && !(k[0].fromX===move.fromX && k[0].fromY===move.fromY && k[0].toX===move.toX && k[0].toY===move.toY)) k[1] = move;
                            }
                            break;
                        }
                    }
                    return maxEval;
                } else {
                    let minEval = Infinity;
                    for (let move of moves) {
                        const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
                        const evalScore = alphaBeta(newBoard, depth-1, alpha, beta, true, aiColor, currentDepth+1);
                        minEval = Math.min(minEval, evalScore);
                        beta = Math.min(beta, evalScore);
                        if (beta <= alpha) {
                            if (move.targetValue === 0) {
                                if (!killerMoves[currentDepth]) killerMoves[currentDepth] = [null, null];
                                const k = killerMoves[currentDepth];
                                if (!k[0]) k[0] = move;
                                else if (!k[1] && !(k[0].fromX===move.fromX && k[0].fromY===move.fromY && k[0].toX===move.toX && k[0].toY===move.toY)) k[1] = move;
                            }
                            break;
                        }
                    }
                    return minEval;
                }
            }

            function getBestMove(boardState, color, depth) {
                initKillers();
                const moves = getAllValidMoves(boardState, color);
                if (moves.length === 0) return null;
                let bestMoves = [];
                let bestScore = -Infinity;
                for (let move of moves) {
                    const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
                    const score = alphaBeta(newBoard, depth-1, -Infinity, Infinity, false, color, 0);
                    if (score > bestScore) {
                        bestScore = score;
                        bestMoves = [move];
                    } else if (score === bestScore) {
                        bestMoves.push(move);
                    }
                }
                return bestMoves[Math.floor(Math.random() * bestMoves.length)];
            }

            function aiMakeMove(color) {
                if (gameOver || isAIPlaying) return;
                const isAITurn = (gameMode === 'ai-black' && color === 'b') || (gameMode === 'ai-red' && color === 'r');
                if (!isAITurn) return;

                isAIPlaying = true;
                setTimeout(() => {
                    if (gameOver) { isAIPlaying = false; return; }
                    const depth = aiDifficulty;
                    const bestMove = getBestMove(board, color, depth);
                    if (!bestMove) {
                        gameOver = true;
                        winner = (color === 'r') ? 'b' : 'r';
                        turnText.innerText = winner === 'r' ? '&#128308; 红方胜' : '&#9899; 黑方胜';
                        drawBoard();
                        isAIPlaying = false;
                        return;
                    }
                    const { fromX, fromY, toX, toY } = bestMove;
                    const boardCopy = board.map(row => row.map(cell => cell));
                    history.push({ board: boardCopy, currentPlayer, lastMoveFrom: [fromX,fromY], lastMoveTo: [toX,toY] });

                    const piece = board[fromX][fromY];
                    board[toX][toY] = piece;
                    board[fromX][fromY] = null;

                    const enemyColor = color === 'r' ? 'b' : 'r';
                    let enemyKingAlive = false;
                    for (let r=0; r<ROWS; r++) for (let c=0; c<COLS; c++) {
                        const p = board[r][c];
                        if (p && p[0]===enemyColor && p[1]==='k') enemyKingAlive = true;
                    }
                    if (!enemyKingAlive) {
                        gameOver = true;
                        winner = color;
                        turnText.innerText = winner === 'r' ? '红方胜' : '黑方胜';
                    } else {
                        currentPlayer = enemyColor;
                        turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
                    }
                    selectedPiece = null;
                    drawBoard();

                    if (!gameOver) {
                        const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
                        if (nextIsAI) {
                            setTimeout(() => { aiMakeMove(currentPlayer); }, 150);
                        }
                    }
                    isAIPlaying = false;
                }, 150);
            }

            function handleCanvasClick(e) {
                if (gameOver) return;
                const isCurrentAITurn = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
                if (isCurrentAITurn) return;

                const rect = canvas.getBoundingClientRect();
                const scaleX = canvas.width / rect.width;
                const scaleY = canvas.height / rect.height;
                const canvasX = (e.clientX - rect.left) * scaleX;
                const canvasY = (e.clientY - rect.top) * scaleY;
                const col = Math.round((canvasX - OFFSET_X) / GRID_SIZE);
                const row = Math.round((canvasY - OFFSET_Y) / GRID_SIZE);
                if (row<0 || row>=ROWS || col<0 || col>=COLS) return;

                if (!selectedPiece) {
                    const piece = board[row][col];
                    if (piece && piece[0] === currentPlayer) {
                        selectedPiece = { row, col, piece };
                    }
                } else {
                    const { row: sRow, col: sCol, piece: sPiece } = selectedPiece;
                    if (board[row][col] && board[row][col][0] === currentPlayer) {
                        selectedPiece = { row, col, piece: board[row][col] };
                        drawBoard();
                        return;
                    }
                    const rawMoves = getPieceRawMoves(board, sRow, sCol, sPiece);
                    let valid = false;
                    for (let m of rawMoves) {
                        if (m.toX === row && m.toY === col) {
                            const newBoard = applyMove(board, sRow, sCol, row, col);
                            if (!isKingInCheck(newBoard, currentPlayer) && !hasGeneralsFacing(newBoard)) {
                                valid = true;
                            }
                            break;
                        }
                    }
                    if (valid) {
                        const boardCopy = board.map(r => r.map(c => c));
                        history.push({ board: boardCopy, currentPlayer, lastMoveFrom: [sRow,sCol], lastMoveTo: [row,col] });

                        const piece = board[sRow][sCol];
                        board[row][col] = piece;
                        board[sRow][sCol] = null;

                        const enemyColor = currentPlayer === 'r' ? 'b' : 'r';
                        let enemyKingAlive = false;
                        for (let r=0; r<ROWS; r++) for (let c=0; c<COLS; c++) {
                            const p = board[r][c];
                            if (p && p[0]===enemyColor && p[1]==='k') enemyKingAlive = true;
                        }
                        if (!enemyKingAlive) {
                            gameOver = true;
                            winner = currentPlayer;
                            turnText.innerText = winner === 'r' ? '红方胜' : '黑方胜';
                        } else {
                            currentPlayer = enemyColor;
                            turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
                        }
                        selectedPiece = null;
                        drawBoard();

                        if (!gameOver) {
                            const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
                            if (nextIsAI) {
                                setTimeout(() => { aiMakeMove(currentPlayer); }, 200);
                            }
                        }
                    } else {
                        selectedPiece = null;
                    }
                }
                drawBoard();
            }

            function undoMove() {
                if (history.length === 0) return;
                if (isAIPlaying) return;
                const last = history.pop();
                board = last.board.map(row => row.map(cell => cell));
                currentPlayer = last.currentPlayer;
                selectedPiece = null;
                gameOver = false;
                winner = null;
                turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
                drawBoard();
                if (!gameOver) {
                    const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
                    if (nextIsAI) {
                        setTimeout(() => { aiMakeMove(currentPlayer); }, 200);
                    }
                }
            }

            function restartGame() {
                if (isAIPlaying) return;
                initBoard();
                drawBoard();
                if (gameMode === 'ai-black' && currentPlayer === 'b') aiMakeMove('b');
                else if (gameMode === 'ai-red' && currentPlayer === 'r') aiMakeMove('r');
            }

            canvas.addEventListener('click', handleCanvasClick);
            undoBtn.addEventListener('click', undoMove);
            restartBtn.addEventListener('click', restartGame);
            modeSelect.addEventListener('change', (e) => {
                gameMode = e.target.value;
                restartGame();
            });
            difficultySelect.addEventListener('change', (e) => {
                aiDifficulty = parseInt(e.target.value, 10);
            });

            initBoard();
            drawBoard();
        })();
    </script>
</body>
</html>

点评

复制代码,保存成html文件,编码选择UTF-8  详情 回复 发表于 10 小时前
感谢分享!  详情 回复 发表于 10 小时前
好人  发表于 11 小时前

评分

参与人数 2无忧币 +10 收起 理由
it323 + 5 很给力!
yyz2191958 + 5

查看全部评分

回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
没有权限上传附件
回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
试用了,体验并不是很好,不如专业象棋应用
回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
懂一点玩的规则,但不会想很多步
回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
感谢分享。
回复

使用道具 举报

发表于 11 小时前 | 显示全部楼层
本帖最后由 gah99sw 于 2026-3-19 09:04 编辑

保存为ntf-8格式,否则乱码。下了一局,AI陪我下,丢了一个车,不能悔棋。
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层

非常热
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
1e3e 发表于 2026-3-19 08:47
中国象棋专业版2.0
   
        * {

感谢分享!
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>中国象棋专业版2.0</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.             user-select: none;
  13.         }
  14.         body {
  15.             background: #1a1a1a;
  16.             min-height: 100vh;
  17.             display: flex;
  18.             justify-content: center;
  19.             align-items: center;
  20.             font-family: '楷体', 'KaiTi', '华文楷体', serif;
  21.         }
  22.         .game-container {
  23.             background: transparent;
  24.             padding: 20px;
  25.             border-radius: 0;
  26.             box-shadow: none;
  27.             border: none;
  28.             display: flex;
  29.             gap: 40px;
  30.             align-items: center;
  31.         }
  32.         /* 强制canvas显示尺寸与像素尺寸一致,防止浏览器缩放导致模糊 */
  33.         canvas {
  34.             display: block;
  35.             width: 560px;
  36.             height: 630px;
  37.             border-radius: 18px;
  38.             box-shadow: 0 10px 20px rgba(0,0,0,0.3);
  39.             background: #f0d9b5;
  40.             cursor: pointer;
  41.         }
  42.         .right-panel {
  43.             display: flex;
  44.             flex-direction: column;
  45.             gap: 15px;
  46.             align-items: center;
  47.             justify-content: center;
  48.             min-width: 160px;
  49.         }
  50.         .turn-indicator {
  51.             background: #5e3e1e;
  52.             color: #f9eac3;
  53.             padding: 8px 12px;
  54.             border-radius: 50px;
  55.             font-size: 22px;
  56.             letter-spacing: 2px;
  57.             box-shadow: inset 0 -3px 0 #2f1f0e, 0 6px 10px black;
  58.             border-bottom: 2px solid #b5914c;
  59.             width: 100%;
  60.             text-align: center;
  61.             margin-bottom: 5px;
  62.         }
  63.         .mode-selector, .difficulty-selector {
  64.             background: #c29e5e;
  65.             border-radius: 40px;
  66.             padding: 8px 12px;
  67.             font-size: 20px;
  68.             font-weight: bold;
  69.             color: #2b1a0a;
  70.             border: 2px solid #efd39e;
  71.             box-shadow: inset 0 -2px 0 #6f4f2e, 0 6px 8px black;
  72.             cursor: pointer;
  73.             outline: none;
  74.             width: 100%;
  75.             text-align: center;
  76.         }
  77.         .action-btn {
  78.             background: #c29e5e;
  79.             border: none;
  80.             font-size: 22px;
  81.             font-family: '楷体', serif;
  82.             font-weight: bold;
  83.             padding: 8px 0;
  84.             border-radius: 40px;
  85.             color: #2b1a0a;
  86.             box-shadow: 0 8px 0 #6f4f2e, 0 10px 15px black;
  87.             cursor: pointer;
  88.             transition: 0.08s linear;
  89.             border: 1px solid #efd39e;
  90.             letter-spacing: 2px;
  91.             width: 100%;
  92.         }
  93.         .action-btn:active {
  94.             transform: translateY(7px);
  95.             box-shadow: 0 2px 0 #5a3f22, 0 8px 12px black;
  96.         }
  97.     </style>
  98. </head>
  99. <body>
  100.     <div class="game-container">
  101.         <canvas id="chessCanvas" width="560" height="630"></canvas>
  102.         <div class="right-panel">
  103.             <div class="turn-indicator" id="turnText">红方走棋</div>
  104.             <select id="modeSelect" class="mode-selector">
  105.                 <option value="human" selected>双人对战</option>
  106.                 <option value="ai-black">&#129302;AI执黑</option>
  107.                 <option value="ai-red">&#129302;AI执红</option>
  108.             </select>
  109.             <select id="difficultySelect" class="difficulty-selector">
  110.                 <option value="1">&#129302;初级1层</option>
  111.                 <option value="2" selected>&#129302;中级2层</option>
  112.                 <option value="3">&#129302;高级3层</option>
  113.             </select>
  114.             <button class="action-btn" id="undoBtn">悔棋</button>
  115.             <button class="action-btn restart-btn" id="restartBtn">新局</button>
  116.         </div>
  117.     </div>

  118.     <script>
  119.         (function() {
  120.             // ---------- 棋盘配置 ----------
  121.             const canvas = document.getElementById('chessCanvas');
  122.             const ctx = canvas.getContext('2d');
  123.             const turnText = document.getElementById('turnText');
  124.             const modeSelect = document.getElementById('modeSelect');
  125.             const difficultySelect = document.getElementById('difficultySelect');
  126.             const undoBtn = document.getElementById('undoBtn');
  127.             const restartBtn = document.getElementById('restartBtn');

  128.             const ROWS = 10;
  129.             const COLS = 9;
  130.             const GRID_SIZE = 56;
  131.             const OFFSET_X = 56;
  132.             const OFFSET_Y = 63;

  133.             // ---------- 全局状态 ----------
  134.             let board = [];
  135.             let currentPlayer = 'r';
  136.             let selectedPiece = null;
  137.             let gameOver = false;
  138.             let winner = null;               
  139.             let history = [];

  140.             // AI 相关
  141.             let gameMode = 'human';
  142.             let aiDifficulty = 2;
  143.             let isAIPlaying = false;

  144.             let killerMoves = [];
  145.             const MAX_DEPTH = 10;
  146.             function initKillers() {
  147.                 killerMoves = [];
  148.                 for (let d = 0; d <= MAX_DEPTH; d++) killerMoves[d] = [null, null];
  149.             }
  150.             initKillers();

  151.             const pieceValue = {
  152.                 'k': 10000, 'a': 200, 'b': 200, 'n': 500, 'r': 1000, 'c': 800, 'p': 100
  153.             };

  154.             function initBoard() {
  155.                 board = Array(ROWS).fill().map(() => Array(COLS).fill(null));

  156.                 board[0][0] = 'br'; board[0][1] = 'bn'; board[0][2] = 'bb'; board[0][3] = 'ba'; board[0][4] = 'bk';
  157.                 board[0][5] = 'ba'; board[0][6] = 'bb'; board[0][7] = 'bn'; board[0][8] = 'br';
  158.                 board[2][1] = 'bc'; board[2][7] = 'bc';
  159.                 board[3][0] = 'bp'; board[3][2] = 'bp'; board[3][4] = 'bp'; board[3][6] = 'bp'; board[3][8] = 'bp';

  160.                 board[9][0] = 'rr'; board[9][1] = 'rn'; board[9][2] = 'rb'; board[9][3] = 'ra'; board[9][4] = 'rk';
  161.                 board[9][5] = 'ra'; board[9][6] = 'rb'; board[9][7] = 'rn'; board[9][8] = 'rr';
  162.                 board[7][1] = 'rc'; board[7][7] = 'rc';
  163.                 board[6][0] = 'rp'; board[6][2] = 'rp'; board[6][4] = 'rp'; board[6][6] = 'rp'; board[6][8] = 'rp';

  164.                 currentPlayer = 'r';
  165.                 selectedPiece = null;
  166.                 gameOver = false;
  167.                 winner = null;
  168.                 history = [];
  169.                 turnText.innerText = '红方走棋';
  170.                 initKillers();
  171.             }

  172.             // ---------- 绘制函数 (锐化处理) ----------
  173.             function drawBoard() {
  174.                 ctx.clearRect(0, 0, canvas.width, canvas.height);
  175.                 drawGrid();
  176.                 drawPieces();
  177.                 if (selectedPiece) {
  178.                     const { row, col } = selectedPiece;
  179.                     // 选中高亮:坐标对齐到半像素,阴影调小
  180.                     ctx.save();
  181.                     ctx.strokeStyle = '#f5e56b';
  182.                     ctx.lineWidth = 3;
  183.                     ctx.shadowBlur = 6;          // 减小阴影模糊
  184.                     ctx.shadowColor = 'gold';
  185.                     ctx.beginPath();
  186.                     // 使用半像素坐标使圆弧边缘更清晰
  187.                     ctx.arc(OFFSET_X + col * GRID_SIZE + 0.5, OFFSET_Y + row * GRID_SIZE + 0.5, 24, 0, 2 * Math.PI);
  188.                     ctx.stroke();
  189.                     ctx.restore();
  190.                 }
  191.             }

  192.             function drawGrid() {
  193.                 ctx.save();
  194.                 ctx.strokeStyle = '#6b4f32';
  195.                 ctx.lineWidth = 1;               // 线宽设为1,配合半像素坐标获得锐利直线

  196.                 // 绘制横线 (坐标+0.5使线条精确覆盖像素)
  197.                 for (let i = 0; i < ROWS; i++) {
  198.                     ctx.beginPath();
  199.                     ctx.moveTo(OFFSET_X + 0.5, OFFSET_Y + i * GRID_SIZE + 0.5);
  200.                     ctx.lineTo(OFFSET_X + (COLS - 1) * GRID_SIZE + 0.5, OFFSET_Y + i * GRID_SIZE + 0.5);
  201.                     ctx.stroke();
  202.                 }
  203.                 // 绘制竖线
  204.                 for (let i = 0; i < COLS; i++) {
  205.                     ctx.beginPath();
  206.                     ctx.moveTo(OFFSET_X + i * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
  207.                     ctx.lineTo(OFFSET_X + i * GRID_SIZE + 0.5, OFFSET_Y + (ROWS - 1) * GRID_SIZE + 0.5);
  208.                     ctx.stroke();
  209.                 }

  210.                 // 绘制楚河汉界 (半透明背景文字,稍微下移一点避开中线,保留原风格)
  211.                 ctx.font = '35px "楷体", "KaiTi"';
  212.                 ctx.fillStyle = '#4e2f15';
  213.                 ctx.globalAlpha = 0.3;
  214.                 const text = '楚 河         汉 界';
  215.                 const textWidth = ctx.measureText(text).width;
  216.                 const boardLeft = OFFSET_X;
  217.                 const boardRight = OFFSET_X + (COLS - 1) * GRID_SIZE;
  218.                 const centerX = (boardLeft + boardRight) / 2;
  219.                 const textX = centerX - textWidth / 2;
  220.                 // 保持文字整数坐标防止模糊 (半透明背景无需锐利)
  221.                 ctx.fillText(text, textX, OFFSET_Y + 5 * GRID_SIZE - 13);
  222.                 ctx.globalAlpha = 1;

  223.                 // 九宫对角线 (同样用半像素坐标)
  224.                 ctx.beginPath();
  225.                 ctx.moveTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
  226.                 ctx.lineTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 2 * GRID_SIZE + 0.5);
  227.                 ctx.stroke();
  228.                 ctx.beginPath();
  229.                 ctx.moveTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 0.5);
  230.                 ctx.lineTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 2 * GRID_SIZE + 0.5);
  231.                 ctx.stroke();

  232.                 ctx.beginPath();
  233.                 ctx.moveTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 7 * GRID_SIZE + 0.5);
  234.                 ctx.lineTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 9 * GRID_SIZE + 0.5);
  235.                 ctx.stroke();
  236.                 ctx.beginPath();
  237.                 ctx.moveTo(OFFSET_X + 5 * GRID_SIZE + 0.5, OFFSET_Y + 7 * GRID_SIZE + 0.5);
  238.                 ctx.lineTo(OFFSET_X + 3 * GRID_SIZE + 0.5, OFFSET_Y + 9 * GRID_SIZE + 0.5);
  239.                 ctx.stroke();

  240.                 // 绘制炮/兵/卒标记点 (使用半像素坐标,使圆点清晰)
  241.                 ctx.fillStyle = '#6b4f32';
  242.                 ctx.shadowBlur = 0; // 标记点不要阴影
  243.                 for (let r of [2, 7]) {
  244.                     for (let c of [1, 7]) {
  245.                         ctx.beginPath();
  246.                         ctx.arc(OFFSET_X + c * GRID_SIZE + 0.5, OFFSET_Y + r * GRID_SIZE + 0.5, 5, 0, 2 * Math.PI);
  247.                         ctx.fill();
  248.                     }
  249.                 }
  250.                 for (let r of [3, 6]) {
  251.                     for (let c of [0, 2, 4, 6, 8]) {
  252.                         ctx.beginPath();
  253.                         ctx.arc(OFFSET_X + c * GRID_SIZE + 0.5, OFFSET_Y + r * GRID_SIZE + 0.5, 4, 0, 2 * Math.PI);
  254.                         ctx.fill();
  255.                     }
  256.                 }
  257.                 ctx.restore();
  258.             }

  259.             function drawPieces() {
  260.                 for (let r = 0; r < ROWS; r++) {
  261.                     for (let c = 0; c < COLS; c++) {
  262.                         const piece = board[r][c];
  263.                         if (!piece) continue;
  264.                         // 圆心坐标对齐半像素,使棋子边缘清晰
  265.                         const x = OFFSET_X + c * GRID_SIZE + 0.5;
  266.                         const y = OFFSET_Y + r * GRID_SIZE + 0.5;

  267.                         ctx.save();
  268.                         // 棋子阴影减弱
  269.                         ctx.shadowColor = '#333';
  270.                         ctx.shadowBlur = 4;        // 原为8,减半减少模糊
  271.                         ctx.shadowOffsetY = 2;      // 原为3
  272.                         ctx.beginPath();
  273.                         ctx.arc(x, y, 24, 0, 2 * Math.PI);
  274.                         ctx.fillStyle = piece[0] === 'r' ? '#c33' : '#222';
  275.                         ctx.fill();
  276.                         ctx.shadowBlur = 2;          // 描边阴影稍弱
  277.                         ctx.strokeStyle = '#f1d28c';
  278.                         ctx.lineWidth = 2;
  279.                         ctx.stroke();

  280.                         // 绘制文字前清除阴影,保证文字清晰
  281.                         ctx.shadowBlur = 0;
  282.                         ctx.shadowOffsetY = 0;
  283.                         ctx.font = '35px "楷体", "KaiTi", "华文楷体", serif';
  284.                         ctx.fillStyle = piece[0] === 'r' ? '#FFD966' : '#ddd';
  285.                         ctx.textAlign = 'center';
  286.                         ctx.textBaseline = 'middle';
  287.                         // 微调偏移量,配合半像素圆心,文字仍保留轻微偏移以居中
  288.                         ctx.fillText(pieceToChinese(piece), x - 1, y + 5);
  289.                         ctx.restore();
  290.                     }
  291.                 }
  292.             }

  293.             function pieceToChinese(p) {
  294.                 const map = {
  295.                     'r': { 'k': '帅', 'a': '仕', 'b': '相', 'n': '马', 'r': '车', 'c': '炮', 'p': '兵' },
  296.                     'b': { 'k': '将', 'a': '士', 'b': '象', 'n': '马', 'r': '车', 'c': '炮', 'p': '卒' }
  297.                 };
  298.                 return map[p[0]][p[1]] || '?';
  299.             }

  300.             // ---------- 规则核心函数 (保持不变,保证AI逻辑正确) ----------
  301.             function getPieceRawMoves(boardState, row, col, piece) {
  302.                 const color = piece[0];
  303.                 const type = piece[1];
  304.                 const moves = [];

  305.                 if (type === 'k') {
  306.                     const palaceRows = (color === 'r') ? [7,8,9] : [0,1,2];
  307.                     const palaceCols = [3,4,5];
  308.                     const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
  309.                     for (let [dx,dy] of dirs) {
  310.                         const nr = row+dx, nc = col+dy;
  311.                         if (palaceRows.includes(nr) && palaceCols.includes(nc)) {
  312.                             const target = boardState[nr]?.[nc];
  313.                             if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  314.                         }
  315.                     }
  316.                 }
  317.                 else if (type === 'a') {
  318.                     const palaceRows = (color === 'r') ? [7,8,9] : [0,1,2];
  319.                     const palaceCols = [3,4,5];
  320.                     const dirs = [[-1,-1],[-1,1],[1,-1],[1,1]];
  321.                     for (let [dx,dy] of dirs) {
  322.                         const nr = row+dx, nc = col+dy;
  323.                         if (palaceRows.includes(nr) && palaceCols.includes(nc)) {
  324.                             const target = boardState[nr]?.[nc];
  325.                             if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  326.                         }
  327.                     }
  328.                 }
  329.                 else if (type === 'b') {
  330.                     const dirs = [[-2,-2],[-2,2],[2,-2],[2,2]];
  331.                     for (let [dx,dy] of dirs) {
  332.                         const nr = row+dx, nc = col+dy;
  333.                         if (nr<0 || nr>=ROWS || nc<0 || nc>=COLS) continue;
  334.                         if (color === 'r' && nr < 5) continue;
  335.                         if (color === 'b' && nr > 4) continue;
  336.                         const eyeRow = row + dx/2, eyeCol = col + dy/2;
  337.                         if (boardState[eyeRow][eyeCol] !== null) continue;
  338.                         const target = boardState[nr][nc];
  339.                         if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  340.                     }
  341.                 }
  342.                 else if (type === 'n') {
  343.                     const jumps = [[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]];
  344.                     for (let [dx,dy] of jumps) {
  345.                         const nr = row+dx, nc = col+dy;
  346.                         if (nr<0 || nr>=ROWS || nc<0 || nc>=COLS) continue;
  347.                         let blockRow = row, blockCol = col;
  348.                         if (Math.abs(dx) === 2) { blockRow = row + (dx>0?1:-1); blockCol = col; }
  349.                         else { blockRow = row; blockCol = col + (dy>0?1:-1); }
  350.                         if (boardState[blockRow][blockCol] !== null) continue;
  351.                         const target = boardState[nr][nc];
  352.                         if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  353.                     }
  354.                 }
  355.                 else if (type === 'r') {
  356.                     const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
  357.                     for (let [dx,dy] of dirs) {
  358.                         let nr = row+dx, nc = col+dy;
  359.                         while (nr>=0 && nr<ROWS && nc>=0 && nc<COLS) {
  360.                             const target = boardState[nr][nc];
  361.                             if (!target) moves.push({toX:nr, toY:nc});
  362.                             else {
  363.                                 if (target[0] !== color) moves.push({toX:nr, toY:nc});
  364.                                 break;
  365.                             }
  366.                             nr += dx; nc += dy;
  367.                         }
  368.                     }
  369.                 }
  370.                 else if (type === 'c') {
  371.                     const dirs = [[-1,0],[1,0],[0,-1],[0,1]];
  372.                     for (let [dx,dy] of dirs) {
  373.                         let nr = row+dx, nc = col+dy;
  374.                         let foundObstacle = false;
  375.                         while (nr>=0 && nr<ROWS && nc>=0 && nc<COLS) {
  376.                             const target = boardState[nr][nc];
  377.                             if (!foundObstacle) {
  378.                                 if (!target) moves.push({toX:nr, toY:nc});
  379.                                 else foundObstacle = true;
  380.                             } else {
  381.                                 if (target && target[0] !== color) {
  382.                                     moves.push({toX:nr, toY:nc});
  383.                                     break;
  384.                                 } else if (target) break;
  385.                             }
  386.                             nr += dx; nc += dy;
  387.                         }
  388.                     }
  389.                 }
  390.                 else if (type === 'p') {
  391.                     const forward = (color === 'r') ? -1 : 1;
  392.                     let nr = row + forward, nc = col;
  393.                     if (nr>=0 && nr<ROWS) {
  394.                         const target = boardState[nr][nc];
  395.                         if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  396.                     }
  397.                     const crossed = (color === 'r') ? row <= 4 : row >= 5;
  398.                     if (crossed) {
  399.                         for (let dc of [-1,1]) {
  400.                             nr = row; nc = col + dc;
  401.                             if (nc>=0 && nc<COLS) {
  402.                                 const target = boardState[nr][nc];
  403.                                 if (!target || target[0] !== color) moves.push({toX:nr, toY:nc});
  404.                             }
  405.                         }
  406.                     }
  407.                 }
  408.                 return moves;
  409.             }

  410.             function applyMove(boardState, fromX, fromY, toX, toY) {
  411.                 const newBoard = boardState.map(row => row.map(cell => cell));
  412.                 const piece = newBoard[fromX][fromY];
  413.                 newBoard[toX][toY] = piece;
  414.                 newBoard[fromX][fromY] = null;
  415.                 return newBoard;
  416.             }

  417.             function isKingInCheck(boardState, color) {
  418.                 let kingPos = null;
  419.                 for (let r=0; r<ROWS; r++) {
  420.                     for (let c=0; c<COLS; c++) {
  421.                         const p = boardState[r][c];
  422.                         if (p && p[0]===color && p[1]==='k') kingPos = [r,c];
  423.                     }
  424.                 }
  425.                 if (!kingPos) return false;
  426.                 const opponent = color==='r' ? 'b' : 'r';
  427.                 for (let r=0; r<ROWS; r++) {
  428.                     for (let c=0; c<COLS; c++) {
  429.                         const p = boardState[r][c];
  430.                         if (p && p[0]===opponent) {
  431.                             const rawMoves = getPieceRawMoves(boardState, r, c, p);
  432.                             for (let m of rawMoves) {
  433.                                 if (m.toX === kingPos[0] && m.toY === kingPos[1]) return true;
  434.                             }
  435.                         }
  436.                     }
  437.                 }
  438.                 return false;
  439.             }

  440.             function hasGeneralsFacing(boardState) {
  441.                 let redKing = null, blackKing = null;
  442.                 for (let r=0; r<ROWS; r++) {
  443.                     for (let c=0; c<COLS; c++) {
  444.                         const p = boardState[r][c];
  445.                         if (p && p[1]==='k') {
  446.                             if (p[0]==='r') redKing = [r,c];
  447.                             else blackKing = [r,c];
  448.                         }
  449.                     }
  450.                 }
  451.                 if (!redKing || !blackKing) return false;
  452.                 if (redKing[1] !== blackKing[1]) return false;
  453.                 const col = redKing[1];
  454.                 const minRow = Math.min(redKing[0], blackKing[0]);
  455.                 const maxRow = Math.max(redKing[0], blackKing[0]);
  456.                 for (let r = minRow+1; r < maxRow; r++) {
  457.                     if (boardState[r][col] !== null) return false;
  458.                 }
  459.                 return true;
  460.             }

  461.             function getAllValidMoves(boardState, color) {
  462.                 const moves = [];
  463.                 for (let r=0; r<ROWS; r++) {
  464.                     for (let c=0; c<COLS; c++) {
  465.                         const piece = boardState[r][c];
  466.                         if (!piece || piece[0] !== color) continue;
  467.                         const raw = getPieceRawMoves(boardState, r, c, piece);
  468.                         for (let m of raw) {
  469.                             const newBoard = applyMove(boardState, r, c, m.toX, m.toY);
  470.                             if (isKingInCheck(newBoard, color)) continue;
  471.                             if (hasGeneralsFacing(newBoard)) continue;
  472.                             const pieceVal = pieceValue[piece[1]] || 0;
  473.                             const target = boardState[m.toX][m.toY];
  474.                             const targetVal = target ? (pieceValue[target[1]] || 0) : 0;
  475.                             moves.push({
  476.                                 fromX: r, fromY: c,
  477.                                 toX: m.toX, toY: m.toY,
  478.                                 pieceValue: pieceVal, targetValue: targetVal
  479.                             });
  480.                         }
  481.                     }
  482.                 }
  483.                 return moves;
  484.             }

  485.             function evaluateBoard(boardState, perspective) {
  486.                 let score = 0;
  487.                 for (let r=0; r<ROWS; r++) {
  488.                     for (let c=0; c<COLS; c++) {
  489.                         const p = boardState[r][c];
  490.                         if (!p) continue;
  491.                         const val = pieceValue[p[1]] || 0;
  492.                         if (p[0] === perspective) score += val;
  493.                         else score -= val;
  494.                     }
  495.                 }
  496.                 return score;
  497.             }

  498.             function alphaBeta(boardState, depth, alpha, beta, maximizing, aiColor, currentDepth) {
  499.                 const currentColor = maximizing ? aiColor : (aiColor==='r' ? 'b' : 'r');
  500.                 const moves = getAllValidMoves(boardState, currentColor);
  501.                 if (moves.length === 0) {
  502.                     return maximizing ? -Infinity : Infinity;
  503.                 }
  504.                 if (depth === 0) return evaluateBoard(boardState, aiColor);

  505.                 const killers = killerMoves[currentDepth] || [null, null];
  506.                 moves.sort((a,b) => {
  507.                     const aKiller = (killers[0] && a.fromX===killers[0].fromX && a.fromY===killers[0].fromY && a.toX===killers[0].toX && a.toY===killers[0].toY) ||
  508.                                     (killers[1] && a.fromX===killers[1].fromX && a.fromY===killers[1].fromY && a.toX===killers[1].toX && a.toY===killers[1].toY);
  509.                     const bKiller = (killers[0] && b.fromX===killers[0].fromX && b.fromY===killers[0].fromY && b.toX===killers[0].toX && b.toY===killers[0].toY) ||
  510.                                     (killers[1] && b.fromX===killers[1].fromX && b.fromY===killers[1].fromY && b.toX===killers[1].toX && b.toY===killers[1].toY);
  511.                     if (aKiller && !bKiller) return -1;
  512.                     if (!aKiller && bKiller) return 1;
  513.                     if (a.targetValue !== b.targetValue) return b.targetValue - a.targetValue;
  514.                     return b.pieceValue - a.pieceValue;
  515.                 });

  516.                 if (maximizing) {
  517.                     let maxEval = -Infinity;
  518.                     for (let move of moves) {
  519.                         const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
  520.                         const evalScore = alphaBeta(newBoard, depth-1, alpha, beta, false, aiColor, currentDepth+1);
  521.                         maxEval = Math.max(maxEval, evalScore);
  522.                         alpha = Math.max(alpha, evalScore);
  523.                         if (beta <= alpha) {
  524.                             if (move.targetValue === 0) {
  525.                                 if (!killerMoves[currentDepth]) killerMoves[currentDepth] = [null, null];
  526.                                 const k = killerMoves[currentDepth];
  527.                                 if (!k[0]) k[0] = move;
  528.                                 else if (!k[1] && !(k[0].fromX===move.fromX && k[0].fromY===move.fromY && k[0].toX===move.toX && k[0].toY===move.toY)) k[1] = move;
  529.                             }
  530.                             break;
  531.                         }
  532.                     }
  533.                     return maxEval;
  534.                 } else {
  535.                     let minEval = Infinity;
  536.                     for (let move of moves) {
  537.                         const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
  538.                         const evalScore = alphaBeta(newBoard, depth-1, alpha, beta, true, aiColor, currentDepth+1);
  539.                         minEval = Math.min(minEval, evalScore);
  540.                         beta = Math.min(beta, evalScore);
  541.                         if (beta <= alpha) {
  542.                             if (move.targetValue === 0) {
  543.                                 if (!killerMoves[currentDepth]) killerMoves[currentDepth] = [null, null];
  544.                                 const k = killerMoves[currentDepth];
  545.                                 if (!k[0]) k[0] = move;
  546.                                 else if (!k[1] && !(k[0].fromX===move.fromX && k[0].fromY===move.fromY && k[0].toX===move.toX && k[0].toY===move.toY)) k[1] = move;
  547.                             }
  548.                             break;
  549.                         }
  550.                     }
  551.                     return minEval;
  552.                 }
  553.             }

  554.             function getBestMove(boardState, color, depth) {
  555.                 initKillers();
  556.                 const moves = getAllValidMoves(boardState, color);
  557.                 if (moves.length === 0) return null;
  558.                 let bestMoves = [];
  559.                 let bestScore = -Infinity;
  560.                 for (let move of moves) {
  561.                     const newBoard = applyMove(boardState, move.fromX, move.fromY, move.toX, move.toY);
  562.                     const score = alphaBeta(newBoard, depth-1, -Infinity, Infinity, false, color, 0);
  563.                     if (score > bestScore) {
  564.                         bestScore = score;
  565.                         bestMoves = [move];
  566.                     } else if (score === bestScore) {
  567.                         bestMoves.push(move);
  568.                     }
  569.                 }
  570.                 return bestMoves[Math.floor(Math.random() * bestMoves.length)];
  571.             }

  572.             function aiMakeMove(color) {
  573.                 if (gameOver || isAIPlaying) return;
  574.                 const isAITurn = (gameMode === 'ai-black' && color === 'b') || (gameMode === 'ai-red' && color === 'r');
  575.                 if (!isAITurn) return;

  576.                 isAIPlaying = true;
  577.                 setTimeout(() => {
  578.                     if (gameOver) { isAIPlaying = false; return; }
  579.                     const depth = aiDifficulty;
  580.                     const bestMove = getBestMove(board, color, depth);
  581.                     if (!bestMove) {
  582.                         gameOver = true;
  583.                         winner = (color === 'r') ? 'b' : 'r';
  584.                         turnText.innerText = winner === 'r' ? '&#128308; 红方胜' : '&#9899; 黑方胜';
  585.                         drawBoard();
  586.                         isAIPlaying = false;
  587.                         return;
  588.                     }
  589.                     const { fromX, fromY, toX, toY } = bestMove;
  590.                     const boardCopy = board.map(row => row.map(cell => cell));
  591.                     history.push({ board: boardCopy, currentPlayer, lastMoveFrom: [fromX,fromY], lastMoveTo: [toX,toY] });

  592.                     const piece = board[fromX][fromY];
  593.                     board[toX][toY] = piece;
  594.                     board[fromX][fromY] = null;

  595.                     const enemyColor = color === 'r' ? 'b' : 'r';
  596.                     let enemyKingAlive = false;
  597.                     for (let r=0; r<ROWS; r++) for (let c=0; c<COLS; c++) {
  598.                         const p = board[r][c];
  599.                         if (p && p[0]===enemyColor && p[1]==='k') enemyKingAlive = true;
  600.                     }
  601.                     if (!enemyKingAlive) {
  602.                         gameOver = true;
  603.                         winner = color;
  604.                         turnText.innerText = winner === 'r' ? '红方胜' : '黑方胜';
  605.                     } else {
  606.                         currentPlayer = enemyColor;
  607.                         turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
  608.                     }
  609.                     selectedPiece = null;
  610.                     drawBoard();

  611.                     if (!gameOver) {
  612.                         const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
  613.                         if (nextIsAI) {
  614.                             setTimeout(() => { aiMakeMove(currentPlayer); }, 150);
  615.                         }
  616.                     }
  617.                     isAIPlaying = false;
  618.                 }, 150);
  619.             }

  620.             function handleCanvasClick(e) {
  621.                 if (gameOver) return;
  622.                 const isCurrentAITurn = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
  623.                 if (isCurrentAITurn) return;

  624.                 const rect = canvas.getBoundingClientRect();
  625.                 const scaleX = canvas.width / rect.width;
  626.                 const scaleY = canvas.height / rect.height;
  627.                 const canvasX = (e.clientX - rect.left) * scaleX;
  628.                 const canvasY = (e.clientY - rect.top) * scaleY;
  629.                 const col = Math.round((canvasX - OFFSET_X) / GRID_SIZE);
  630.                 const row = Math.round((canvasY - OFFSET_Y) / GRID_SIZE);
  631.                 if (row<0 || row>=ROWS || col<0 || col>=COLS) return;

  632.                 if (!selectedPiece) {
  633.                     const piece = board[row][col];
  634.                     if (piece && piece[0] === currentPlayer) {
  635.                         selectedPiece = { row, col, piece };
  636.                     }
  637.                 } else {
  638.                     const { row: sRow, col: sCol, piece: sPiece } = selectedPiece;
  639.                     if (board[row][col] && board[row][col][0] === currentPlayer) {
  640.                         selectedPiece = { row, col, piece: board[row][col] };
  641.                         drawBoard();
  642.                         return;
  643.                     }
  644.                     const rawMoves = getPieceRawMoves(board, sRow, sCol, sPiece);
  645.                     let valid = false;
  646.                     for (let m of rawMoves) {
  647.                         if (m.toX === row && m.toY === col) {
  648.                             const newBoard = applyMove(board, sRow, sCol, row, col);
  649.                             if (!isKingInCheck(newBoard, currentPlayer) && !hasGeneralsFacing(newBoard)) {
  650.                                 valid = true;
  651.                             }
  652.                             break;
  653.                         }
  654.                     }
  655.                     if (valid) {
  656.                         const boardCopy = board.map(r => r.map(c => c));
  657.                         history.push({ board: boardCopy, currentPlayer, lastMoveFrom: [sRow,sCol], lastMoveTo: [row,col] });

  658.                         const piece = board[sRow][sCol];
  659.                         board[row][col] = piece;
  660.                         board[sRow][sCol] = null;

  661.                         const enemyColor = currentPlayer === 'r' ? 'b' : 'r';
  662.                         let enemyKingAlive = false;
  663.                         for (let r=0; r<ROWS; r++) for (let c=0; c<COLS; c++) {
  664.                             const p = board[r][c];
  665.                             if (p && p[0]===enemyColor && p[1]==='k') enemyKingAlive = true;
  666.                         }
  667.                         if (!enemyKingAlive) {
  668.                             gameOver = true;
  669.                             winner = currentPlayer;
  670.                             turnText.innerText = winner === 'r' ? '红方胜' : '黑方胜';
  671.                         } else {
  672.                             currentPlayer = enemyColor;
  673.                             turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
  674.                         }
  675.                         selectedPiece = null;
  676.                         drawBoard();

  677.                         if (!gameOver) {
  678.                             const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
  679.                             if (nextIsAI) {
  680.                                 setTimeout(() => { aiMakeMove(currentPlayer); }, 200);
  681.                             }
  682.                         }
  683.                     } else {
  684.                         selectedPiece = null;
  685.                     }
  686.                 }
  687.                 drawBoard();
  688.             }

  689.             function undoMove() {
  690.                 if (history.length === 0) return;
  691.                 if (isAIPlaying) return;
  692.                 const last = history.pop();
  693.                 board = last.board.map(row => row.map(cell => cell));
  694.                 currentPlayer = last.currentPlayer;
  695.                 selectedPiece = null;
  696.                 gameOver = false;
  697.                 winner = null;
  698.                 turnText.innerText = currentPlayer === 'r' ? '红方走棋' : '黑方走棋';
  699.                 drawBoard();
  700.                 if (!gameOver) {
  701.                     const nextIsAI = (gameMode === 'ai-black' && currentPlayer === 'b') || (gameMode === 'ai-red' && currentPlayer === 'r');
  702.                     if (nextIsAI) {
  703.                         setTimeout(() => { aiMakeMove(currentPlayer); }, 200);
  704.                     }
  705.                 }
  706.             }

  707.             function restartGame() {
  708.                 if (isAIPlaying) return;
  709.                 initBoard();
  710.                 drawBoard();
  711.                 if (gameMode === 'ai-black' && currentPlayer === 'b') aiMakeMove('b');
  712.                 else if (gameMode === 'ai-red' && currentPlayer === 'r') aiMakeMove('r');
  713.             }

  714.             canvas.addEventListener('click', handleCanvasClick);
  715.             undoBtn.addEventListener('click', undoMove);
  716.             restartBtn.addEventListener('click', restartGame);
  717.             modeSelect.addEventListener('change', (e) => {
  718.                 gameMode = e.target.value;
  719.                 restartGame();
  720.             });
  721.             difficultySelect.addEventListener('change', (e) => {
  722.                 aiDifficulty = parseInt(e.target.value, 10);
  723.             });

  724.             initBoard();
  725.             drawBoard();
  726.         })();
  727.     </script>
  728. </body>
  729. </html>
复制代码

点评

好人  发表于 10 小时前
复制代码,打开记事本,粘贴,改扩展名为html,双击运行  详情 回复 发表于 10 小时前

评分

参与人数 1无忧币 +5 收起 理由
yyz2191958 + 5

查看全部评分

回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层

复制代码,打开记事本,粘贴,改扩展名为html,双击运行
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
支持3#分享
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
1e3e 发表于 2026-3-19 08:47
中国象棋专业版2.0
   
        * {

复制代码,保存成html文件,编码选择UTF-8
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
热心网友确实厉害!
回复

使用道具 举报

发表于 10 小时前 | 显示全部楼层
高手在民间
回复

使用道具 举报

发表于 9 小时前 | 显示全部楼层
水平太差,高级三层都下不过我……
回复

使用道具 举报

发表于 9 小时前 | 显示全部楼层
要是链接着某个ai啥的还行,单机的就没什么好奇心了。
回复

使用道具 举报

发表于 9 小时前 | 显示全部楼层
感谢分享!
回复

使用道具 举报

发表于 9 小时前 | 显示全部楼层
在线玩中国象棋
https://xiangqi.taadis.com/
这个棋力可以
回复

使用道具 举报

发表于 9 小时前 | 显示全部楼层
中国象棋专业版2.0:http://4275.com/3ny0xc
回复

使用道具 举报

发表于 8 小时前 | 显示全部楼层
棋子丑,人机对战,机器完全没水平,瞎走棋,别求了!
回复

使用道具 举报

发表于 8 小时前 | 显示全部楼层
打开是乱码
回复

使用道具 举报

发表于 8 小时前 | 显示全部楼层
感谢分享
回复

使用道具 举报

发表于 8 小时前 | 显示全部楼层
AI执红,第一步居然是帅五进一~~
回复

使用道具 举报

发表于 7 小时前 | 显示全部楼层
html的能力也这么好了
回复

使用道具 举报

发表于 5 小时前 | 显示全部楼层
谢谢3楼热心网友
回复

使用道具 举报

发表于 4 小时前 | 显示全部楼层
感谢分享
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层
我的帖子这么火?!今天我在研究中国象棋,弄好了再分享给大家。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1|闽公网安备35020302032614号 )

GMT+8, 2026-3-19 20:05

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表