成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

從零到一,擼一個(gè)在線斗地主(下篇)

CloudDeveloper / 1672人閱讀

摘要:原文從零到一,擼一個(gè)在線斗地主下篇作者上篇回顧我們說(shuō)了斗地主游戲的渲染展示部分,最后也講了下中交互的情況,下篇的重點(diǎn)就是游戲邏輯。

原文:從零到一,擼一個(gè)在線斗地主(下篇) | AlloyTeam
作者:TAT.vorshen

上篇回顧:我們說(shuō)了斗地主游戲的渲染展示部分,最后也講了下canvas中交互的情況,下篇的重點(diǎn)就是游戲邏輯。

邏輯主要分成兩塊:流程邏輯和撲克牌對(duì)比邏輯。

github地址:https://github.com/vorshen/landlord

流程邏輯 分析

這里流程上的邏輯分為兩部分,一個(gè)是場(chǎng)景切換,還有一個(gè)就是房間頁(yè)中游戲進(jìn)行的流程

先簡(jiǎn)單說(shuō)下場(chǎng)景切換,我們這個(gè)斗地主游戲有如下三種場(chǎng)景切換

首頁(yè) -> 大廳頁(yè)

大廳頁(yè) -> 房間頁(yè)

房間頁(yè) -> 大廳頁(yè)

我們這里偷了懶,首頁(yè)和大廳頁(yè)沒(méi)有用canvas,直接上了dom,寫起來(lái)也很奔放,沒(méi)有用框架。如果游戲想正式一點(diǎn),千萬(wàn)不要這樣。看起來(lái)首頁(yè)和大廳頁(yè)邏輯很簡(jiǎn)單,那是因?yàn)槲覀兟┑袅撕芏帱c(diǎn)(時(shí)間真的不夠。。)。

用一張圖表現(xiàn)一下,我們漏掉的點(diǎn):

在我們?nèi)绱撕?jiǎn)化的背景下,如果說(shuō)還有什么需要注意的,可能就兩點(diǎn)

1、是否提前加載模塊

比如當(dāng)我們進(jìn)入首頁(yè)的時(shí)候,要不要把大廳頁(yè)和房間頁(yè)都初始化完畢?

這里我沒(méi)有選擇初始化,一定是真正使用到才會(huì)初始化。理由主要就是后面用到再初始化的開(kāi)銷并不大,可以接受。

如果當(dāng)遇到,某一個(gè)場(chǎng)景很復(fù)雜,切換需要較大的開(kāi)銷,可以考慮提前進(jìn)行一些初始化的工作。

2、銷毀是真銷毀還是隱藏

大廳頁(yè)和房間頁(yè)存在來(lái)回切換的情況,當(dāng)發(fā)生大廳切換到房間的時(shí)候,可以選擇將大廳頁(yè)隱藏,也可以選擇將大廳頁(yè)銷毀,后面用到再初始化。

這里我們選擇只是將頁(yè)面隱藏,也就是說(shuō)當(dāng)房間頁(yè)第一次展示的時(shí)候,需要進(jìn)行初始化(較大開(kāi)銷),以后再展示,就是很少的性能開(kāi)銷了。
大概代碼如下:

/**
 * 房間展示,主要是生成stage
 * @param info 
 */
private _show(info: i_RoomShowOptions) {
    this._roomId = info.roomId;

    if (this._inited) {
        // 初始化過(guò)了,stage肯定初始化過(guò)了,直接展示
        this._stage.show();
    } else {
        // 第一次展示,初始化stage
        this._initStage();

        this._inited = true;
    }

    ……
}

具體代碼在Hall.ts和Room.ts中

因?yàn)槲覀冺?yè)面簡(jiǎn)單而且小,常駐的話對(duì)性能影響不大,如果打算常駐的頁(yè)面展示率低或者隱藏運(yùn)行也很占用資源,那還是推薦把真的干掉。

房間中流程 消息驅(qū)動(dòng)

首先我們認(rèn)為在房間中,流程的變化都是事件驅(qū)動(dòng),具體可以看下圖:

注意:右側(cè)如果有箭頭,意味著可能該階段自己切換到該階段(只是該階段主角玩家發(fā)生變化)

在每個(gè)階段,前端只能有對(duì)應(yīng)的操作。那么每個(gè)階段的切換,事件的發(fā)起者是誰(shuí)呢?寫代碼的時(shí)候,我發(fā)現(xiàn)可以有兩種模式進(jìn)行階段切換。

