react完成井字棋小游戏

2022-08-21 12:08:22 浏览数 (1)

上次说到我们按照官方文档体验了一下React

这次我们搭建本地react开发环境,首先需要将node升级到14以上并且npm需要5.6以上,这个去官网下载安装包覆盖安装即可

然后我们按照教程创建项目

代码语言:javascript复制
npx create-react-app my-app

注意 第一行的 npx 不是拼写错误 —— 它是 npm 5.2 附带的 package 运行工具

然后删除src目录下的默认文件,创建一个index.css以及index.js

index.css

代码语言:javascript复制
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;
  }
  
  .square:focus {
    outline: none;
  }
  
  .kbd-navigation .square:focus {
    background: #ddd;
  }
  
  .game {
    display: flex;
    flex-direction: row;
  }
  
  .game-info {
    margin-left: 20px;
  }

然后是index.js

代码语言:javascript复制
class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {/* TODO */}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square />;
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

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

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

完成后按照教程一步一步来

做到最后实现了整个功能,我还进行了总结中的拓展

如果你还有充裕的时间,或者想练习一下刚刚学会的 React 新技能,这里有一些可以改进游戏的想法供你参考,这些功能的实现顺序的难度是递增的:

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

最后我的index.js为:

代码语言:javascript复制
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

function Square(props) {
    return (<button className='square' style={{ color: props.highlight ? 'red' : '' }} onClick={props.onClick}>{props.value}</button>)
}

class Board extends React.Component {
    constructor(props) {
        super(props);
        this.state = { squares: Array(9).fill(null), xIsNext: true, line: null }
    }

    renderSquare(i) {
        return <Square highlight={this.props.line?.includes(i)} key={i} value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
    }

    render() {
        let index = 0
        // 3.使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。
        return (
            <div>
                {[1, 2, 3].map(i => (
                    <div key={i} className='board-row'>
                        {Array(3).fill(null).map(() => this.renderSquare(index  ))}
                    </div>
                ))}
            </div>);
    }
}

class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            history: [{ squares: Array(9).fill(null), index: null }],
            stepNumber: 0,
            xIsNext: true,
            historyIsDesc: false
        }
    }

    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 (calculateWinner(squares).winner || squares[i]) {
            return
        }
        squares[i] = this.state.xIsNext ? 'X' : 'O'
        // 1.在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
        let row = i   1;
        let cell = 1;
        while (row > 3) {
            row -= 3;
            cell  ;
        }

        this.setState({
            history: history.concat([{ squares, index: { row, cell } }]),
            stepNumber: history.length,
            xIsNext: !this.state.xIsNext
        })
    }

    changeHistoryOrderBy() {
        this.setState({ ...this.state, historyIsDesc: !this.state.historyIsDesc })
    }

    jumpTo(stepNumber) {
        this.setState({
            stepNumber, xIsNext: (stepNumber % 2) === 0
        })
    }

    render() {
        const history = this.state.history;
        const current = history[this.state.stepNumber];
        const { winner, line } = calculateWinner(current.squares);
        let historyIsDesc = this.state.historyIsDesc;

        const moves = history.map((step, move) => {
            // console.log({ '步数': move, '记录': step.squares, '坐标': step.index, '是否选中': this.state.stepNumber == move })

            const desc = move ? `Go to move #${move} (${step.index.row},${step.index.cell})` : 'Go to game start';
            return (
                <li key={move}>
                    {/* 2.在历史记录列表中加粗显示当前选择的项目。 */}
                    <button style={{ fontWeight: this.state.stepNumber == move ? 'bold' : '' }} onClick={() => this.jumpTo(move)}>{desc}</button>
                </li>
            )
        })

        let status;
        if (winner) {
            status = 'Winner: '   winner;
        } else {
            // 6.当无人获胜时,显示一个平局的消息。
            // console.log({ 'this.state.stepNumber': this.state.stepNumber })
            if (this.state.stepNumber < 9) {
                status = 'Next player: '   (this.state.xIsNext ? 'X' : 'O');
            } else {
                status = 'Draw!';
            }
        }


        return (
            <div className="game">
                <div className="game-board">
                    <Board line={line} squares={current.squares} onClick={(i) => this.handleClick(i)} />
                </div>
                <div className="game-info">
                    <div>{status}</div>
                    {/* 4.添加一个可以升序或降序显示历史记录的按钮。 */}
                    <button onClick={() => this.changeHistoryOrderBy()}>{`切换为${historyIsDesc ? '升' : '降'}序显示历史记录`}</button>
                    <ol>{historyIsDesc ? moves.reverse() : moves}</ol>
                </div>
            </div>
        );
    }
}

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

ReactDOM.render(
    <Game />,
    document.getElementById('root')
);


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];
        // console.log([a, b, c])
        // console.log({ 'squares[a]': squares[a], 'squares[b]': squares[b], 'squares[c]': squares[c] })
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            // 5.每当有人获胜时,高亮显示连成一线的 3 颗棋子。
            return { winner: squares[a], line: [a, b, c] };
        }
    }
    return {};
}

完整代码放到了码云:https://gitee.com/VampireAchao/simple-react.git

0 人点赞