井字棋游戏实现

AI 智能总结

简介

井字棋(Tic-Tac-Toe)是最简单的棋类游戏之一,在 3x3 棋盘上两人轮流落子,先连成一线者胜。游戏厅版本实现了人机对战模式,电脑 AI 采用攻守兼备的策略,支持比分统计和获胜连线高亮动画。

棋盘表示

使用一维数组存储 3x3 棋盘状态:

1var board = ['', '', '', '', '', '', '', '', ''];
2// 索引映射:
3// 0 1 2
4// 3 4 5
5// 6 7 8

玩家使用 “x”,电脑使用 “o”:

1var PLAYER = 'x';
2var COMPUTER = 'o';

CSS Grid 渲染

 1.ttt-board {
 2  display: inline-grid;
 3  grid-template-columns: repeat(3, var(--ttt-cell, 100px));
 4  grid-template-rows: repeat(3, var(--ttt-cell, 100px));
 5  gap: 4px;
 6  background: #e2e8f0;
 7  border-radius: 10px;
 8  padding: 4px;
 9}
10.ttt-cell {
11  width: var(--ttt-cell, 100px);
12  height: var(--ttt-cell, 100px);
13  background: #fff;
14  border-radius: 8px;
15  font-size: calc(var(--ttt-cell) * 0.5);
16  cursor: pointer;
17  user-select: none;
18}
19.ttt-cell.x { color: #6366f1; }  /* 玩家 - 靛色 */
20.ttt-cell.o { color: #f97316; }  /* 电脑 - 橙色 */

游戏逻辑

落子处理

玩家点击空格 → 检查胜负/平局 → 电脑自动落子:

 1function placePiece(idx) {
 2  if (gameOver || board[idx] !== '') return;
 3
 4  board[idx] = PLAYER;
 5
 6  // 检查玩家是否获胜
 7  var win = checkWin(PLAYER);
 8  if (win) {
 9    gameOver = true;
10    scoreX++;
11    highlightWin(win);
12    showStatus('你赢了!');
13    return;
14  }
15
16  // 检查平局
17  if (board.every(function(c) { return c !== ''; })) {
18    gameOver = true;
19    showStatus('平局!');
20    return;
21  }
22
23  render();
24
25  // 电脑回合(延迟 400ms,模拟思考)
26  setTimeout(computerMove, 400);
27}

胜负检测

8 条可能的连线(3 横、3 竖、2 对角线):

 1var WIN_LINES = [
 2  [0, 1, 2], [3, 4, 5], [6, 7, 8],  // 横
 3  [0, 3, 6], [1, 4, 7], [2, 5, 8],  // 竖
 4  [0, 4, 8], [2, 4, 6]              // 对角线
 5];
 6
 7function checkWin(player) {
 8  for (var i = 0; i < WIN_LINES.length; i++) {
 9    var [a, b, c] = WIN_LINES[i];
10    if (board[a] === player && board[b] === player && board[c] === player) {
11      return WIN_LINES[i]; // 返回获胜连线
12    }
13  }
14  return null;
15}

获胜连线高亮动画

1.ttt-cell.win {
2  animation: ttt-win-pulse 0.5s ease-in-out infinite alternate;
3  background: #fef3c7 !important;
4}
5@keyframes ttt-win-pulse {
6  0%   { transform: scale(1); }
7  100% { transform: scale(1.06); }
8}
1function highlightWin(winLine) {
2  var cells = boardEl.children;
3  winLine.forEach(function(idx) {
4    cells[idx].classList.add('win');
5  });
6}

电脑 AI 策略

采用两层优先级的攻守策略:

1. 进攻优先 — 寻找自己的必胜位置

 1function findBestMove(player) {
 2  // 检查所有空格,找到能让自己获胜的位置
 3  for (var i = 0; i < 9; i++) {
 4    if (board[i] === '') {
 5      board[i] = player;          // 尝试落子
 6      if (checkWin(player)) {     // 是否获胜?
 7        board[i] = '';            // 恢复
 8        return i;                 // 返回必胜位置
 9      }
10      board[i] = '';              // 恢复
11    }
12  }
13  return -1;
14}

2. 防守优先 — 阻止玩家获胜

同一函数,传入 PLAYER 即可找到需要防守的位置:

 1function computerMove() {
 2  // 策略 1:检查自己能否获胜
 3  var move = findBestMove(COMPUTER);
 4  if (move !== -1) { board[move] = COMPUTER; /* 获胜!*/ return; }
 5
 6  // 策略 2:阻止玩家获胜
 7  move = findBestMove(PLAYER);
 8  if (move !== -1) { board[move] = COMPUTER; /* 防守成功 */ return; }
 9
10  // 策略 3:按优先级选择空位
11  var preferred = [4, 0, 2, 6, 8, 1, 3, 5, 7]; // 中心 > 四角 > 四边
12  for (var i = 0; i < preferred.length; i++) {
13    if (board[preferred[i]] === '') {
14      board[preferred[i]] = COMPUTER;
15      return;
16    }
17  }
18}

策略优先级表

优先级策略说明
1进攻获胜检查自己能否一步获胜
2防守阻止阻止玩家下一步获胜
3中心位置占据棋盘中心(位置 4)
4角落位置占据四角(位置 0, 2, 6, 8)
5边缘位置占据四边(位置 1, 3, 5, 7)

比分统计

实时显示玩家 vs 电脑的胜负比分:

1var scoreX = 0;  // 玩家胜场
2var scoreO = 0;  // 电脑胜场
3
4function updateScore() {
5  document.getElementById('attt-score-x').textContent = scoreX;
6  document.getElementById('attt-score-o').textContent = scoreO;
7}

状态提示

根据不同阶段显示不同状态:

1function showStatus(msg) {
2  var statusEl = document.getElementById('attt-status');
3  statusEl.innerHTML = '<span>' +
4    (gameOver ? '🎉' : '❌') +
5    '</span><span>' + msg + '</span>';
6}

状态流转:

  • 初始:❌ 你的回合
  • 玩家落子后:⭕ 电脑思考中...
  • 电脑落子后:❌ 你的回合
  • 游戏结束:🎉 你赢了! / 💀 电脑赢了! / 🤝 平局!

响应式格子尺寸

1function render() {
2  var maxW = Math.min(window.innerWidth - 300, 340);
3  if (window.innerWidth <= 900) maxW = window.innerWidth - 50;
4  var cellSize = Math.floor((maxW - 8) / 3);
5  cellSize = Math.min(cellSize, 100);
6  boardEl.style.setProperty('--ttt-cell', cellSize + 'px');
7}

总结

井字棋虽然规则简单,但 AI 策略设计体现了博弈论的基本思想——攻守兼备。两阶段检测(先看自己能否赢,再看是否需要挡)配合位置优先级(中心 > 角落 > 边缘),构建了一个有一定挑战性的电脑对手。获胜连线脉冲动画增强了游戏反馈的仪式感。

留言评论

期待你的想法

评论加载中