1、前端控制

以「叫地主階段」->「搶地主階段」為例,首先前端肯定知道游戲的輪轉(zhuǎn)順序(必須知道,因?yàn)椴季志偷每紤]),輪轉(zhuǎn)順序是逆時(shí)針的。

當(dāng)服務(wù)器下發(fā)一條「xx叫地主的消息」后,前端可以知道

接下來(lái)要進(jìn)行搶地主階段了

xx的下一個(gè)是yy

那么前端可以主動(dòng)將狀態(tài)轉(zhuǎn)為「yy進(jìn)行搶地主狀態(tài)」。

這個(gè)沒(méi)有問(wèn)題,邏輯上也講得通,而且樂(lè)觀UI的思想,能讓用戶最快的感知到變化,理論上體驗(yàn)最佳。甚至!可以節(jié)省與后臺(tái)的傳輸,因?yàn)楹笈_(tái)只需要下發(fā)「xx叫地主」,都不需要下發(fā)「yy進(jìn)入到搶地主狀態(tài)」了

不過(guò)情況不是這么簡(jiǎn)單……寫代碼過(guò)程中發(fā)現(xiàn)了些問(wèn)題。

「叫地主階段」->「搶地主階段」沒(méi)問(wèn)題,走的通;「搶地主狀態(tài)」->「搶地主狀態(tài)」也沒(méi)問(wèn)題,走得通;「搶地主階段」->「出牌階段」怎么辦?

我們可以在前端將每個(gè)玩家叫地主、搶地主的結(jié)果記錄下來(lái),然后保證和后端一樣的邏輯,也可以得到這局游戲的地主是誰(shuí)。但是地主獲得的三張牌呢?這是一定要得從服務(wù)器獲得的,出現(xiàn)了沖突,或者說(shuō)前端無(wú)法完整實(shí)現(xiàn)的地方。

更明顯的還有「準(zhǔn)備階段」->「叫地主階段」,前端完全不知道誰(shuí)是叫地主的,因?yàn)檫@個(gè)可能不按輪轉(zhuǎn)順序來(lái)。

到這里,是不是我們也可以前端+后臺(tái)配合的方式?嘗試了一下,并不好,這種組合的形式讓代碼變得難寫,我不推薦這種方式。

不過(guò)也不敢保證,也許是我寫法上的問(wèn)題,如果對(duì)這里有建議和想法,可以一起討論。

2、后臺(tái)控制

所以我最后采用了后端精準(zhǔn)控制的方式,一切都是以后臺(tái)下發(fā)為準(zhǔn)。

我選擇「叫地主」之后,理論上可以將前端狀態(tài)轉(zhuǎn)到「下個(gè)人搶地主」,但是并沒(méi)有,我一定得等到后臺(tái)狀態(tài)變化的消息才進(jìn)行轉(zhuǎn)換。

注意:但是按鈕,還是得提前反饋啊,否則網(wǎng)絡(luò)延遲會(huì)讓用戶抓狂的。

所以房間邏輯這里,整個(gè)流程,是靠后臺(tái)消息進(jìn)行驅(qū)動(dòng)的。代碼大概:

private _addMessageListener() {
    // 對(duì)手進(jìn)入
    this._app.network.addEventListener("Room.PlayerEnterRoom", this._playerEnterRoom);

    // 對(duì)手離開(kāi)
    this._app.network.addEventListener("Room.PlayerLeaveRoom", this._playerLeaveRoom);

    // 監(jiān)聽(tīng)玩家準(zhǔn)備
    this._app.network.addEventListener("Room.PlayerReady", this._playerReady);

    // 進(jìn)入叫地主階段
    this._app.network.addEventListener("Room.EnterAskLandlord", this._enterAskLandlord);

    // 對(duì)手叫地主
    this._app.network.addEventListener("Room.PlayerAskLandlord", this._playerAskLandlord);

    // 進(jìn)入搶地主階段
    this._app.network.addEventListener("Room.EnterGrabLandlord", this._enterGrabLandlord);

    // 對(duì)手搶地主
    this._app.network.addEventListener("Room.PlayerGrabLandlord", this._playerGrabLandlord);

    // 游戲開(kāi)始
    this._app.network.addEventListener("Room.GameStart", this._gameStart);

    // 出牌
    this._app.network.addEventListener("Room.PlayerShotPukes", this._playerPukes);

    // 繼續(xù)出牌
    this._app.network.addEventListener("Room.LoopPukes", this._loopPukes);

    // 游戲結(jié)束
    this._app.network.addEventListener("Room.GameOver", this._gameOver);
}

