React井字棋小游戏

React井字棋小游戏。

官方教程地址

假设你已经完全跟着官方教程全部走完。

下面是官方建议,优化,已经优化。

  • 游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。(完成
  • 在历史记录列表中加粗显示当前选择的项目。(完成
  • 使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。(完成
  • 添加一个可以升序或降序显示历史记录的按钮。(完成
  • 每当有人获胜时,高亮显示连成一线的 3 颗棋子。(完成
  • 当无人获胜时,显示一个平局的消息。(完成

全部代码

body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol,
ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.lightsq {
  color: red;
}

.jumpto {
  font-weight: bold;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

// 棋子组件
function Square(props) {
  return (
    <button
      className={props.lt ? "square lightsq" : "square"}
      onClick={props.onClick}
    >
      {props.value}
    </button>
  );
}

// 棋盘组件
class Board extends React.Component {
  // 画棋盘方法
  renderSquare() {
    const square = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
    ];
    // 二维数组,遍历棋盘
    return square.map((sq, index) => {
      return (
        <div className="board-row" key={index}>
          {sq.map((i) => {
            let flag = -1;

            if (this.props.ltArr) {
              flag = this.props.ltArr.indexOf(i);
            }
            return (
              <Square
                lt={flag === -1 ? "" : "233"}
                key={i}
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)}
              />
            );
          })}
        </div>
      );
    });
  }

  render() {
    return <div>{this.renderSquare()}</div>;
  }
}

class Game extends React.Component {
  // 全局的变量
  ltArr;
  constructor(props) {
    super(props);
    this.state = {
      // 历史棋盘
      history: [
        {
          // 第几步
          step: 0, 
          // 棋盘状态
          squares: Array(9).fill(null),
          // 此步的落子坐标
          axis: null, 
        },
      ],
      // 备份一个,正序倒序显示步数记录使用
      historyMove: [
        {
          // 同上
          step: 0,
          squares: Array(9).fill(null),
          axis: null,
        },
      ],
      // 当前走了第几步
      stepNumber: 0,
      // 玩家标识
      xIsNext: true,
      // 正序倒序标识
      order: true,
      // 高亮赢了的棋子索引
      ltArr: null,
    };
  }
  // 点击下棋方法
  handleClick(i) {
    // 获取全部历史记录
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    // 获取最新的历史记录
    const current = history[history.length - 1];
    // 获取最新历史记录中的棋盘数据
    const squares = current.squares.slice();
    // 判断是否胜利
    if (this.calculateWinner(squares)) {
      return;
    }
    // 判断坐标上是否已有棋子
    if (squares[i]) {
      return;
    }
    // 下棋人,
    squares[i] = this.state.xIsNext ? "X" : "O";
    // 更新状态
    this.setState({
      history: history.concat([
        {
          step: this.state.stepNumber + 1,
          squares: squares,
          axis: getAxis(i),
        },
      ]),
      historyMove: history.concat([
        {
          step: this.state.stepNumber + 1,
          squares: squares,
          axis: getAxis(i),
        },
      ]),
      // 步数加一
      stepNumber: history.length,
      // 切换下棋人
      xIsNext: !this.state.xIsNext,
    });
  }
  // 跳转到第几步
  jumpTo(move) {
    this.setState({
      stepNumber: move,
      xIsNext: move % 2 === 0,
    });
  }
  // 反转历史记录函数
  reverseHistory() {
    const history = this.state.historyMove.slice();
    this.setState({
      historyMove: history.reverse().slice(),
      order: !this.state.order,
    });
  }

// 判断输赢
  calculateWinner(squares) {
    // 胜利的坐标
    const lines = [
      [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 i = 0; i < lines.length; i++) {
      const [a, b, c] = lines[i];
      if (
        squares[a] &&
        squares[a] === squares[b] &&
        squares[a] === squares[c]
      ) {
        // 这里赢了之后会给ltArr这个赋值
        this.ltArr = [a, b, c];
        // 返回赢方标识,X or O
        return squares[a];
      }
    }
    // 不赢的话,赋值为null
    this.ltArr = null;
    return null;
  }
  // 渲染dom的方法
  render() {
    // 获取历史记录的备份版,用备份版主要实现倒序时不影响原来的棋盘
    const history = this.state.historyMove;
    // 当前的状态
    const current = this.state.history[this.state.stepNumber];
    // 是否胜利
    const winner = this.calculateWinner(current.squares);
    
    // 遍历历史记录的方法
    const moves = history.map((square, move) => {
      const desc = square.step
        ? "回到第" + square.step + "步, 落子坐标:" + square.axis
        : "回到起点";
      return (
        <li key={move}>
          <button
            className={square.step === this.state.stepNumber ? "jumpto" : ""}
            onClick={() => this.jumpTo(square.step)}
          >
            {desc}
          </button>
        </li>
      );
    });

    let status;
    if (winner) {
      status = winner + "赢了";
    } else {
      status = "玩家标记: " + (this.state.xIsNext ? "X" : "O");
    }

    return (
      <div className="game">
        <div className="game-board">
          <Board
            ltArr={this.ltArr}
            squares={current.squares}
            onClick={(i) => this.handleClick(i)}
          />
        </div>
        <div className="game-info">
          <div>
            <button
              onClick={() => {
                this.reverseHistory();
              }}
            >
              {this.state.order ? "升序" : "降序"}
            </button>
          </div>
          <div>{this.state.stepNumber === 9 ? "平局" : status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game />);

function getAxis(index) {
  const addes = [
    [0, 0],
    [0, 1],
    [0, 2],
    [1, 0],
    [1, 1],
    [1, 2],
    [2, 0],
    [2, 1],
    [2, 2],
  ];
  return addes[index];
}

封面

原神 春节 灯笼 喜庆 热闹 聚会 刻晴 胡桃 高清


React井字棋小游戏
https://wangijun.com/2023/03/03/react-01/
作者
无良芳
发布于
2023年3月3日
许可协议