象棋麻將
說到象棋麻將,得先從麻將說起。
麻將乃我國國粹之一,從唐、宋的馬弔牌演變至今日的麻將牌,已經有將近千年的時間。雖然講起麻將,難免與賭博脫離不了關係,不過麻將在中國民俗中佔有相當的地位,尤其是過年期間一家團聚守歲打個幾圈消遣消遣,嚴然成為一種廣義的「民俗活動」。麻將既然如此盛行,坊間的電腦麻將遊戲自然比比皆是。無論是網路多人對戰、單人與電腦拼鬥,形形色色的麻將電腦遊戲出現在市面上。基本上,麻將是一種機率性遊戲,由個人手中的牌面以及猜測到對方的牌面,來計算獲勝機率,依照機率的大小來主導打牌時的策略。然而除了網路「人對人」的麻將遊戲不需要任何電腦思考解法之外,大部分的麻將遊戲乃是以人腦的「經驗法則」來解題,並沒有精確的去計算勝利的機率為何。依台灣麻將來說,共有144張牌,每人取16張,其複雜度之大,要利用電腦來計算機率,也不是一件簡單的事。於是我們便先從簡易的麻將遊戲探討起,也就是所謂的「象棋麻將」。
機率性遊戲,是一種以「運氣」為基礎,在遊戲的同時卻又需要靠正確的判斷來處理牌面,以讓自己有利於達到勝利的目的。人來打牌往往是利用自己的感覺、經驗、嘗試錯誤,經驗不足的玩家往往不能達到較佳的勝率,畢竟人腦的記憶力與推理能力有限。然而電腦卻能以極快的速度,利用機率的計算、強烈的記憶力,去做吃牌、丟牌的最佳判斷,達到最佳的勝率。本研究就是去尋找一個演算法及資料結構,在公平原則下(即電腦的牌家無法得知其他玩家手中的牌以及其他未被掀開的牌為何),使電腦能在最短的時間內計算出勝利機率,在打牌的同時做出最佳的判斷,達到勝率最高的遊戲軟體。無論是人在機器上直接與本遊戲對戰,或是人與人在打牌時利用本軟體來輔助判斷,期望本軟體都是常勝者。於是乎,期望利用本研究結果所研究出來的演算法開發的遊戲軟體能有高勝率、高挑戰性,以及富有趣味性。
「象棋麻將」這個遊戲說起來並不風行,也較少有人研究,相較於其他遊戲紙牌等,它是一個較冷門的研究。然而之所以要研究這個遊戲,是希望利用「象棋麻將」的最佳勝率解法,進而發展出各種麻將以及相關遊戲的最佳解法。
象棋麻將玩法簡介
象棋麻將的遊戲規則非常簡單,其玩法乃是麻將規則的簡化,通常與麻將同為四人的遊戲。
1.握牌
遊戲最初,所有的棋子必須蓋在桌面,接著每位玩家依序從中取回四顆棋子,而遊戲的第一家可以直接取回五顆棋子,以順便執行打牌的動作。
2.勝利條件
當「摸牌」,或「胡牌」後,手中的五顆棋子達到勝利條件的牌面如下表時,便達到勝利條件。在五顆棋子當中可分成甲、乙兩部份來看:
甲部份(三張) | 乙部份(兩張) |
將、士、象 | 任兩張相同者,如車、車 (將配帥不算) |
車、馬、包 | |
卒、卒、卒 | |
帥、仕、相 | |
俥、傌、炮 | |
兵、兵、兵 |
甲部份的三張必須是列表中的其中一個組合,例如:「將士象」符合,但「象車馬」或「帥士象」不符合。另外,乙部份必須是一模一樣的兩顆棋子的組合,例如:「車車」或「卒卒」,但「將帥」或「馬傌」就不符合。
3.摸牌與自摸
當輪到自己時,可以選擇從蓋住的棋子中抽出一張棋子到自己手中,當達到上述勝利條件,即「自摸」,本局便得勝。若沒有就必須選擇手裡五支中的一支放出,並將放出的棋子翻開在桌面。
4.吃牌
當輪到自己時,除了可以選擇摸牌以外,若「上一家」所放出的牌對自己有利,也可以選擇「吃牌」,亦即將對手剛放出的牌收回。接自己也同樣執行放牌的動作,然後輪到下一家。
5.胡牌
當「任何一家」所放出的牌,足以讓自己達到勝利條件時,即可進行胡牌的動作,當玩家胡牌時,本局也獲勝。胡牌的動作不侷限於只能收「上一家」的牌。
電腦自動決策之演算法
這個遊戲最重要的演算法是用在計算勝利的機率,而我們的做法為利用「Spanning Tree」來分析。首先我們假設電腦的運算能力足夠,有足夠的空間以及可以在能忍受的時間範圍內完成整個遊戲樹的展開。先完成第一手牌的判斷。
Spanning Tree:如圖一
機率算法主要概念:
假設有上述樹狀圖來看,整顆樹狀完整展開至最底層,我們要去判斷Root下所有Child(在這裡為放牌)的勝利機率。由於象棋棋子總共為32顆,在樹狀圖中可以看到單數層為放牌(假設Root為第0層),偶數層為得牌(包括吃和摸)。因為電腦手中已經握有5張,於是未知的牌共有32-5=27張,也就是說電腦可能再摸到或吃到最多的次數為27次,於是整個Tree最深有27×2=54層(不包含Root)。在展開到底層的過程中,會有一些機會在中途便達到勝利條件,於是我們把該點以下最底層的Leaf都視為已達到勝利條件。假設每個放牌的子樹最底層的Leaf共有N個,達到勝利條件的有K個,於是每個放牌的子樹的勝利機率便為K/N。
問題:空間與時間的浪費
由於整個樹狀展開非常的龐大,若要完全展開且保存整個Spanning Tree再來計算K與N的值得話,會造成空間與時間的嚴重浪費。假設每次都有5種牌要放,接下來每次都有可能吃到14種牌,放與得的次數最多27次,整個樹(不包括Root)最嚴重的情況會有(5×14)27個node。這對於當前電腦的運算能力與容量都是一種嚴重的負擔。
解決空間問題的策略—機率累加 & Backtracking
若要正確算出K與N的值,勢必得要展開完整的樹。於是為了節省空間,我們用了Backtracking與機率累加的方式。
☆機率累加:遊戲過程中每一條由Root到Leaf的Path都可能是遊戲發展的路線,假設有一條路線線如圖二所示。其中灰色者為所走的路線。
A點之下共有N1個Children,所以B點在A之下出現的機會為1/(N1)。又B點下有N2個Children,於是C點在B點下出現的機會為1/( N2)。又剛好C點為勝利點,則在A之下,走到這條路線達到勝利的機率為1/(N1×N2)。換言之,達到假設勝利條件的點P在第k層,由子樹的Root到P點的Parent沿路的node的Children數分別為N1、N2、N3…、Nk,則遊戲落到P的機率為1/(N1×N2×N3…×Nk)。
又假設某個子樹下有n個達到勝利的點,並且每個點機率為P1、P2…、Pn,於是整體的勝利機率則為(P1+P2+…+Pn) 。在展開樹的過程中,已經達到勝利的點就不用再繼續展開下去。透過機率累加的方法,可以得到某點的勝利機率。
☆Backtracking:為了實作出機率累加的算法,利用了Backtracking的樹狀展開方式,如圖三中數字所示。其中灰色圓為達到勝利的點。灰色方格為展開的順序。
圖三 樹狀展開的順序
如圖,整顆樹向下展開,在遇到勝利點、最底層或所有Child皆展完時,不再展開下去並且回到Parent,若該Parent還有未展開Children,則繼續向下展開其他的Children。Backtracking可以利用Recursion的方式來實作。由於Backtracking利用一種變相的Stack,在象棋自摸的遊戲樹中,整顆樹最深為54層,所以我們最多只須同時儲存54個node的data,每個點的資料量並不是很龐大,所以空間問題得以解決。
利用Backtracking 與機率累加的方式,便可以在不佔用龐大空間的情況下,完整的計算出某點的勝利機率。
解決時間問題的策略—不完全展開樹
若純粹利用Backtracking還是需要經過整顆樹的大部分node,空間問題解決了,但是時間問題還是存在。然而之前有提到過,象棋自摸遊戲是在最少次的輪替次數內(因為必須比對方先自摸或胡牌),爭取達到勝利條件的牌面。於是便利用不完全展開樹來計算機率的近似值解決這個問題。假設整顆樹最深為54層,而通常遊戲不太會展開到最底層才分出勝負。所以選擇只展開若干層(例如10層),而不完全展開。以下為各種Level數,在個人電腦上執行的時間分析:
CPU:AMD-K6-2 350MHZ
RAM:64MB
OS:Windows 98
Language:C++
時間:
LEVEL | 時間 |
2 | <<0.1秒 |
4 | <<0.1秒 |
6(*) | ≒1.5秒 |
8 | ≒70秒 |
10 | ≒40分鐘 |
表一 遊戲樹深度與花費時間比較
Level數都是偶數的理由是因為只有得牌時(五張)才可能達到勝利條件,而得牌與得牌之間還有一層是放牌(四張),所以必須以2層為單位累進。就人類的容忍度而言,可以忍受等待的時間為0~3秒,所以展開6層以下的Level數就時間上是較為合理。當然,樹狀展開的深度與機率的精確性有相當的關係,所以我們選擇以6層為基準,在可容忍的時間內做較精確的機率判斷。
問題:不完全展開樹的「迷路」問題
不完全展開樹所得到的值為近似值,然而有時候當手中的牌面複雜,無法在少數層中找到勝利點。假設目前已經被丟出的牌有【將象士車車兵兵兵帥俥卒卒卒卒】(包括電腦與人所放出的),這些資料電腦必須記住,才能判斷哪些棋子可拿;又假設電腦手中的牌為【象士馬包兵】,然而再與已知被放出的牌配合,要達到勝利條件最少必須要展開8層:
所以要在上述達到時間容忍度的6層之內,所有子樹的累積勝利機率都會得到0,無法做任何近似的判斷,若純粹依照機率大小來判斷下法,則程式可能會任意選擇其中一種實際上並不是最佳解的下法。假設一種情況如下頁圖四所示,灰色G點為勝利點,B 點為所選擇的下法。如圖,第一個勝利點在G,但是在Level 8,於是在A的下一步下法B、C、D、E的累積機率皆為0。若依照常理,選擇任何一種下法機率皆為相同(皆為0),所以選任何一種皆不吃虧。通常我們會選擇第一個下法B。但是實際上就整體機率而言,選擇C下法會比較有利,因為第一個勝利點G出現在C之下。於是選擇B似乎是不正確的,但是在展開符合時間限制的6層的條件下,我們無法得知這樣的訊息,而會走上不正確的路徑。於是便造成不完全展開樹「迷路」問題。
解決「迷路」問題—加入經驗法則
造成迷路問題的例子,在整個遊戲的所有牌面中並不是佔很大比例,但是為了讓所有的牌面都有正確的判斷,於是加入了經驗法則。利用一般常理來判斷該如何處理牌面。但是經驗法則適用的時機,是用在當所有下一步的下法機率皆相同時(例如上述機率皆為0時),而不是整個遊戲過程都用會用到。經驗是一種常理的判斷,但是沒有一個較完整依據來支持這樣的理論是完全正確,只能當作一種判斷的輔助。又這個遊戲的研究目的是為了利用電腦來計算機率,以克服人類經驗法則的不足,所以以機率計算為主,以經驗法則為輔。而經驗法則的做法是加入權數:
加入權數:首先先看勝利條件如下表一
甲部份(三張) | 乙部份(兩張) |
將、士、象 | 任兩張相同者,如車、車 (將配帥不算) |
車、馬、包 | |
卒、卒、卒 | |
帥、仕、相 | |
俥、傌、炮 | |
兵、兵、兵 |
表二 勝利條件表
依照勝利條件來看我們先把所有的棋子分成四個種類如下,權數越高者,保留下來的Priority越高:
種類 | 棋子 | 棋子個數 | 可配成甲部份 | 可配成乙部份 | 權數 |
A | 卒、兵 | 5支 | ü | ü | 4 |
B | 車、馬、包、俥、傌、炮 | 2支 | ü | ü | 3 |
C | 士、象、仕、相 | 2支 | ü | ü | 2 |
D | 將、帥 | 1支 | ü | | 1 |
表三 權數表
B與C的個數相同,而且都有機會配成勝利條件的甲部與乙部,但是因為C類要配成甲部份必須等待將或帥,但將、帥皆只有1支,相較於B皆為兩支,是處於劣勢,所以B比C高。利用上列權數表,當拿到一手牌,但是所有下一步下法的機率皆為0時,我們可以參考所有棋子的權數,將權數最低的棋子釋放,留下權數較高者,因此可以補「迷路」問題的缺點。
在此有一個概念要釐清:「迷路」問題的發生是在手中的牌很難得勝,Spanning Tree無法在少數層(在此為6層)內找到勝利,雖然勝利機率不高,但是不能否定其勝利機會。所以當這種情況發生時,我們利用經驗法則(權數)來推測放出哪一支棋子或吃不吃棋子是否較有利,以防止隨意的選擇而錯失了勝利的機會。而權數的判斷也並不一定是正確的判斷,是屬於「經驗上的猜測」性質,所以我們不能將經驗法則完全應用在整個遊戲的推理過程。這就是為什麼要以機率計算為主,經驗法則為輔的目的了。所幸這種問題發生的機會並不是很多,大部分的牌面利用6層的展開樹便綽綽有餘。
以上為象棋自摸遊戲的演算法整體發展過程,以Spanning Tree為整體概念,以Backtracking和機率累加為方法,輔以不完全展開樹和經驗權數,便架構出一套處理象棋自摸遊戲判斷遊戲下法的演算方法。
其他相關問題
1. 是否考慮欺騙隱瞞的行為?
假設我方為了不讓對方猜測到其手中的牌,會進行所謂的「欺騙隱瞞行為」。這種情形是否需要考慮?結論是不用。因為要進行欺騙隱瞞行為勢必不能進行最佳的下法。例如手中握有【將士馬兵兵】,最佳下法是放馬,聽象,但為了不讓對方猜到自己在收集【將士象】,而不放馬,反而放【將】或【士】;或者是手中握有【將士兵兵】,為了混淆對方視聽,讓對方誤以為自己要收集【車馬包】,而特地吃了一支【馬】。如此看來,欺騙隱瞞的行為對於勝利並沒有幫助,反而會壞了大局。所以我們不需要去考慮對方是否有欺騙隱瞞行為,而電腦也不用進行欺騙隱瞞行為。
2. 是否要從對方所放出或收回的牌去猜測對方手中的牌?
有時我們可以從對方的一些動作來猜測對方手中的牌,例如對方吃了【將】,便可以知道對方要收集【將士象】,所以盡量不要放【士】或【象】以免被胡牌;或者是對方放出【兵】,就可以得知,對方不收集【兵兵兵】或【兵兵】,所以可以大膽的放出【兵】因為絕對不會被胡牌。於是結論是猜測行為對自己有利。這些猜測可以在程式中加入獨立的判斷來實作,但是困難是在猜牌的動作非常散亂,如果真的要一項一項的情況獨立判斷,程式會變得散亂而無結構,並且也有可能會有一些遺漏的情形發生。目前也還沒有一套General的演算法來處理所有可能情況的猜測行為。又猜測行為所推測出的對方的牌面,並非絕對完全正確,例如對方吃了一支【車】,有可能是收集【車馬包】,若是,則盡量不要放【馬】和【包】;另一種可能是收集【車車】,若是這種情形,則對方不可能同時收集【車馬包】(因為車只有2支,所以不是收集【車車】,便是收集【車馬包】,兩者互斥。),所以不要放【車】,而【馬】與【包】可以大膽的下。然而單純的從對方吃【車】,無法判斷到底對方要收集【車車】,或是【車馬包】,於是便造成應對上的困惑,因為兩者的應對方式是完全不同的。又如果進行猜測行為去猜測對方的牌,要如何量化其手中的牌對電腦的行為的影響力,也是一項問題。所以針對「如何實作猜牌與如何利用猜牌」這個問題,還有待進一步研究。
結論與建議
一、象棋麻將沒有必勝的策略,原因是我方與對手「摸牌」的動作純粹屬於機率的問題,並不是玩家所能控制,並且我們不能得知其他對手手中的牌正確內容為何。所以棋局的變化無法完全掌握。舉一個最簡單而明顯的例子:假設我方是第二家,而第一家在最開始時便自摸。所以我們所努力的方向是利用前述的演算法,來指引電腦在Game Tree中,走向下方有最多勝利點的方向,以期望能有更多勝利的「機會」。
二、由於象棋麻將的棋子共32支,各家手中的牌為5~4支,其Game Tree的展開遠小於真正的麻將遊戲。如果將本演算法應用在真正的麻將遊戲中,還是相當的吃力。不過希望本演算法能夠帶給未來想破解麻將遊戲或其他類似遊戲的研究者一個基本的方向與幫助。
三、有時候適當的猜測對方的牌會幫助我們判斷更加正確。然而目前還沒有一個完整的策略來對「猜牌」這樣的動作做一個完整的處理。目前所能做的是發現對方吃下「將」(帥)時,表示對方可能在收集「將士象」(帥仕相),我們相對應的做法是儘可能的不要放出「士」(仕)和「象」(相)。然而是否有其他更正確的判斷以及如何計算所猜測的結果其勝利機率為何,目前並沒有一個較完善的方法來處理。
小編吃喝玩樂小編愛旅遊小編碎碎唸小編電影時間夯新聞老虎機星座瞎扯娛樂城真人百家樂彩票娛樂現金版博彩棋牌遊戲運動體育電子遊戲骰寶遊戲德州撲克輪盤遊戲優惠活動
0 Comments