• 沒有找到結果。

叫牌演算法及資料結構

第三章 資料結構及演算法

第三節 叫牌演算法及資料結構

觀察蜜月橋牌的遊戲方式,可以發現,在進行打牌時,影響 Min-Max 演算法 展開結果的是牌的「順序」,牌的大小差距並不會影響最後的結果,在蜜月橋牌 的規則中只有分大或小,例如當某一磴當中,玩家 B 出了 Ace 去吃玩家 A 的 5,

或者出了 6 去吃 5,結果都是一樣獲得一磴的積分,並不會因為出的牌特別地大 或小有加分或減分,所以針對這種特性我們設計了一種只記錄順序的資料結構,

如圖 3-6 及圖 3-7。

  圖 3-7 玩家手牌排序圖 1

在圖 3-7 中,我們以花色作分隔,將每個花色作排序。以黑桃花色為例子,

在雙方的手牌中,黑桃由大到小分別為 A、K、J、9、7、5、3,持有的玩家分別 為 A、B、A、A、B、A、A,我們以 1 代表玩家 A,0 代表玩家 B,可以得到一組 26 bits 表示的玩家手牌的排序。但是只有這樣還不夠,我們需要更明確的指出 花色的分布情況,也就是我們必須記錄各種花色各有幾張。在這個例子中,黑桃 有 7 張,紅心有 7 張,方塊及梅花各有 6 張。由於每個花色最多不會超過 13 張,

所以如下圖每個花色只需 4 bits 即可以記錄所有情況。

花色 1 張數 花色 2 張數 花色 3 張數 花色 4 張數 Bit12~15 Bit8~11 Bit4~7 Bit0~3

  圖 3-8 玩家手牌排序圖 2

在圖 3-7 及圖 3-8 的兩副牌中,發出的手牌不同,但經過花色排序後得到了 相同的排序結果和花色分佈,因此經過 Min-Max 演算法的展開搜尋後,這兩副牌 最後的得分會相同。在這種資料結構中,只需要兩個整數(int)大小的參數,就 能藉由程式得到打牌的結果。

3.3.2 演算法設計

在第二章曾經提過,叫牌的難度在於如何讓電腦能分辨牌的好壞價值,在這 裡我提出一個評估的方法。在一般情況下,基於蜜月橋牌的遊戲玩法,雙方玩家 一開始的手牌經過換牌的步驟後,雙方手牌的好壞對比會與原來相差不多,所以 玩家才能由一開始的手牌來進行叫牌的動作。於是當我的程式拿到了一開始的十

三張牌,會採用蒙地卡羅模擬法(Monte Carlo Simulation)使用亂數隨機發給對 方十三張牌,雙方直接以這些牌模擬打牌的步驟,以 Min-Max 演算法展開並記錄 結果,經過大量的模擬後,將所有結果取平均,可以得到一個分數,程式會以這 些分數當基礎來做叫牌的評估依據。

延續上一小節,每一種排序及花色分佈經過 Min-Max 法展開搜尋而得到一個 相對應的得分,我們可以將這些排序、花色分佈及得分預先計算好,建立一個資 料庫,採用 hash table 的方式,在程式開始時載入記憶體,這樣就可以節省遊 戲進行中展開計算所花的時間,節省下來的時間可以增加模擬次數以增加準確 度。

3.3.3 資料庫的建立:

在圖 3-8 中我們得到了排序如下:

        在蜜月橋牌中,有王牌這一個花色的存在,同樣的一局牌,以不同的花色作 王牌來比賽,結果也會不同,所以我們還需額外指出王牌是哪一種花色,為了解 決這個問題,我將王牌放在第一組花色的位置,根據王牌的不同,排序也會隨著 改變,所以每一局牌,會計算分別以四種花色為王牌的四個分數。 

以黑桃為王牌:

以紅心為王牌: 

  以方塊為王牌:

  以梅花為王牌:

   

經過上面的排序,我們根據每個序列的分數,可以很明顯地看出選擇哪一種 花色作為王牌較為有利,前提是我們必須能快速的得知這些序列的分數,所以這 些序列分數資料必須事先計算好並載入記憶體才能夠快速存取,這也限制了資料 庫的大小,為了減少一些相同情況的資料重覆載入記憶體,我又對序列作了一些 調整,下面會進一步解釋說明。

  圖 3-9 序列比較圖

