React + Ts 實現三子棋小游戲

来源:https://www.cnblogs.com/LHLVS/archive/2019/09/12/11511673.html
-Advertisement-
Play Games

"在這裡閱讀效果更佳" 還記得當年和同桌在草稿紙上下三子棋的時光嗎 今天我們就用代碼來重溫一下年少(假設你有react基礎,沒有也行,只要你會三大框架的任意一種,上手react不難) 游戲規則 + 雙方各執一子,在九宮格內一方三子連成線則游戲結束 + 九宮格下滿未有三子連線則視為平局 "你可以點擊這 ...


在這裡閱讀效果更佳

還記得當年和同桌在草稿紙上下三子棋的時光嗎

今天我們就用代碼來重溫一下年少(假設你有react基礎,沒有也行,只要你會三大框架的任意一種,上手react不難)

游戲規則

  • 雙方各執一子,在九宮格內一方三子連成線則游戲結束
  • 九宮格下滿未有三子連線則視為平局

你可以點擊這裡來體驗最終效果,樣式有點醜,請見諒

準備階段

建議先全局安裝typescript 和 create-react-app(安裝過請忽略

npm install typescript create-react-app -g

使用typescript初始化項目

create-react-app demo --typescript

初始化成功後ts環境已經配好了,不需要你手動加ts配置

此時就是tsx語法,我們就可以愉快的寫ts了

src文件夾就是開發目錄,所有代碼都寫在src文件夾下

我們使用sass來寫樣式,先安裝sass

npm install node-sass --save

運行項目

npm run start

刪掉初始化界面的一些代碼

開發階段

組件化

開發一個項目其實就是開發組件

把一個項目拆分一個個小組件,方便後期維護以及復用

  1. 棋子組件
  2. 棋盤組件
  3. 游戲規則組件
  4. 游戲狀態組件

react中組件分為類組件和函數組件

需要管理狀態的最好使用類組件

所以我們先把App改成類組件

import React from 'react';
import './App.css';
class App extends React.Component{
    render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
        return (
            <div className="App">
            </div>
        );
    }
};

export default App;

開發棋子組件

在src下新建component文件夾,在component文件夾下新建ChessComp.tsx,ChessComp.css

以後我們的組件都放在component文件夾下

棋子組件我們使用函數組件,思考需要傳入組件的屬性的類型:

  1. type(棋子的類型)
  2. onClick(點擊棋子觸發的回調函數)

棋子類型有三種(紅子 ,黑子, 空),

為了約束棋子類型,我們使用一個枚舉類型,

在src下新建types文件夾,專門放類型約束,

在types下新建enums.ts約束棋子類型

export enum ChessType {
    none,
    red,
    black
}

併在棋子tsx中導入

傳入tsx的所有屬性用一個IProps介面約束

interface IProps {
    type: ChessType
    onClick?: () => void
}

全部tsx代碼:

import React from 'react';
import {ChessType} from "../types/enums";
import './ChessComp.css';

interface IProps {
    type: ChessType
    onClick?: () => void
}
function ChessComp ({type, onClick}: IProps) {
    let chess = null;
    switch (type) {
        case ChessType.red:
            chess = <div className="red chess-item"></div>;
        break;
        case ChessType.black:
            chess = <div className="black chess-item"></div>;
        break;
        default:
            chess = null;
    }
    return (
        <div className="chess" onClick={() => {
            if (type === ChessType.none && onClick) {
                onClick();
            }
        }}>
            {chess}
        </div>
    )
};
export default ChessComp;

其中棋子只有為none類型時才能被點擊
scss 代碼:

棋子我們用背景顏色徑向漸變來模擬

