基於android平台的斗地主AI


本軟件是基於android平台的斗地主AI,我們在源代碼的基礎之上,旨在改進AI的算法,使玩家具有更豐富的體驗感,讓NPC可以更為智能。

(一)玩法解析:

(1)發牌和叫牌:一副撲克54張,先為每個人發17張,剩下的3張作為底牌,玩家視自己手中的牌來確定自己是否叫牌。按順序叫牌,誰出的分多誰就是地主,一般分數有1分,2分,3分。地主的底牌需要給其他玩家看過后才能拿到手中,最后地主20張牌,農民分別17張牌。

(2)出牌:地主先出牌,按照逆時針順序依次進行,農民利用手中的牌組織地主繼續出牌,並和同伴配合(這種配合的默契程度,之后會在算法中體現)盡快出完手中的牌。當一手牌在另外兩家打不過的情況下,出牌的玩家繼續出牌。

(3)牌型以及大小:單牌大小順序為:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3,組合牌大小順序為:火箭最大,炸彈其次,大過任何的牌型。

如果是同種牌型,則比較其主牌作為單牌時的順序。下面是各種牌型的使用說明:

單牌:任何一張牌都是單牌,大小為:大王,小王,2,A,K,Q,J……

對牌:2張數值相同,花色不同的牌。

三張:3張數值相同,花色不同的牌。

單順:5張或5張以上,數值連續的牌,2和雙王不能列入其中。

雙順:3個或者3個以上數值連續的對牌。

三順:2個或2個以上數值連續的三張。

飛機:三順加數量相同的單牌或者對牌。

炸彈:4張數值相同的牌。

火箭:兩個王。

四帶二:炸彈+(兩張單牌或者對牌)。

(二)UI布局省略:

(三)代碼的詳細解析:

游戲的工程目錄如下:

 

整個游戲將近3000行代碼,共由10個類組成,以下先將10個類的具體作用在表格中展示出來,再具體分析每個類的具體功能:

DDZ:入口Activity類

MenuView:菜單類

GameView:游戲類

Person:游戲中的玩家,其中兩家是NPC(這里僅限於單機游戲,目的是要測試計算機的AI智商)

Desk:一個游戲桌位,存儲着三個玩家的共有信息,和當前游戲的進度控制

Card:每一手牌都是一個Card類的實例

Poke:洗牌,比較大小等操作

PokeType:定義了牌型的接口,里面定義了十二種牌型常量和兩種繪制牌的方向

AnalyzePoke:分析手中的所有牌,將各種牌型划分

(人工智能主要在Person類中NPC出牌的過程中展現)

UniqInt:一個存儲不同數值的輔助工具類

  1. Desk(桌子) ,Person(玩家),Card(一手牌)

本游戲是一個單機游戲,所以桌子(Desk)只有一個,桌子上有3個玩家(Person),其中兩個是NPC玩家,一個是用戶玩家,每一個玩家都有一把牌,17張或者20張,每一把牌都可以分成很多手牌(Card)。

上面已經分析了Desk,Person,Card之間的關系,下面逐個逐個進行解析。

Desk是桌子類,它持有3個玩家的實例對象,負責為3個玩家分牌,控制出牌的順序流程,當前分數的倍數等全局性信息。

Desk類地成員變量:

 

復制代碼
 1 public static int threePokes[] = new int[3];// 三張底牌
 2 
 3 private int threePokesPos[][] = new int[][] { { 170, 17 }, { 220, 17 },
 4 
 5 { 270, 17 } };
 6 
 7 private int[][] rolePos = { { 60, 310 }, { 63, 19 }, { 396, 19 }, };
 8 
 9 public static Person[] persons = new Person[3];// 三個玩家
10 
11 public static int[] deskPokes = new int[54];// 一副撲克牌
12 
13 public static int currentScore = 3;// 當前分數
14 
15 public static int boss = 0;// 地主
16 
17 /**
18 
19 * -2:發牌<br>
20 
21 * -1:隨機地主<br>
22 
23 * 0:游戲中 <br>
24 
25 * 1:游戲結束,重新來,活退出<br>
26 
27 */
28 
29 private int op = -1;// 游戲的進度控制
30 
31 public static int currentPerson = 0;// 當前操作的人
32 
33 public static int currentCircle = 0;// 本輪次數
34 
35 public static Card currentCard = null;// 最新的一手牌
36 
37 控制游戲邏輯的代碼:
38 
39 public void gameLogic() {
40 
41 switch (op) {
42 
43 case -2:
44 
45 break;
46 
47 case -1:
48 
49 init();
50 
51 op = 0;
52 
53 break;
54 
55 case 0:
56 
57 gaming();
58 
59 break;
60 
61 case 1:
62 
63 break;
64 
65 case 2:
66 
67 break;
68 
69 }
70 
71 }
復制代碼

關於發牌的代碼:

 