具體代碼在Room.ts中

客戶端同步

稍微延伸一下,剛剛說(shuō)的那種情況,很類似游戲中,客戶端同步的兩種方式:幀同步和狀態(tài)同步

幀同步(行為同步)

幀同步的核心就是 不同的客戶端 + 相同的輸入(行為) = 相同的輸出(狀態(tài))

如果能一直保證這個(gè)公式成立,那么服務(wù)器只需要推下發(fā)行為,無(wú)需下發(fā)狀態(tài),行為的開(kāi)銷肯定遠(yuǎn)遠(yuǎn)小于狀態(tài),優(yōu)勢(shì)在于性能。這一般用于實(shí)時(shí)性要求高的游戲中,比如格斗類、fps類游戲。

狀態(tài)同步

狀態(tài)同步就好理解了,客戶端以服務(wù)器下發(fā)的狀態(tài)為準(zhǔn),客戶端就像一個(gè)播放器一樣。這種優(yōu)勢(shì)在于服務(wù)器掌握絕對(duì)控制權(quán),一般用于實(shí)時(shí)性要求不高的游戲中。

與服務(wù)器對(duì)接

游戲和傳統(tǒng)web開(kāi)發(fā)在網(wǎng)絡(luò)上的差距也是很大的,傳統(tǒng)web開(kāi)發(fā),資源加載完畢后,也就是cgi拉取一些數(shù)據(jù)或者上傳一些數(shù)據(jù)會(huì)與后臺(tái)對(duì)接,總而言之就是前端與后臺(tái)的交流并不密切。

但是游戲不一樣,游戲是需要頻繁交換數(shù)據(jù)的,而且必須要有后臺(tái)主動(dòng)推送的能力。斗地主這款游戲算是上行很少的游戲了,理論上其實(shí)cgi+長(zhǎng)輪詢也能滿足我們的需求,但是現(xiàn)在websocket這么好用,不可能不用啊。

websocket

websocket這里我們也是裸寫的,沒(méi)用開(kāi)源的庫(kù),也沒(méi)寫重連啥的邏輯,如果在線游戲想正規(guī)一點(diǎn),一定要考慮重連啊。

如果還不了解websocket的同學(xué),可以找介紹看下,很簡(jiǎn)單。

但是websocket也有尷尬的地方,主要有兩點(diǎn):

下行消息一個(gè)通道,沒(méi)有回調(diào)的概念

無(wú)法攜帶session

先說(shuō)1,我們用websocket進(jìn)行send調(diào)用,調(diào)用就調(diào)用了,沒(méi)有回調(diào)函數(shù)的概念的。后臺(tái)如果想針對(duì)我們的請(qǐng)求進(jìn)行回報(bào),也得走統(tǒng)一的下發(fā)消息,對(duì)于前端來(lái)說(shuō),就是觸發(fā)了onmessage。

這樣肯定是不行的,既然底層不支持,我們就得進(jìn)行一次封裝,其實(shí)核心就是版本號(hào)控制一下。

原理如下圖:

我們發(fā)送消息的時(shí)候,如果有回調(diào)函數(shù),就會(huì)記錄一下(自增id標(biāo)示),然后這個(gè)自增id會(huì)發(fā)送給后臺(tái)。

后臺(tái)下發(fā)消息的時(shí)候,有兩種,一種是帶著回調(diào)id,如果發(fā)現(xiàn)是這種消息,就拿著id去回調(diào)函數(shù)池子里面找到對(duì)應(yīng)的函數(shù)執(zhí)行。如果沒(méi)有回調(diào)id,意味著是單純的推送,對(duì)應(yīng)執(zhí)行。

大概代碼如下,具體代碼在Network.ts中

