JavaScript 和 React Component 实现井字棋游戏

简介: 这是一个基于 React 实现的简洁井字棋(Tic-Tac-Toe)游戏:3×3 棋盘,X/O 轮流落子,8 种胜局模式判定,支持胜负/平局提示与一键重开。代码清晰,样式美观,响应迅速。👉 在线体验:nanojs.net/tool/game/tictactoe/

成品:
https://nanojs.net/tool/game/tictactoe/

井字棋游戏,也叫圈叉棋,英文:Tic-Tac-Toe

规则:

  • 3×3 的九宫格棋盘。
  • 一方画“○”,一方画“×”。
  • 轮流在空格中画自己的符号。
  • 先在横、竖、或对角线上连成一条直线的玩家获胜。
  • 如果格子填满且无人连成一线,则为平局。

用一个 9 个元素的数组表示棋盘

const [board, setBoard] = useState(Array(9).fill(null));

用一个 boolean 表示轮到 X 还是 O

const [xIsNext, setXIsNext] = useState(true);

总共只有 8 种胜利方法,分别是三横,三竖,两个斜线。枚举 8 种胜利情况判断是否胜利

  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]
  ];

  const winner = winPatterns.some(([a, b, c]) =>
    board[a] && board[a] === board[b] && board[a] === board[c]
  );

如果没有胜利且棋盘满了,则为平局

const isDraw = !winner && board.every(cell => cell !== null);

实现下棋

const handleClick = (index) => {
   
  if (board[index] || winner || isDraw) return;

  const newBoard = board.map((cell, i) => 
    i === index ? (xIsNext ? 'X' : 'O') : cell
  );
  setBoard(newBoard);
  setXIsNext(!xIsNext);
};

完整 React Component

import React, { useState } from 'react';

export default function TicTacToe() {
  const [board, setBoard] = useState(Array(9).fill(null));
  const [xIsNext, setXIsNext] = useState(true);

  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]
  ];

  const winner = winPatterns.some(([a, b, c]) =>
    board[a] && board[a] === board[b] && board[a] === board[c]
  );

  const isDraw = !winner && board.every(cell => cell !== null);

  const handleClick = (index) => {
    if (board[index] || winner || isDraw) return;

    const newBoard = board.map((cell, i) => 
      i === index ? (xIsNext ? 'X' : 'O') : cell
    );
    setBoard(newBoard);
    setXIsNext(!xIsNext);
  };

  const resetGame = () => {
    setBoard(Array(9).fill(null));
    setXIsNext(true);
  };

  const getStatus = () => {
    if (winner) return `Player ${xIsNext ? 'O' : 'X'} Wins!`;
    if (isDraw) return "It's a Draw!";
    return `Current: Player ${xIsNext ? 'X' : 'O'}`;
  };

  const getStatusStyle = () => {
    if (winner) return { ...styles.status, color: '#16a34a' };
    if (isDraw) return { ...styles.status, color: '#f59e0b' };
    return styles.status;
  };

  const getCellBorders = (index) => {
    const row = Math.floor(index / 3);
    const col = index % 3;

    return {
      borderTop: row > 0 ? '2px solid #e2e8f0' : 'none',
      borderBottom: row < 2 ? '2px solid #e2e8f0' : 'none',
      borderLeft: col > 0 ? '2px solid #e2e8f0' : 'none',
      borderRight: col < 2 ? '2px solid #e2e8f0' : 'none',
    };
  };

  return (
    <div style={styles.container}>
      <h2 style={styles.title}>Tic-Tac-Toe</h2>

      <div style={getStatusStyle()}>
        {getStatus()}
      </div>

      <div style={styles.board}>
        {board.map((cell, index) => (
          <button
            key={index}
            style={
  {
              ...styles.cell,
              ...getCellBorders(index),
              ...(cell === 'X' ? styles.cellX : {}),
              ...(cell === 'O' ? styles.cellO : {}),
            }}
            onClick={() => handleClick(index)}
            disabled={!!cell || winner || isDraw}
          >
            {cell}
          </button>
        ))}
      </div>

      {(winner || isDraw) && (
        <button style={styles.newGameButton} onClick={resetGame}>
          New Game
        </button>
      )}
    </div>
  );
}

const styles = {
  container: {
    maxWidth: '400px',
    margin: '20px auto',
    padding: '20px',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  title: {
    fontSize: '28px',
    fontWeight: '700',
    color: '#1e40af',
    margin: '0 0 20px 0',
  },
  status: {
    fontSize: '20px',
    fontWeight: '700',
    color: '#1e293b',
    marginBottom: '20px',
  },
  board: {
    display: 'grid',
    gridTemplateColumns: 'repeat(3, 80px)',
    gridTemplateRows: 'repeat(3, 80px)',
    gap: '0',
    margin: '0 auto 20px',
  },
  cell: {
    width: '80px',
    height: '80px',
    fontSize: '36px',
    fontWeight: '700',
    borderRadius: '0',
    background: '#fff',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    transition: 'all 0.2s ease',
    padding: '0',
    boxSizing: 'border-box',
  },
  cellX: {
    color: '#3b82f6',
    background: '#eff6ff',
  },
  cellO: {
    color: '#ef4444',
    background: '#fef2f2',
  },
  newGameButton: {
    padding: '12px 32px',
    fontSize: '16px',
    fontWeight: '700',
    color: '#fff',
    background: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)',
    border: 'none',
    borderRadius: '10px',
    cursor: 'pointer',
    boxShadow: '0 4px 14px rgba(59, 130, 246, 0.4)',
  },
};
相关文章
|
3天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10488 48
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
9天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
2278 5
|
23天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
23751 121
|
3天前
|
人工智能 IDE API
2026年国内 Codex 安装教程和使用教程:GPT-5.4 完整指南
Codex已进化为AI编程智能体,不仅能补全代码,更能理解项目、自动重构、执行任务。本文详解国内安装、GPT-5.4接入、cc-switch中转配置及实战开发流程,助你从零掌握“描述需求→AI实现”的新一代工程范式。(239字)
1764 126

热门文章

最新文章