復制代碼
  1 public void fenpai(int[] pokes) {
  2 
  3 for (int i = 0; i < 51;) {
  4 
  5 personPokes[i / 17][i % 17] = pokes[i++];
  6 
  7 }
  8 
  9 threePokes[0] = pokes[51];
 10 
 11 threePokes[1] = pokes[52];
 12 
 13 threePokes[2] = pokes[53];
 14 
 15 }
 16 
 17 personPokes是一個二維數組,存三堆17張的牌,threePokes存放最后三張。
 18 
 19 Desk類中隨機生成地主的代碼:
 20 
 21 // 隨機地主,將三張底牌給地主
 22 
 23 private void randDZ() {
 24 
 25 boss = Poke.getDZ();
 26 
 27 currentPerson = boss;
 28 
 29 int[] newPersonPokes = new int[20];
 30 
 31 for (int i = 0; i < 17; i++) {
 32 
 33 newPersonPokes[i] = personPokes[boss][i];
 34 
 35 }
 36 
 37 newPersonPokes[17] = threePokes[0];
 38 
 39 newPersonPokes[18] = threePokes[1];
 40 
 41 newPersonPokes[19] = threePokes[2];
 42 
 43 personPokes[boss] = newPersonPokes;
 44 
 45 }
 46 
 47 利用getDZ()方法隨機找到地主,然后,將當前人設為地主,newPersonPokes數組存放地主的牌。
 48 
 49 Desk類在游戲進行中的邏輯方法如下:
 50 
 51 // 存儲當前一句的勝負得分信息
 52 
 53 int rs[] = new int[3];
 54 
 55 private void gaming() {
 56 
 57 for (int k = 0; k < 3; k++) {
 58 
 59 // 當三個人中其中一個人牌的數量為0,則游戲結束
 60 
 61 if (persons[k].pokes.length == 0) {
 62 
 63 // 切換到游戲結束狀態
 64 
 65 op = 1;
 66 
 67 // 得到最先出去的人的id
 68 
 69 winId = k;
 70 
 71 // 判斷哪方獲勝
 72 
 73 if (boss == winId) {
 74 
 75 // 地主方獲勝后的積分操作
 76 
 77 for (int i = 0; i < 3; i++) {
 78 
 79 if (i == boss) {
 80 
 81 // 地主需要加兩倍積分
 82 
 83 rs[i] = currentScore * 2;
 84 
 85 personScore[i] += currentScore * 2;
 86 
 87 } else {
 88 
 89 // 農民方需要減分
 90 
 91 rs[i] = -currentScore;
 92 
 93 personScore[i] -= currentScore;
 94 
 95 }
 96 
 97 }
 98 
 99 } else {
100 
101 // 如果農民方勝利
102 
103 for (int i = 0; i < 3; i++) {
104 
105 if (i != boss) {
106 
107 // 農民方加分
108 
109 rs[i] = currentScore;
110 
111 personScore[i] += currentScore;
112 
113 } else {
114 
115 // 地主方減分
116 
117 rs[i] = -currentScore * 2;
118 
119 personScore[i] -= currentScore * 2;
120 
121 }
122 
123 }
124 
125 }
126 
127 return;
128 
129 }
130 
131 }
132 
133 // 游戲沒有結束,繼續。
134 
135 // 如果本家ID是NPC,則執行語句中的操作
136 
137 if (currentPerson == 1 || currentPerson == 2) {
138 
139 if (timeLimite <= 300) {
140 
141 // 獲取手中的牌中能夠打過當前手牌
142 
143 Card tempcard = persons[currentPerson].chupaiAI(currentCard);
144 
145 if (tempcard != null) {
146 
147 // 手中有大過的牌,則出
148 
149 currentCircle++;
150 
151 currentCard = tempcard;
152 
153 nextPerson();
154 
155 } else {
156 
157 // 沒有打過的牌,則不要
158 
159 buyao();
160 
161 }
162 
163 }
164 
165 }
166 
167 // 時間倒計時
168 
169 timeLimite -= 2;
170 
171 }
復制代碼

這里調用了一個出牌人的AI函數,對於timeLimite的理解,感覺是讓機器再多算一會兒,控制回溯的深度?這里有待考慮。

如果NPC沒有大牌了,就選擇不要牌,如下是對於不要牌的操作:

 

復制代碼
 1 //不要牌的操作
 2 
 3 private void buyao() {
 4 
 5 // 輪到下一個人
 6 
 7 currentCircle++;
 8 
 9 // 清空當前不要牌的人的最后一手牌
10 
11 persons[currentPerson].card = null;
12 
13 // 定位下一個人的id
14 
15 nextPerson();
16 
17 // 如果已經轉回來,則該人繼續出牌,本輪清空,新一輪開始
18 
19 if (currentCard != null && currentPerson == currentCard.personID) {
20 
21 currentCircle = 0;
22 
23 currentCard = null;// 轉回到最大牌的那個人再出牌
24 
25 persons[currentPerson].card = null;
26 
27 }
28 
29 }
30 
31 // 定位下一個人的id並重新倒計時
32 
33 private void nextPerson() {
34 
35 switch (currentPerson) {
36 
37 case 0:
38 
39 currentPerson = 2;
40 
41 break;
42 
43 case 1:
44 
45 currentPerson = 0;
46 
47 break;
48 
49 case 2:
50 
51 currentPerson = 1;
52 
53 break;
54 
55 }
56 
57 timeLimite = 310;
58 
59 }
60 
61 }
復制代碼

nextPerson()函數用於定位下一個出牌的人(對於不要之后的輪轉函數),而對於buyao()函數,這里有待進一步理解。

(如下的代碼和UI有關,這里就不列出了)

對於Person類自己的一些見解:

 

復制代碼
  1 // 玩家手中的牌
  2 
  3 int[] pokes;
  4 
  5 // 玩家選中牌的標志
  6 
  7 boolean[] pokesFlag;
  8 
  9 // 玩家所在桌面上的坐標
 10 
 11 int top, left;
 12 
 13 // 玩家ID
 14 
 15 int id;
 16 
 17 // 玩家所在桌子的實例
 18 
 19 Desk desk;
 20 
 21 // 玩家最近一手牌
 22 
 23 Card card;
 24 
 25 DDZ ddz;
 26 
 27 以上為Person類的一些成員變量
 28 
 29 同樣略去“美工代碼”,Person類中NPC出牌的人工智能代碼如下:
 30 
 31 // 判斷出牌的人工智能
 32 
 33 public Card chupaiAI(Card card) {
 34 
 35 int[] pokeWanted = null;
 36 
 37 if (card == null) {
 38 
 39 // 玩家隨意出一手牌
 40 
 41 pokeWanted = Poke.outCardByItsself(pokes, last, next);
 42 
 43 } else {
 44 
 45 // 玩家需要出一手比card大的牌
 46 
 47 pokeWanted = Poke.findTheRightCard(card, pokes, last, next);
 48 
 49 }
 50 
 51 // 如果不能出牌,則返回
 52 
 53 if (pokeWanted == null) {
 54 
 55 return null;
 56 
 57 }
 58 
 59 // 以下為出牌的后續操作,將牌從玩家手中剔除
 60 
 61 int num = 0;
 62 
 63 for (int i = 0; i < pokeWanted.length; i++) {
 64 
 65 for (int j = 0; j < pokes.length; j++) {
 66 
 67 if (pokes[j] == pokeWanted[i]) {
 68 
 69 pokes[j] = -1;
 70 
 71 num++;
 72 
 73 break;
 74 
 75 }
 76 
 77 }
 78 
 79 }
 80 
 81 int[] newpokes = new int[0];
 82 
 83 if (pokes.length - pokeWanted.length > 0) {
 84 
 85 newpokes = new int[pokes.length - pokeWanted.length];
 86 
 87 }
 88 
 89 int j = 0;
 90 
 91 for (int i = 0; i < pokes.length; i++) {
 92 
 93 if (pokes[i] != -1) {
 94 
 95 newpokes[j] = pokes[i];
 96 
 97 j++;
 98 
 99 }
100 
101 }
102 
103 this.pokes = newpokes;
104 
105 Card thiscard = new Card(pokeWanted, pokeImage, id);
106 
107 // 更新桌子最近一手牌
108 
109 desk.currentCard = thiscard;
110 
111 this.card = thiscard;
112 
113 return thiscard;
114 
115 }
復制代碼