圖 3-9 的兩組序列,在王牌部分的順序都是 1011011,不同的只是花色,一 副是以黑桃為王牌,另一副則是以紅心為王牌,在其他三個花色則分別由 1010010、100100、000111 三組序列組成,也就是說這兩組序列的不同只有花色 分布的情況而已,兩組序列最後的得分應該會相同,我們在建立資料庫時應該把 他們視為同一種序列。在這裡,我在建立資料庫時,對非王牌的三個花色排列順 序作了調整,依照花色序列的長短(各花色的張數)來決定順序,由短到長排列,

  雖然還是會有相同長度造成的一些重複情況,但已經大幅的減少資料庫的儲 存盤面數量。雖然如此,但在實做的過程中,發現資料量還是太過龐大,無法全 部載入記憶體,必須改變做法。完全的 Min-Max 法展開計算時間太長,完整的資 料庫又太佔空間,於是我採用了折衷的做法,依靠展開並搭配部分的資料庫來完 成,如圖 3-10。

………

………

………

Level 13

Level 12

Level 9

Level 8

Main memory Level 8 資料

Hash functionHash function Hash function

  圖 3-10 Min-Max 演算法與資料庫架構圖

這邊我基於實作上的考量,最後選擇八層的資料庫(雙方都只剩八張牌),在 第八層以上使用 Min-Max 演算法作展開,展開到第八層再從記憶體讀取結果。

資料庫大小:

牌的 張數

1 2 3 4 5 6 7 8

檔案 大小

88B 748B 5.36KB 35.3KB 213KB 1.24MB 6.97MB 37.8MB

資料庫格式:

上圖是第八層資料庫的一部分,以圈選的部分為例做說明:

31372 表示雙方牌的大小順序,轉成二進制為 0111101010001100(請看下圖),

這是雙方各有八張牌的混合排序,1 表示這回合有出牌權的一方,0 是跟牌方的 牌。

1、2、7 表是非王牌的各花色有幾張牌。此例王牌的張數有 16-1-2-7 = 6 張。

4 表示先出牌的玩家經過 Min-Max 法展開後能獲得的磴數為 4。

011110 王牌

1 花色 1

01 花色 2

0001100 花色 3

1 張 2張 7張

6張

 

資料庫建立過程:

不同的兩副牌,在展開的過程中,卻可能產生相同盤面的子節點,如圖 3-11 所示。如果我們直接對每一種牌型做展開,其子節點盤面可能在許多不同副牌型 都重新被展開,為了避免這種情形,資料庫必須由底層往上建立,將每一層所有 可能結果都儲存起來,再由這些結果建立更高層的資料庫。

…… ……

  圖 3-11 相同盤面

 

…………

…………

……

Level n

Level n-1

  圖 3-12 資料庫建立過程示意圖

在打牌的過程中,隨著遊戲進行,玩家每回合的手牌張數都會減少一張,所 以每一層展開的子節點盤面一定都存在下一層的資料庫中,如圖 3-12。例如,

假設玩家目前手中還有六張牌,要選擇某一張來打,而我們已經有了所有五張牌 時可能的情況及分數,此時我們只要對手中的六張牌做一層的展開搜尋就能知道 應該要打哪一張。每一子節點盤面只需要一次的展開計算儲存後,就能被多次的 讀取使用。

3.3.4 叫牌模擬過程

圖 3-13 叫牌模擬畫面

程式執行時,玩家的手牌會顯示在執行視窗中,電腦方的手牌會被記錄在文 件檔裡,以上為程式執行畫面,每次輪到電腦方都亂數模擬五十副牌,在綠色框 框中代表模擬計算出電腦方四個花色的分數,紅色框框中表示四個花色獲勝需贏 得的磴數,在真正的比賽時玩家只能看到自己的手牌及對方的叫牌,其餘資訊則 不會被玩家看到,為了說明程式流程,因此顯示在畫面上。

第一次模擬如下表: 以紅心的模擬分數和獲勝需要磴數差距最大,因此選擇叫紅 心為王牌,也就是叫 1 紅心。

模擬分數 獲勝需要磴數 差距

黑桃 5.6 7 -1.4

紅心 9.26 7 2.26

方塊 8.16 7 1.16

梅花 6.8 7 -0.2

如圖 3-13 所示,這時人類玩家喊 1 黑桃。

第二次模擬如下表: 以紅心的模擬分數和獲勝需要磴數差距最大,選擇叫紅心,

也就是 2 紅心。

模擬分數 獲勝需要磴數 差距

黑桃 5.52 7 -1.48

紅心 9.7 8 1.7

方塊 9.04 8 1.04

梅花 6.4 8 -1.6

如圖 3-13 所示,這時人類玩家喊 2 黑桃。

第三次模擬: 以紅心的模擬分數和獲勝需要磴數差距最大,選擇叫紅心為王牌,

這個契約,從另一個角度看,對方獲勝條件高就是我方獲勝條件低,這也增加了

enum flower {spade,heart,diamond,club};

for (i = 0;i < simulation_times; i++) { //設定模擬次數

make_choice(score); //根據目前盤面及模擬分數做叫牌選擇

相關文件