吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 685|回复: 12
收起左侧

[学习记录] AI生成井字棋小游戏html代码

[复制链接]
xiaokeng0322 发表于 2026-3-23 13:10
本帖最后由 xiaokeng0322 于 2026-3-25 14:07 编辑

看到很多井字棋都是由python或者c实现,不能直接打开使用
所以我使用AI多轮生成最终版,此版本可以直接打开就能玩,人机还是比较难的
PixPin_2026-03-23_11-10-33.png

如果代码复制出现问题那就直接下载附件 将txt后缀改成html即可
井字棋.txt (21.83 KB, 下载次数: 9)

[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>井字棋·究极AI | 无抖动版</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
        }

        /* 防止滚动条抖动:固定html/body基础样式 */
        html, body {
            margin: 0;
            padding: 0;
            height: 100%;
            overflow-x: hidden;
            overflow-y: auto;
        }

        body {
            background: linear-gradient(145deg, #0b1a24 0%, #07121c 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', 'Poppins', 'Roboto', system-ui, -apple-system, sans-serif;
            padding: 20px;
        }

        .game-container {
            background: rgba(18, 30, 38, 0.8);
            backdrop-filter: blur(3px);
            border-radius: 64px;
            padding: 24px 28px 36px 28px;
            box-shadow: 0 25px 45px rgba(0, 0, 0, 0.6), inset 0 1px 1px rgba(255, 255, 255, 0.08);
            border: 1px solid rgba(255, 215, 150, 0.25);
            transition: all 0.2s;
            /* 固定最小高度,避免因内容变化引起页面总高度波动 */
            min-height: 620px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        .game-header {
            text-align: center;
            margin-bottom: 24px;
        }

        h1 {
            margin: 0 0 6px 0;
            font-size: 2.2rem;
            font-weight: 800;
            background: linear-gradient(135deg, #FFC857, #FF9430, #E67E22);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
            letter-spacing: 1px;
        }

        .sub {
            color: #c0e0ff;
            font-size: 0.8rem;
            font-weight: 500;
            background: #00000040;
            display: inline-block;
            padding: 2px 14px;
            border-radius: 40px;
        }

        .mode-bar {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 28px;
            flex-wrap: wrap;
        }

        .mode-btn {
            background: #2c3f44;
            border: none;
            padding: 8px 28px;
            font-size: 1rem;
            font-weight: 600;
            border-radius: 60px;
            cursor: pointer;
            color: #eef4ff;
            transition: all 0.2s;
            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
            font-family: inherit;
        }

        .mode-btn.active {
            background: linear-gradient(105deg, #F39C12, #E67E22);
            color: white;
            box-shadow: 0 0 14px rgba(243,156,18,0.7);
            border: 1px solid rgba(255,215,0,0.6);
        }

        .mode-btn:active {
            transform: scale(0.96);
        }

        /* 状态面板固定高度,防止文字换行导致页面高度变化 */
        .status-panel {
            background: #07141ecc;
            border-radius: 80px;
            padding: 12px 20px;
            margin-bottom: 28px;
            text-align: center;
            backdrop-filter: blur(8px);
            border: 1px solid rgba(255,200,120,0.5);
            min-height: 80px;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
        }

        .status-text {
            font-size: 1.35rem;
            font-weight: 700;
            color: #FFF2DF;
            text-shadow: 0 1px 2px black;
            line-height: 1.3;
        }

        .turn-indicator {
            display: inline-block;
            background: #00000066;
            padding: 4px 14px;
            border-radius: 40px;
            margin-left: 12px;
            font-weight: bold;
            font-size: 1.2rem;
        }

        .board {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 14px;
            background-color: #10262e;
            padding: 20px;
            border-radius: 56px;
            box-shadow: inset 0 0 6px #00000044, 0 20px 30px -8px black;
            margin-bottom: 28px;
        }

        .cell {
            aspect-ratio: 1 / 1;
            background: #fef5e6;
            background-image: radial-gradient(circle at 40% 30%, rgba(255,235,200,0.7) 1.8%, transparent 2.2%);
            background-size: 20px 20px;
            border-radius: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 4.5rem;
            font-weight: 800;
            font-family: 'Segoe UI', 'Quicksand', system-ui;
            cursor: pointer;
            transition: 0.12s linear;
            box-shadow: 0 10px 0 rgba(0, 0, 0, 0.2);
            color: #2c3e4e;
        }

        .cell.X {
            color: #E67E22;
            text-shadow: 0 0 6px #FFB347;
        }

        .cell.O {
            color: #2c9b77;
            text-shadow: 0 0 6px #4ce0b0;
        }

        .cell:hover:not(.disabled-cell) {
            transform: scale(1.02);
            background: #fff8ef;
            box-shadow: 0 6px 0 rgba(0, 0, 0, 0.2);
        }

        .disabled-cell {
            cursor: default;
            filter: brightness(0.96);
            transform: none;
        }

        .reset-btn {
            background: #ffb347;
            border: none;
            width: 70%;
            padding: 14px 0;
            font-size: 1.3rem;
            font-weight: bold;
            border-radius: 60px;
            color: #1e2a2f;
            display: block;
            margin: 0 auto;
            cursor: pointer;
            transition: all 0.1s;
            font-family: inherit;
            box-shadow: 0 8px 0 #b4621a;
        }

        .reset-btn:active {
            transform: translateY(3px);
            box-shadow: 0 4px 0 #b4621a;
        }

        .footer-note {
            text-align: center;
            margin-top: 20px;
            font-size: 0.7rem;
            color: #8da3b0;
        }

        @media (max-width: 500px) {
            .game-container {
                padding: 18px 18px 28px 18px;
                min-height: 560px;
            }
            .cell {
                font-size: 3rem;
            }
            .board {
                gap: 10px;
                padding: 14px;
            }
            .status-text {
                font-size: 1rem;
            }
            .mode-btn {
                padding: 6px 18px;
                font-size: 0.85rem;
            }
            .status-panel {
                min-height: 70px;
                padding: 10px 16px;
            }
        }
    </style>
</head>
<body>
<div class="game-container">
    <div class="game-header">
        <h1>&#9889; 天元·不败井字棋 &#9889;</h1>
        <div class="sub">Minimax + 绝对先手防御 | AI 永不失误</div>
    </div>

    <div class="mode-bar">
        <button class="mode-btn" data-mode="twoPlayer">&#128101; 双人对弈</button>
        <button class="mode-btn active" data-mode="vsAI">&#129302; 人机 · 无敌模式</button>
    </div>

    <div class="status-panel">
        <span class="status-text" id="gameStatusText">&#10024; X 先手 &#10024;</span>
        <span class="turn-indicator" id="turnIcon">&#127922;</span>
    </div>

    <div class="board" id="board"></div>

    <button class="reset-btn" id="resetGameBtn">&#128260; 重开棋局</button>
    <div class="footer-note">&#129302; 强化AI:既会立即获胜,也会精准堵杀 | 结局要么AI赢,要么平局</div>
</div>

<script>
    // ---------- DOM 元素 ----------
    const boardElement = document.getElementById('board');
    const gameStatusSpan = document.getElementById('gameStatusText');
    const turnIconSpan = document.getElementById('turnIcon');
    const resetBtn = document.getElementById('resetGameBtn');
    const modeBtns = document.querySelectorAll('.mode-btn');

    // ---------- 游戏核心变量 ----------
    let board = Array(9).fill(null);        // 0-8 索引, null / 'X' / 'O'
    let currentPlayer = 'X';               // X 先手
    let gameActive = true;
    let gameMode = 'vsAI';                 // 'twoPlayer' 或 'vsAI'
    let winnerInfo = null;                 // 'X', 'O', 'draw'
    
    // AI 标识
    const AI_PIECE = 'O';
    const HUMAN_PIECE = 'X';
    let isAIPlaying = false;               // 防止AI递归或重复触发
    
    // ---------- 辅助函数 ----------
    function updateBoardUI() {
        const cells = document.querySelectorAll('.cell');
        for (let i = 0; i < cells.length; i++) {
            const val = board[i];
            cells[i].textContent = val === 'X' ? '&#10006;' : (val === 'O' ? '&#9711;' : '');
            if (val === 'X') {
                cells[i].classList.add('X');
                cells[i].classList.remove('O');
            } else if (val === 'O') {
                cells[i].classList.add('O');
                cells[i].classList.remove('X');
            } else {
                cells[i].classList.remove('X', 'O');
            }
            // 禁用样式(有棋子或者游戏结束则不可点)
            if (val !== null || !gameActive) {
                cells[i].classList.add('disabled-cell');
            } else {
                cells[i].classList.remove('disabled-cell');
            }
        }
    }
    
    // 检查赢家 (返回 'X', 'O' 或 null)
    function checkWinner(boardState) {
        const winPatterns = [
            [0,1,2], [3,4,5], [6,7,8],
            [0,3,6], [1,4,7], [2,5,8],
            [0,4,8], [2,4,6]
        ];
        for (let pattern of winPatterns) {
            const [a,b,c] = pattern;
            if (boardState[a] && boardState[a] === boardState[b] && boardState[a] === boardState[c]) {
                return boardState[a];
            }
        }
        return null;
    }
    
    function isDraw(boardState) {
        return boardState.every(cell => cell !== null) && !checkWinner(boardState);
    }
    
    // 更新界面文字
    function updateGameStatusDisplay() {
        if (!gameActive) {
            if (winnerInfo === 'X') {
                gameStatusSpan.innerHTML = '&#127942; 胜利者:&#10006; X 方!&#127942;';
                turnIconSpan.innerHTML = '&#9889;';
            } else if (winnerInfo === 'O') {
                gameStatusSpan.innerHTML = '&#127942; 胜利者:&#9711; O 方 (AI)!&#127942;';
                turnIconSpan.innerHTML = '&#129302;';
            } else if (winnerInfo === 'draw') {
                gameStatusSpan.innerHTML = '&#129309; 平局! AI 依然不败 &#129309;';
                turnIconSpan.innerHTML = '&#127744;';
            } else {
                gameStatusSpan.innerHTML = '&#9881;&#65039; 终局 &#9881;&#65039;';
            }
            return;
        }
        
        if (gameMode === 'twoPlayer') {
            if (currentPlayer === 'X') {
                gameStatusSpan.innerHTML = '&#127919; X 的回合 · 执叉先行 &#127919;';
                turnIconSpan.innerHTML = '&#10060;';
            } else {
                gameStatusSpan.innerHTML = '&#127919; O 的回合 · 画圆博弈 &#127919;';
                turnIconSpan.innerHTML = '&#11093;';
            }
        } else {  // vsAI 模式
            if (currentPlayer === HUMAN_PIECE) {
                gameStatusSpan.innerHTML = '&#129504; 你的回合 (X) · 落子破局 &#129504;';
                turnIconSpan.innerHTML = '&#10006;&#65039;';
            } else {
                gameStatusSpan.innerHTML = '&#9881;&#65039; AI 极致计算中 (O) · 完美博弈 &#9881;&#65039;';
                turnIconSpan.innerHTML = '&#129302;';
            }
        }
    }
    
    // 刷新游戏结束标志,更新 active 和 winnerInfo
    function refreshGameOverState() {
        const winner = checkWinner(board);
        if (winner) {
            gameActive = false;
            winnerInfo = winner;
            updateGameStatusDisplay();
            updateBoardUI();
            return true;
        }
        if (isDraw(board)) {
            gameActive = false;
            winnerInfo = 'draw';
            updateGameStatusDisplay();
            updateBoardUI();
            return true;
        }
        gameActive = true;
        winnerInfo = null;
        updateGameStatusDisplay();
        updateBoardUI();
        return false;
    }
    
    // 核心落子方法 (不做AI触发, 仅执行落子并更新状态)
    function placeMove(index, player) {
        if (!gameActive) return false;
        if (index < 0 || index > 8) return false;
        if (board[index] !== null) return false;
        if (player !== currentPlayer) return false;
        
        board[index] = player;
        updateBoardUI();
        const ended = refreshGameOverState();
        
        if (!ended) {
            currentPlayer = (currentPlayer === 'X' ? 'O' : 'X');
            updateGameStatusDisplay();
        } else {
            updateBoardUI();
        }
        return true;
    }
    
    // ---------- 增强型AI:立即获胜 + 防守 + minimax完美决策 ----------
    // 获取某玩家能否一步获胜的位置 (返回位置索引,若无返回 -1)
    function getImmediateWinMove(boardState, player) {
        for (let i = 0; i < 9; i++) {
            if (boardState[i] === null) {
                boardState[i] = player;
                if (checkWinner(boardState) === player) {
                    boardState[i] = null;
                    return i;
                }
                boardState[i] = null;
            }
        }
        return -1;
    }
    
    // 防守:返回对手即将获胜的位置(需要堵住的位置)
    function getBlockingMove(boardState, opponent) {
        for (let i = 0; i < 9; i++) {
            if (boardState[i] === null) {
                boardState[i] = opponent;
                if (checkWinner(boardState) === opponent) {
                    boardState[i] = null;
                    return i;
                }
                boardState[i] = null;
            }
        }
        return -1;
    }
    
    // ---------- Minimax 核心 (带 alpha-beta,完美评分) ----------
    function evaluateBoard(boardState) {
        const winner = checkWinner(boardState);
        if (winner === AI_PIECE) return 10;
        if (winner === HUMAN_PIECE) return -10;
        return 0;
    }
    
    function minimax(boardState, isMaximizing, alpha = -Infinity, beta = Infinity) {
        const score = evaluateBoard(boardState);
        if (score === 10 || score === -10) return score;
        if (boardState.every(cell => cell !== null)) return 0;
        
        if (isMaximizing) {
            // AI 回合 (O)
            let best = -Infinity;
            for (let i = 0; i < 9; i++) {
                if (boardState[i] === null) {
                    boardState[i] = AI_PIECE;
                    let value = minimax(boardState, false, alpha, beta);
                    boardState[i] = null;
                    best = Math.max(best, value);
                    alpha = Math.max(alpha, best);
                    if (beta <= alpha) break;
                }
            }
            return best;
        } else {
            // 玩家回合 (X) -> 最小化AI得分
            let best = Infinity;
            for (let i = 0; i < 9; i++) {
                if (boardState[i] === null) {
                    boardState[i] = HUMAN_PIECE;
                    let value = minimax(boardState, true, alpha, beta);
                    boardState[i] = null;
                    best = Math.min(best, value);
                    beta = Math.min(beta, best);
                    if (beta <= alpha) break;
                }
            }
            return best;
        }
    }
    
    // 获取最佳AI落子(先尝试必胜/防守,再调用Minimax确保全局最优)
    function getBestAIMove() {
        // 1. 优先自己立即获胜
        let winMove = getImmediateWinMove(board, AI_PIECE);
        if (winMove !== -1) return winMove;
        
        // 2. 防守玩家立即获胜 (堵杀)
        let blockMove = getBlockingMove(board, HUMAN_PIECE);
        if (blockMove !== -1) return blockMove;
        
        // 3. 没有立即胜负威胁,使用Minimax全局最优
        let bestScore = -Infinity;
        let bestMove = -1;
        const boardCopy = [...board];
        
        for (let i = 0; i < 9; i++) {
            if (boardCopy[i] === null) {
                boardCopy[i] = AI_PIECE;
                let moveScore = minimax(boardCopy, false);
                boardCopy[i] = null;
                if (moveScore > bestScore) {
                    bestScore = moveScore;
                    bestMove = i;
                }
                if (bestScore === 10) break;
            }
        }
        return bestMove;
    }
    
    // AI 执行移动 (带锁,防止并发)
    async function aiMove() {
        if (isAIPlaying) return;
        if (gameMode !== 'vsAI') return;
        if (!gameActive) return;
        if (currentPlayer !== AI_PIECE) return;
        
        isAIPlaying = true;
        // 微小延迟,让UI更自然,同时保证状态稳定
        await new Promise(resolve => setTimeout(resolve, 30));
        
        if (!gameActive || gameMode !== 'vsAI' || currentPlayer !== AI_PIECE) {
            isAIPlaying = false;
            return;
        }
        
        const bestIndex = getBestAIMove();
        if (bestIndex !== -1 && board[bestIndex] === null) {
            placeMove(bestIndex, AI_PIECE);
        }
        
        isAIPlaying = false;
        
        // 若AI移动后游戏未结束,且当前玩家变成了AI(理论上不会,但防止极端情况)
        if (gameActive && gameMode === 'vsAI' && currentPlayer === AI_PIECE && !isAIPlaying) {
            setTimeout(() => {
                if (gameActive && gameMode === 'vsAI' && currentPlayer === AI_PIECE && !isAIPlaying) {
                    aiMove();
                }
            }, 20);
        }
    }
    
    function triggerAIMoveIfNeeded() {
        if (gameMode !== 'vsAI') return;
        if (!gameActive) return;
        if (currentPlayer === AI_PIECE && !isAIPlaying) {
            aiMove();
        }
    }
    
    // ---------- 玩家移动逻辑 ----------
    function handlePlayerMove(index) {
        if (!gameActive) return false;
        if (board[index] !== null) return false;
        if (isAIPlaying) return false;   // AI思考中不能落子
        
        if (gameMode === 'twoPlayer') {
            const success = placeMove(index, currentPlayer);
            if (success) {
                // 双人模式无需额外AI
                return true;
            }
            return false;
        } 
        else if (gameMode === 'vsAI') {
            // 只有人类回合才能下
            if (currentPlayer !== HUMAN_PIECE) return false;
            const success = placeMove(index, HUMAN_PIECE);
            if (success) {
                if (gameActive && currentPlayer === AI_PIECE) {
                    triggerAIMoveIfNeeded();
                }
                return true;
            }
            return false;
        }
        return false;
    }
    
    // ---------- 重置游戏(保留当前模式)----------
    function resetGame() {
        isAIPlaying = false;
        board.fill(null);
        currentPlayer = 'X';     // X 永远先手
        gameActive = true;
        winnerInfo = null;
        
        updateBoardUI();
        refreshGameOverState();
        
        // 人机模式下,重置后玩家先手(X),AI为O后手,不需要立即触发AI
        if (gameMode === 'vsAI' && currentPlayer === AI_PIECE) {
            // 正常情况下不会发生,但若发生则触发AI
            if (gameActive && !isAIPlaying) {
                triggerAIMoveIfNeeded();
            }
        }
    }
    
    // 切换模式
    function setGameMode(mode) {
        gameMode = mode;
        resetGame();
        // 更新按钮样式
        modeBtns.forEach(btn => {
            if (btn.getAttribute('data-mode') === mode) {
                btn.classList.add('active');
            } else {
                btn.classList.remove('active');
            }
        });
        updateGameStatusDisplay();
    }
    
    // ---------- 棋盘UI构建 ----------
    function createBoardUI() {
        boardElement.innerHTML = '';
        for (let i = 0; i < 9; i++) {
            const cell = document.createElement('div');
            cell.classList.add('cell');
            cell.setAttribute('data-index', i);
            cell.addEventListener('click', (e) => {
                const idx = parseInt(cell.getAttribute('data-index'));
                handlePlayerMove(idx);
            });
            boardElement.appendChild(cell);
        }
        updateBoardUI();
    }
    
    // 事件绑定
    function initEventListeners() {
        resetBtn.addEventListener('click', () => {
            resetGame();
        });
        
        modeBtns.forEach(btn => {
            btn.addEventListener('click', () => {
                const mode = btn.getAttribute('data-mode');
                if (mode === 'twoPlayer') setGameMode('twoPlayer');
                else if (mode === 'vsAI') setGameMode('vsAI');
            });
        });
    }
    
    // 初始化
    function init() {
        createBoardUI();
        initEventListeners();
        setGameMode('vsAI');   // 默认无敌AI模式
        refreshGameOverState();
    }
    
    init();
</script>
</body>
</html>

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
dadadagang + 1 + 1 好玩有趣

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

zhubaihui 发表于 2026-3-23 15:35
人机比较厉害,非常感谢
fengbaby2003 发表于 2026-3-23 15:48
dadadagang 发表于 2026-3-23 15:58
mingtianyy 发表于 2026-3-23 16:10
十分优秀
FZchun 发表于 2026-3-23 17:42
好像点满就结束了,不能消第一次点的位置。
不知道是否是打开错了
斌斌A 发表于 2026-3-23 20:21
感谢分享,学习了
liuxiaocao 发表于 2026-3-24 15:19
非常感谢分享
wapjltb 发表于 2026-3-24 16:42
复制代码运行X显示成“&#10006;”,O显示成“&#9711;”
 楼主| xiaokeng0322 发表于 2026-3-25 13:59
FZchun 发表于 2026-3-23 17:42
好像点满就结束了,不能消第一次点的位置。
不知道是否是打开错了

下满了就是平局了 可以重新开始的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-13 06:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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