自己寫三國殺之架構分析

2021-06-03 02:26:16 字數 3246 閱讀 5581

本文分析的是三國殺標準包+ex擴充套件包所帶的功能如何在我的程式裡實現的,不包括其他的擴充套件包。實際的程式可以支援2至10人在控制台下進行網路對戰。博主僅僅憑興趣寫的,設計有缺陷,請多多包涵。

三國殺實體牌分遊戲牌、體力牌、身份牌、武將牌,進行遊戲時以玩家的座次順序來依次進行遊戲,大家都共同遵守同樣的遊戲規則。程式的設計就以此為切入點設計。

一、遊戲牌(poker)

所有的遊戲牌主動打出時大致的流程就是:確定使用->指定目標->進入結算流程(生效或失效),被動打出則較為簡單。所以定義乙個基類,儲存牌的基本資訊以及執行牌的通用動作,例如訪問牌的數字、花色、名稱等;不同的牌繼承自這個基類(基本牌、錦囊牌、裝備牌),具體實現各自不同的「確定使用」 「指定目標」 「進入結算流程」的函式。在**中這三個函式分別是poker::selected()、poker::use()、trickpoker::effect()

1,、殺、閃、桃。繼承自基本牌,類名分別為slash、jink、peach。閃不能主動使用,桃的使用也比較簡單,殺的使用就較為複雜了,涉及目標合法性檢查、**技能發動(每個**的技能有不同的發動時機)、對方防具技能的選擇發動等等,需要在設計時充分考慮後再動手編碼,以免有遺漏導致實現出來的功能不正確。

2、各種**、防具、馬。繼承自裝備牌,類名與中文的對應見各自的標頭檔案注釋。裝備牌的特點是在使用後要確定裝備於玩家的裝備區,所以每個裝備牌在初始化時就有乙個變數:這張牌應該處於什麼位置(enum equippoker::location)。

3、錦囊牌,分為延時錦囊和即時錦囊。它們在使用後要在全場詢問無懈可擊,在沒有人打出或者一共打出偶數個無懈可擊後才生效,進入結算。

4、虛牌。它用於武將技能發動、卡牌轉換,在**中它包含乙個子卡的集合、記錄相關資訊。比如制衡、仁德,就是將相應的卡牌作為一張虛牌的子牌,提交處理;或者大喬的國色、甘寧的奇襲,是將方片牌或者黑色牌轉換成樂不思蜀或者過河拆橋進行結算。

二、牌堆(deskpoker)

牌堆分為未用牌堆、棄牌堆、正在結算牌堆。其中要注意考慮武將技能對牌堆的影響,比如諸葛亮的觀星。正在結算牌堆,在結算殺閃、五穀豐登等等的時候所有的牌先置於這裡,結算完畢時這個牌堆裡的牌統一清理到棄牌堆。

在具體實現時,我一開始用自己寫的鍊錶去儲存相應的資料(按照《資料結構》書上講的方法)。結果在測試時,發現如果對這個鍊錶進行上萬次操作後它會隨機崩潰,我拿著**請教老師,他也說不出來什麼錯誤。最後我在程式裡,所有包含多個資料的東西用的是vector來做,比如玩家的手牌、裝備、判定區等等。

三、服務(service)

在遊戲裡,這個服務扮演裁判的角色。計算玩家之間的距離、進行殺結算流程的一部分、在玩家打出錦囊時進行詢問無懈可擊、處理轉換卡牌(前面的虛卡)等等,最重要的功能是指導遊戲進行流程(service::setphase):在遊戲開始時,服務通知n號位玩家進行他的回合的某一階段(回合開始階段、判定階段、摸牌階段、出牌階段、棄牌階段、回合結束階段),玩家返回這個階段是怎樣結束的(是正常結束還是跳過某一階段,亦或是觸發勝利條件),服務對這個返回值檢查並處理之後,再通知n+1位玩家進行他的回合。

四、玩家(player)

玩家的設計曾經困擾我很長時間,直到現在的設計也不是讓我很滿意。在只有標準包+ex時,我認為玩家所用武將就是「素將+技能」,而且我認為「玩家=素將」,所以我在player類裡面實現了所有素將的功能,具體的武將從素將繼承,並改寫相關函式實現武將技能。

在這裡我說一點,在三國殺「神話再臨」版本裡,武將與技能的關係不再是一一對應的,也就是說有可能你用的是「孫權」,但是你的技能有可能不再是「制衡」和「救援」,所以技能和武將設計時一定要解耦,我的實現方式直接決定我不能實現「神話再臨」裡面的武將。

