五子棋专业版2.4 – 网页版源码


马上注册,结交更多好友,享用更多功能,让你轻松玩转小K网。

您需要 内容被隐藏 才可以下载或查看,没有账号?内容被隐藏
内容被隐藏

×

使用 DeepSeek 写的,去掉了颜色选项。经过几次的调整,看起来还行!

复制以下代码,创建TXT文本,然后复制到里面,文本后缀名改成html即可

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>五子棋 · 宽格版(修复悔棋BUG)</title>
  7.     <style>
  8.         * {
  9.             box-sizing: border-box;
  10.             user-select: none;
  11.             margin: 0;
  12.             padding: 0;
  13.         }
  14.         body {
  15.             background: #2c3e4f;
  16.             display: flex;
  17.             justify-content: center;
  18.             align-items: center;
  19.             min-height: 100vh;
  20.             font-family: 'Segoe UI', 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', sans-serif;
  21.             padding: 20px;
  22.         }
  23.         .game-layout {
  24.             display: flex;
  25.             align-items: stretch;
  26.             justify-content: center;
  27.             gap: 28px;
  28.             width: 100%;
  29.             max-width: 1800px;
  30.             margin: 0 auto;
  31.         }
  32.         .control-panel {
  33.             width: 280px;
  34.             background: rgba(35, 35, 45, 0.85);
  35.             backdrop-filter: blur(10px);
  36.             -webkit-backdrop-filter: blur(10px);
  37.             border-radius: 48px;
  38.             padding: 30px 20px;
  39.             color: #f0e9e0;
  40.             box-shadow: 0 20px 30px -8px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,215,140,0.2) inset;
  41.             border: 1px solid rgba(255, 215, 150, 0.3);
  42.             display: flex;
  43.             flex-direction: column;
  44.             gap: 18px;
  45.             max-height: 90vh;
  46.             overflow-y: auto;
  47.             scrollbar-width: thin;
  48.             scrollbar-color: #c09a6b #3a2a2a;
  49.         }
  50.         .control-panel::-webkit-scrollbar {
  51.             width: 6px;
  52.         }
  53.         .control-panel::-webkit-scrollbar-thumb {
  54.             background: #c09a6b;
  55.             border-radius: 20px;
  56.         }
  57.         .board-wrapper {
  58.             background: transparent;
  59.             padding: 0;
  60.             border-radius: 36px;
  61.             box-shadow: 0 25px 40px rgba(0, 0, 0, 0.5);
  62.             display: flex;
  63.             align-items: center;
  64.             justify-content: center;
  65.             flex: 0 0 auto;
  66.         }
  67.         canvas#boardCanvas {
  68.             display: block;
  69.             width: min(85vh, 85vw);
  70.             height: min(85vh, 85vw);
  71.             background: #ebc28e;
  72.             border-radius: 32px;
  73.             cursor: pointer;
  74.             box-shadow: inset 0 0 0 2px #b87c3a, 0 15px 25px rgba(0,0,0,0.4);
  75.             transition: background-color 0.3s ease;
  76.         }
  77.         canvas#boardCanvas.victory-gray {
  78.             background: #b0b0b0 !important;
  79.         }
  80.         .panel-title {
  81.             font-size: 34px;
  82.             font-weight: 700;
  83.             text-align: center;
  84.             background: linear-gradient(135deg, #fde6b6, #dba870);
  85.             -webkit-background-clip: text;
  86.             -webkit-text-fill-color: transparent;
  87.             margin-bottom: 8px;
  88.             text-shadow: 0 2px 5px #00000050;
  89.         }
  90.         .group-title {
  91.             font-size: 20px;
  92.             font-weight: 600;
  93.             color: #eddabc;
  94.             margin: 8px 0 6px 6px;
  95.             letter-spacing: 1px;
  96.             border-left: 6px solid #e6b567;
  97.             padding-left: 12px;
  98.             text-transform: uppercase;
  99.         }
  100.         .button-row {
  101.             display: flex;
  102.             gap: 12px;
  103.             justify-content: center;
  104.             margin-bottom: 8px;
  105.             flex-wrap: wrap;
  106.         }
  107.         .btn {
  108.             background: #2f2f3a;
  109.             border: 2px solid #7e6b5a;
  110.             color: #f2e3cf;
  111.             font-size: 18px;
  112.             font-weight: 600;
  113.             padding: 14px 8px;
  114.             border-radius: 60px;
  115.             text-align: center;
  116.             cursor: pointer;
  117.             transition: all 0.15s ease;
  118.             box-shadow: 0 6px 0 #1a1a22, 0 4px 12px black;
  119.             flex: 1 1 0px;
  120.             min-width: 100px;
  121.             backdrop-filter: blur(4px);
  122.             letter-spacing: 0.8px;
  123.             display: flex;
  124.             align-items: center;
  125.             justify-content: center;
  126.             gap: 6px;
  127.         }
  128.         .btn.small {
  129.             font-size: 16px;
  130.             padding: 12px 6px;
  131.             min-width: 80px;
  132.         }
  133.         .btn.active {
  134.             background: #3c6e8f;
  135.             border-color: #ffcf9a;
  136.             box-shadow: 0 6px 0 #1d404b, 0 4px 12px black;
  137.             color: white;
  138.         }
  139.         .btn:hover {
  140.             background: #4a4a5a;
  141.             border-color: #dbb27c;
  142.             transform: translateY(-2px);
  143.             box-shadow: 0 8px 0 #1a1a22, 0 8px 16px black;
  144.         }
  145.         .btn:active {
  146.             transform: translateY(4px);
  147.             box-shadow: 0 2px 0 #1a1a22;
  148.         }
  149.         /* 方形图标样式 */
  150.         .btn-icon {
  151.             display: inline-flex;
  152.             align-items: center;
  153.             justify-content: center;
  154.             width: 24px;
  155.             height: 24px;
  156.             background: rgba(255, 255, 255, 0.15);
  157.             border: 2px solid #dbb27c;
  158.             border-radius: 6px;
  159.             font-size: 16px;
  160.             font-weight: bold;
  161.             color: #f2e3cf;
  162.             margin-right: 4px;
  163.         }
  164.         .btn.small .btn-icon {
  165.             width: 20px;
  166.             height: 20px;
  167.             font-size: 14px;
  168.         }
  169.         .status-area {
  170.             font-size: 30px;
  171.             font-weight: 700;
  172.             text-align: center;
  173.             background: #2b2b38cc;
  174.             border-radius: 60px;
  175.             padding: 22px 8px;
  176.             margin: 10px 0 14px 0;
  177.             border: 2px solid #c9a468;
  178.             box-shadow: inset 0 2px 6px #00000060, 0 6px 0 #18181e;
  179.             color: #fff2d4;
  180.             backdrop-filter: blur(4px);
  181.         }
  182.         .review-indicator {
  183.             background: #b06e30;
  184.             color: #ffefc0;
  185.             font-weight: bold;
  186.             text-align: center;
  187.             padding: 18px 8px;
  188.             border-radius: 60px;
  189.             font-size: 24px;
  190.             border: 2px solid #ffcf8a;
  191.             box-shadow: 0 6px 0 #5f3e1f;
  192.         }
  193.         .file-actions {
  194.             gap: 16px;
  195.         }
  196.         #colorPicker {
  197.             position: absolute;
  198.             left: -9999px;
  199.         }
  200.         /* 弹窗样式 */
  201.         .victory-modal {
  202.             position: fixed;
  203.             top: 0;
  204.             left: 0;
  205.             width: 100%;
  206.             height: 100%;
  207.             background: rgba(0, 0, 0, 0.85);
  208.             display: flex;
  209.             justify-content: center;
  210.             align-items: center;
  211.             z-index: 9999;
  212.             backdrop-filter: blur(8px);
  213.             -webkit-backdrop-filter: blur(8px);
  214.         }
  215.         .modal-content {
  216.             background: linear-gradient(135deg, #433b32, #2a2620);
  217.             border-radius: 40px;
  218.             padding: 60px 40px;
  219.             text-align: center;
  220.             border: 4px solid #e6b567;
  221.             box-shadow: 0 0 50px rgba(255, 207, 154, 0.6);
  222.             min-width: 400px;
  223.             max-width: 600px;
  224.         }
  225.         .modal-title {
  226.             font-size: 48px;
  227.             font-weight: 800;
  228.             margin-bottom: 20px;
  229.             text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
  230.         }
  231.         .win-text {
  232.             background: linear-gradient(135deg, #ffdf88, #ffb74d);
  233.             -webkit-background-clip: text;
  234.             -webkit-text-fill-color: transparent;
  235.         }
  236.         .lose-text {
  237.             background: linear-gradient(135deg, #ff8a80, #ef5350);
  238.             -webkit-background-clip: text;
  239.             -webkit-text-fill-color: transparent;
  240.         }
  241.         .modal-subtitle {
  242.             font-size: 28px;
  243.             color: #f2e3cf;
  244.             margin-bottom: 40px;
  245.         }
  246.         .modal-btn {
  247.             background: #2f2f3a;
  248.             border: 3px solid #e6b567;
  249.             color: #f2e3cf;
  250.             font-size: 22px;
  251.             font-weight: 700;
  252.             padding: 18px 40px;
  253.             border-radius: 60px;
  254.             cursor: pointer;
  255.             transition: all 0.2s ease;
  256.             box-shadow: 0 8px 0 #1a1a22, 0 6px 20px black;
  257.         }
  258.         .modal-btn:hover {
  259.             background: #4a4a5a;
  260.             border-color: #ffcf9a;
  261.             transform: translateY(-4px);
  262.             box-shadow: 0 12px 0 #1a1a22, 0 8px 25px black;
  263.         }
  264.         .modal-btn:active {
  265.             transform: translateY(4px);
  266.             box-shadow: 0 4px 0 #1a1a22;
  267.         }
  268.         @media (max-width: 1200px) {
  269.             .game-layout {
  270.                 flex-wrap: wrap;
  271.             }
  272.             .control-panel {
  273.                 width: 320px;
  274.                 max-height: none;
  275.             }
  276.             .modal-content {
  277.                 min-width: 80%;
  278.                 padding: 40px 20px;
  279.             }
  280.             .modal-title {
  281.                 font-size: 36px;
  282.             }
  283.             .modal-subtitle {
  284.                 font-size: 22px;
  285.             }
  286.         }
  287.     </style>
  288. </head>
  289. <body>
  290.     <input type="color" id="colorPicker" value="#000000">
  291.     <div class="game-layout">
  292.         <!-- 左侧控制面板 -->
  293.         <div class="control-panel left-panel" id="leftPanel"></div>
  294.         <!-- 中央棋盘 (方格放大,边缘留两子宽度) -->
  295.         <div class="board-wrapper">
  296.             <canvas id="boardCanvas" width="900" height="900"></canvas>
  297.         </div>
  298.         <!-- 右侧控制面板 -->
  299.         <div class="control-panel right-panel" id="rightPanel"></div>
  300.     </div>
  301. <script>
  302.     (function() {
  303.         // ---------- 常量 & 全局变量 ----------
  304.         const BOARD_SIZE = 15;
  305.         const CELL_SIZE = 46;           
  306.         const BOARD_LEFT = 128;         
  307.         const BOARD_TOP = 128;         
  308.         const PIECE_RADIUS = 20;        
  309.         // 棋盘底色
  310.         let BOARD_COLOR = [235, 194, 142];
  311.         let bodyBgColor = [44, 62, 79];
  312.         let victoryModalShown = false;
  313.         // 内容被隐藏状态类
  314.         class Game {
  315.             constructor() {
  316.                 this.mode = 'pve';
  317.                 this.difficulty = '中等';
  318.                 this.board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0));
  319.                 this.turn = 1;
  320.                 this.history = [];
  321.                 this.winner = 0;
  322.                 this.aiThinking = false;
  323.                 this.showCoords = true;
  324.                 this.player1Color = [0, 0, 0];
  325.                 this.player2Color = [255, 255, 255];
  326.                 this.reviewMode = false;
  327.                 this.reviewStep = -1;
  328.                 this.reviewHistory = [];
  329.                 this.isUndoing = false; // 新增:悔棋状态锁
  330.             }
  331.             reset() {
  332.                 this.board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0));
  333.                 this.history = [];
  334.                 this.turn = 1;
  335.                 this.winner = 0;
  336.                 this.reviewMode = false;
  337.                 this.reviewStep = -1;
  338.                 this.reviewHistory = [];
  339.                 this.aiThinking = false;
  340.                 this.isUndoing = false; // 重置悔棋锁
  341.                 victoryModalShown = false;
  342.             }
  343.             placePiece(row, col) {
  344.                 // 悔棋过程中禁止落子
  345.                 if (this.isUndoing || row<0||row>=BOARD_SIZE||col<0||col>=BOARD_SIZE||this.board[row][col]!==0||this.winner!==0||this.reviewMode) return false;
  346.                 this.board[row][col] = this.turn;
  347.                 this.history.push([row, col, this.turn]);
  348.                 if (this.checkWin(row, col)) {
  349.                     this.winner = this.turn;
  350.                     setTimeout(showVictoryModal, 300);
  351.                 }
  352.                 this.turn = this.turn === 1 ? 2 : 1;
  353.                 return true;
  354.             }
  355.             // 核心修复:悔棋逻辑
  356.             undo() {
  357.                 // 禁止在复盘/AI思考/无历史记录时悔棋
  358.                 if (this.history.length === 0 || this.reviewMode || this.aiThinking) return false;
  359.                  
  360.                 // 加悔棋锁,防止AI在悔棋过程中落子
  361.                 this.isUndoing = true;
  362.                  
  363.                 try {
  364.                     let undoCount = 0;
  365.                     // 人机模式:一次性撤销玩家+AI的两步棋
  366.                     if (this.mode === 'pve') {
  367.                         // 先撤销最后一步(AI的棋)
  368.                         if (this.history.length > 0) {
  369.                             let [row, col] = this.history.pop();
  370.                             this.board[row][col] = 0;
  371.                             undoCount++;
  372.                         }
  373.                         // 再撤销玩家的棋(如果有)
  374.                         if (this.history.length > 0) {
  375.                             let [row, col, player] = this.history.pop();
  376.                             this.board[row][col] = 0;
  377.                             this.turn = player; // 恢复到玩家回合
  378.                             undoCount++;
  379.                         }
  380.                     }
  381.                     // 人人模式:只撤销一步
  382.                     else {
  383.                         let [row, col, player] = this.history.pop();
  384.                         this.board[row][col] = 0;
  385.                         this.turn = player;
  386.                         undoCount++;
  387.                     }
  388.                     // 重置内容被隐藏状态
  389.                     this.winner = 0;
  390.                     victoryModalShown = false;
  391.                      
  392.                     // 移除胜利弹窗
  393.                     const modal = document.querySelector('.victory-modal');
  394.                     if (modal) modal.remove();
  395.                      
  396.                     return undoCount > 0;
  397.                 } finally {
  398.                     // 延迟释放锁,确保状态完全同步
  399.                     setTimeout(() => {
  400.                         this.isUndoing = false;
  401.                     }, 200);
  402.                 }
  403.             }
  404.             checkWin(row, col) {
  405.                 const player = this.board[row][col];
  406.                 const dirs = [[1,0],[0,1],[1,1],[1,-1]];
  407.                 for (let [dr, dc] of dirs) {
  408.                     let count = 1;
  409.                     for (let i=1; i<5; i++) { let nr=row+dr*i, nc=col+dc*i; if (nr<0||nr>=BOARD_SIZE||nc<0||nc>=BOARD_SIZE||this.board[nr][nc]!==player) break; count++; }
  410.                     for (let i=1; i<5; i++) { let nr=row-dr*i, nc=col-dc*i; if (nr<0||nr>=BOARD_SIZE||nc<0||nc>=BOARD_SIZE||this.board[nr][nc]!==player) break; count++; }
  411.                     if (count >= 5) return true;
  412.                 }
  413.                 return false;
  414.             }
  415.             aiMoveEasy() {
  416.                 let weights = Array(BOARD_SIZE).fill().map(()=>Array(BOARD_SIZE).fill(0));
  417.                 const dirs = [[1,0],[0,1],[1,1],[1,-1]];
  418.                 for (let r=0; r<BOARD_SIZE; r++) {
  419.                     for (let c=0; c<BOARD_SIZE; c++) {
  420.                         if (this.board[r][c] !== 0) continue;
  421.                         let score = 0;
  422.                         for (let [dr, dc] of dirs) {
  423.                             let cnt = 1;
  424.                             for (let i=1; i<5; i++) { let rr=r+dr*i, cc=c+dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==2) break; cnt++; }
  425.                             for (let i=1; i<5; i++) { let rr=r-dr*i, cc=c-dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==2) break; cnt++; }
  426.                             if (cnt>=5) score += 10000; else score += cnt*cnt;
  427.                         }
  428.                         for (let [dr, dc] of dirs) {
  429.                             let cnt = 1;
  430.                             for (let i=1; i<5; i++) { let rr=r+dr*i, cc=c+dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==1) break; cnt++; }
  431.                             for (let i=1; i<5; i++) { let rr=r-dr*i, cc=c-dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==1) break; cnt++; }
  432.                             if (cnt>=5) score += 8000; else score += cnt*cnt*0.8;
  433.                         }
  434.                         weights[r][c] = score;
  435.                     }
  436.                 }
  437.                 let maxScore = -1, best = null;
  438.                 for (let r=0; r<BOARD_SIZE; r++) for (let c=0; c<BOARD_SIZE; c++) if (weights[r][c] > maxScore) { maxScore = weights[r][c]; best = [r,c]; }
  439.                 return best;
  440.             }
  441.             evaluateMove(r, c, color) {
  442.                 if (this.board[r][c] !== 0) return 0;
  443.                 const dirs = [[1,0],[0,1],[1,1],[1,-1]];
  444.                 let total = 0;
  445.                 for (let [dr, dc] of dirs) {
  446.                     let cnt1 = 0, cnt2 = 0;
  447.                     for (let i=1; i<5; i++) { let rr=r+dr*i, cc=c+dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==color) break; cnt1++; }
  448.                     for (let i=1; i<5; i++) { let rr=r-dr*i, cc=c-dc*i; if (rr<0||rr>=BOARD_SIZE||cc<0||cc>=BOARD_SIZE||this.board[rr][cc]!==color) break; cnt2++; }
  449.                     let totalLen = 1 + cnt1 + cnt2;
  450.                     if (totalLen >= 5) return 1000000;
  451.                     let leftOpen = true, rightOpen = true;
  452.                     let rr = r + (cnt1+1)*dr, cc = c + (cnt1+1)*dc;
  453.                     if (rr>=0&&rr<BOARD_SIZE&&cc>=0&&cc<BOARD_SIZE && this.board[rr][cc]!==0) rightOpen = false;
  454.                     rr = r - (cnt2+1)*dr; cc = c - (cnt2+1)*dc;
  455.                     if (rr>=0&&rr<BOARD_SIZE&&cc>=0&&cc<BOARD_SIZE && this.board[rr][cc]!==0) leftOpen = false;
  456.                     let live = leftOpen && rightOpen;
  457.                     if (totalLen >= 5) total += 100000;
  458.                     else if (totalLen === 4 && live) total += 10000;
  459.                     else if (totalLen === 4) total += 2000;
  460.                     else if (totalLen === 3 && live) total += 2500;
  461.                     else if (totalLen === 3) total += 400;
  462.                     else total += totalLen * totalLen;
  463.                 }
  464.                 return total;
  465.             }
  466.             aiMoveMedium(defenseWeight = 0.8) {
  467.                 let bestScore = -1, bestMove = null;
  468.                 for (let r=0; r<BOARD_SIZE; r++) {
  469.                     for (let c=0; c<BOARD_SIZE; c++) {
  470.                         if (this.board[r][c] !== 0) continue;
  471.                         let attack = this.evaluateMove(r, c, 2);
  472.                         let defense = this.evaluateMove(r, c, 1);
  473.                         let total = attack + defense * defenseWeight;
  474.                         if (total > bestScore) { bestScore = total; bestMove = [r,c]; }
  475.                     }
  476.                 }
  477.                 return bestMove;
  478.             }
  479.             saveSgf() {
  480.                 return JSON.stringify({
  481.                     boardSize: BOARD_SIZE,
  482.                     player1Color: this.player1Color,
  483.                     player2Color: this.player2Color,
  484.                     history: this.history,
  485.                     mode: this.mode,
  486.                     difficulty: this.difficulty
  487.                 }, null, 2);
  488.             }
  489.             loadSgf(jsonStr) {
  490.                 try {
  491.                     let data = JSON.parse(jsonStr);
  492.                     if (data.boardSize !== BOARD_SIZE) return false;
  493.                     this.reset();
  494.                     this.player1Color = data.player1Color;
  495.                     this.player2Color = data.player2Color;
  496.                     this.reviewHistory = data.history || [];
  497.                     this.mode = data.mode || 'pve';
  498.                     this.difficulty = data.difficulty || '中等';
  499.                     this.reviewMode = true;
  500.                     this.reviewStep = -1;
  501.                     this.board = Array(BOARD_SIZE).fill().map(()=>Array(BOARD_SIZE).fill(0));
  502.                     this.turn = 1;
  503.                     this.winner = 0;
  504.                     victoryModalShown = false;
  505.                     return true;
  506.                 } catch (e) { return false; }
  507.             }
  508.             reviewForward() {
  509.                 if (!this.reviewMode) return;
  510.                 if (this.reviewStep + 1 < this.reviewHistory.length) {
  511.                     this.reviewStep++;
  512.                     let [row, col, player] = this.reviewHistory[this.reviewStep];
  513.                     this.board[row][col] = player;
  514.                     this.turn = player === 1 ? 2 : 1;
  515.                     if (this.checkWin(row, col)) this.winner = player;
  516.                 }
  517.             }
  518.             reviewBackward() {
  519.                 if (!this.reviewMode || this.reviewStep < 0) return;
  520.                 let [row, col, player] = this.reviewHistory[this.reviewStep];
  521.                 this.board[row][col] = 0;
  522.                 this.reviewStep--;
  523.                 if (this.reviewStep >= 0) {
  524.                     let [,,lastPlayer] = this.reviewHistory[this.reviewStep];
  525.                     this.turn = lastPlayer === 1 ? 2 : 1;
  526.                     this.winner = 0;
  527.                 } else { this.turn = 1; this.winner = 0; }
  528.                 victoryModalShown = false;
  529.             }
  530.             exitReview() {
  531.                 this.reviewMode = false;
  532.                 this.reviewStep = -1;
  533.                 this.reviewHistory = [];
  534.                 this.reset();
  535.             }
  536.         }
  537.         const game = new Game();
  538.         const canvas = document.getElementById('boardCanvas');
  539.         const ctx = canvas.getContext('2d');
  540.         const colorPicker = document.getElementById('colorPicker');
  541.         // 显示胜利弹窗
  542.         function showVictoryModal() {
  543.             if (victoryModalShown) return;
  544.             victoryModalShown = true;
  545.             const modal = document.createElement('div');
  546.             modal.className = 'victory-modal';
  547.             
  548.             let isPlayerWin = false;
  549.             let titleText = '';
  550.             let subText = '';
  551.             if (game.mode === 'pve') {
  552.                 if (game.winner === 1) {
  553.                     isPlayerWin = true;
  554.                     titleText = '恭喜!';
  555.                     subText = '你赢了 太牛逼了 &#127881;';
  556.                 } else {
  557.                     isPlayerWin = false;
  558.                     titleText = '很遗憾!';
  559.                     subText = '你输了 小瘪三 &#128557;';
  560.                 }
  561.             } else {
  562.                 if (game.winner === 1) {
  563.                     titleText = '黑棋胜利!';
  564.                     subText = '恭喜黑棋获胜 &#127881;';
  565.                 } else {
  566.                     titleText = '白棋胜利!';
  567.                     subText = '恭喜白棋获胜 &#127881;';
  568.                 }
  569.             }
  570.             modal.innerHTML = `
  571.                 <div class="modal-content">
  572.                     <div class="modal-title ${isPlayerWin ? 'win-text' : 'lose-text'}">${titleText}</div>
  573.                     <div class="modal-subtitle">${subText}</div>
  574.                     <button class="modal-btn" id="modalRestartBtn">再来一局</button>
  575.                 </div>
  576.             `;
  577.             document.body.appendChild(modal);
  578.             document.getElementById('modalRestartBtn').addEventListener('click', () => {
  579.                 game.reset();
  580.                 drawBoard();
  581.                 renderPanels();
  582.                 modal.remove();
  583.             });
  584.         }
  585.         // 绘制棋盘
  586.         function drawBoard() {
  587.             ctx.clearRect(0, 0, 900, 900);
  588.             
  589.             let boardBgColor = game.winner !== 0 ? '#b0b0b0' : `rgb(${BOARD_COLOR[0]},${BOARD_COLOR[1]},${BOARD_COLOR[2]})`;
  590.             
  591.             ctx.fillStyle = boardBgColor;
  592.             ctx.fillRect(0, 0, 900, 900);
  593.             
  594.             ctx.strokeStyle = '#000';
  595.             ctx.lineWidth = 2;
  596.             for (let i=0; i<BOARD_SIZE; i++) {
  597.                 ctx.beginPath();
  598.                 ctx.moveTo(BOARD_LEFT, BOARD_TOP + i*CELL_SIZE);
  599.                 ctx.lineTo(BOARD_LEFT + (BOARD_SIZE-1)*CELL_SIZE, BOARD_TOP + i*CELL_SIZE);
  600.                 ctx.stroke();
  601.                 ctx.beginPath();
  602.                 ctx.moveTo(BOARD_LEFT + i*CELL_SIZE, BOARD_TOP);
  603.                 ctx.lineTo(BOARD_LEFT + i*CELL_SIZE, BOARD_TOP + (BOARD_SIZE-1)*CELL_SIZE);
  604.                 ctx.stroke();
  605.             }
  606.             const stars = [[7,7],[3,3],[11,3],[3,11],[11,11]];
  607.             ctx.fillStyle = '#000';
  608.             for (let [r,c] of stars) {
  609.                 let x = BOARD_LEFT + c*CELL_SIZE, y = BOARD_TOP + r*CELL_SIZE;
  610.                 ctx.beginPath(); ctx.arc(x, y, 6, 0, 2*Math.PI); ctx.fill();
  611.             }
  612.             for (let r=0; r<BOARD_SIZE; r++) for (let c=0; c<BOARD_SIZE; c++) {
  613.                 if (game.board[r][c] === 0) continue;
  614.                 let x = BOARD_LEFT + c*CELL_SIZE, y = BOARD_TOP + r*CELL_SIZE;
  615.                 let color = game.board[r][c] === 1 ? game.player1Color : game.player2Color;
  616.                 ctx.beginPath(); ctx.arc(x, y, PIECE_RADIUS, 0, 2*Math.PI);
  617.                 ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`; ctx.fill();
  618.                 ctx.strokeStyle = '#444'; ctx.lineWidth = 2; ctx.stroke();
  619.             }
  620.             if (game.showCoords) {
  621.                 ctx.font = 'bold 20px "Segoe UI", "Microsoft YaHei"';
  622.                 ctx.fillStyle = '#3a2a1a';
  623.                 for (let i=0; i<BOARD_SIZE; i++) {
  624.                     ctx.fillText(i+1, BOARD_LEFT-38, BOARD_TOP + i*CELL_SIZE+8);
  625.                     ctx.fillText(String.fromCharCode(65+i), BOARD_LEFT + i*CELL_SIZE-14, BOARD_TOP + (BOARD_SIZE-1)*CELL_SIZE+42);
  626.                 }
  627.             }
  628.             if (game.reviewMode) {
  629.                 ctx.font = 'bold 26px "Microsoft YaHei"';
  630.                 ctx.fillStyle = '#ffb347';
  631.                 let steps = game.reviewHistory.length;
  632.                 let stepIdx = game.reviewStep+1;
  633.                 let text = `&#128203; 复盘 ${stepIdx}/${steps}`;
  634.                 if (steps === 0) text = '&#128203; 复盘模式';
  635.                 ctx.fillText(text, BOARD_LEFT, BOARD_TOP-40);
  636.             }
  637.         }
  638.         // 渲染面板
  639.         function renderPanels() {
  640.             const leftDiv = document.getElementById('leftPanel');
  641.             const rightDiv = document.getElementById('rightPanel');
  642.             if (game.reviewMode) {
  643.                 leftDiv.innerHTML = `<div class="panel-title">&#9881;&#65039; 复盘</div>
  644.                     <div class="review-indicator">&#128204; 复盘进行中</div>
  645.                     <div class="group-title">&#9198;&#65039; 导航</div>
  646.                     <div class="button-row">
  647.                         <div class="btn" id="reviewPrev"><span class="btn-icon">&#9664;</span> 上一步</div>
  648.                         <div class="btn" id="reviewNext">下一步 <span class="btn-icon">&#9654;</span></div>
  649.                     </div>
  650.                     <div class="button-row"><div class="btn" id="reviewExit" style="width:100%;"><span class="btn-icon">&#9167;&#65039;</span> 退出复盘</div></div>`;
  651.                 rightDiv.innerHTML = `<div class="panel-title">&#128193; 棋谱信息</div>
  652.                     <div style="font-size:22px; padding:20px; text-align:center;">步数: ${game.reviewHistory.length}</div>`;
  653.                 document.getElementById('reviewPrev')?.addEventListener('click', ()=>{ game.reviewBackward(); drawBoard(); renderPanels(); });
  654.                 document.getElementById('reviewNext')?.addEventListener('click', ()=>{ game.reviewForward(); drawBoard(); renderPanels(); });
  655.                 document.getElementById('reviewExit')?.addEventListener('click', ()=>{ game.exitReview(); drawBoard(); renderPanels(); });
  656.                 return;
  657.             }
  658.             leftDiv.innerHTML = `
  659.                 <div class="panel-title">&#9899; 五子棋</div>
  660.                 <div class="group-title">&#127918; 模式</div>
  661.                 <div class="button-row">
  662.                     <div class="btn ${game.mode==='pve'?'active':''}" id="modePve"><span class="btn-icon">&#129302;</span> 人机</div>
  663.                     <div class="btn ${game.mode==='pvp'?'active':''}" id="modePvp"><span class="btn-icon">&#128101;</span> 人人</div>
  664.                 </div>
  665.                 <div class="group-title">&#128202; 难度</div>
  666.                 <div class="button-row">
  667.                     <div class="btn small ${game.difficulty==='简单'?'active':''}" id="diff0"><span class="btn-icon">★</span> 简单</div>
  668.                     <div class="btn small ${game.difficulty==='中等'?'active':''}" id="diff1"><span class="btn-icon">★★</span> 中等</div>
  669.                     <div class="btn small ${game.difficulty==='困难'?'active':''}" id="diff2"><span class="btn-icon">★★★</span> 困难</div>
  670.                 </div>
  671.                 <div class="status-area">${game.winner!==0?(game.winner===1?'&#9899; 黑胜':'&#9898; 白胜'):(game.turn===1?'&#9899; 黑棋走':'&#9898; 白棋走')}</div>
  672.                 <div class="group-title">&#128377;&#65039; 控制</div>
  673.                 <div class="button-row">
  674.                     <div class="btn small" id="resetBtn"><span class="btn-icon">&#8635;</span> 重开</div>
  675.                     <div class="btn small" id="undoBtn"><span class="btn-icon">&#8617;</span> 悔棋</div>
  676.                     <div class="btn small" id="toggleCoords"><span class="btn-icon">&#128506;&#65039;</span> 坐标</div>
  677.                 </div>
  678.             `;
  679.             rightDiv.innerHTML = `
  680.                 <div class="panel-title">&#128203; 棋谱</div>
  681.                 <div class="group-title">&#128190; 保存 / 加载</div>
  682.                 <div class="button-row file-actions">
  683.                     <div class="btn" id="saveBtn"><span class="btn-icon">&#128190;</span> 保存</div>
  684.                     <div class="btn" id="loadBtn"><span class="btn-icon">&#128194;</span> 加载</div>
  685.                 </div>
  686.                 <div style="margin-top: 40px;"></div>
  687.                 <div class="group-title">&#128204; 提示</div>
  688.                 <div style="background:#2d2d3a; border-radius: 36px; padding: 22px; font-size: 18px; text-align: center; border:1px solid #b5976b;">
  689.                     <span class="btn-icon" style="margin-right: 8px;">&#11036;</span> 点击棋盘落子<br><span class="btn-icon" style="margin-right: 8px;">&#129302;</span> 人机自动响应
  690.                 </div>
  691.             `;
  692.             // 事件绑定
  693.             document.getElementById('modePve')?.addEventListener('click', ()=>{ if(game.mode!=='pve'){game.mode='pve'; game.reset(); drawBoard(); renderPanels();}});
  694.             document.getElementById('modePvp')?.addEventListener('click', ()=>{ if(game.mode!=='pvp'){game.mode='pvp'; game.reset(); drawBoard(); renderPanels();}});
  695.             for (let i=0; i<3; i++) {
  696.                 document.getElementById(`diff${i}`)?.addEventListener('click', ()=>{ game.difficulty=['简单','中等','困难'][i]; renderPanels(); });
  697.             }
  698.             document.getElementById('resetBtn')?.addEventListener('click', ()=>{
  699.                 game.reset();
  700.                 const modal = document.querySelector('.victory-modal');
  701.                 if (modal) modal.remove();
  702.                 drawBoard();
  703.                 renderPanels();
  704.             });
  705.             // 修复悔棋按钮:增加失败提示
  706.             document.getElementById('undoBtn')?.addEventListener('click', ()=>{
  707.                 if(game.undo()) {
  708.                     drawBoard();
  709.                     renderPanels();
  710.                 } else {
  711.                     alert('无法悔棋!(无历史记录/复盘模式/AI思考中)');
  712.                 }
  713.             });
  714.             document.getElementById('toggleCoords')?.addEventListener('click', ()=>{ game.showCoords = !game.showCoords; drawBoard(); renderPanels(); });
  715.             document.getElementById('saveBtn')?.addEventListener('click', ()=>{
  716.                 if(game.history.length) { const json=game.saveSgf(); const blob=new Blob([json]); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='gobang.json'; a.click(); } else alert('无棋谱');
  717.             });
  718.             document.getElementById('loadBtn')?.addEventListener('click', ()=>{
  719.                 const input = document.createElement('input'); input.type='file'; input.accept='.json';
  720.                 input.onchange = (e) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (ev) => { if(game.loadSgf(ev.target.result)) { drawBoard(); renderPanels(); } else alert('加载失败'); }; reader.readAsText(file); };
  721.                 input.click();
  722.             });
  723.         }
  724.         // 鼠标落子
  725.         canvas.addEventListener('click', (e)=>{
  726.             // 悔棋过程中禁止落子
  727.             if (game.isUndoing) return;
  728.             
  729.             const rect = canvas.getBoundingClientRect();
  730.             const scale = canvas.width / rect.width;
  731.             const mouseX = (e.clientX - rect.left) * scale;
  732.             const mouseY = (e.clientY - rect.top) * scale;
  733.             if (mouseX >= BOARD_LEFT && mouseX <= BOARD_LEFT+(BOARD_SIZE-1)*CELL_SIZE && mouseY >= BOARD_TOP && mouseY <= BOARD_TOP+(BOARD_SIZE-1)*CELL_SIZE) {
  734.                 let col = Math.round((mouseX - BOARD_LEFT)/CELL_SIZE);
  735.                 let row = Math.round((mouseY - BOARD_TOP)/CELL_SIZE);
  736.                 row = Math.max(0, Math.min(BOARD_SIZE-1, row));
  737.                 col = Math.max(0, Math.min(BOARD_SIZE-1, col));
  738.                 if (!game.reviewMode) {
  739.                     let isPlaced = false;
  740.                     if (game.mode==='pve' && game.turn===1 && game.winner===0) {
  741.                         isPlaced = game.placePiece(row,col);
  742.                     } else if (game.mode==='pvp' && game.winner===0) {
  743.                         isPlaced = game.placePiece(row,col);
  744.                     }
  745.                     // 只有落子成功才刷新界面
  746.                     if (isPlaced) {
  747.                         drawBoard();
  748.                         renderPanels();
  749.                     }
  750.                 }
  751.             }
  752.         });
  753.         // 修复AI落子逻辑:增加悔棋锁检查
  754.         function aiTurn() {
  755.             // 悔棋中/复盘/非人机模式/非AI回合/内容被隐藏结束/AI思考中 都不执行
  756.             if (game.isUndoing || game.reviewMode || game.mode !== 'pve' || game.turn !== 2 || game.winner !== 0 || game.aiThinking) {
  757.                 return;
  758.             }
  759.             
  760.             game.aiThinking = true;
  761.             // 增加AI思考延迟(从30ms改为300ms),提升体验
  762.             setTimeout(() => {
  763.                 let best = game.difficulty === '简单' ? game.aiMoveEasy() : game.aiMoveMedium(game.difficulty === '中等' ? 0.8 : 0.9);
  764.                 if (best) game.placePiece(best[0], best[1]);
  765.                 game.aiThinking = false;
  766.                 drawBoard();
  767.                 renderPanels();
  768.             }, 300);
  769.         }
  770.         function loop() {
  771.             drawBoard();
  772.             aiTurn();
  773.             requestAnimationFrame(loop);
  774.         }
  775.         document.body.style.backgroundColor = `rgb(${bodyBgColor[0]},${bodyBgColor[1]},${bodyBgColor[2]})`;
  776.         renderPanels();
  777.         loop();
  778.     })();
  779. </script>
  780. </body>
  781. </html>
复制代码

clipboard.on('error', function(e) { setCopy(e.text, '代码已复制到剪贴板'); //console.log(e); });

内容已被隐藏,请输入验证码查看

关注微信公众号(魔王科技),发送"验证码"获取

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索