class Network extends EventDispatcher {
    // 收到服務(wù)器下發(fā)消息
    private _processMessage(msg: any) {
        // response消息
        if (msg.id) {
            let cb = this._callbacks[msg.id];

            delete this._callbacks[msg.id];
            if (typeof cb !== "function") {
                console.error("callback is not a function for request: ", msg.id);
                return;
            }

            cb(msg.body);

            return;
        }

        // 服務(wù)器推送消息
        let route = msg.route;

        if (!route) {
            console.error("no route in message");
            return;
        }

        this.dispatchEvent(route, msg.data);
    }

    // 想服務(wù)器推送消息
    notify(msg: any, callback?: Function) {
        if (!this._ws) {
            return;
        }

        if (typeof callback === "function") {
            msg.id = ++this._callbackIndex;

            this._callbacks[msg.id] = callback;
        }

        this._ws.send(JSON.stringify(msg));
    }

至于無(wú)法攜帶session,這個(gè)就沒(méi)辦法了,只能相當(dāng)于每次手動(dòng)將uid帶上去,服務(wù)器會(huì)根據(jù)uid拿到用戶信息。

撲克牌對(duì)比邏輯

到了斗地主最核心邏輯部分了,那就是撲克牌大小的對(duì)比,也是我們使用webassembly的地方。

webassembly

對(duì)不了解webassembly的同學(xué)先簡(jiǎn)單介紹一下webassembly,可以理解為:將其他的語(yǔ)言(比如C++,go,java等)寫的代碼,跑在瀏覽器上。其他基礎(chǔ)知識(shí)就不在這里提了哈,可以自行查閱。

外界看好wasm的優(yōu)勢(shì)在于快!雖然js有v8,但是相比較那些靜態(tài)語(yǔ)言老流氓們,還是有些差距的。目前wasm應(yīng)用場(chǎng)景最多的應(yīng)該在于音視頻的解析、字符串操作、大量數(shù)學(xué)計(jì)算等一些高cpu操作上。

我覺(jué)得wasm不僅僅有速度上的優(yōu)勢(shì),還有代碼復(fù)用這個(gè)被忽視的特性。在游戲上,這個(gè)特性幫助會(huì)很大。

以我們這個(gè)斗地主為例,核心部分是撲克牌對(duì)比邏輯。這個(gè)邏輯,前端要用把,判斷是否可以出牌的時(shí)候,如圖

但是后端不能無(wú)腦信任前端的牌吧,后臺(tái)也必須得校驗(yàn)一次。一份邏輯,寫一次總比寫兩次好吧,況且還是一個(gè)比較復(fù)雜的邏輯。wasm的出現(xiàn)解決了這種場(chǎng)景的痛點(diǎn),主要也是游戲開(kāi)發(fā)中,這種情況也比較多,很常見(jiàn)的就是碰撞檢測(cè)。

具體一份代碼是怎么用的,我們稍后再說(shuō),我們先把撲克牌對(duì)比的邏輯用C++寫出來(lái),否則其他都是白搭。

如何對(duì)比

因?yàn)楸容^簡(jiǎn)單,我沒(méi)有去網(wǎng)上搜實(shí)現(xiàn),自己寫了一套,目前看來(lái)應(yīng)該沒(méi)啥問(wèn)題,是不是最優(yōu)思想不清楚。原理如下

我們先把撲克牌分類一下,如下圖:

對(duì)應(yīng)的枚舉:

enum PukeType {
    ERROR,    // 無(wú)法匹配
    EMPTY,    // 空張
    SINGLE,    // 單張
    DOUBLE,    // 對(duì)子
    THREE,    // 三不帶
    BOOM,    // 炸
    THREE_SINGLE,    // 三帶一
    THREE_DOUBLE,    // 三帶二
    DOUBLE_ROW,    // 連對(duì)
    THREE_ROW,    // 連三不帶
    THREE_SINGLE_ROW,    // 三帶一飛機(jī)
    THREE_DOUBLE_ROW,    // 三帶二飛機(jī)
};

這里「炸彈」是比較特殊的,因?yàn)?strong>它可以和其他類型進(jìn)行大小比對(duì),其他類型,必須相同類型進(jìn)行對(duì)比,可以理解為對(duì)2也打不過(guò)一單張3

因?yàn)轭愋投啵雌饋?lái)同類型對(duì)比復(fù)雜,其實(shí)并不是,因?yàn)橥愋蛯?duì)比,核心比的是某一單張牌。

3帶1/2,比的是3張中的牌誰(shuí)大

連對(duì),無(wú)論連了幾對(duì),比的是最大的那對(duì)中的牌誰(shuí)大

