本软件是基于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:一个存储不同数值的辅助工具类
- 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,绘图,以及线程刷频等一些东西我就不拿出来了。