🚀🚀 使用react+ts实现井字棋小游戏
使用React + TS 实现井字棋小游戏
在阅读react官网文档时,看到有个井字棋实例,感兴趣的小伙伴也可以看看官网的小例子: 官网井字棋小游戏
效果图
创建项目
使用命令新建一个react + ts
模板项目
create-react-app react-ts-chess-game --template typescript
分析
通过展示组件 + 容器组件模式,把井字棋小游戏分为四个组件,棋子组件、棋盘组件、游戏状态展示组件、游戏容器
展示组件:通常是函数式组件
容器组件:通常是类组件
定义枚举类型
export enum ChessType{
null,
red,
black
}
export enum GameStatus {
/**
* 游戏进行中
*/
gaming,
/**
* 红方胜利
*/
redWin,
/**
* 黑方胜利
*/
blackWin,
/**
* 平局
*/
deuce
}
棋子组件的开发
/**
* 旗子组件
*/
interface IProps {
type: ChessType
onClick?: () => void
}
export const ChessComp = (props: IProps) => {
let content: JSX.Element
if (props.type === ChessType.red) {
content = <div className="chess-item red" ></div>
}
else if (props.type === ChessType.black) {
content = <div className="chess-item black" ></div>
}
else {
content = <div className="chess-item" ></div>
}
return (
<div className="chess" onClick={() => {
if (props.type === ChessType.null && props.onClick) {
props.onClick()
}
}}
>
{content}
</div>
)
}
棋盘组件开发
/**
* 棋盘组件
*/
interface IProps {
chesses: ChessType[],
onClick?: (index: number) => void
isGameOver?: boolean
}
export const BoardComp: React.FC<IProps> = (props) => {
// const isGameOver = props.isGameOver as Boolean
const isGameOver = props.isGameOver!
const list = props.chesses.map((type, i) =>
<ChessComp
type={type}
key={i}
onClick={() => {
if (props.onClick && !isGameOver) {
props.onClick(i)
}
}}
/>
)
return (
<div className="board">
{list}
</div>
)
}
BoardComp.defaultProps = {
isGameOver: false
}
需注意,在函数组件中给参数设置默认值2种处理方法:
- 在定义接收参数接口类型的时候,参数修改为可选参数,比如这里游戏是否结束
isGameOver?: boolean
- 使用React.FC对函数组件进行类型约束
- 设置默认值
BoardComp.defaultProps = { isGameOver: false }
在做完前面步骤的时候,我们移动鼠标到 props.onClick && !isGameOver
TS检查到的类型为boolean | undefined
先让不符合我们这里的处理逻辑,怎么处理呢?
这里使用非空断言:在数据之后加上一个!
,告诉TS,不用考虑该数据为空的情况或者也可使用类型断言,如下:
const isGameOver = props.isGameOver as Boolean
const isGameOver = props.isGameOver!
游戏容器组件开发
这里游戏的核心就是判断当前落子之后游戏的状态变化:
- 其中一方胜利
- 平局
- 游戏正在进行中
在此,我们需要寻找当前落子和整个棋盘的下标之间的联系,我们可分为横向、纵向、斜线上面的三种情况来解决
找到当前index与横向、纵向连线的棋盘下标最小值的关系即可
interface IState {
chesses: ChessType[];
nextChess: ChessType.black | ChessType.red;
gameStatus: GameStatus
}
export class GameComp extends React.Component<{}, IState> {
state: IState = {
chesses: [],
nextChess: ChessType.black,
gameStatus: GameStatus.gaming
};
componentDidMount(): void {
this.init()
}
/**
* @description: 初始化棋盘数据
* @return {*}
*/
init() {
const chesses: ChessType[] = []
for (let i = 0; i < 9; i++) {
chesses.push(ChessType.null)
}
this.setState({
chesses,
nextChess: ChessType.black,
gameStatus: GameStatus.gaming
})
}
/**
* @description: 点击单个旗子触发的事件
* @return {*}
*/
handelChessClick(index: number) {
const chesses = [...this.state.chesses]
chesses[index] = this.state.nextChess
this.setState(prevState => ({
chesses,
nextChess: prevState.nextChess === ChessType.red ? ChessType.black : ChessType.red,
gameStatus: this.getGameStatua(index, chesses)
}))
}
/**
* @description: 游戏状态判断核心逻辑
* @param {number} index
* @param {ChessType} chesses
* @return {*}
*/
getGameStatua(index: number, chesses: ChessType[]): GameStatus {
// 1. 其中一方胜利
const herMin = Math.floor(index / 3) * 3
const verMin = index % 3
if ((chesses[herMin] === chesses[herMin + 1] && chesses[herMin] === chesses[herMin + 2])
|| (chesses[verMin] === chesses[verMin + 3] && chesses[verMin] === chesses[verMin + 6])
|| (chesses[0] === chesses[4] && chesses[0] === chesses[8] && chesses[0] !== ChessType.null)
|| (chesses[2] === chesses[4] && chesses[2] === chesses[6] && chesses[2] !== ChessType.null)
) {
if (chesses[index] === ChessType.red) {
return GameStatus.redWin
}
else {
return GameStatus.blackWin
}
}
// 2. 平局
if (!chesses.includes(ChessType.null)) return GameStatus.deuce
// 3. 游戏正在进行中
return GameStatus.gaming
}
render(): React.ReactNode {
return (
<div className="game">
<ShowGameStatus
nextChess={this.state.nextChess}
gameStatus={this.state.gameStatus}
/>
<BoardComp
chesses={this.state.chesses}
onClick={this.handelChessClick.bind(this)}
isGameOver={this.state.gameStatus !== GameStatus.gaming}
/>
<button onClick={() => {
this.init()
}}>重新开始</button>
</div>
);
}
}
游戏展示函数组件开发
interface IProps {
nextChess: ChessType.black | ChessType.red
gameStatus: GameStatus
}
export const ShowGameStatus = (props: IProps) => {
let content: JSX.Element;
if (props.gameStatus === GameStatus.gaming) {
if (props.nextChess === ChessType.black) {
content = <div className="black">黑子落棋</div>
}
else {
content = <div className="red">红子落棋</div>
}
}
else if (props.gameStatus === GameStatus.redWin) {
content = <div className="win red">红方胜利</div>
}
else if (props.gameStatus === GameStatus.blackWin) {
content = <div className="win black">黑方胜利</div>
}
else {
content = <div className="win deuce">平局</div>
}
return (
<div className="status">
{content}
</div>
)
}
结语
到此,使用 react+ts
就完成了井字棋游戏开发
如果有小伙伴感兴趣可以看具体demo代码,喜欢的话一键三连🌹🌹🌹