React官网井字格游戏拓展实现(附完整源码和最终成果截图)

前提

需要先依据react官网教程编写出基础的井字格游戏。

拓展实现及说明

最后面会有全部的源代码。

1、在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)

直接给history里的squres再添加一列,保存点击的i值,即第几个方格,再在moves里计算得出列号和行号。
改为10的长度
增加一列
加下标

ps: Math.trunc()方法是通过除去小数位来返回浮点数的整数部分

2、在历史记录列表中加粗显示当前选择的项目

给历史记录列表的按钮绑定一个动态样式即可。
加粗当前选择的记录

3、使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)

双重for循环

4、添加一个可以升序或降序显示历史记录的按钮

定义一个排序标志,升序为true,降序false。然后通过这个标志来升序或降序显示history数组的内容即可。
降序或升序

5、每当有人获胜时,高亮显示连成一线的 3 颗棋子

需要修改原来的计算成功者的方法calculateWinner,返回连成一线的下标数组winnerList,board组件双重循环渲染squre组件时可根据这个winnerList传一个flag标志给squre组件,然后squre组件里如果这个flag标志为true就背景色改变,就OK了。
计算成功的方法
board组件里修改的内容,注意第一行一定要取返回值得第一项,否则会出现点击没反应的情况。
board组件修改
最后,square组件根据lightFlag标志,动态显示背景颜色就大功告成了!
square组件修改

6、当无人获胜时,显示一个平局的消息

判断当前的square是否填满,如果填满了,且未胜利即平局。
增加平局描述

源码和最终成果截图

完整源码

import {useState} from 'react';

function Square({value,lightFlag, onSquareClick}) {
    return (
        <button
            style={{backgroundColor: lightFlag ? 'green' : 'transparent'}}
            className="square"
            onClick={onSquareClick}>
            {value}
        </button>
    );
}

function Board({xIsNext, squares, onPlay}) {
    function handleClick(i) {
        if (calculateWinner(squares)[0] || squares[i]) {
            return;
        }
        const nextSquares = squares.slice();
        if (xIsNext) {
            nextSquares[i] = 'X';
        } else {
            nextSquares[i] = 'O';
        }
        nextSquares[9] = i //增加一列记录下标,可通过计算转坐标
        onPlay(nextSquares);
    }
    const [winner,winnerLine] = calculateWinner(squares);
    let status;
    if (winner) {
        status = '获胜的是: ' + winner;
    } else {
        if(!squares.includes(null)){
            status='实力伯仲之间,平局'
        }else {
            status = '下一步棋手: ' + (xIsNext ? 'X' : 'O');
        }
    }
    const numbers = [0, 1, 2]
    const listItems = numbers.map((number, rowIndex) => {
        return (
            <div className="board-row" key={rowIndex}>
                {
                    numbers.map((item, columnIndex) => {
                        let lightFlag=winnerLine.includes(rowIndex * 3 + columnIndex)
                        return renderSquare(rowIndex * 3 + columnIndex,lightFlag)
                    })
                }
            </div>
        )
    })
    //生成方格
    function renderSquare(i,lightFlag) {
        return (
            <Square
                key={i}
                value={squares[i]}
                lightFlag={lightFlag}
                onSquareClick={() => handleClick(i)}/>
        );
    }

    return (
        <>
            <div className="status">{status}</div>
            {listItems}
            {/*<div className="board-row">*/}
            {/*    <Square value={squares[0]} onSquareClick={() => handleClick(0)} />*/}
            {/*    <Square value={squares[1]} onSquareClick={() => handleClick(1)} />*/}
            {/*    <Square value={squares[2]} onSquareClick={() => handleClick(2)} />*/}
            {/*</div>*/}
            {/*<div className="board-row">*/}
            {/*    <Square value={squares[3]} onSquareClick={() => handleClick(3)} />*/}
            {/*    <Square value={squares[4]} onSquareClick={() => handleClick(4)} />*/}
            {/*    <Square value={squares[5]} onSquareClick={() => handleClick(5)} />*/}
            {/*</div>*/}
            {/*<div className="board-row">*/}
            {/*    <Square value={squares[6]} onSquareClick={() => handleClick(6)} />*/}
            {/*    <Square value={squares[7]} onSquareClick={() => handleClick(7)} />*/}
            {/*    <Square value={squares[8]} onSquareClick={() => handleClick(8)} />*/}
            {/*</div>*/}
        </>
    );
}

export default function Game() {
    const [history, setHistory] = useState([Array(10).fill(null)]);
    const [currentMove, setCurrentMove] = useState(0);
    const [sortReverseFlag,setSortReverseFlag]=useState(true); //排序标志
    const xIsNext = currentMove % 2 === 0;
    const currentSquares = history[currentMove];

    function handlePlay(nextSquares) {

        const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
        setHistory(nextHistory);
        setCurrentMove(nextHistory.length - 1);
    }

    function jumpTo(nextMove) {
        setCurrentMove(nextMove);
    }

    const moves = history.map((squares, move) => {
        let description;
        move=sortReverseFlag?move:history.length-move-1
        if (move > 0) {
            description = '跳到第' + move + '步,坐标('
                + Math.trunc(squares[9] / 3 + 1) + ',' + (squares[9] % 3 + 1) + ')'
            +",下棋者:"+ (move%2 ?'X':'O');
        } else {
            description = '重新开始游戏';
        }
        return (
            <li key={move}>
                <button
                    style={{fontWeight: (move === currentMove) ? 'bold' : 'normal'}}
                    onClick={() => jumpTo(move)}>
                    {description}
                </button>
            </li>
        );
    });
    return (
        <div className="game">
            <div className="game-board">
                <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay}/>
            </div>
            <div className="game-info">
                <button
                    onClick={() => setSortReverseFlag(!sortReverseFlag)}
                >
                    {sortReverseFlag ? '升序' : '降序'}
                </button>
                <ol>{moves}</ol>
            </div>

        </div>
    );
}

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 [squares[a],lines[i]];
        }
    }
    return [null,[]];
}

最终成果截图

获胜截图:
胜利
倒序截图:
历史记录倒序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值