前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >react完成井字棋小游戏

react完成井字棋小游戏

作者头像
阿超
发布2022-08-21 12:08:22
5930
发布2022-08-21 12:08:22
举报
文章被收录于专栏:快乐阿超快乐阿超

上次说到我们按照官方文档体验了一下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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档