這里,AI考慮當玩家沒有任何“威脅”的情況下,任意出了一手牌,其實按照習慣,應該出一手玩家認為在當前時刻最為迫切出的牌,所以,這里的AI有待補完。

PokeType類,也就是其中的對於牌型的定義:

 

復制代碼
 1 package com.peiandsky;
 2 
 3 public interface PokeType {
 4 
 5 int danpai=1;
 6 
 7 int duipai=2;
 8 
 9 int sanzhang=3;
10 
11 int sandaiyi=4;
12 
13 int danshun=5;
14 
15 int shuangshun=6;
16 
17 int sanshun=7;
18 
19 int feiji=8;
20 
21 int sidaier=9;
22 
23 int zhadan=10;
24 
25 int huojian=11;
26 
27 int error=12;//錯誤類型
28 
29 int dirH=0;//繪制方向為橫向
30 
31 int dirV=1;//繪制方向為豎向
32 
33 }
復制代碼

Poke獲取牌型信息:

Poke定義了一些關於撲克牌的核心操作,例如洗牌,獲取牌型,出牌等。作為一個關於撲克操作的工具欄,Poke中的所有方法全部是靜態方法,可以直接通過類名調用,不實例化Poke類。

關於洗牌的操作,實際上,根據經驗可知,在物理上,洗牌六次可以達到最大的混亂度,但是,這里采用了一個隨機算法,也就是讓54張牌分別和隨機生成的牌發生交換,這種方法不置可否,應該沒有常規洗牌那么“隨機性”,不過,作為置亂,也已經足夠隨機了。

 

復制代碼
  1 // 0-53表示54張牌
  2 
  3 public static void shuffle(int[] pokes) {
  4 
  5 int len = pokes.length;
  6 
  7 // 對於54張牌中的任何一張,都隨機找一張和它互換,將牌順序打亂。
  8 
  9 for (int l = 0; l < len; l++) {
 10 
 11 int des = rand.nextInt(54);
 12 
 13 int temp = pokes[l];
 14 
 15 pokes[l] = pokes[des];
 16 
 17 pokes[des] = temp;
 18 
 19 }
 20 
 21 }
 22 
 23 利用冒泡排序方法,對pokes進行從大到小的排序:
 24 
 25 // 對pokes進行從大到小排序,采用冒泡排序
 26 
 27 public static void sort(int[] pokes) {
 28 
 29 for (int i = 0; i < pokes.length; i++) {
 30 
 31 for (int j = i + 1; j < pokes.length; j++) {
 32 
 33 if (pokes[i] < pokes[j]) {
 34 
 35 int temp = pokes[i];
 36 
 37 pokes[i] = pokes[j];
 38 
 39 pokes[j] = temp;
 40 
 41 }
 42 
 43 }
 44 
 45 }
 46 
 47 }
 48 
 49 給出一張牌的索引值,可以返回它真實的可以比較大小的值(稱為getPokeValue)
 50 
 51 /**
 52 
 53 * 16小王,17大王
 54 
 55 */
 56 
 57 public static int getPokeValue(int poke) {
 58 
 59 // 當撲克值為52時,是小王
 60 
 61 if (poke == 52) {
 62 
 63 return 16;
 64 
 65 }
 66 
 67 // 當撲克值為53時,是大王
 68 
 69 if (poke == 53) {
 70 
 71 return 17;
 72 
 73 }
 74 
 75 // 其它情況下返回相應的值(3,4,5,6,7,8,9,10,11(J),12(Q),13(K),14(A),15(2))
 76 
 77 return poke / 4 + 3;
 78 
 79 }
 80 
 81 判斷牌型的辦法:
 82 
 83 首先是兩個輔助函數:
 84 
 85 (1)統計一手牌中同值的牌出現的次數:
 86 
 87 // 統計一手牌中同值的牌出現的次數來判斷是對牌,三順,三帶一,炸彈,四代二等
 88 
 89 public static int getPokeCount(int[] pokes, int poke) {
 90 
 91 int count = 0;
 92 
 93 for (int i = 0; i < pokes.length; i++) {
 94 
 95 if (getPokeValue(pokes[i]) == getPokeValue(poke)) {
 96 
 97 count++;
 98 
 99 }
100 
101 }
102 
103 return count;
104 
105 }
106 
107 (2)判斷一組牌的值是不是連續的:
108 
109 /**
110 
111 * 判斷是不是順子
112 
113 *
114 
115 * @param pokes
116 
117 * @return
118 
119 */
120 
121 public static boolean shunzi(int[] pokes) {
122 
123 int start = getPokeValue(pokes[0]);
124 
125 // 順子中不能包含2,king
126 
127 if (start >= 15) {
128 
129 return false;
130 
131 }
132 
133 int next;
134 
135 for (int i = 1; i < pokes.length; i++) {
136 
137 next = getPokeValue(pokes[i]);
138 
139 if (start - next != 1) {
140 
141 return false;
142 
143 }//如果有一個不等於1的都是不行的
144 
145 start = next;
146 
147 }
148 
149 return true;
150 
151 }
152 
153 利用這兩個輔助函數(或者稱為方法),可以適當判斷牌型:
154 
155 /**
156 
157 * pokes中的牌的順序要按照牌的值排列,順牌中不包含2
158 
159 *
160 
161 * @param pokes
162 
163 * @return
164 
165 */
166 
167 public static int getPokeType(int[] pokes) {
168 
169 int len = pokes.length;
170 
171 // 當牌數量為1時,單牌
172 
173 if (len == 1) {
174 
175 return PokeType.danpai;
176 
177 }
178 
179 // 當牌數量為2時,可能是對牌和火箭
180 
181 if (len == 2) {
182 
183 if (pokes[0] == 53 && pokes[1] == 52) {
184 
185 return PokeType.huojian;
186 
187 }
188 
189 if (getPokeValue(pokes[0]) == getPokeValue(pokes[1])) {
190 
191 return PokeType.duipai;
192 
193 }
194 
195 }
196 
197 // 當牌數為3時,只可能是三順
198 
199 if (len == 3) {
200 
201 if (getPokeValue(pokes[0]) == getPokeValue(pokes[1])
202 
203 && getPokeValue(pokes[2]) == getPokeValue(pokes[1])) {
204 
205 return PokeType.sanzhang;
206 
207 }
208 
209 }
210 
211 // 當牌數為4時,可能是三帶一或炸彈
212 
213 if (len == 4) {
214 
215 int firstCount = getPokeCount(pokes, pokes[0]);
216 
217 if (firstCount == 3 || getPokeCount(pokes, pokes[1]) == 3) {
218 
219 return PokeType.sandaiyi;
220 
221 }
222 
223 if (firstCount == 4) {
224 
225 return PokeType.zhadan;
226 
227 }
228 
229 }
230 
231 // 當牌數大於5時,判斷是不是單順
232 
233 if (len >= 5) {
234 
235 if (shunzi(pokes)) {
236 
237 return PokeType.danshun;
238 
239 }
240 
241 }
242 
243 // 當牌數為6時,四帶二
244 
245 if (len == 6) {
246 
247 boolean have4 = false;
248 
249 boolean have1 = false;
250 
251 for (int i = 0; i < len; i++) {
252 
253 int c = getPokeCount(pokes, pokes[i]);
254 
255 if (c == 4) {
256 
257 have4 = true;
258 
259 }
260 
261 if (c == 1) {
262 
263 have1 = true;
264 
265 }
266 
267 }
268 
269 if (have4 && have1) {
270 
271 return PokeType.sidaier;
272 
273 }
274 
275 }
276 
277 // 當牌數大於等於6時,先檢測是不是雙順和三順
278 
279 if (len >= 6) {
280 
281 // 雙順
282 
283 boolean shuangshunflag = true;
284 
285 for (int i = 0; i < len; i++) {
286 
287 if (getPokeCount(pokes, pokes[i]) != 2) {
288 
289 shuangshunflag = false;
290 
291 break;
292 
293 }
294 
295 }
296 
297 if (shuangshunflag) {
298 
299 int[] tempPokes = new int[len / 2];
300 
301 for (int i = 0; i < len / 2; i++) {
302 
303 tempPokes[i] = pokes[i * 2];
304 
305 }
306 
307 if (shunzi(tempPokes)) {
308 
309 return PokeType.shuangshun;
310 
311 }
312 
313 }
314 
315 System.out.println("shuangshun:" + shuangshunflag);
316 
317 // 三順
318 
319 boolean sanshunflag = true;
320 
321 for (int i = 0; i < len; i++) {
322 
323 if (getPokeCount(pokes, pokes[i]) != 3) {
324 
325 sanshunflag = false;
326 
327 break;
328 
329 }
330 
331 }
332 
333 if (sanshunflag) {
334 
335 int[] tempPokes = new int[len / 3];
336 
337 for (int i = 0; i < len / 3; i++) {
338 
339 tempPokes[i] = pokes[i * 3];
340 
341 }
342 
343 if (shunzi(tempPokes)) {
344 
345 return PokeType.sanshun;
346 
347 }
348 
349 }
350 
351 }
352 
353 // 當牌數大於等於8,且能夠被4整除時,判斷是不是飛機
354 
355 if (len >= 8 && len % 4 == 0) {
356 
357 UniqInt ui = new UniqInt();
358 
359 int have1 = 0;
360 
361 for (int i = 0; i < pokes.length; i++) {
362 
363 int c = getPokeCount(pokes, pokes[i]);
364 
365 if (c == 3) {
366 
367 ui.addInt(pokes[i]);
368 
369 } else if (c == 1) {
370 
371 have1++;
372 
373 }
374 
375 }
376 
377 if (ui.size() == have1) {
378 
379 int[] tempArray = ui.getArray();
380 
381 sort(tempArray);
382 
383 if (shunzi(tempArray)) {
384 
385 return PokeType.feiji;
386 
387 }
388 
389 }
390 
391 }
392 
393 // 如果不是可知牌型,返回錯誤型
394 
395 return PokeType.error;
396 
397 }
398 
399 //對於牌型的解析,有點類似於編譯原理中的詞法分析,但是,對於每種牌型解析的方法都不太一樣,總是,一步一步走即可(AI的過程則有點類似於語法分析的過程)
400 
401 以下列出判斷牌型的有限自動機:
復制代碼

 

  

