react井字棋游戏及其完善
井字棋完善内容
如果你之后还会有充裕的时间并且想练习你刚掌握的新技能的话,这里有一些可以完善的游戏功能实现供你参考,列表是由易到难排序的:
- 在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
- 在历史记录列表中加粗显示当前选择的项目。
- 使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)。
- 添加一个可以升序或降序显示历史记录的按钮。
- 每当有人获胜时,高亮显示连成一线的 3 颗棋子。
- 当无人获胜时,显示一个平局的消息。
图片示例

编码实战
Square 组件
import React from 'react';function Square(props) {return (<button className={props.className} onClick={props.onClick}>{props.value}</button>);
}export default Square;
Board 组件
import React, { Component } from 'react';
import Square from '../Square';class Board extends Component {renderSquare(i) {const { lines } = this.props;const [a, b, c] = lines;let className = 'square'if (i === a || i === b || i === c) {className = 'squareWinner'}return (<Squarekey={i}className={className}value={this.props.squares[i]}onClick={() => this.props.onClick(i)}/>);}render() {/***@function 渲染3*3的棋盘*@return react元素* */const renderBoard = () => {let n = 0;let board = [];for (let i = 0; i < 3; i++) {const boardRow = [];for (let j = 0; j < 3; j++, n++) {boardRow.push(this.renderSquare(n));}board.push(<div className="board-row" key={i}>{boardRow}</div>);}return board;}return (<div>{renderBoard()}</div>);}
}
export default Board;
Game 组件
js
import React, { Component } from 'react';
import Board from '../Board';
import './index.css';/*** @function 游戏结果判断* @param {array} squares 存储棋子的数组* @returns {object} 如果有胜利者,返回包含对应胜利者字符串和三连棋子数组的对象* @returns {null} 如果棋子已经占满棋盘但是没有胜利者,返回null* @returns {null} 如果棋子没有占满棋盘且没有胜利者,返回null* */
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]) {// alert('游戏结束胜利者为' + squares[a] + '!')const winnerObject = {winner: squares[a],lines: lines[i],}return winnerObject;}}if (squares.indexOf(null) === -1) {alert('Game Over ,无人生还');return null;}return null;
}class Game extends Component {constructor(props) {super(props);this.state = {history: [{squares: Array(9).fill(null),}],stepNumber: 0,xIsNext: true,descendingOrder: false, // 判断是否需要降序显示,默认为false升序显示lines: [], // 存储三连棋子的索引的数组};}/*** @function 读取游戏记录* */jumpTo(step) {this.setState({stepNumber: step,xIsNext: (step % 2) === 0,});}/*** @function 方格对应的点击事件* @description 游戏未结束,存档;游戏决出胜利者,三连高亮显示;游戏结束,弹出提示;* @param {number} i 对应方格在数组中的索引index* * */handleClick(i) {const history = this.state.history.slice(0, this.state.stepNumber + 1);const current = history[this.state.stepNumber];const squares = current.squares.slice();// 如果游戏结束或者该位置上已经有棋子跳出函数if (calculateWinner(squares) || squares[i]) {return;}squares[i] = this.state.xIsNext ? 'X' : 'O'; // 在数组对应位置填充棋子// 如果游戏有胜利者,更新三联棋子索引数组if (calculateWinner(squares)) {this.setState({lines: calculateWinner(squares).lines,});}this.setState({history: history.concat([{squares: squares,squareIndex: i}]),stepNumber: history.length,xIsNext: !this.state.xIsNext,});}/*** @function 修改用于判断历史记录正序/逆序显示的参数descendingOrder* */reverseHistory = () => {const { descendingOrder } = this.state;this.setState({descendingOrder: !descendingOrder})}/*** @function 根据棋子索引分配坐标* @returns {array} 返回包含棋子索引的数组* */produceCoordinate = () => {const size = 3; // 指定棋盘大小,现棋盘大小为3*3const arrayLength = size * size;let coordinateArray = []; // 创建初始数组let yInit = size; // 初始化y坐标的值for (let i = 0; i < arrayLength; i++) {let indexNumber = i + 1;if (indexNumber % size > 0) {coordinateArray.push({ x: indexNumber % size, y: yInit })} else {coordinateArray.push({ x: size, y: yInit })yInit = yInit - 1;}}return coordinateArray;}/*** @function 根据棋子索引分配坐标-另一种方法* */produceCoordinate_another = () => {let coordinateArray = [];for (let j = 3; j > 0; j--) {for (let i = 1; i < 4; i++) {coordinateArray.push({ x: i, y: j })}};return coordinateArray;}render() {let { lines } = this.state;const { history, stepNumber, descendingOrder, } = this.state;const current = history[stepNumber];const winner = calculateWinner(current.squares) ? calculateWinner(current.squares).winner : null;const coordinateArray = this.produceCoordinate();const moves = history.map((item, index) => {let coordinate = "";if (item.squareIndex || item.squareIndex === 0) {const moveIndex = item.squareIndex;coordinate = `(${coordinateArray[moveIndex].x},${coordinateArray[moveIndex].y})`;}const desc = index ?`移动至第${index}步,棋子坐标为${coordinate}` :'重新开始游戏';return (<li key={index}>{/* 在历史记录列表中加粗显示当前选择的项目,动态加载类名 */}<buttonclassName={index === stepNumber ? 'currentButton' : 'button'}onClick={() => this.jumpTo(index)}>{desc}</button></li>);});// 根据descendingOrder参数选择列表的升序/降序排序if (descendingOrder) {moves.reverse();}// 如果没有胜利者,清除保存的三连棋子索引数组if (!calculateWinner(current.squares)) {lines = []}let status;if (winner) {status = '胜利者: ' + winner;} else {status = '下一位玩家: ' + (this.state.xIsNext ? 'X' : 'O');}return (<div className="game"><div className="game-board"><Boardsquares={current.squares}onClick={(i) => this.handleClick(i)}lines={lines}/></div><div className="game-info"><div>{status}</div>{/* 添加一个可以升序或降序显示历史记录的按钮 */}<buttonclassName='button'onClick={() => this.reverseHistory()}>{descendingOrder ? '升序排列' : '降序排列'}</button><buttonclassName='button'onClick={() => this.produceCoordinate()}>根据棋子索引分配坐标</button><ol>{moves}</ol></div></div>);}
}export default Game;
css
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;
}.squareWinner {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;
}.squareWinner:focus {outline: none;
}.kbd-navigation .squareWinner:focus {background: #ddd;
}.squareWinner {background: green;
}.game {display: flex;flex-direction: row;
}.game-info {margin-left: 20px;
}.button {margin-top: 5px;
}
.currentButton {margin-top: 5px;font-weight: 900;
}
项目地址
天心天地生的GitHub
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
