井字棋游戏案例: 用react实现的一个简单的综合性例题

编程前准备

运行本案例需要下载和配置react的运行环境,具体参考我的上一篇博客:react运行配置环境的详细配置(网址:(初学者速成React必备)React运行配置环境的详细配置-CSDN博客),配置号运行环境后我们开始下面的步骤

正式编程

第一步:编程文件的创建和修改

首先我们打开创建文件的src,先修改styles.css文件:

* {

    box-sizing: border-box;

  }

  body {

    font-family: sans-serif;

    margin: 20px;

    padding: 0;

  }

  h1 {

    margin-top: 0;

    font-size: 22px;

  }

  h2 {

    margin-top: 0;

    font-size: 20px;

  }

  h3 {

    margin-top: 0;

    font-size: 18px;

  }

  h4 {

    margin-top: 0;

    font-size: 16px;

  }

  h5 {

    margin-top: 0;

    font-size: 14px;

  }

  h6 {

    margin-top: 0;

    font-size: 12px;

  }

  code {

    font-size: 1.2em;

  }

  ul {

    padding-inline-start: 20px;

  }

  * {

    box-sizing: border-box;

  }

  body {

    font-family: sans-serif;

    margin: 20px;

    padding: 0;

  }

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

  }

  .board-row:after {

    clear: both;

    content: '';

    display: table;

  }

  .status {

    margin-bottom: 10px;

  }

  .game {

    display: flex;

    flex-direction: row;

  }

  .game-info {

    margin-left: 20px;

  }

接着修改index.js文件:

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';

import "./styles.css"

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>

);

最后我们创建App.js文件进行编程,具体文件排列如图所示:

 第二步:九个方格的创建和排列

首先我们在App.js中输入以下代码:

export default function Square() {

return <button className="square"></button>;

}

这段代码单独创建了一个空表格(如图):

若我们输入九个相同的代码(如图):

我们发现九个表格并不是成井字格排列的,这时我们要对代码稍做修改使其排列成井字格

(为了直观感受我们在表格里面加入数字):

至此,我们方格的创建和排列完成了,接下来就是完善井字棋的功能了。

第三步:实现交替落子功能

上一步我们搭建好框架后,功能部分我们就要用到React的核心方法useState方法来记录棋盘状态并不断更新棋盘状态实现交替落子的功能,具体代码如下:

import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    if (squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

效果如图:

第四步:添加获胜条件和宣布获胜者

要加入获胜条件并宣布获胜者,我们可以把获胜条件部分代码写到另一个function组件中,并在后续function组件的相互调用中插入获胜功能:

获胜条件代码如图(我们运用了数组进行遍历索引):

组件相互引用后,具体代码为:

import { useState } from 'react';

function Square({value, onSquareClick}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

function 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]) {
      return squares[a];
    }
  }
  return null;
}
若有人获胜,效果如图:

第五步:添加记录落子和回溯的进阶功能

这里涉及了React的核心知识部分'状态提升',感兴趣的可以去查看React的文档!

现在将编写一个名为 Game 的新顶级组件来显示过去的着法列表。这就是放置包含整个游戏历史的 history state 的地方(如图):

将Game组件嵌套进其他组件实现历史记录和回溯功能,至此,井字棋所有功能已经完善。

案例的全部具体代码如下:

import { useState } from 'react';

function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = 'Winner: ' + winner;
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];

  function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

function 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]) {
      return squares[a];
    }
  }
  return null;
}

效果如图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值