獲取牌型之后,通過判斷兩手牌的大小,需要知道一手牌的牌值:


復制代碼
  1 // 通過給給出的一手牌,來返回它的牌值大小,pokes中的順序是排列好的
  2 
  3 public static int getPokeTypeValue(int[] pokes, int pokeType) {
  4 
  5 // 這幾種類型直接返回第一個值
  6 
  7 if (pokeType == PokeType.danpai || pokeType == PokeType.duipai
  8 
  9 || pokeType == PokeType.danshun || pokeType == PokeType.sanshun
 10 
 11 || pokeType == PokeType.shuangshun
 12 
 13 || pokeType == PokeType.sanzhang || pokeType == PokeType.zhadan) {
 14 
 15 return getPokeValue(pokes[0]);
 16 
 17 }
 18 
 19 // 三帶一和飛機返回數量為3的牌的最大牌值
 20 
 21 if (pokeType == PokeType.sandaiyi || pokeType == PokeType.feiji) {
 22 
 23 for (int i = 0; i <= pokes.length - 3; i++) {
 24 
 25 if (getPokeValue(pokes[i]) == getPokeValue(pokes[i + 1])
 26 
 27 && getPokeValue(pokes[i + 1]) == getPokeValue(pokes[i + 2])) {
 28 
 29 return getPokeValue(pokes[i]);
 30 
 31 }
 32 
 33 }
 34 
 35 }
 36 
 37 // 四帶二返回數量為4的牌值
 38 
 39 if (pokeType == PokeType.sidaier) {
 40 
 41 for (int i = 0; i < pokes.length - 3; i++) {
 42 
 43 if (getPokeValue(pokes[i]) == getPokeValue(pokes[i + 1])
 44 
 45 && getPokeValue(pokes[i + 1]) == getPokeValue(pokes[i + 2])
 46 
 47 && getPokeValue(pokes[i + 2]) == getPokeValue(pokes[i + 3])) {
 48 
 49 return getPokeValue(pokes[i]);
 50 
 51 }
 52 
 53 }
 54 
 55 }
 56 
 57 return 0;
 58 
 59 }
 60 
 61 三帶一,飛機,以及四帶二,只能通過其主鍵值(key-value)來作為其關鍵牌來比較大小。
 62 
 63 下面,我們就可以比較兩手牌的大小了!
 64 
 65 /**
 66 
 67 * true 第一個大
 68 
 69 *
 70 
 71 * @param f
 72 
 73 * @param s
 74 
 75 * @return
 76 
 77 */
 78 
 79 public static boolean compare(Card f, Card s) {
 80 
 81 // 當兩種牌型相同時
 82 
 83 if (f.pokeType == s.pokeType) {
 84 
 85 // 兩手牌牌型相同時,數量不同將無法比較,默認為第二個大,使s不能出牌
 86 
 87 if (f.pokes.length != s.pokes.length)
 88 
 89 return false;
 90 
 91 // 牌型相同,數量相同時,比較牌值
 92 
 93 return f.value > s.value;
 94 
 95 }
 96 
 97 // 在牌型不同的時候,如果f的牌型是火箭,則返回true
 98 
 99 if (f.pokeType == PokeType.huojian) {
100 
101 return true;
102 
103 }
104 
105 if (s.pokeType == PokeType.huojian) {
106 
107 return false;
108 
109 }
110 
111 // 排除火箭的類型,炸彈最大
112 
113 if (f.pokeType == PokeType.zhadan) {
114 
115 return true;
116 
117 }
118 
119 if (s.pokeType == PokeType.zhadan) {
120 
121 return false;
122 
123 }
124 
125 // 無法比較的情況,默認為s大於f
126 
127 return false;
128 
129 }
復制代碼

