制作之前网上搜了一圈资料,关于中国象棋的还真少,不过倒是找到了国际象棋的资料,让我很钦佩的国际同行的专业精神,一个小小的象棋游戏,人家制定一系列标准,还组建了协会,开发了几种不同语言的引擎(可惜没有javascript的),佩服的五体投地。
制作过程先不说了,今儿就说说AI吧,有兴趣想学习html5制作过程的话,请留言,如果需要的话,后面补上。
————————————
言归正传,说到算法,可能许多程序员都会先皱眉头,其实那东西没有那么难,不是数学家也能写算法。
先说说人机对战AI的原理吧
树搜索
其实很简单
1、取得走法很简单,根据规则一步步的去算每个棋子哪个点能走,源文件里面有个 com.bylaw ,他下面是取得每种棋子走点的方法;
1. //棋子能走的着点
2. com.bylaw ={}
3. //车
4. com.bylaw.c = function (x,y,map,my){
5. var d=[];
6. //左侧检索
7. for (var i=x-1; i>= 0; i--){
8. if (map[y][i]) {
9. if (com.mans[map[y][i]].my!=my) d.push([i,y]);
10. break
11. }else{
12. d.push([i,y])
13. }
14. }
15. //右侧检索
16. for (var i=x+1; i <= 8; i++){
17. if (map[y][i]) {
18. if (com.mans[map[y][i]].my!=my) d.push([i,y]);
19. break
20. }else{
21. d.push([i,y])
22. }
23. }
24. //上检索
25. for (var i = y-1 ; i >= 0; i--){
26. if (map[i][x]) {
27. if (com.mans[map[i][x]].my!=my) d.push([x,i]);
28. break
29. }else{
30. d.push([x,i])
31. }
32. }
33. //下检索
34. for (var i = y+1 ; i<= 9; i++){
35. if (map[i][x]) {
36. if (com.mans[map[i][x]].my!=my) d.push([x,i]);
37. break
38. }else{
39. d.push([x,i])
40. }
41. }
42. return d;
43. }
44.
45. //马
46. com.bylaw.m = function (x,y,map,my){
47. var d=[];
48. //1点
49. if ( y-2>= 0 && x+1<= 8 && !play.map[y-1][x] &&(!com.mans[map[y-2][x+1]] || com.mans[map[y-2][x+1]].my!=my)) d.push([x+1,y-2]);
50. //2点
51. if ( y-1>= 0 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y-1][x+2]] || com.mans[map[y-1][x+2]].my!=my)) d.push([x+2,y-1]);
52. //4点
53. if ( y+1<= 9 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y+1][x+2]] || com.mans[map[y+1][x+2]].my!=my)) d.push([x+2,y+1]);
54. //5点
55. if ( y+2<= 9 && x+1<= 8 && !play.map[y+1][x] &&(!com.mans[map[y+2][x+1]] || com.mans[map[y+2][x+1]].my!=my)) d.push([x+1,y+2]);
56. //7点
57. if ( y+2<= 9 && x-1>= 0 && !play.map[y+1][x] &&(!com.mans[map[y+2][x-1]] || com.mans[map[y+2][x-1]].my!=my)) d.push([x-1,y+2]);
58. //8点
59. if ( y+1<= 9 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y+1][x-2]] || com.mans[map[y+1][x-2]].my!=my)) d.push([x-2,y+1]);
60. //10点
61. if ( y-1>= 0 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y-1][x-2]] || com.mans[map[y-1][x-2]].my!=my)) d.push([x-2,y-1]);
62. //11点
63. if ( y-2>= 0 && x-1>= 0 && !play.map[y-1][x] &&(!com.mans[map[y-2][x-1]] || com.mans[map[y-2][x-1]].my!=my)) d.push([x-1,y-2]);
64.
65. return d;
66. }
67.
68. //相
69. com.bylaw.x = function (x,y,map,my){
70. var d=[];
71. if (my===1){ //红方
72. //4点半
73. if ( y+2<= 9 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]);
74. //7点半
75. if ( y+2<= 9 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]);
76. //1点半
77. if ( y-2>= 5 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]);
78. //10点半
79. if ( y-2>= 5 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]);
80. }else{
81. //4点半
82. if ( y+2<= 4 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]);
83. //7点半
84. if ( y+2<= 4 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]);
85. //1点半
86. if ( y-2>= 0 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]);
87. //10点半
88. if ( y-2>= 0 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]);
89. }
90. return d;
91. }
92.
93. //士
94. com.bylaw.s = function (x,y,map,my){
95. var d=[];
96. if (my===1){ //红方
97. //4点半
98. if ( y+1<= 9 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]);
99. //7点半
100. if ( y+1<= 9 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]);
101. //1点半
102. if ( y-1>= 7 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]);
103. //10点半
104. if ( y-1>= 7 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]);
105. }else{
106. //4点半
107. if ( y+1<= 2 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]);
108. //7点半
109. if ( y+1<= 2 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]);
110. //1点半
111. if ( y-1>= 0 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]);
112. //10点半
113. if ( y-1>= 0 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]);
114. }
115. return d;
116.
117. }
118.
119. //将
120. com.bylaw.j = function (x,y,map,my){
121. var d=[];
122. var isNull=(function (y1,y2){
123. var y1=com.mans["j0"].y;
124. var x1=com.mans["J0"].x;
125. var y2=com.mans["J0"].y;
126. for (var i=y1-1; i>y2; i--){
127. if (map[i][x1]) return false;
128. }
129. return true;
130. })();
131.
132. if (my===1){ //红方
133. //下
134. if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
135. //上
136. if ( y-1>= 7 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
137. //老将对老将的情况
138. if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["J0"].x,com.mans["J0"].y]);
139.
140. }else{
141. //下
142. if ( y+1<= 2 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
143. //上
144. if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
145. //老将对老将的情况
146. if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["j0"].x,com.mans["j0"].y]);
147. }
148. //右
149. if ( x+1<= 5 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]);
150. //左
151. if ( x-1>= 3 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
152. return d;
153. }
154.
155. //炮
156. com.bylaw.p = function (x,y,map,my){
157. var d=[];
158. //左侧检索
159. var n=0;
160. for (var i=x-1; i>= 0; i--){
161. if (map[y][i]) {
162. if (n==0){
163. n++;
164. continue;
165. }else{
166. if (com.mans[map[y][i]].my!=my) d.push([i,y]);
167. break
168. }
169. }else{
170. if(n==0) d.push([i,y])
171. }
172. }
173. //右侧检索
174. var n=0;
175. for (var i=x+1; i <= 8; i++){
176. if (map[y][i]) {
177. if (n==0){
178. n++;
179. continue;
180. }else{
181. if (com.mans[map[y][i]].my!=my) d.push([i,y]);
182. break
183. }
184. }else{
185. if(n==0) d.push([i,y])
186. }
187. }
188. //上检索
189. var n=0;
190. for (var i = y-1 ; i >= 0; i--){
191. if (map[i][x]) {
192. if (n==0){
193. n++;
194. continue;
195. }else{
196. if (com.mans[map[i][x]].my!=my) d.push([x,i]);
197. break
198. }
199. }else{
200. if(n==0) d.push([x,i])
201. }
202. }
203. //下检索
204. var n=0;
205. for (var i = y+1 ; i<= 9; i++){
206. if (map[i][x]) {
207. if (n==0){
208. n++;
209. continue;
210. }else{
211. if (com.mans[map[i][x]].my!=my) d.push([x,i]);
212. break
213. }
214. }else{
215. if(n==0) d.push([x,i])
216. }
217. }
218. return d;
219. }
220.
221. //卒
222. com.bylaw.z = function (x,y,map,my){
223. var d=[];
224. if (my===1){ //红方
225. //上
226. if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
227. //右
228. if ( x+1<= 8 && y<=4 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]);
229. //左
230. if ( x-1>= 0 && y<=4 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
231. }else{
232. //下
233. if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
234. //右
235. if ( x+1<= 8 && y>=6 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]);
236. //左
237. if ( x-1>= 0 && y>=6 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
238. }
239.
240. return d;
241. }
2、根据各个走法,去生成新的棋谱,然后搜索新的走法
3、评估
评估是个很麻烦的事情,一开始我考虑的是给每个棋子价值,比如
士 20 象 20 马 90 车 200 炮 100 兵 40
不过会有一个问题,每个棋子在不同的局面,不同的位置,其价值是不同的,最明显的就是“士”,过河以后明显价值增加了不少,而且越靠近对方老将价值越大,甚至到最后会超越马或者炮,所以我把价值评估做了改进,详情看源码com.value这块。这里的棋子价值,借鉴了一些其他象棋游戏的经验
1. com.value = {
2.
3. //车价值
4. c:[
5. [206, 208, 207, 213, 214, 213, 207, 208, 206],
6. [206, 212, 209, 216, 233, 216, 209, 212, 206],
7. [206, 208, 207, 214, 216, 214, 207, 208, 206],
8. [206, 213, 213, 216, 216, 216, 213, 213, 206],
9. [208, 211, 211, 214, 215, 214, 211, 211, 208],
10.
11. [208, 212, 212, 214, 215, 214, 212, 212, 208],
12. [204, 209, 204, 212, 214, 212, 204, 209, 204],
13. [198, 208, 204, 212, 212, 212, 204, 208, 198],
14. [200, 208, 206, 212, 200, 212, 206, 208, 200],
15. [194, 206, 204, 212, 200, 212, 204, 206, 194]
16. ],
17.
18. //马价值
19. m:[
20. [90, 90, 90, 96, 90, 96, 90, 90, 90],
21. [90, 96,103, 97, 94, 97,103, 96, 90],
22. [92, 98, 99,103, 99,103, 99, 98, 92],
23. [93,108,100,107,100,107,100,108, 93],
24. [90,100, 99,103,104,103, 99,100, 90],
25.
26. [90, 98,101,102,103,102,101, 98, 90],
27. [92, 94, 98, 95, 98, 95, 98, 94, 92],
28. [93, 92, 94, 95, 92, 95, 94, 92, 93],
29. [85, 90, 92, 93, 78, 93, 92, 90, 85],
30. [88, 85, 90, 88, 90, 88, 90, 85, 88]
31. ],
32.
33. //相价值
34. x:[
35. [0, 0,20, 0, 0, 0,20, 0, 0],
36. [0, 0, 0, 0, 0, 0, 0, 0, 0],
37. [0, 0, 0, 0,23, 0, 0, 0, 0],
38. [0, 0, 0, 0, 0, 0, 0, 0, 0],
39. [0, 0,20, 0, 0, 0,20, 0, 0],
40.
41. [0, 0,20, 0, 0, 0,20, 0, 0],
42. [0, 0, 0, 0, 0, 0, 0, 0, 0],
43. [18,0, 0, 0,23, 0, 0, 0,18],
44. [0, 0, 0, 0, 0, 0, 0, 0, 0],
45. [0, 0,20, 0, 0, 0,20, 0, 0]
46. ],
47.
48. //士价值
49. s:[
50. [0, 0, 0,20, 0,20, 0, 0, 0],
51. [0, 0, 0, 0,23, 0, 0, 0, 0],
52. [0, 0, 0,20, 0,20, 0, 0, 0],
53. [0, 0, 0, 0, 0, 0, 0, 0, 0],
54. [0, 0, 0, 0, 0, 0, 0, 0, 0],
55.
56. [0, 0, 0, 0, 0, 0, 0, 0, 0],
57. [0, 0, 0, 0, 0, 0, 0, 0, 0],
58. [0, 0, 0,20, 0,20, 0, 0, 0],
59. [0, 0, 0, 0,23, 0, 0, 0, 0],
60. [0, 0, 0,20, 0,20, 0, 0, 0]
61. ],
62.
63. //奖价值
64. j:[
65. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0],
66. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0],
67. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0],
68. [0, 0, 0, 0, 0, 0, 0, 0, 0],
69. [0, 0, 0, 0, 0, 0, 0, 0, 0],
70.
71. [0, 0, 0, 0, 0, 0, 0, 0, 0],
72. [0, 0, 0, 0, 0, 0, 0, 0, 0],
73. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0],
74. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0],
75. [0, 0, 0, 8888, 8888, 8888, 0, 0, 0]
76. ],
77.
78. //炮价值
79. p:[
80.
81. [100, 100, 96, 91, 90, 91, 96, 100, 100],
82. [ 98, 98, 96, 92, 89, 92, 96, 98, 98],
83. [ 97, 97, 96, 91, 92, 91, 96, 97, 97],
84. [ 96, 99, 99, 98, 100, 98, 99, 99, 96],
85. [ 96, 96, 96, 96, 100, 96, 96, 96, 96],
86.
87. [ 95, 96, 99, 96, 100, 96, 99, 96, 95],
88. [ 96, 96, 96, 96, 96, 96, 96, 96, 96],
89. [ 97, 96, 100, 99, 101, 99, 100, 96, 97],
90. [ 96, 97, 98, 98, 98, 98, 98, 97, 96],
91. [ 96, 96, 97, 99, 99, 99, 97, 96, 96]
92. ],
93.
94. //卒价值
95. z:[
96. [ 9, 9, 9, 11, 13, 11, 9, 9, 9],
97. [19, 24, 34, 42, 44, 42, 34, 24, 19],
98. [19, 24, 32, 37, 37, 37, 32, 24, 19],
99. [19, 23, 27, 29, 30, 29, 27, 23, 19],
100. [14, 18, 20, 27, 29, 27, 20, 18, 14],
101.
102. [ 7, 0, 13, 0, 16, 0, 13, 0, 7],
103. [ 7, 0, 7, 0, 15, 0, 7, 0, 7],
104. [ 0, 0, 0, 0, 0, 0, 0, 0, 0],
105. [ 0, 0, 0, 0, 0, 0, 0, 0, 0],
106. [ 0, 0, 0, 0, 0, 0, 0, 0, 0]
107. ]
108. }
109.
110. //黑子为红字价值位置的倒置
111. com.value.C = com.arr2Clone(com.value.c).reverse();
112. com.value.M = com.arr2Clone(com.value.m).reverse();
113. com.value.X = com.value.x;
114. com.value.S = com.value.s;
115. com.value.J = com.value.j;
116. com.value.P = com.arr2Clone(com.value.p).reverse();
117. com.value.Z = com.arr2Clone(com.value.z).reverse();
有了价值,评估就是很简单的事啊,双方健在棋子的价值差,就是这个分支的最后得分。
1. //评估棋局 取得棋盘双方棋子价值差
2. AI.evaluate = function (map,my){
3. var val=0;
4. for (var i=0; i<map.length; i++){
5. for (var n=0; n<map[i].length; n++){
6. var key = map[i][n];
7. if (key){
8. val += play.mans[key].value[i][n] * play.mans[key].my;
9. }
10. }
11. }
12. val+=Math.floor( Math.random() * 10); //让AI走棋增加随机元素
13. AI.number++;
14. return val*my;
15. }
有了这个算法,我可以骄傲的说只要给我足够强大的计算机,和足够长的计算时间,我就能算出必胜的走棋方法,信不,你信不信反正我是信了。
不过我大概统计了一下,中国象棋平均每步大概有42个走法,如果这盘期走了100个回合的话,那么就是42的200次方个结果,大概需要计算这么多分支,4.4653764701754888195833543328375e+324,大概也就够现在最强大的服务器计算几百年的工作量吧。
看来是不现实的,还是看看现实一点的吧
最大最小搜索
只搜到某个深度,在这个深度来进行评估,决定AI走哪一步棋。
这个时候又有个问题,我走棋以后,对手也不一定选择哪步期来对付我啊,我该怎么判断对手会走哪一步呢,想这个问题之前,看看下面的题目就懂了
现在有30个桔子,有大的有小的,有好的有烂的,我们按照好坏级别给他评分了
随机平分为3堆,两个人抢桔子,规则是,A选择其中的一堆,B在A选的那堆里面,选一个给A,如果你是A,你该怎么选呢?
选NO1很有诱惑力,因为里面有最好的桔子16,不过你选了NO1后,你是得不到16的,因为B会拿-6给你,这样的话,对你最有优势的选择应该是NO3,也就是我要选的是每堆里面最次的那个桔子,里面最好的所在的那一堆,比较绕,不知道你听明白没。
走棋也是一样的,对方肯定是会选择对他损失最小的走法来对付你,是的,这就是最大最小算法的原理,想想如何应用到程序中。
好了,AI的基础算法完成了,依靠这套理论就能写出能够走棋的AI了,欢呼吧,虽然性能很低,虽然走得傻傻的。
不知道你能看明白不,确实比较绕,加上本人表达能力有限,有啥意见多提啊,下次改进