炸彈,其實(shí)比單張

其他的就不羅列了,其實(shí)都是這樣

那么我們就可以這樣

格式化傳入的pukes

得到pukes的類型 和 這個(gè)類型下,能代表最大的那張牌

除了炸彈,如果類型對(duì)不上,認(rèn)為比不過(guò)

類型一樣,比核心牌

代碼如下,具體代碼在puke-compare.h中

/**
 * 對(duì)比兩組牌的大小
 */
bool PukeCompare(std::vector& pukesA, std::vector& pukesB) {
    // 先格式化兩組牌
    Parse(pukesA);
    Parse(pukesB);
    
    // 分析牌的類型
    PukeCompareResult bResult = GetCore(pukesB);
    PukeCompareResult aResult = GetCore(pukesA);

    // 不合法,直接認(rèn)為出牌小
    if (bResult.type == PukeType::ERROR) {
        return false;
    }

    // 如果本身牌為空,則也認(rèn)為出牌小
    if (bResult.type == PukeType::EMPTY) {
        return false;
    }

    // 對(duì)比的牌為空,則認(rèn)為出牌大
    if (aResult.type == PukeType::EMPTY) {
        return true;
    }
    // 一方是炸彈,另一方不是炸彈
    if (bResult.type == PukeType::BOOM && aResult.type != PukeType::BOOM) {
        return true;
    }

    if (bResult.type != PukeType::BOOM && aResult.type == PukeType::BOOM) {
        return false;
    }

    // 如果類型不一致,也認(rèn)為小
    if (bResult.type != aResult.type) {
        return false;
    } else {
        // 類型一致,比核心牌
        return (pukesB[bResult.core]) > (pukesA[aResult.core]);
    }
}

格式化牌和分析牌類型這兩塊,也不復(fù)雜,稍微有些細(xì)節(jié),感興趣的話可以看,代碼都在puke-compare.h中

js調(diào)用c++函數(shù)

代碼寫完了,服務(wù)器端ok了,我們就得讓前端能跑起來(lái)C++的代碼。借助emscripten,其實(shí)調(diào)用起來(lái)也挺方便的,這里沒(méi)有時(shí)間和篇幅說(shuō)具體怎么弄的,但可以說(shuō)的抽象一些。

js調(diào)用C++代碼有兩種方向

一種是直接調(diào)用C++函數(shù)

還有一種是在js環(huán)境下,new出C++對(duì)象,這個(gè)不好畫圖,我就不畫了哈

二者的區(qū)別主要也是寫法上的區(qū)別,只調(diào)用函數(shù)的方式控制力較弱;new對(duì)象的方式,控制能力強(qiáng),但是如果設(shè)計(jì)的不好,容易玩壞,而且麻煩些。

注意要考慮垃圾回收,在js側(cè)new出來(lái)的C++對(duì)象,v8可不會(huì)幫你垃圾回收,得自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的引用計(jì)數(shù)的垃圾回收(代碼在my_glue_wrapper.cpp中)。所以說(shuō),如果選擇new對(duì)象的方式,一定要考慮周全。

我們這里相當(dāng)于兩者結(jié)合使用了,畢竟本來(lái)就是為了練手,涉及到webidl相關(guān)的知識(shí)(將C++對(duì)象,轉(zhuǎn)換為js可以理解的對(duì)象)。具體代碼在assembly下puke.idl和my_glue_wrapper.cpp中

webassembly這里,本來(lái)打算多寫點(diǎn),但是發(fā)現(xiàn)不好下手,如果寫的詳細(xì),內(nèi)容會(huì)較多。感覺(jué)又能開(kāi)一篇文章了,但最近實(shí)在是比較忙,能抽出空寫這兩篇已經(jīng)到極限了……不過(guò)現(xiàn)在網(wǎng)上webassembly相關(guān)的文章資料已經(jīng)很多了,感興趣的同學(xué)可以帶著一起看,應(yīng)該就很有助于理解了。

思考

寫這個(gè)游戲期間,因?yàn)椴煌谄綍r(shí)業(yè)務(wù)開(kāi)發(fā),只考慮自己前端的那部分,這次從產(chǎn)品到前端后臺(tái)都是一個(gè)人,有一些非前端的感觸。