這里只對於“絕對優勢”的牌做出了比較,比如:火箭和炸彈。

下 面繼續人工智能,Poke類中給出了這樣一個方法,可以直接從牌中選中能夠打過card值得牌,如果沒有則返回false;(其中運用了C++--STL 中的vector作為存儲容器,這里還有一個小亮點,利用must值來判斷緊迫性,如果neesZd為true,則需要炸彈了配合,也就是到了一些關鍵關 頭,另外,在選擇拆牌的時候,考慮到“火箭”和“4個2”的牌是擁有絕對實力的,這樣的牌都不能拆開。在檢查炸彈的時候,也根據緊迫性幾率出牌,如果下家 是和自己一伙的,則順延給下家)


復制代碼
  1 // 從pokes數組中找到比card大的一手牌
  2 
  3 public static int[] findBigThanCardSimple2(Card card, int pokes[], int must) {
  4 
  5 try {
  6 
  7 // 獲取card的信息,牌值,牌型
  8 
  9 int[] cardPokes = card.pokes;
 10 
 11 int cardValue = card.value;
 12 
 13 int cardType = card.pokeType;
 14 
 15 int cardLength = cardPokes.length;
 16 
 17 // 使用AnalyzePoke來對牌進行分析
 18 
 19 AnalyzePoke analyz = AnalyzePoke.getInstance();
 20 
 21 analyz.setPokes(pokes);
 22 
 23 Vector<int[]> temp;
 24 
 25 int size = 0;
 26 
 27 // 根據適當牌型選取適當牌
 28 
 29 switch (cardType) {
 30 
 31 case PokeType.danpai:
 32 
 33 temp = analyz.getCard_danpai();
 34 
 35 size = temp.size();
 36 
 37 for (int i = 0; i < size; i++) {
 38 
 39 int[] cardArray = temp.get(i);
 40 
 41 int v = Poke.getPokeValue(cardArray[0]);
 42 
 43 if (v > cardValue) {
 44 
 45 return cardArray;
 46 
 47 }
 48 
 49 }
 50 
 51 // 如果單牌中沒有,則選擇現有牌型中除火箭和4個2后的最大一個
 52 
 53 int st = 0;
 54 
 55 if (analyz.getCountWang() == 2) {
 56 
 57 st += 2;
 58 
 59 }
 60 
 61 if (analyz.getCount2() == 4) {
 62 
 63 st += 4;
 64 
 65 }
 66 
 67 if (Poke.getPokeValue(pokes[st]) > cardValue)
 68 
 69 return new int[] { pokes[st] };
 70 
 71 // 檢查炸彈,根據緊迫性幾率出牌,如果下家是和自己一伙的則順延給下家
 72 
 73 break;
 74 
 75 case PokeType.duipai:
 76 
 77 temp = analyz.getCard_duipai();
 78 
 79 size = temp.size();
 80 
 81 for (int i = 0; i < size; i++) {
 82 
 83 int[] cardArray = temp.get(i);
 84 
 85 int v = Poke.getPokeValue(cardArray[0]);
 86 
 87 if (v > cardValue) {
 88 
 89 return cardArray;
 90 
 91 }
 92 
 93 }
 94 
 95 // 如果對子中沒有,則需要檢查雙順
 96 
 97 temp = analyz.getCard_shuangshun();
 98 
 99 size = temp.size();
100 
101 for (int i = 0; i < size; i++) {
102 
103 int[] cardArray = temp.get(i);
104 
105 for (int j = cardArray.length - 1; j > 0; j--) {
106 
107 int v = Poke.getPokeValue(cardArray[j]);
108 
109 if (v > cardValue) {
110 
111 return new int[] { cardArray[j], cardArray[j - 1] };
112 
113 }
114 
115 }
116 
117 }
118 
119 // 如果雙順中沒有,則需要檢查三張
120 
121 temp = analyz.getCard_sanzhang();
122 
123 size = temp.size();
124 
125 for (int i = 0; i < size; i++) {
126 
127 int[] cardArray = temp.get(i);
128 
129 int v = Poke.getPokeValue(cardArray[0]);
130 
131 if (v > cardValue) {
132 
133 return new int[] { cardArray[0], cardArray[1] };
134 
135 }
136 
137 }
138 
139 // 如果三張中沒有,則就考慮炸彈,下家也可以順牌
140 
141 break;
142 
143 case PokeType.sanzhang:
144 
145 temp = analyz.getCard_sanzhang();
146 
147 size = temp.size();
148 
149 for (int i = 0; i < size; i++) {
150 
151 int[] cardArray = temp.get(i);
152 
153 int v = Poke.getPokeValue(cardArray[0]);
154 
155 if (v > cardValue) {
156 
157 return cardArray;
158 
159 }
160 
161 }
162 
163 break;
164 
165 case PokeType.sandaiyi:
166 
167 if (pokes.length < 4) {
168 
169 break;
170 
171 }
172 
173 boolean find = false;
174 
175 int[] sandaiyi = new int[4];
176 
177 temp = analyz.getCard_sanzhang();
178 
179 size = temp.size();
180 
181 for (int i = 0; i < size; i++) {
182 
183 int[] cardArray = temp.get(i);
184 
185 int v = Poke.getPokeValue(cardArray[0]);
186 
187 if (v > cardValue) {
188 
189 for (int j = 0; j < cardArray.length; j++) {
190 
191 sandaiyi[j] = cardArray[j];
192 
193 find = true;
194 
195 }
196 
197 }
198 
199 }
200 
201 // 沒有三張滿足條件
202 
203 if (!find) {
204 
205 break;
206 
207 }
208 
209 // 再找一張組合成三帶一
210 
211 temp = analyz.getCard_danpai();
212 
213 size = temp.size();
214 
215 if (size > 0) {
216 
217 int[] t = temp.get(0);
218 
219 sandaiyi[3] = t[0];
220 
221 } else {
222 
223 temp = analyz.getCard_danshun();
224 
225 size = temp.size();
226 
227 for (int i = 0; i < size; i++) {
228 
229 int[] danshun = temp.get(i);
230 
231 if (danshun.length >= 6) {
232 
233 sandaiyi[3] = danshun[0];
234 
235 }
236 
237 }
238 
239 }
240 
241 // 從中隨便找一個最小的
242 
243 if (sandaiyi[3] == 0) {
244 
245 for (int i = pokes.length - 1; i >= 0; i--) {
246 
247 if (Poke.getPokeValue(pokes[i]) != Poke
248 
249 .getPokeValue(sandaiyi[0])) {
250 
251 sandaiyi[3] = pokes[i];
252 
253 }
254 
255 }
256 
257 }
258 
259 if (sandaiyi[3] != 0) {
260 
261 Poke.sort(sandaiyi);
262 
263 return sandaiyi;
264 
265 }
266 
267 break;
268 
269 case PokeType.danshun:// 還值得優化
270 
271 temp = analyz.getCard_danshun();
272 
273 size = temp.size();
274 
275 for (int i = 0; i < size; i++) {
276 
277 int[] danshun = temp.get(i);
278 
279 if (danshun.length == cardLength) {
280 
281 if (cardValue < Poke.getPokeValue(danshun[0])) {
282 
283 return danshun;
284 
285 }
286 
287 }
288 
289 }
290 
291 for (int i = 0; i < size; i++) {
292 
293 int[] danshun = temp.get(i);
294 
295 if (danshun.length > cardLength) {
296 
297 if (danshun.length < cardLength
298 
299 || danshun.length - cardLength >= 3) {
300 
301 if (rand.nextInt(100) < must) {
302 
303 if (cardValue >= Poke.getPokeValue(danshun[0])) {
304 
305 continue;
306 
307 }
308 
309 int index = 0;
310 
311 for (int k = 0; k < danshun.length; k++) {
312 
313 if (cardValue < Poke
314 
315 .getPokeValue(danshun[k])) {
316 
317 index = k;
318 
319 } else {
320 
321 break;
322 
323 }
324 
325 }
326 
327 if (index + cardLength > danshun.length) {
328 
329 index = danshun.length - cardLength;
330 
331 }
332 
333 int[] newArray = new int[cardLength];
334 
335 int n = 0;
336 
337 for (int m = index; m < danshun.length; m++) {
338 
339 newArray[n++] = danshun[m];
340 
341 }
342 
343 return newArray;
344 
345 }
346 
347 break;
348 
349 }
350 
351 if (cardValue >= Poke.getPokeValue(danshun[0])) {
352 
353 continue;
354 
355 }
356 
357 int start = 0;
358 
359 int end = 0;
360 
361 if (danshun.length - cardLength == 1) {
362 
363 if (cardValue < Poke.getPokeValue(danshun[1])) {
364 
365 start = 1;
366 
367 } else {
368 
369 start = 0;
370 
371 }
372 
373 } else if (danshun.length - cardLength == 2) {
374 
375 if (cardValue < Poke.getPokeValue(danshun[2])) {
376 
377 start = 2;
378 
379 } else if (cardValue < Poke
380 
381 .getPokeValue(danshun[1])) {
382 
383 start = 1;
384 
385 } else {
386 
387 start = 0;
388 
389 }
390 
391 }
392 
393 int[] dan = new int[cardLength];
394 
395 int m = 0;
396 
397 for (int k = start; k < danshun.length; k++) {
398 
399 dan[m++] = danshun[k];
400 
401 }
402 
403 return dan;
404 
405 }
406 
407 }
408 
409 break;
410 
411 case PokeType.shuangshun:
412 
413 temp = analyz.getCard_shuangshun();
414 
415 size = temp.size();
416 
417 for (int i = size - 1; i >= 0; i--) {
418 
419 int cardArray[] = temp.get(i);
420 
421 if (cardArray.length < cardLength) {
422 
423 continue;
424 
425 }
426 
427 if (cardValue < Poke.getPokeValue(cardArray[0])) {
428 
429 if (cardArray.length == cardLength) {
430 
431 return cardArray;
432 
433 } else {
434 
435 int d = (cardArray.length - cardLength) / 2;
436 
437 int index = 0;
438 
439 for (int j = cardArray.length - 1; j >= 0; j--) {
440 
441 if (cardValue < Poke.getPokeValue(cardArray[j])) {
442 
443 index = j / 2;
444 
445 break;
446 
447 }
448 
449 }
450 
451 int total = cardArray.length / 2;
452 
453 int cardTotal = cardLength / 2;
454 
455 if (index + cardTotal > total) {
456 
457 index = total - cardTotal;
458 
459 }
460 
461 int shuangshun[] = new int[cardLength];
462 
463 int m = 0;
464 
465 for (int k = index * 2; k < cardArray.length; k++) {
466 
467 shuangshun[m++] = cardArray[k];
468 
469 }
470 
471 return shuangshun;
472 
473 }
474 
475 }
476 
477 }
478 
479 break;
480 
481 case PokeType.sanshun:
482 
483 temp = analyz.getCard_sanshun();
484 
485 size = temp.size();
486 
487 for (int i = size - 1; i >= 0; i--) {
488 
489 int[] cardArray = temp.get(i);
490 
491 if (cardLength > cardArray.length) {
492 
493 continue;
494 
495 }
496 
497 if (cardValue < Poke.getPokeValue(cardArray[0])) {
498 
499 if (cardLength == cardArray.length) {
500 
501 return cardArray;
502 
503 } else {
504 
505 int[] newArray = new int[cardLength];
506 
507 for (int k = 0; k < cardLength; k++) {
508 
509 newArray[k] = cardArray[k];
510 
511 }
512 
513 return newArray;
514 
515 }
516 
517 }
518 
519 }
520 
521 break;
522 
523 case PokeType.feiji:
524 
525 // 暫時不處理
526 
527 break;
528 
529 case PokeType.zhadan:
530 
531 temp = analyz.getCard_zhadan();
532 
533 size = temp.size();
534 
535 int zd[] = null;
536 
537 if (size > 0) {
538 
539 for (int i = 0; i < size; i++) {
540 
541 zd = temp.elementAt(i);
542 
543 if (cardValue < Poke.getPokeValue(zd[0])) {
544 
545 return zd;
546 
547 }
548 
549 }
550 
551 }
552 
553 break;
554 
555 case PokeType.huojian:
556 
557 return null;
558 
559 case PokeType.sidaier:
560 
561 // 暫時不處理,留待讀者完成
562 
563 break;
564 
565 }
566 
567 // TODO 如果可以一次性出完,無論如何都要,留待讀者完成
568 
569 // 根據must的值來判斷要牌的必要性
570 
571 boolean needZd = false;
572 
573 if (must < 90) {
574 
575 must *= 0.2;
576 
577 if (rand.nextInt(100) < must) {
578 
579 needZd = true;
580 
581 }
582 
583 } else {
584 
585 needZd = true;
586 
587 }
588 
589 if (needZd) {
590 
591 temp = analyz.getCard_zhadan();
592 
593 size = temp.size();
594 
595 if (size > 0) {
596 
597 return temp.elementAt(size - 1);
598 
599 }
600 
601 }
602 
603 } catch (Exception e) {
604 
605 e.printStackTrace();
606 
607 }
608 
609 return null;
610 
611 }
復制代碼

