作者:Mick West
(該英文文獻所在的網址為http://rrurl.cn/lQlN3B)
本文最初發表於“Inner Product”中的“游戲開發者”欄目,時間是2005年11月,我最近編寫了為撲克牌的AI編寫了一系列。我一開始以為這將是一件容易的事,但它證明了這比我想象的要復雜很多。本文為初露頭角的撲克AI程序員提供了一個基礎的,一個簡單的實現。無制約的德州撲克AI,覆蓋一副牌力量的評估的基本知識和下注的基本知識。通過運用本文提供的一些原理,你可以很快地模擬出一個強悍的德州撲克AI,並且對你將來做牌類的AI的每一步到底做什么有一個堅實的基礎,我這里假設你懂得紙牌的基本術語。
(如圖,這是一個可以計算德州撲克的牌的賠率的計數器)
德州撲克(TEXAS HOLDEM)
游戲AI的目標,我認為有兩點:
(1) 讓玩家有一種有趣和令人愉快的體驗。
(2) 從屬於目標(1),也就是說在玩家擁有了一種有趣而愉快的體驗的基礎上,盡量讓他們得到一種“挑戰”,而不像游戲《植物大戰僵屍》一樣,玩家沒有一絲一毫的挫敗感。
撲克的數據類型
你需要考慮利用一些數據結構來標識這些狀態信息(這一點,我已經在吳昊品游戲核心算法 Round 15之吳昊教你玩德州撲克中做到了,方法就是位標識存儲),以下就是利用位/字節對如下信息的一些存儲(更好的存儲方式,本文留給了讀者自己去想)
花色(suit)是在0—3范圍的整型變量,梅花=0,方塊=1,紅心=2,黑桃=3
點數(rank)是在0—12范圍的整型變量,其中,令2(deuce)=0,3=1……13(King)=11,1(Ace)=12,每一個花色都有13個不同大小的點數
一張紙牌(card)是在0—51范圍內的整型變量,我們提出如下公式
card = suit*13 + rank.
Suit = card/13
Rank = card%13
我們利用64bit的空間來存儲一手牌(實際上,空間上面依然有一些浪費,其中的52bit被使用,而有14bit被留作陪葬品了)
我們利用一個整型變量來描述你手上的牌型(poker type), 其中 0= no pair, 1=pair, 2=two pair, 3=trips, 4=straight, 5=flush, 6=full house, 7=quads, 8=straight flush.
牌值分析
我們利用一個32位的整型變量來表征一手牌的值,它表示一手牌的相對價值和一手牌的實力。通過兩手牌的值,你可以判斷出哪一手牌的實力更強悍。
我們利用6個半字節(4位)來表征一手牌,其中,最重要的四位代表牌的牌型,后面的5個半字節量來表征不同等級的牌在牌值分析中的價值。
例如:
樣例1:AH QD 4S KH 8C是一個沒有對子的牌型(有時候我們說成是散牌或者是高A),所以,所以,type設置為0,剩下的五個ranks按照五張牌的遞減順序排列,(A,K,Q,8,4)被翻譯為以下的五個數:12,11,10,6,2(或者對於16進制來說,為C,B,A,6,2),再結合高位的牌型0標識,給出了一個32bit的整型變量:0x000CBA62.這里,我們需要注意兩點:(1)我們的這種數據結構忽略了花色的信息,但是,唯有我們在分析同花順的時候,才有必要了解到高位信息。(2)注意到兩個高位的牌值都為0.
樣例2和樣例3的解釋同理,所以,我在這里就忽略了。
計算牌值
我們現在需要的就是得到一手牌,然后計算以下這手牌的牌值。
這牽扯到牌型,插入半字節變量的牌的等級,以上。
一手擁有着四種花色(梅花,方塊,紅桃,黑桃)都有13bit(對於每一種花色來說),13bit可以提供僅僅8192種組合,我們可以通過預處理8K的表中的若干像這樣的位(在13bit內部的)(如果你有五個或者更多的相同花色的牌,那你就得到了一個同花順),或者是一手牌中的任何強悍的牌,你也可以從一個特別的bit組合中預處理出最高的五張牌,作為起步牌。
如果你要去計算等級(草花 方塊 紅桃 黑桃),那么該行列值就應該是一個位字段。這個位字段會為你所擁有的至少一個牌設定一個值。在這里設定的這個值是你所擁有的,也是一個特定的值。我們計算出每個草花方塊 紅桃 黑桃中的設定片段的數字值,並減去在每一個特定的行列值中的數字值,得到重復的行列數值,以此用來作為確定你有什么類型的底牌的基礎。
例如:如果你有2D AS AH 2C 2H,你可以迅速確認你有五張牌,只有兩個獨特的rank,重要的是,你必須有一個葫蘆(福爾豪斯)或者是一個鐵支。更多的簡單測試將幾乎決定你要什么。整個評估函數將包括像這樣的測試,逐步削減可能的牌型。
因為這個函數更多地包含了位運算,表查詢和簡單比較,它會變得非常快(位運算的優勢嘛),它也非常適合於微調優化,確切的實現將取決於目標體系結構,你可能可以利用一些特定的處理器指令,這樣會變得更有效率。
計算一手牌的力量
一手牌的力量計算是你這手牌可以贏的概率,給你底牌,明牌和留在對手手中的牌,一手牌的力量是一個介於0.0(徹底地輸)和1.0(徹底地贏)之間的一個浮點數,例如,HS為33%的話,說明你有33%的概率可以贏。
一個最簡單和最方便的手段來計算HS的方法就是模擬許多許多次游戲的過程,計算你的牌可以贏的次數(這有點像數學建模里面的黑箱操作),比如你模擬1000次這個游戲,在模擬中,你贏了423 次,那你可以近似的確定,你贏這場游戲的概率(HS值)為0.423.
模擬整個過程是很簡單的:
(1)設置分數0
(2)移除你所知的牌(底牌和明牌)
(3)重復1000次(或者更多吧,這取決於CPU的資源和期望得到的精確程度)
(4)隨機化剩下的組
(5)對付你對手的底牌,和你剩下的公共牌
(6)評估所有的手牌,看看誰有最好的!
(7)如果你有最好的話,那么加上1/(和你擁有相同命運的牌值的人的數目)(這通常是1)
(8)結束if循環
(9)結束1000次模擬
你這一手牌的力量=你所得的分數/你進行模擬實驗的總次數
更精確的考慮我覺得幾乎沒有必要,所以,在這里也略去。
(如圖,此為2011年百度之星的總決賽,當年的題目就是德州撲克的AI)
POT的賠率
POT的賠率的計算=你為了叫牌下的注/(你叫牌下的注+POT內的錢的總數)
回報率
回報率指的是,你如果要下這手牌,可以得到的金額與你下注的比值(引入了牌力的大小)
回報率=一手牌的力量/POT的賠率
我們的基本策略就是如果回報率大於1的話,我們就將拍拿在手上。
對於棄牌(FOLD)/叫牌(CALL)/加倍(RAISE)的選擇
對於每一個Round(不同於吳昊系列的Round,這里指的是一次游戲)的下注中計算機都需要決定是否需要棄牌/叫牌/加倍(被稱為FCR決定),忽略目前叫加倍有多大的價值,我們得到一個比率量(返回值RR),這里提供一個基於一定可能性的既簡單又非常實用的映射(映射的兩個量為RR和FCR)
如果RR<0.8,那么95%選擇棄牌,0%選擇叫牌,5%選擇加倍(這里加倍的目的是為了虛張聲勢)
如果RR<1.0,那么80%選擇棄牌,5%選擇叫牌,15%選擇加倍(虛張聲勢,虛張聲勢!!!)
如果RR<1.3,那么0%選擇棄牌,60%選擇叫牌,40%選擇加倍
另外,如果RR>=1.3,那么0%選擇棄牌,30%選擇叫牌,70%選擇加倍
如果棄牌和叫牌的數量都為0的話,那么,叫牌吧!
不要過於在意以上列出的精確的百分比,這些數目將決定於你計算你的一手牌的力量值,你也許想過通過上一輪下注的多少來改變你目前的下注,你甚至想通過改變這些數值來創建具有不同個性的對手。
利用這個非常簡單的介於RR和FCR之間的映射決定可以讓你擁有一個令人驚訝的既有道理的又有娛樂性的AI玩家。他們將趨於玩強有力的手牌。他們也許偶爾會虛張聲勢,他們也不會輕易因為他們手上的牌太好而感到驚訝,他們也會在虛張聲勢地叫加倍之后處理薄弱的手牌,他們也會堅持尋找一個合理的機會來得到一個同花順或者是鐵支,讓游戲的娛樂性大為增強。
沒有一種情況是必勝或者是必敗的,這是一個非常重要的道理,這說明你永遠都不能根據你的AI對手的行動來揣測出他的牌(除非他們棄牌,這種信息也不能真正幫到你),如果他們加倍的話,那你可是要小心了,他們可能是有一手非常好的牌,但是也是有1比20的概率,他們的手上可能只有一手非常非常差勁的牌。
籌碼保護
在你還有很多錢而且盲注比較小的時候,這個簡單的規則可以支持的工作。但是,當你的籌碼稀釋,盲注增加的之后,你就得考慮一下你的金錢的可持續性了。同樣地,偶爾,那些玩家也會“全力投入”,他們會賭上自己籌碼內的所有的金錢,所以我們必須讓AI變得更有邏輯性,來防止在籌碼內的金錢很少的時候,不讓差的叫牌發生。
假設你有AD,2D,公共牌是QC,KC,2C,那么你有一對牌,但是也有可能是同花順,在POT內又500美元,賭注是100美元,對手為兩個玩家,但是,這是你最后的100美元。POT賠率為100/600=0.1666,你的一手牌的力量為0.297,所以你的回報率為1.8.如果你將這種情景一遍一遍地重復,你將又可能每次得到平均80%的回報率。然而,這是你最后的100美元了,你有70%的概率會失去一切,那么,不要再下注了!
來處理這些事情,我們可以給予一個簡單的啟發式:
如果我的建議賭注將大大地維持我現在的籌碼,那么在我有一次很有自信的贏的機會的時候,不要去下注。
可以部分地描述為:
如果(籌碼-下注)<(盲注*4)並且(HS<0.5)那么就棄牌
含義是如果叫牌會讓你只剩下不到四倍的盲注,那就不用叫牌,除非你有50%以上的勝算。
撲克是一個復雜的游戲,有着非常多種類的不可思議的情況等你去處理。我建議你讓這些極少數個別的情況越少越好,這樣可以減少游戲中更少的風險漏洞,但是,我們可以利用一些啟發式算法(經驗法則)來處理這種模糊的情況,讓AI的邏輯具備更多的復雜性,大大提高可玩性。
(我有一個朋友是華中科技大學的百度俱樂部的,他當年也來參觀了總決賽,聽說得第一名的一個人是利用了一個無理手,讓玩家們不斷出牌,自己堅決不出,到了最后大家都沒有好牌之后才出自己的牌,利用這種奇葩的AI戰術取得了非常好的效果!)
測試撲克的智能性
平均來說,和一般玩家快速地玩單付德州撲克游戲只需要大約30分鍾的時間。理想情況下,通過自然人玩家和智能玩家來競爭你才能完成你的測試,並且找出其中包含的問題。不幸的是,由於玩家的隨意性正在一步步得到解決,玩家很容易通過低於標准桿邏輯獲得幸運的牌號並硬的游戲,或者甚至於通過有缺陷的邏輯也可以實現這么一點。我已經發現至少需要10場比賽來開始得到對於AI玩家的素質的清晰了解,通過超過100次的游戲才能真正確定這種素質。
這對於測試項目來說往往會造成一種不理智的負擔,並在獲取AI 玩家身上發生的變化上引入一個非常長時間的時延。 解決的辦法是自動化測試。認證機構應該設定不同的變種AI玩家,以使得不同變種的AI可以在一個設定的速度非常高的游戲中互相對戰。你也應該編寫一些簡單的撲克AI的組合,如AI,它總是適於所有的,其他那些易於用手而不是用手指。然后, 你對AI的松緊程度進行設置來應對這些對手,同時確保其贏得適當比例的比賽。如果你寫的評價和模擬得當,那么你應該能夠在一秒種左右時間內模擬一整場比賽(您可能要減少反復的模擬,以加快測試速度)。
自然人測試的最好用途就是去試圖使他們找到AI的利用性,然后你可以編纂到一個臨時的AI對手,包括利用此漏洞的測試套件。你進而可以調整你的AI,直到探測到它失敗的漏洞,同時仍然能夠打敗所有其他(標准)的對手。