$borderColor: #dddddd;
$redChess: #ff4400;
$blackChess: #282c34;
.chess{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 50px;
    height: 50px;
    border: 2px solid $borderColor;
    box-sizing: border-box;
    cursor: pointer;
    .chess-item{
        width: 30px;
        height: 30px;
        border-radius: 50%;
    }
    .red{
        background: radial-gradient(#fff, $redChess);
    }
    .black{
        background: radial-gradient(#fff, $blackChess);
    }
}

開發棋盤組件

同理在component文件夾下新建BoardComp.tsx,BoardComp.scss

棋盤組件我們需要傳遞三個參數:

  1. 棋子的數組
  2. 游戲是否結束
  3. 點擊事件函數

迴圈數組渲染棋子, 並給游戲是否結束一個預設值
全部tsx代碼:

import React from 'react';
import {ChessType} from "../types/enums";
import ChessComp from "./ChessComp";
import "./BoardComp.scss";
interface IProps {
    chesses: ChessType[];
    isGameOver?: boolean
    onClick?: (index: number) => void
}
const BoardComp: React.FC<IProps> = function(props) {
    // 類型斷言
    const isGameOver = props.isGameOver as boolean;
    // 非空斷言
    // const isGameOver = props.isGameOver!;
    const list = props.chesses.map((type, index) => {
        return (
            <ChessComp
                type={type}
                key={index}
            onClick={() => {
                if (props.onClick && !isGameOver) {
                    props.onClick(index)
                }
            }}/>
        )
    });
    return (
        <div className="board">
            {list}
        </div>
    )
};
BoardComp.defaultProps = {
  isGameOver: false
};
export default BoardComp;

scss 代碼:

使用flex佈局

.board{
    display: flex;
    flex-wrap: wrap;
    width: 150px;
    height: 150px;
}

開發游戲規則組件

在component文件夾下新建Game.tsx,Game.scss

游戲規則組件不需要傳參,我們使用類組件來管理狀態

在types文件夾下的enums.ts里新增游戲狀態的枚舉類型

export enum ChessType {
    none,
    red,
    black
}
export enum GameStatus {
    /**
     * 游戲中
     */
    gaming,
    /**
     * 紅方勝利
     */
    redWin,
    /**
     * 黑方勝利
     */
    blackWin,
    /**
     * 平局
     */
    equal,
}

核心的代碼就是如何判斷游戲的狀態,我的方法有點死,你們可以自己重構,

import React from 'react';
import {ChessType, GameStatus} from "../types/enums";
import BoardComp from "./BoardComp";
import GameStatusComp from "./GameStatusComp";
import './Game.scss';

/**
 * 棋子的數組
 * 游戲狀態
 * 下一次下棋的類型
 */
interface Istate {
    chesses: ChessType[],
    gameStatus: GameStatus,
    nextChess: ChessType.red | ChessType.black
}
class Game extends React.Component<{}, Istate> {
    state: Istate = {
        chesses: [],
        gameStatus: GameStatus.gaming,
        nextChess: ChessType.black
    };

    /**
     * 組件掛載完初始化
     */
    componentDidMount(): void {
        this.init();
    }
    /**
     * 初始化9宮格
     */
    init() {
        const arr: ChessType[] = [];
        for (let i = 0; i < 9; i ++) {
            arr.push(ChessType.none)
        }
        this.setState({
            chesses: arr,
            gameStatus: GameStatus.gaming,
            nextChess: ChessType.black
        })
    }

    /**
     * 處理點擊事件,改變棋子狀態和游戲狀態
     */
    handleChessClick(index: number) {
        const chesses: ChessType[] = [...this.state.chesses];
        chesses[index] = this.state.nextChess;
        this.setState(preState => ({
            chesses,
            nextChess: preState.nextChess === ChessType.black? ChessType.red : ChessType.black,
            gameStatus: this.getStatus(chesses, index)
        }))
    }

    /**
     * 獲取游戲狀態
     */
    getStatus(chesses: ChessType[], index: number): GameStatus {
        // 判斷是否有一方勝利
        const horMin = Math.floor(index/3) * 3;
        const verMin = index % 3;
        // 橫向, 縱向, 斜向勝利
        if ((chesses[horMin] === chesses[horMin + 1] && chesses[horMin + 1] === chesses[horMin + 2]) ||
            (chesses[verMin] === chesses[verMin + 3] && chesses[verMin + 3] === chesses[verMin + 6]) ||
            (chesses[0] === chesses[4] && chesses[4] === chesses[8] && chesses[0] !== ChessType.none) ||
            ((chesses[2] === chesses[4] && chesses[4] === chesses[6] && chesses[2] !== ChessType.none))) {
            return chesses[index] === ChessType.black ? GameStatus.blackWin : GameStatus.redWin;
        }
        // 平局
        if (!chesses.includes(ChessType.none)) {
            return GameStatus.equal;
        }
        // 游戲中
        return GameStatus.gaming;
    }
    render(): React.ReactNode {
        return <div className="game">
            <h1>三子棋游戲</h1>
            <GameStatusComp next={this.state.nextChess} status={this.state.gameStatus}/>
            <BoardComp
                chesses={this.state.chesses}
                isGameOver={this.state.gameStatus !== GameStatus.gaming}
                onClick={this.handleChessClick.bind(this)}/>
                <button onClick={() => {
                this.init()}
                }>重新開始</button>
        </div>;
    }
}

export default Game;

樣式

.game{
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
  top: 100px;
  width: 250px;
  height: 400px;
  left: 50%;
  transform: translateX(-50%);
}

開發顯示游戲狀態的組件

這個組件用來顯示狀態,在component文件夾下新建GameStatus.tsx,GameStatus.scss

沒什麼好說的,直接上代碼

import React from 'react';
import {ChessType, GameStatus} from "../types/enums";
import './GameStatus.scss';
interface Iprops {
    status: GameStatus
    next: ChessType.red | ChessType.black
}
function GameStatusComp(props: Iprops) {
    let content: JSX.Element;
    if (props.status === GameStatus.gaming) {
        if (props.next === ChessType.red) {
            content = <div className="next red">紅方落子</div>
        } else {
            content = <div className="next black">黑方落子</div>
        }
    } else {
        if (props.status === GameStatus.redWin) {
            content = <div className="win red">紅方勝利</div>
        } else if (props.status === GameStatus.blackWin) {
            content = <div className="win black">黑方勝利</div>
        } else {
            content = <div className="win equal">平局</div>
        }
    }
    return (
        <div className="status">
            {content}
        </div>
    )
}

export default GameStatusComp;
.status {
  width: 150px;
  .next,.win{
    font-size: 18px;
  }
  .win{
    border: 2px solid;
    border-radius: 5px;
    width: 100%;
    padding: 10px 0;
  }
  .equal{
    background-color: antiquewhite;
  }
  .red{
    color: #ff4400;
  }
  .black{
    color: #282c34;
  }
}

收尾

最後在app.tsx里調用game組件

import React from 'react';
import './App.scss';
import Game from "./component/Game";

class App extends React.Component{
    render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
        return (
            <div className="App">
                <Game/>
            </div>
        );
    }
};

export default App;

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • redux和react redux的關係: redux是react的狀態管理工具,卻不僅僅只是為了react而生的,所以在使用中會存在痛點。而react redux是專門為了react定製,目的是為瞭解決redux的痛點,起到了補充的作用。 redux和flux思想: 從代碼層面而言,flux無非就 ...
  • Casper prototyp back() 具體樣式: back() Moves back a step in browser’s history: 在瀏覽器歷史中回退一步: 同樣看一眼 "forward()" 方法 base64encode() 具體樣式: base64encode(String ...
  • The Casper class The easiest way to get a casper instance is to use the module’s create() method: 最簡單獲取casper實例的方法是casper模塊的create()方法: But you can al ...
  • 首先附上jq插件庫,裡面的東西太炫了,建議學前端的可以看看學習下:http://www.jq22.com/ 裡面有個“超個性動畫版本的個人簡歷”,通過屏幕上不斷列印內容,改變相應樣式來實現動畫簡歷,我從來沒想到過還有這種操作; 再附上一個比較有趣的基於vue的圓形時鐘: <!DOCTYPE html ...
  • 前言 HTML作為前端三大基礎知識點之一,是每一個前端開發人員都要掌握的部分。今天這篇文章我們來看看一些平時不太會註意,卻在面試時可能會問到的題目,來看看你都會嗎? 如何使用div模擬實現textarea? 我們都知道textarea是form表單中一個很常見的元素,用於多行文本輸入,可以設置行數和 ...
  • 在之前的《JavaScript對象基礎》中,我們大概瞭解了對象的創建和使用,知道對象可以使用構造函數和字面量方式創建。那麼今天,我們就一起來深入瞭解一下JavaScript中的構造函數以及對象的原型及原型鏈。 一 構造函數 1,什麼是構造函數 Javascript中使用構造函數的目的是批量創建擁有相 ...
  • 前戲 到目前為止,我們已經學過了JavaScript的一些簡單的語法。但是這些簡單的語法,並沒有和瀏覽器有任何交互。 也就是我們還不能製作一些我們經常看到的網頁的一些交互,我們需要繼續學習BOM和DOM相關知識。 JavaScript分為 ECMAScript,DOM,BOM。 BOM(Browse ...
  • 一.模板缺陷 模板的最大特點是擴展難度大,不易擴展。可能會造成邏輯冗餘 Level組件需要對不同的type產生不同的標簽 二.函數式組件 函數式組件沒有模板,只允許提供render函數 複雜的邏輯變得非常簡單 三.JSX應用 使用jsx會讓代碼看起來更加簡潔易於讀取 四.render方法訂製組件 編 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...