player裡面最重要的函式是player::setphase()和player::askleadpoker()。前者在自己回合內通過接收service的傳值知道自己處於什麼階段,並提示玩家進行相應操作、選擇,比如玩家在出牌階段要出什麼牌,是在這個函式裡程式向玩家詢問的;後者在玩家回合外提示玩家被要求出什麼牌,比如別的玩家殺自己、打出南蠻入侵、萬箭齊發時,這個函式通知自己要打出「殺」或者"閃"。其他函式是為了配合對應牌的功能而實現的,比如丟棄牌、亮出牌、交出一張牌等等。

具體武將的類名是武將名稱的拼音,基本上是重寫player::setphase()和player::askleadpoker()。

五、伺服器內部訊息傳遞

在struct.h裡面定義了一些訊息結構體,比如用於傳遞「殺」訊息的slasheffectstruct、用於錦囊訊息的trickeffectstruct等等。主要傳遞的訊息就是「**」、「目標」、「牌」。其中**一定是單一的,但是目標和牌有可能是單一的,也有可能是多個。存在多個目標時一定要按照遊戲規則的結算順序來依次結算。

六、伺服器/客戶端訊息傳遞

在開始時我編寫的**是為了單機玩的,所以沒有設計通訊協議,基本上都是cout一段中文提示讓使用者進行一種選擇,再cin乙個數字,進行檢查合法後繼續流程。在單機版寫完後突然想加入網路功能,這時候改中文重新設計通訊協議已經來不及了。我的折中辦法就是將客戶端做成乙個「螢幕」,客戶端僅僅是用於顯示文字並接受使用者的輸入,將資料回傳至伺服器進行相關檢查、操作等等,也就是說客戶端根本沒有關於遊戲的內部資料,不知道本玩家手裡有什麼牌。

原來的所有cin cout,有的東西是要求所有玩家都知道的、有的東西只能讓乙個人知道,訊息一共有三種型別:向所有人通知、向單個人通知、向單個人詢問。前兩個客戶端僅僅需要接收資料並顯示,最後乙個要求在顯示資料之後讓使用者輸入乙個選擇之後回傳至伺服器。

在實現上,我寫了乙個gameserver類,用於網路通訊,採用tcp方式連線。因為tcp是流式協議,伺服器/客戶端內部用了string用於接髮資料,也方便進行格式轉換,比如讓使用者進行選擇時,使用者只需要輸入乙個數字作為選擇,伺服器接受的「數字」其實是字串格式的,用stringstream可以很方便地進行字串/數字型別的轉換。

七、健壯性

1、輸入輸出健壯性

這個程式的輸出都是字串格式的,不會出現什麼錯誤。所有輸入僅僅是乙個整數作為選擇,在伺服器接受使用者做出某種選擇時,用stringstream去接收,再進行轉換,比如:

string recv="4";//接收到的字串格式的資料

stringstream ss;//用於轉換的字串流

int a=0;//真正能用於伺服器內部處理的int型變數

while(輸入不合法){

ss>a;//從字串流中讀取乙個int型資料並儲存

if(a在範圍內)  輸入合法跳出迴圈

這樣就能保證即使輸入錯誤也不會導致程式崩潰。

2、網路通訊健壯性

這方面比較遺憾,因為時間緊迫所以沒有做更好的處理,在**裡規定,如果網路通訊出現中斷,則所有人斷開與主機聯絡。

到此為止,這個三國殺的設計思想差不多介紹完了。

寫個三國殺

自己老早以前就想寫個區域網的三國殺,以前思考過其遊戲邏輯的實現,覺得有點複雜,一直也沒有真正動手,上個週末,真正動手做了起來,2天裡寫了大約2000來行 算是把整個框架有了個大致的實現。下面把整個思路整理一下。既然是輕量級的區域網版本,就沒必要把伺服器和客戶端分開來做,就讓程式自帶服務端和客戶端,類...

三國殺的理解

11 人生就像一局三國殺。有可能你還沒判斷出誰是好人誰是壞人你就掛了。12 人生就像一局三國殺。你對別人好,別人不一定對你好。13 人生就像一局三國殺。你要在這個世界生存有時需要偽裝。14 人生就像一局三國殺。你可能來這個世界只是來打醬油的。15 人生就像一局三國殺。有可能一出生就已經決定了你的身份...

三國殺自走棋

這個陣法叫做五騎兵陣 首先 從協戰上來說還沒有乙個協戰是浪費的 那麼這套陣容就是攻防兼備 2群可以得到閃 而3魏則是加上一點體力上限 這是防守 那麼5騎兵就是進攻 它可以是敵人有概率無法使用閃 1 當人口 1時 最好用非五騎兵陣容裡面的人 用像袁術 周泰這種可以扛住第二回合的武將 當然 如果有二星的...