標籤:
這是華為舉辦的一個軟體競賽,華為提供一個德州撲克台桌的server,我們要根據牌型等因素,給出出牌的策略,類似類比牌手的程式。從知道挑戰的題目到提交最終版本的程式中間只有一個月的時間,剛看到這個題目一點頭緒沒有,看了論文有用蒙特卡洛類比,決策樹等,各種沒聽過的詞彙,感覺寫出這個程式會很難,和我一個教研室的小夥伴們看到這個題目的時候陸續都放棄了。
思考了半天我也放棄了,因為接下來幾個星期還有實習的面試和小論文等著我完成,就這樣過了2個星期,期間我把小論文與實習面試都完成了,在一天早晨無意中又點開了比賽的頁面,我想試一試,最起碼調通一個與sever通訊的socket,說動手就動手,先選定程式設計語言,用的java,java我每天都會編一些小程式,所以用的比較熟練。中午就開始搭建環境,用的unbuntu的環境,以前用的都是VMware來搭建虛擬環境,這次華為指定用的是VirtualBox,很快第一個問題就來了,我用遠程登陸工具登陸linux的22連接埠怎麼也登不上去。期間上網查了各種原因,都是無用功,最後只有使用終極大招,將網路連接方式改為host only,但是這樣的方式linux虛擬機器就不能連網了。
接下來就要寫socket部分的軟體了,原以為這是最簡單的部分,但就是這部分我改了不下10遍。首先華為的server是用c++寫的,所以與java進行socket通訊的方式只能用位元組流的方式,很快利用最簡單的帶參數newsocket方法,就與server端建立了連結.問題1很快就來了,由於server端可能比client端啟動要慢所以當client端檢測不到遠端sever連接埠的時候,就直接跪了。解決的方法很簡單在client端加了個延時sleep方法,這個問題就解決了。問題2就是每次啟動完後一個client後,運行結束後,立即再運行就不可以,報出unconnect錯,這個問題搜了一下就解決掉了,原來是client端可能有連接埠複用的問題,於是將socket改為無參數newsocket的方法,然後將socket的setReuseAddress設為true,同時為了防止其他的情況,我寫了一個while迴圈,來判斷socket是否已經建立,如果沒有建立,每隔450毫秒就重建立立,這樣的socket就完美了。socket部分就這樣完成了,最起碼能夠與server穩定的建立串連收到註冊成功訊息。我將策略設為每次都fold,也就是無論什麼牌都棄牌,這樣除了大小盲要付出籌碼,其他的情況都不需要付出籌碼,所以全fold的情況最起碼能玩到200多局,這時候最詭異的錯誤出現了,每次到100多局就會出現unconnected。華為提供的server有個機制,就是每次詢問你的出牌策略後(notify訊息),如果10次沒有應答就會把你unconnect,然而我設定的是每次收到notify就發送fold訊息,怎麼會出錯呢。我把每次收到的訊息列印到本地檔案,才發現原來有的訊息發送了一般,有的把下一個訊息也發送了,原來這是tcp的通病,叫做粘包。很快我就找到瞭解決的思路,我把收到的訊息統一儲存到一個大字串裡,每次截取一個有用資訊,這樣就解決了粘包的問題。
在解決socket的同時還有一個問題,就是每次啟動我的本地程式,都是利用一個game的指令碼,由於對shell編程基本是只聞其名,所以看了幾小時的部落格,算是將就著把shell簡單瞭解下,其實我的shell指令碼只要實現一個功能,根據相對路徑找到Game.class啟動程式,原來linux的簡單指令可以直接用在shell指令碼中,cd到我的主程式目錄,啟動我的java程式,就這樣搞定了。順帶著把makefile檔案的指令碼編了,這樣每次只需使用makefile指令碼就可以完成編譯與編譯檔案的轉移了,省的每次都敲一大串javac指令。
完成搭建環境與socket部分已經過了3天了,接著就開始進入出牌策略的環節了,我自己建立了一個模型,包括撲克和對牌局的抽象。撲克的抽象主要是花色和大小,較為困難的是判斷牌型的問題。牌局的抽象也很複雜,由於之前根本就沒玩過德州撲克,也不知道要提取哪些資訊,於是下載了個天天德州,每天睡覺前都會晚上幾個小時,與此同時通過看別人的論文,知道了一些德州撲克的常用技巧,建完模型,還沒有寫出牌策略離提交代碼就只剩下不到4天了,而中還沒有調通過一次。我把出牌的策略分為兩個部分,一個是在拿到兩張手牌之前,這個階段的出牌策略較為簡單。一個是在拿到手牌之後,這個階段的出牌策略就比較複雜,由於時間緊湊,我沒有寫一些高端的機器學習等策略。就對一些情況做出判斷後,給出了不同的出牌策略。這部分也是很難調試,期間遇到各種NullPoint,各種摸不著頭腦的錯誤。但是每遇到一個困難,通過debug都能很好地找到出錯原因,java就是比c++好調試啊。
第一次將socket和策略部分聯調,竟然奇蹟般的成功,沒有一個bug。然後就與華為提供的葫蘆娃玩了幾局,所謂的葫蘆娃就是一群只會一個策略的無腦程式,然而就是簡單地無腦娃把我得程式各種花式吊打,最終花了一天的時間調試各種參數,總算是能玩過葫蘆娃了。最後的一段時間真的很緊迫,要不是組委會決定程式可以緩兩天交,我真的就沒法完成了。組後的幾Apsara Infrastructure Management Framework本都是一起床就開始搞,看遊戲log,調程式,改bug。某天在貼吧裡看到可以把程式發給某個官方的郵箱,就可以在實際的環境中與其他人對決。規則是這樣的,隨機抽取8人一個牌桌,前四名進如下一輪,我總共比了三次,每次都在round3,看來很穩定啊,謀事在人,成事在天。不可強也!坐等比賽的結果了。
過了大約一個星期出了結果,進入了32強。然而只有地區的4強才能進入決賽,從知道進入複賽到複賽開始只有一個星期的時間。複賽的內容不變,只是我們有了和別人比賽的log,可以作為自己制定策略的參考。複賽的水平肯定比預賽的要強不少,用我的初賽if else程式肯定是沒戲的,這時候我看到了一篇哈工大的碩士論文,用的是蒙特卡洛類比。於是仔細搜了一下這個東東,原來也沒有聽上去那麼高大上,就是盡量多的類比,來求出你獲勝的機率,類比的次數越多也就越接近真實的獲勝機率。就像在一個正方形中有一個內切圓,然後向其中丟黃豆,就可已根據掉在圓中的黃豆的比例求出圓的面積。很快我就把類比的程式寫好了,並且作為一個線程調用,這樣就可以開多個線程進行牌桌的類比了,當然線程數也不是越多越好,在我本機上測試4線程的類比效率最高。然而結果並不是我想象的那樣,每次都被我原來的程式吊打,綜合分析原因,還是類比次數太少,於是開始各種搜,終於在github上看到一個很牛逼的想法,只需32位int整數就可以儲存並判斷你的牌型,而且所有的牌型都已經存在數組裡,判斷牌型快的飛起,於是果斷的移植,類比次數瞬間增加了100倍。原來的程式也是各種被花式吊打,然而寫完這套程式離複賽只有一個晚上了,並沒有在實際環境中與別人比過,當要用這個新程式比賽,其實開始我心裡是拒絕的。第一:我的新程式要多線程類比,所以必須開一個守護線程式控制制類比時間,發回訊息的時間只有500ms,但是我要盡量多的時間去類比,所以實際環境中socket的發送,接收延時是我要考慮的因素,畢竟在我的機器上server與cliernt是在一起的,所以延時可以不作為考慮因素。第二:類比的時間可以從知道自己手牌開始,而不一定是從接收到notify資訊開始,但是這存線上程參數的傳遞與何時結束的問題,因為我知道手牌的時候並不知道牌桌上還有多少人,而我的類比要知道牌桌上剩的人數,所以在知道牌型的時候,要在不同的人數同時類比,同時在接到nontify訊息的時候找到人數符合的線程獲得相應的參數,最終我沒有找到好的解決方案。
複賽的結果沒給我驚喜,我還是被淘汰了,但是經過這一段時間的編程給我的協助真的很大,我獨立一個人完成了整個項目的建模,編寫,調試,測試。這是最讓我自豪的一件事。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
華為軟體精英挑戰賽【德州撲克】心得體會