產(chǎn)品流程圖很重要,能提前理清楚一些邏輯坑點(diǎn),防止無(wú)腦擼代碼然后返工。這里吃了不少虧

設(shè)計(jì)大大們是真的牛皮

聯(lián)調(diào)過(guò)程保證后端穩(wěn)定性,盡量少改代碼了,后臺(tái)重新編譯、重啟的成本高很多

時(shí)間關(guān)系,沒(méi)有弄單元測(cè)試,但能準(zhǔn)備單元測(cè)試,還是要準(zhǔn)備,很重要

撲克對(duì)比,是否可以引用配置的方式,這樣就可以很好的支持其他撲克模式的對(duì)比了

結(jié)尾

終于到結(jié)尾了,感謝閱讀到這里的同學(xué)。這個(gè)游戲本來(lái)是一個(gè)無(wú)心之作,不過(guò)也起到了練手的作用。

兩篇文章更側(cè)重于思路和宏觀的一些東西,加上可能一些小坑。斗地主算是一個(gè)簡(jiǎn)單的游戲,但是我低估了他完成基本閉環(huán)需要的時(shí)間,所以很多地方都在趕,如果發(fā)現(xiàn)有寫的不好的、考慮的不好的地方,歡迎斧正~

大家一起交流溝通~

AlloyTeam 歡迎優(yōu)秀的小伙伴加入。
簡(jiǎn)歷投遞: alloyteam@qq.com
詳情可點(diǎn)擊 騰訊AlloyTeam招募Web前端工程師(社招)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/106224.html

相關(guān)文章

  • 前端之從零開(kāi)始系列

    摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫(kù)中高級(jí)前端面試手寫代碼無(wú)敵秘籍如何用不到行代碼寫一款屬于自己的類庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...

    Youngdze 評(píng)論0 收藏0
  • 從零到一一個(gè)在線地主(上篇)

    摘要:原文從零到一,擼一個(gè)在線斗地主上篇作者背景朋友來(lái)深圳玩,若說(shuō)到在深圳有什么好玩的,那當(dāng)然是宅在家里斗地主了可是天算不如人算,撲克牌丟了幾張不全大熱天的,誰(shuí)愿意出去買牌啊。 原文:從零到一,擼一個(gè)在線斗地主(上篇) | AlloyTeam作者:TAT.vorshen 背景:朋友來(lái)深圳玩,若說(shuō)到在深圳有什么好玩的,那當(dāng)然是宅在家里斗地主了!可是天算不如人算,撲克牌丟了幾張不全……大熱天的,...

    raoyi 評(píng)論0 收藏0
  • 從零到一:用Phaser.js寫意地開(kāi)發(fā)小游戲(Chapter 1 - 認(rèn)識(shí)Phaser.js)

    摘要:由于公司項(xiàng)目轉(zhuǎn)型,需要?jiǎng)?chuàng)造一個(gè)小游戲平臺(tái),需要使用一個(gè)比較成熟的前端游戲框架來(lái)快速開(kāi)發(fā)小游戲。僅支持開(kāi)發(fā)游戲,因?yàn)閷Wⅲ愿咝?。早在年的光棍?jié)前一天晚上,這個(gè)游戲就誕生了。原型是一個(gè)之前很火的非常魔性的小游戲,叫尋找程序員。 showImg(https://segmentfault.com/img/bVMGY5?w=900&h=500); 寫在前面 實(shí)際上我從未想過(guò)我會(huì)接觸到H5小游...

    didikee 評(píng)論0 收藏0
  • 從零到一:用深度優(yōu)先算法檢測(cè)有向圖的環(huán)路(應(yīng)用場(chǎng)景:性格測(cè)試)

    摘要:小結(jié)使用深度優(yōu)先算法,我們能夠檢測(cè)性格測(cè)試游戲的邏輯正確性,相比以往課堂上的理論,在這里算是一個(gè)具體的應(yīng)用場(chǎng)景吧。其實(shí)深度優(yōu)先算法的應(yīng)用面也很廣,遲早還會(huì)再碰面的。 showImg(https://segmentfault.com/img/bVStEU?w=900&h=500); 寫在前面 在開(kāi)始前想先說(shuō)一下關(guān)于這個(gè)課題的感想——能學(xué)以致用是一件很快樂(lè)的事情。 深度優(yōu)先算法(簡(jiǎn)稱DFS...

    nevermind 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<