react初学练手五子棋
react初学练手五子棋
一、项目说明
先贴一个官方连接:https://react.docschina.org/tutorial/tutorial.html
1.被白棋普通规则,三点一线获胜。
2.记录下棋的历史步骤,且可点击回退到某一历史脚步。
3.每次给出提示:下一个谁出子。
4.在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)。
5.在历史记录列表中加粗显示当前选择的项目。
6.添加一个可以升序或降序显示历史记录的按钮。
7.每当有人获胜时,高亮显示连成一线的 3 颗棋子。
8.当无人获胜时,显示一个平局的消息。
二、js代码
js代码
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 一个方格
function Square(props) {
// 胜利时的高亮样式 是一个数组 赢的那条线有独特的高亮颜色,其他棋盘各自没有特殊的高亮样式
let hightLightClass = Array(9).fill('square')
// 胜利的那条线 从Game传给Bord 再从Bord传给Square 没胜利线时为null
const winLine = props.winLine
// 当有胜利的那条线的时候,执行修改高亮样式数组操作
if (winLine) {
for (let i = 0; i < props.winLine.length; i++) {
// num为胜利线的坐标号 通过遍历胜利线数组获取
let num = winLine[i]
// 根据胜利线坐标号 设置对应位置的棋盘格子的高亮样式
hightLightClass[num] = 'hightLight square'
}
}
// 返回H5
return (
// 每个button就是一个棋盘格子 数据和事件均通过props由父对象传入
<button className={hightLightClass[props.index]} onClick={props.onClick} key={props.index}>
{props.value}
</button>
)
}
// 整个棋盘 是Game的子元素 是Square的父元素
class Board extends React.Component {
// i是棋盘格子个坐标号
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={
() => this.props.onClick(i)
}
winLine={
this.props.winLine
}
index={i}
/>
)
}
render() {
return (
<div>
{
Array(3).fill(null).map((itemx, indexx) => (
<div className="board-row" key={indexx}>
{
Array(3).fill(null).map((itemy, indexy) => (
this.renderSquare(indexx * 3 + indexy)
))
}
</div>
))
}
</div>
);
}
}
// 顶级父组件
class Game extends React.Component {
// 要用到的数据的初始化
constructor(props) {
super(props);
this.state = {
// 历史记录数据 包含棋子的内容和位置
history: [
{
// 历史记录的棋子内容数据 ,每条记录坐标内的棋子内容(x/o)
squares: Array(9).fill(null),
// 历史纪录的棋子坐标数据
stX: -1,
stY: -1,
}
],
// 下一个棋子是X
XisNext: true,
// 当前操作序号
stepNumber: 0,
// 默认历史记录升序
up: true
}
}
// 下棋之后的操作
handleClick(i) {
// 拿到没下这颗棋子之前的历史列表
const history = this.state.history.slice(0, this.state.stepNumber + 1)
// 拿到下这颗棋子之前的最后一条历史数据
const current = history[history.length - 1]
// 拿到下这颗棋子之前的那一条棋子内容数据([x,x,o,o,x,o,...])
const squares = current.squares.slice();
// 当有人获胜或者其棋盘满了(重复按下过的地方),就不给按
if (calculateWinner(squares) || squares[i]) {
return
}
// 改变按下的那个位置的棋子的内容
squares[i] = this.state.XisNext ? 'X' : 'O';
this.setState({
// 改变棋盘的状态
history: history.concat([{
squares: squares,
stX: parseInt(i / 3) + 1,
stY: parseInt(i % 3) + 1,
}]),
// 改变下一颗棋子的形态
XisNext: !this.state.XisNext,
// 改变当前操作号
stepNumber: history.length - 1 + 1,
})
}
// 跳到历史看记录去
jumpTo(historyIndex) {
this.setState({
//当前操作序号变为跳过去的历史编号
stepNumber: historyIndex,
XisNext: (historyIndex % 2) === 0,
})
}
// 排序
paixu() {
this.setState({
up: !this.state.up
})
}
render() {
const history = this.state.history;//历史列表
const current = history[this.state.stepNumber]
const winner = calculateWinner(current.squares) && calculateWinner(current.squares).winPeople
const winnerLine = calculateWinner(current.squares) && calculateWinner(current.squares).winLine
// 判断胜利的文字
let status;
// 获胜提示标记 0为没有获胜 默认细字体 1为有人获胜 加粗字体
let winClor = 0;
// 历史记录按钮列表
const moves = history.map((historyX, historyIndex) => {
// 历史记录按钮文字
const wenzi = historyIndex ? '到第' + historyIndex + '步' : '回到最开始'
// 历史记录中坐标
const stXY = historyX.stY < 0 && historyX.stX < 0 ? null : '(' + historyX.stX + ',' + historyX.stY + ')'
// 历史记录加粗样式变量
const blod = this.state.stepNumber === historyIndex ? 'blod' : null
return (
<li key={historyIndex} className={blod}>
<button className={blod} onClick={() => { this.jumpTo(historyIndex) }} >
{wenzi}
</button>
<span>{stXY}</span>
</li>
)
})
if (winner) {
status = winner + '获胜啦'
winClor = 1
}
else if (current.squares.indexOf(null) < 0) {
status = '这是一个平局'
winClor = 0
}
else {
status = '下一个棋子是' + (this.state.XisNext ? 'X' : 'O')
winClor = 0
}
// 获胜的高亮
let winClorClass = winClor ? 'blod hightLight' : ''
// 排序默认升序
const up = this.state.up
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => { this.handleClick(i) }} winLine={winnerLine} />
</div>
<div className="game-info">
<div className={winClorClass}>{status}</div>
<button onClick={() => this.paixu()} style={{ backgroundColor: 'white', marginLeft: 50 + 'px', marginTop: 20 + 'px' }}>{up ? '降序' : '升序'}</button>
<ol>{up ? moves : moves.reverse()}</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]
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c])
return {
winPeople: squares[a],
winLine: lines[i]
}
}
return null
}
三、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;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
.blod{
font-weight: 900;
}
.hightLight{
color: rgb(218, 66, 47);
}