這里,對於不同的人工智能解析,會有不同的版本,這里的版本暫時設定為Simple2。

以下是出牌智能,上述的解析函數運用在出牌智能中。

該出牌智能基於如下一些原則:

對於出牌者:

玩家只剩下一手牌的時候,此時無論如何也應該要出牌。

對於要牌者(這里理解為挑戰者):

當玩家為BOSS時,要牌的緊迫程度隨着牌數量的減少而線性減少------- int must=pokeLength*100/17;

當pokeLength<=2時,迫切程度達到100。

當玩家非地主時,如果是地主出的牌,則和BOSS的緊迫性原則差不多,但是,如果是自己家的牌的話,遵循以下的一些原則:

(1) 如果我很可能一次性或者幾乎一次性地出完牌(c<=3),那么我就出牌。

(2) 如果我手中的牌的大小大於一定的值(這個值可以經過商議之后微調,不過,這里暫時限定為card.value=12),否則,我可以順延一個。

這里,有一個很好的想法是“緊迫程度原則”,但是,是否就用must變量來處理,有待進一步分析。

最后一個類為AnalyzePoke類,進行牌型的分析,與牌型的分析不同,這里是對一副牌的分析,上面是對一手牌的分析。AnalyzePoke類本來應該也放置在Poke類中,但是為了減少Poke類中的代碼量,於是就將牌型的分析這個模塊分離出來,

AnalyzePoke最主要的方法就是分析方法,它將一副牌中所有的牌型全部分析出來(按照牌的威力的依次下降逐層逐層分析的),放置到各自的Vector容器中,這樣就可以在需要的時候從各自的牌型中取得,或者進行組合得到。


復制代碼
  1 // 分析幾大主要牌型
  2 
  3 private void analyze() {
  4 
  5 // 初始化牌型容器
  6 
  7 init();
  8 
  9 // 分析王,2,普通牌的數量
 10 
 11 for (int i = 0; i < pokes.length; i++) {
 12 
 13 int v = Poke.getPokeValue(pokes[i]);
 14 
 15 if (v == 16 || v == 17) {
 16 
 17 countWang++;
 18 
 19 } else if (v == 15) {
 20 
 21 count2++;
 22 
 23 } else {
 24 
 25 countPokes[v - 3]++;
 26 
 27 }
 28 
 29 }
 30 
 31 // 分析三順牌型
 32 
 33 int start = -1;
 34 
 35 int end = -1;
 36 
 37 for (int i = 0; i <= countPokes.length - 1; i++) {
 38 
 39 if (countPokes[i] == 3) {
 40 
 41 if (start == -1) {
 42 
 43 start = i;
 44 
 45 } else {
 46 
 47 end = i;
 48 
 49 }
 50 
 51 } else {
 52 
 53 if (end != -1 && start != -1) {
 54 
 55 int dur = end - start + 1;
 56 
 57 int[] ss = new int[dur * 3];
 58 
 59 int m = 0;
 60 
 61 for (int j = 0; j < pokes.length; j++) {
 62 
 63 int v = Poke.getPokeValue(pokes[j]) - 3;
 64 
 65 if (v >= start && v <= end) {
 66 
 67 ss[m++] = pokes[j];
 68 
 69 }
 70 
 71 }
 72 
 73 if (m == dur * 3 - 1) {
 74 
 75 System.out.println("sanshun is over!!!");
 76 
 77 } else {
 78 
 79 System.out.println("sanshun error!!!");
 80 
 81 }
 82 
 83 card_sanshun.addElement(ss);
 84 
 85 for (int s = start; s <= end; s++) {
 86 
 87 countPokes[s] = -1;
 88 
 89 }
 90 
 91 start = end = -1;
 92 
 93 continue;
 94 
 95 } else {
 96 
 97 start = end = -1;
 98 
 99 }
100 
101 }
102 
103 }
104 
105 // 分析雙順牌型
106 
107 int sstart = -1;
108 
109 int send = -1;
110 
111 for (int i = 0; i < countPokes.length; i++) {
112 
113 if (countPokes[i] == 2) {
114 
115 if (sstart == -1) {
116 
117 sstart = i;
118 
119 } else {
120 
121 send = i;
122 
123 }
124 
125 } else {
126 
127 if (sstart != -1 && send != -1) {
128 
129 int dur = send - sstart + 1;
130 
131 if (dur < 3) {
132 
133 sstart = send = -1;
134 
135 continue;
136 
137 } else {
138 
139 int shuangshun[] = new int[dur * 2];
140 
141 int m = 0;
142 
143 for (int j = 0; j < pokes.length; j++) {
144 
145 int v = Poke.getPokeValue(pokes[j]) - 3;
146 
147 if (v >= sstart && v <= send) {
148 
149 shuangshun[m++] = pokes[j];
150 
151 }
152 
153 }
154 
155 card_shuangshun.addElement(shuangshun);
156 
157 for (int s = sstart; s <= send; s++) {
158 
159 countPokes[s] = -1;
160 
161 }
162 
163 sstart = send = -1;
164 
165 continue;
166 
167 }
168 
169 } else {
170 
171 sstart = send = -1;
172 
173 }
174 
175 }
176 
177 }
178 
179 // 分析單順牌型
180 
181 int dstart = -1;
182 
183 int dend = -1;
184 
185 for (int i = 0; i < countPokes.length; i++) {
186 
187 if (countPokes[i] >= 1) {
188 
189 if (dstart == -1) {
190 
191 dstart = i;
192 
193 } else {
194 
195 dend = i;
196 
197 }
198 
199 } else {
200 
201 if (dstart != -1 && dend != -1) {
202 
203 int dur = dend - dstart + 1;
204 
205 if (dur >= 5) {
206 
207 int m = 0;
208 
209 int[] danshun = new int[dur];
210 
211 for (int j = 0; j < pokes.length; j++) {
212 
213 int v = Poke.getPokeValue(pokes[j]) - 3;
214 
215 if (v == dend) {
216 
217 danshun[m++] = pokes[j];
218 
219 countPokes[dend]--;
220 
221 dend--;
222 
223 }
224 
225 if (dend == dstart - 1) {
226 
227 break;
228 
229 }
230 
231 }
232 
233 card_danshun.addElement(danshun);
234 
235 }
236 
237 dstart = dend = -1;
238 
239 } else {
240 
241 dstart = dend = -1;
242 
243 }
244 
245 }
246 
247 }
248 
249 // 分析三張牌型
250 
251 for (int i = 0; i < countPokes.length; i++) {
252 
253 if (countPokes[i] == 3) {
254 
255 countPokes[i] = -1;
256 
257 int[] sanzhang = new int[3];
258 
259 int m = 0;
260 
261 for (int j = 0; j < pokes.length; j++) {
262 
263 int v = Poke.getPokeValue(pokes[j]) - 3;
264 
265 if (v == i) {
266 
267 sanzhang[m++] = pokes[j];
268 
269 }
270 
271 }
272 
273 card_sanzhang.addElement(sanzhang);
274 
275 }
276 
277 }
278 
279 // 分析對牌
280 
281 for (int i = 0; i < countPokes.length; i++) {
282 
283 if (countPokes[i] == 2) {
284 
285 int[] duipai = new int[2];
286 
287 for (int j = 0; j < pokes.length; j++) {
288 
289 int v = Poke.getPokeValue(pokes[j]) - 3;
290 
291 if (v == i) {
292 
293 duipai[0] = pokes[j];
294 
295 duipai[1] = pokes[j + 1];
296 
297 card_duipai.addElement(duipai);
298 
299 break;
300 
301 }
302 
303 }
304 
305 countPokes[i] = -1;
306 
307 }
308 
309 }
310 
311 // 分析單牌
312 
313 for (int i = 0; i < countPokes.length; i++) {
314 
315 if (countPokes[i] == 1) {
316 
317 for (int j = 0; j < pokes.length; j++) {
318 
319 int v = Poke.getPokeValue(pokes[j]) - 3;
320 
321 if (v == i) {
322 
323 card_danpai.addElement(new int[] { pokes[j] });
324 
325 countPokes[i] = -1;
326 
327 break;
328 
329 }
330 
331 }
332 
333 }
334 
335 }
336 
337 // 根據2的數量進行分析
338 
339 switch (count2) {
340 
341 case 4:
342 
343 card_zhadan.addElement(new int[] { pokes[countWang],
344 
345 pokes[countWang + 1], pokes[countWang + 2],
346 
347 pokes[countWang + 3] });
348 
349 break;
350 
351 case 3:
352 
353 card_sanzhang.addElement(new int[] { pokes[countWang],
354 
355 pokes[countWang + 1], pokes[countWang + 2] });
356 
357 break;
358 
359 case 2:
360 
361 card_duipai.addElement(new int[] { pokes[countWang],
362 
363 pokes[countWang + 1] });
364 
365 break;
366 
367 case 1:
368 
369 card_danpai.addElement(new int[] { pokes[countWang] });
370 
371 break;
372 
373 }
374 
375 // 分析炸彈
376 
377 for (int i = 0; i < countPokes.length - 1; i++) {
378 
379 if (countPokes[i] == 4) {
380 
381 card_zhadan.addElement(new int[] { i * 4 + 3, i * 4 + 2,
382 
383 i * 4 + 1, i * 4 });
384 
385 countPokes[i] = -1;
386 
387 }
388 
389 }
390 
391 // 分析火箭
392 
393 if (countWang == 1) {
394 
395 card_danpai.addElement(new int[] { pokes[0] });
396 
397 } else if (countWang == 2) {
398 
399 card_zhadan.addElement(new int[] { pokes[0], pokes[1] });
400 
401 }
402 
403 }
404 
405 }
復制代碼

 

 

 

(這樣,可以初步解析出一副牌,以及分析出牌的“綜合實力”了,需要改進的地方還有很多,比如,有些時候,牌的“威力”並不是那么固定的)

以 上,就是所有的主要代碼,電腦NPC的智商還是有些偏低,這里沒有用到一些人工智能的算法(利用博弈樹,DFS,BFS,A*)等等,貌似用到了回溯,但 是,其中有些亮點值得注意,這里並沒有純粹客觀地利用搜索的深度來逐漸擴大NPC的“智商”,因為,這樣會引入一些比較大的時間復雜度,而這在作為玩家的 游戲中時不可取的,我們應該在加強NPC的聰明程度的同時考慮到玩家的耐心程度,斗地主AI的優秀程度,也就是這兩個方面的利弊權衡吧!

以上就是我對那3000行代碼的一些見解,關於UI,繪圖,以及線程刷頻等一些東西我就不拿出來了。

 

http://www.cnblogs.com/tuanzang/archive/2013/02/27/2935723.html

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com