标题不大好起,姑且叫这个名字。
2010年5月,做了一个下棋的Demo,目前有五子棋和黑白棋两种。可支持两人对弈,也可人机对弈。留下了计算机走棋的接口。
本人是07级软件工程本科生,写过不少程序。但过去写的程序代码质量不高,存在大量复制粘贴。
本人经过学习,现在能做一部分的优化。
就本程序而言,仍然是比较紧的耦合。在此将拙作展示,望同侪指正。
开发语言为C# 3.5。WinForm结构。
先看看运行结果: (界面比较简陋,见笑了)
下面作者将从多方面阐述本程序。
====================================================================================
一、设计。
类图如下:
- 左边这棵树是基于对象的设计,封装了棋类的通用操作,并且作者希望通过继承达到多态性的效果(这一点会在后面提到)。其中ChessManager为抽象类,由其派生出OthelloManager,FiveChessManager(分别为黑白棋和五子棋),然后OthelloManager派生出AIOthelloManager(支持人机对战)。
- Common类保存全局变量,如棋子的颜色。
- ChessType为枚举类型,表示一个棋子的状态。包括四状态:NONE, FIRST, SECOND, BOTH。分别表示【无】【先手方(黑棋)】【后手方(白棋)】【双方(这个状态是用来表示平局的)】。有两个地方要用,一是用来表示棋盘上每个点的有无棋子,是黑是白;二是表示获胜的玩家,如无人胜,黑胜,白胜,双方胜(可理解为平局)。
- IJudgeable接口。用来判断哪一方获胜。由ChessManager来实现,事后发现此接口没有什么用(囧)。
- IAIChess接口。声明了与AI有关的方法。
二、实现
从简入手。
1.Common类,代码如下:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Drawing;
6
7 namespace MyChess
8 {
9 public enum ChessType
10 {
11 NONE,
12 FIRST,
13 SECOND,
14 BOTH // 平局,五子棋不出现,黑白棋可能出现
15 }
16
17 public class Common
18 {
19 /// <summary>
20 /// 随机数生成器
21 /// </summary>
22 public static Random random = new Random();
23
24 #region 窗口信息
25 public static int leftBound = 100;
26 public static int topBound = 50;
27 #endregion
28
29 #region 棋盘、棋子信息
30 /// <summary>
31 /// 棋盘边长
32 /// </summary>
33 public static int defaultBoardSize = 5;
34
35 /// <summary>
36 /// 棋子半径
37 /// </summary>
38 public static float diameter = 30;
39
40
41 /// <summary>
42 /// 网格线颜色
43 /// </summary>
44 public static Color boardColor = Color.Black;
45
46 /// <summary>
47 /// 玩家1棋子颜色
48 /// </summary>
49 public static Color oneColor = Color.Black;
50
51 /// <summary>
52 /// 玩家2棋子颜色
53 /// </summary>
54 public static Color twoColor = Color.White;
55 #endregion
56
57 }
58 }
59
2. 接口定义
2 {
3 ChessType JudgeWinner();
4 }
5
2 {
3 /// <summary>
4 /// 得到AI的落子坐标
5 /// </summary>
6 /// <returns>AI计算得到的落子坐标</returns>
7 Point getAIposition();
8 }
3. ChessManager抽象类。为核心类,封装了Form与其的关系,以及棋类的自治功能。
public 方法:
AddChess方法:在指定位置落子,首先判断是否成功落子(位置原来没有棋子),然后判定胜负(每落一子,就会作一次判定)。
SwitchPlayer方法:切换双方。
Init方法:初始化
Clear方法:清空棋盘。
Pass方法: 弃权。
JudgeWinner方法:判定胜负,包括黑胜,白胜,未下完,平局。
private方法:
SetChess方法 :在指定位置落子。
等等
以上方法多数可以覆盖,供不同种类的棋配置。
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Drawing;
6
7 namespace MyChess
8 {
9 public delegate void WinningHandler(ChessType winner);
10
11 /// <summary>
12 /// 两人对战棋类游戏的基类
13 ///
14 /// IJugeable包含了JudgeWinner方法,用以判断某方获胜,请在继承时覆盖。
15 /// winning事件用以处理一方获胜。
16 ///
17 /// SetChess方法指定落子归则,默认为落子后不对其他棋子产生影响。
18 /// 对于五子棋,不需覆盖。对于黑白棋、围棋,请覆盖该方法。
19 /// </summary>
20 public abstract class ChessManager : IJudgeable
21 {
22 #region 变量
23 protected ChessType[,] chessBoard;
24 protected ChessType currentType = ChessType.NONE;
25
26 protected int boardSize = 5;
27
28 /// <summary>
29 /// 当前落子的位置 X表示行,Y表示列
30 /// </summary>
31 protected Point currentChess = new Point(-1, -1);
32 private bool isAlive = true;
33 #endregion
34
35 #region 属性
36 /// <summary>
37 /// 当前棋子状态
38 /// </summary>
39 public ChessType CurrentType
40 {
41 get { return currentType; }
42 }
43
44 /// <summary>
45 /// 棋盘大小
46 /// </summary>
47 public int BoardSize
48 {
49 get { return boardSize; }
50 }
51
52 /// <summary>
53 /// 棋局正在进行:true;已经结束:false
54 /// </summary>
55 public bool IsAlive
56 {
57 get { return isAlive; }
58 }
59 #endregion
60
61 #region 事件
62 public event WinningHandler winning;
63 private void OnWin(ChessType player)
64 {
65 if (winning != null)
66 {
67 winning(player);
68 }
69 }
70 #endregion
71
72 #region 构造器
73 public ChessManager()
74 : this(5)
75 {
76 }
77
78 public ChessManager(int pBoardSize)
79 {
80 this.boardSize = pBoardSize;
81 this.chessBoard = new ChessType[this.boardSize, this.boardSize];
82 this.currentType = ChessType.FIRST;
83 }
84 #endregion
85
86 #region 公共方法
87 /// <summary>
88 /// 渲染
89 /// </summary>
90 /// <param name="graphics"></param>
91 public abstract void Render(Graphics graphics);
92
93 /// <summary>
94 /// 在指定位置落子
95 /// 切换选手
96 /// 判定胜负
97 /// </summary>
98 /// <param name="px"></param>
99 /// <param name="py"></param>
100 /// <returns>true if success</returns>
101 public bool AddChess(int px, int py)
102 {
103 bool ret = false;
104 if (chessBoard[px, py] == ChessType.NONE)
105 {
106 currentChess = new Point(px, py);
107 SetChess(px, py, this.currentType);
108 if (chessBoard[px, py] != ChessType.NONE) // 下子成功则切换选手
109 {
110 ret = true;
111 SwitchPlayer();
112 }
113 ChessType winner = JudgeWinner();
114 if (winner != ChessType.NONE)
115 {
116 this.isAlive = false;
117 OnWin(winner);
118 }
119 }
120 return ret;
121 }
122
123 /// <summary>
124 /// 切换选手
125 /// </summary>
126 protected void SwitchPlayer()
127 {
128 if (currentType == ChessType.FIRST)
129 currentType = ChessType.SECOND;
130 else if (currentType == ChessType.SECOND)
131 currentType = ChessType.FIRST;
132 }
133
134 /// <summary>
135 /// 随机分布
136 /// 测试用
137 /// </summary>
138 public void Shuffle()
139 {
140 for (int i = 0; i < this.boardSize; i++)
141 {
142 for (int j = 0; j < this.boardSize; j++)
143 {
144 int tmp = Common.random.Next(3);
145 switch (tmp)
146 {
147 case 0:
148 chessBoard[i, j] = ChessType.NONE;
149 break;
150 case 1:
151 chessBoard[i, j] = ChessType.FIRST;
152 break;
153 case 2:
154 chessBoard[i, j] = ChessType.SECOND;
155 break;
156 default:
157 throw new Exception("Error");
158 break;
159 }
160 }
161 }
162 }
163
164 /// <summary>
165 /// 初始化
166 /// </summary>
167 public virtual void Init()
168 {
169 Clear();
170 this.currentType = ChessType.FIRST;
171 this.isAlive = true;
172 }
173
174 /// <summary>
175 /// 清空棋盘
176 /// </summary>
177 public void Clear()
178 {
179 for (int i = 0; i < this.boardSize; i++)
180 {
181 for (int j = 0; j < this.boardSize; j++)
182 {
183 chessBoard[i, j] = ChessType.NONE;
184 }
185 }
186 }
187
188
189 #endregion
190
191 #region 判定胜负
192 public abstract ChessType JudgeWinner();
193 #endregion
194
195 #region 工具方法
196 /// <summary>
197 /// 设计指定位置棋子
198 /// 并处理该棋子影响到的其他棋子
199 /// </summary>
200 /// <param name="px">横坐标</param>
201 /// <param name="py">纵坐标</param>
202 /// <param name="type">棋子类别:无、玩家1、玩家2</param>
203 protected virtual void SetChess(int px, int py, ChessType type)
204 {
205 if (px < 0 || px >= this.boardSize || py < 0 || py > this.boardSize)
206 {
207 throw new Exception("Index out of bound");
208 }
209 chessBoard[px, py] = type;
210 }
211 #endregion
212
213 /// <summary>
214 /// 当前一步放弃下子
215 /// 黑白棋中不允许随便切换
216 /// </summary>
217 /// <returns></returns>
218 public virtual bool Pass()
219 {
220 this.SwitchPlayer();
221 return true;
222 }
223 }
224 }
225
4. OthelloManager继承自ChessManager,复用棋类通用方法。同时在region 辅助 中有其特有的方法。
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Drawing;
6
7 namespace MyChess
8 {
9 /// <summary>
10 /// 黑白棋
11 /// </summary>
12 public class OthelloManager : ChessManager
13 {
14 public OthelloManager()
15 : base(8)
16 {
17 // nop
18 }
19
20 public override void Init()
21 {
22 base.Init();
23 int half = this.boardSize / 2;
24 chessBoard[half - 1, half - 1] = chessBoard[half, half] = ChessType.FIRST;
25 chessBoard[half - 1, half] = chessBoard[half, half - 1] = ChessType.SECOND;
26 }
27
28 protected override void SetChess(int px, int py, ChessType type)
29 {
30 #region 搜索
31 // 向八个方向搜索
32 ChessType curType = type;
33 // 八个方向的搜索终点的长度
34 // 记录该方向数几个棋子,将其反色
35 int upleft = 0, up = 0, upright = 0, left = 0, right = 0, downleft = 0, down = 0, downright = 0;
36 #region 左上
37 {
38 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
39 for (int i = currentChess.X - 1, j = currentChess.Y - 1; i >= 0 && j >= 0; i--, j--)
40 {
41 if (chessBoard[i, j] == ChessType.NONE)
42 {
43 upleft = 0;
44 break;
45 }
46 else if (chessBoard[i, j] == curType)
47 {
48 limitType = currentType;
49 break;
50 }
51 else
52 {
53 upleft++;
54 }
55 limitType = chessBoard[i, j];
56 }
57 if (limitType != curType) // 搜索至边界无同色棋子,不反色
58 {
59 upleft = 0;
60 }
61 }
62 {
63 int tmpCount = upleft;
64 for (int i = currentChess.X - 1, j = currentChess.Y - 1; tmpCount > 0 && i >= 0 && j >= 0; i--, j--, tmpCount--)
65 {
66 chessBoard[i, j] = currentType;
67 }
68 }
69 #endregion
70 #region 上
71 {
72 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
73 int j = currentChess.Y;
74 for (int i = currentChess.X - 1; i >= 0; i--)
75 {
76 if (chessBoard[i, j] == ChessType.NONE)
77 {
78 up = 0;
79 break;
80 }
81 else if (chessBoard[i, j] == curType)
82 {
83 limitType = currentType;
84 break;
85 }
86 else
87 {
88 up++;
89 }
90 limitType = chessBoard[i, j];
91 }
92 if (limitType != curType) // 搜索至边界无同色棋子,不反色
93 {
94 up = 0;
95 }
96 }
97 {
98 int tmpCount = up;
99 int j = currentChess.Y;
100 for (int i = currentChess.X - 1; tmpCount > 0 && i >= 0; i--, tmpCount--)
101 {
102 chessBoard[i, j] = currentType;
103 }
104 }
105 #endregion
106 #region 右上
107 {
108 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
109 for (int i = currentChess.X - 1, j = currentChess.Y + 1; i >= 0 && j < this.boardSize; i--, j++)
110 {
111 if (chessBoard[i, j] == ChessType.NONE)
112 {
113 upright = 0;
114 break;
115 }
116 else if (chessBoard[i, j] == curType)
117 {
118 limitType = currentType;
119 break;
120 }
121 else
122 {
123 upright++;
124 }
125 limitType = chessBoard[i, j];
126 }
127 if (limitType != curType) // 搜索至边界无同色棋子,不反色
128 {
129 upright = 0;
130 }
131 }
132 {
133 int tmpCount = upright;
134 for (int i = currentChess.X - 1, j = currentChess.Y + 1; tmpCount > 0 && i >= 0 && j < this.boardSize; i--, j++, tmpCount--)
135 {
136 chessBoard[i, j] = currentType;
137 }
138 }
139 #endregion
140 #region 左
141 {
142 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
143 int i = currentChess.X;
144 for (int j = currentChess.Y - 1; j >= 0; j--)
145 {
146 if (chessBoard[i, j] == ChessType.NONE)
147 {
148 left = 0;
149 break;
150 }
151 else if (chessBoard[i, j] == curType)
152 {
153 limitType = currentType;
154 break;
155 }
156 else
157 {
158 left++;
159 }
160 limitType = chessBoard[i, j];
161 }
162 if (limitType != curType) // 搜索至边界无同色棋子,不反色
163 {
164 left = 0;
165 }
166 }
167 {
168 int tmpCount = left;
169 int i = currentChess.X;
170 for (int j = currentChess.Y - 1; tmpCount > 0 && j >= 0; j--, tmpCount--)
171 {
172 chessBoard[i, j] = currentType;
173 }
174 }
175 #endregion
176 #region 右
177 {
178 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
179 int i = currentChess.X;
180 for (int j = currentChess.Y + 1; j < this.boardSize; j++)
181 {
182 if (chessBoard[i, j] == ChessType.NONE)
183 {
184 right = 0;
185 break;
186 }
187 else if (chessBoard[i, j] == curType)
188 {
189 limitType = currentType;
190 break;
191 }
192 else
193 {
194 right++;
195 }
196 limitType = chessBoard[i, j];
197 }
198 if (limitType != curType) // 搜索至边界无同色棋子,不反色
199 {
200 right = 0;
201 }
202 }
203 {
204 int tmpCount = right;
205 int i = currentChess.X;
206 for (int j = currentChess.Y + 1; tmpCount > 0 && j < this.boardSize; j++, tmpCount--)
207 {
208 chessBoard[i, j] = currentType;
209 }
210 }
211 #endregion
212 #region 左下
213 {
214 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
215 for (int i = currentChess.X + 1, j = currentChess.Y - 1; i < this.boardSize && j >= 0; i++, j--)
216 {
217 if (chessBoard[i, j] == ChessType.NONE)
218 {
219 downleft = 0;
220 break;
221 }
222 else if (chessBoard[i, j] == curType)
223 {
224 limitType = currentType;
225 break;
226 }
227 else
228 {
229 downleft++;
230 }
231 limitType = chessBoard[i, j];
232 }
233 if (limitType != curType) // 搜索至边界无同色棋子,不反色
234 {
235 downleft = 0;
236 }
237 }
238 {
239 int tmpCount = downleft;
240 for (int i = currentChess.X + 1, j = currentChess.Y - 1; tmpCount > 0 && i < this.boardSize && j >= 0; i++, j--, tmpCount--)
241 {
242 chessBoard[i, j] = currentType;
243 }
244 }
245 #endregion
246 #region 下
247 {
248 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
249 int j = currentChess.Y;
250 for (int i = currentChess.X + 1; i < this.boardSize; i++)
251 {
252 if (chessBoard[i, j] == ChessType.NONE)
253 {
254 down = 0;
255 break;
256 }
257 else if (chessBoard[i, j] == curType)
258 {
259 limitType = currentType;
260 break;
261 }
262 else
263 {
264 down++;
265 }
266 limitType = chessBoard[i, j];
267 }
268 if (limitType != curType) // 搜索至边界无同色棋子,不反色
269 {
270 down = 0;
271 }
272 }
273 {
274 int tmpCount = down;
275 int j = currentChess.Y;
276 for (int i = currentChess.X + 1; tmpCount > 0 && i < this.boardSize; i++, tmpCount--)
277 {
278 chessBoard[i, j] = currentType;
279 }
280 }
281 #endregion
282 #region 右下
283 {
284 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
285 for (int i = currentChess.X + 1, j = currentChess.Y + 1; i < this.boardSize && j < this.boardSize; i++, j++)
286 {
287 if (chessBoard[i, j] == ChessType.NONE)
288 {
289 downright = 0;
290 break;
291 }
292 else if (chessBoard[i, j] == curType)
293 {
294 limitType = currentType;
295 break;
296 }
297 else
298 {
299 downright++;
300 }
301 limitType = chessBoard[i, j];
302 }
303 if (limitType != curType) // 搜索至边界无同色棋子,不反色
304 {
305 downright = 0;
306 }
307 }
308 {
309 int tmpCount = downright;
310 for (int i = currentChess.X + 1, j = currentChess.Y + 1; tmpCount > 0 && i < this.boardSize && j < this.boardSize; i++, j++, tmpCount--)
311 {
312 chessBoard[i, j] = currentType;
313 }
314 }
315 #endregion
316 #endregion
317
318 if (upleft + up + upright + left + right + downleft + down + downright <= 0)
319 {
320 // 不能在此下子
321 chessBoard[currentChess.X, currentChess.Y] = ChessType.NONE;
322 }
323 else
324 {
325 base.SetChess(px, py, type);
326 }
327 }
328
329 public override bool Pass()
330 {
331 int firstCount;
332 int secondCount;
333 int firstAvailablePosLeft; // 玩家一 剩余可以下子的位置
334 int secondAvailablePosLeft; // 玩家二 剩余可以下子的位置
335 getCurrentInfo(out firstCount, out secondCount, out firstAvailablePosLeft, out secondAvailablePosLeft);
336
337 bool canPass = true; // 是否允许弃权 当前棋局还有可以下的位置,禁止弃权
338 if (currentType == ChessType.FIRST)
339 {
340 if (firstAvailablePosLeft > 0)
341 canPass = false;
342 }
343 else if (currentType == ChessType.SECOND)
344 {
345 if (secondAvailablePosLeft > 0)
346 canPass = false;
347 }
348 else
349 {
350 throw new Exception("当前状态不是合法状态(FIRST SECOND)");
351 }
352 if (canPass)
353 {
354 SwitchPlayer();
355 }
356 return canPass;
357 }
358
359 public override void Render(System.Drawing.Graphics graphics)
360 {
361 Pen pen = new Pen(Common.boardColor);
362 Brush oneBrush = new SolidBrush(Common.oneColor);
363 Brush twoBrush = new SolidBrush(Common.twoColor);
364
365 #region 画棋盘
366 for (int i = 0; i < this.boardSize + 1; i++)
367 {
368 float t1 = i * Common.diameter;
369 float s1 = 0;
370 float s2 = s1 + this.boardSize * Common.diameter;
371
372 graphics.DrawLine(pen, (Common.leftBound + t1), (Common.topBound + s1),
373 (Common.leftBound + t1), (Common.topBound + s2));
374 graphics.DrawLine(pen, (Common.leftBound + s1), (Common.topBound + t1),
375 (Common.leftBound + s2), (Common.topBound + t1));
376 }
377 #endregion
378
379 #region 画棋子
380 for (int i = 0; i < this.boardSize; i++)
381 {
382 for (int j = 0; j < this.boardSize; j++)
383 {
384 float x = Common.leftBound + j * Common.diameter;
385 float y = Common.topBound + i * Common.diameter;
386 switch (chessBoard[i, j])
387 {
388 case ChessType.FIRST:
389 graphics.FillEllipse(oneBrush, x, y, Common.diameter, Common.diameter);
390 break;
391 case ChessType.SECOND:
392 graphics.FillEllipse(twoBrush, x, y, Common.diameter, Common.diameter);
393 break;
394 default:
395 break;
396 }
397 }
398 }
399 #endregion
400 }
401
402 public override ChessType JudgeWinner()
403 {
404 int firstCount;
405 int secondCount;
406 int firstAvailablePosLeft; // 玩家一 剩余可以下子的位置
407 int secondAvailablePosLeft; // 玩家二 剩余可以下子的位置
408
409 ChessType winner = ChessType.NONE;
410
411 getCurrentInfo(out firstCount, out secondCount, out firstAvailablePosLeft, out secondAvailablePosLeft);
412
413 if (firstAvailablePosLeft + secondAvailablePosLeft <= 0)
414 {
415 if (firstCount > secondCount)
416 winner = ChessType.FIRST;
417 else if (firstCount < secondCount)
418 winner = ChessType.SECOND;
419 else //平局
420 winner = ChessType.BOTH;
421 }
422 return winner;
423 }
424
425 #region 辅助
426 /// <summary>
427 /// 计算八个方向上影响到的对方棋子数
428 /// </summary>
429 /// <param name="px"></param>
430 /// <param name="py"></param>
431 /// <param name="cType">以cType的观点</param>
432 /// <returns></returns>
433 protected int CountAffectedChess(int px, int py, ChessType cType)
434 {
435 if (!(cType == ChessType.FIRST || cType == ChessType.SECOND))
436 throw new Exception("cType must be FIRST or SECOND");
437 #region 搜索
438 // 向八个方向搜索
439 ChessType tmpType = cType;
440 Point chess = new Point(px, py);
441 // 八个方向的搜索终点的长度
442 // 记录该方向数几个棋子
443 int upleft = 0, up = 0, upright = 0, left = 0, right = 0, downleft = 0, down = 0, downright = 0;
444 #region 左上
445 {
446 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
447 for (int i = chess.X - 1, j = chess.Y - 1; i >= 0 && j >= 0; i--, j--)
448 {
449 if (chessBoard[i, j] == ChessType.NONE)
450 {
451 upleft = 0;
452 break;
453 }
454 else if (chessBoard[i, j] == tmpType)
455 {
456 limitType = tmpType;
457 break;
458 }
459 else
460 {
461 upleft++;
462 }
463 limitType = chessBoard[i, j];
464 }
465 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
466 {
467 upleft = 0;
468 }
469 }
470 #endregion
471 #region 上
472 {
473 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
474 int j = chess.Y;
475 for (int i = chess.X - 1; i >= 0; i--)
476 {
477 if (chessBoard[i, j] == ChessType.NONE)
478 {
479 up = 0;
480 break;
481 }
482 else if (chessBoard[i, j] == tmpType)
483 {
484 limitType = tmpType;
485 break;
486 }
487 else
488 {
489 up++;
490 }
491 limitType = chessBoard[i, j];
492 }
493 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
494 {
495 up = 0;
496 }
497 }
498 #endregion
499 #region 右上
500 {
501 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
502 for (int i = chess.X - 1, j = chess.Y + 1; i >= 0 && j < this.boardSize; i--, j++)
503 {
504 if (chessBoard[i, j] == ChessType.NONE)
505 {
506 upright = 0;
507 break;
508 }
509 else if (chessBoard[i, j] == tmpType)
510 {
511 limitType = tmpType;
512 break;
513 }
514 else
515 {
516 upright++;
517 }
518 limitType = chessBoard[i, j];
519 }
520 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
521 {
522 upright = 0;
523 }
524 }
525 #endregion
526 #region 左
527 {
528 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
529 int i = chess.X;
530 for (int j = chess.Y - 1; j >= 0; j--)
531 {
532 if (chessBoard[i, j] == ChessType.NONE)
533 {
534 left = 0;
535 break;
536 }
537 else if (chessBoard[i, j] == tmpType)
538 {
539 limitType = tmpType;
540 break;
541 }
542 else
543 {
544 left++;
545 }
546 limitType = chessBoard[i, j];
547 }
548 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
549 {
550 left = 0;
551 }
552 }
553 #endregion
554 #region 右
555 {
556 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
557 int i = chess.X;
558 for (int j = chess.Y + 1; j < this.boardSize; j++)
559 {
560 if (chessBoard[i, j] == ChessType.NONE)
561 {
562 right = 0;
563 break;
564 }
565 else if (chessBoard[i, j] == tmpType)
566 {
567 limitType = tmpType;
568 break;
569 }
570 else
571 {
572 right++;
573 }
574 limitType = chessBoard[i, j];
575 }
576 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
577 {
578 right = 0;
579 }
580 }
581 #endregion
582 #region 左下
583 {
584 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
585 for (int i = chess.X + 1, j = chess.Y - 1; i < this.boardSize && j >= 0; i++, j--)
586 {
587 if (chessBoard[i, j] == ChessType.NONE)
588 {
589 downleft = 0;
590 break;
591 }
592 else if (chessBoard[i, j] == tmpType)
593 {
594 limitType = tmpType;
595 break;
596 }
597 else
598 {
599 downleft++;
600 }
601 limitType = chessBoard[i, j];
602 }
603 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
604 {
605 downleft = 0;
606 }
607 }
608 #endregion
609 #region 下
610 {
611 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
612 int j = chess.Y;
613 for (int i = chess.X + 1; i < this.boardSize; i++)
614 {
615 if (chessBoard[i, j] == ChessType.NONE)
616 {
617 down = 0;
618 break;
619 }
620 else if (chessBoard[i, j] == tmpType)
621 {
622 limitType = tmpType;
623 break;
624 }
625 else
626 {
627 down++;
628 }
629 limitType = chessBoard[i, j];
630 }
631 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
632 {
633 down = 0;
634 }
635 }
636 #endregion
637 #region 右下
638 {
639 ChessType limitType = ChessType.NONE; // 该方向上的最靠边的棋子
640 for (int i = chess.X + 1, j = chess.Y + 1; i < this.boardSize && j < this.boardSize; i++, j++)
641 {
642 if (chessBoard[i, j] == ChessType.NONE)
643 {
644 downright = 0;
645 break;
646 }
647 else if (chessBoard[i, j] == tmpType)
648 {
649 limitType = tmpType;
650 break;
651 }
652 else
653 {
654 downright++;
655 }
656 limitType = chessBoard[i, j];
657 }
658 if (limitType != tmpType) // 搜索至边界无同色棋子,不反色
659 {
660 downright = 0;
661 }
662 }
663 #endregion
664 #endregion
665 int totalCount = upleft + up + upright + left + right + downleft + down + downright;
666 return totalCount;
667 }
668
669 /// <summary>
670 /// 得到当前棋局的信息
671 /// </summary>
672 /// <param name="firstCount">玩家一的棋子数</param>
673 /// <param name="secondCount">玩家二的棋子数</param>
674 /// <param name="firstAvailablePosLeft">玩家一能下的位置的个数</param>
675 /// <param name="secondAvailablePosLeft">玩家二能下的位置的个数</param>
676 private void getCurrentInfo(out int firstCount, out int secondCount, out int firstAvailablePosLeft, out int secondAvailablePosLeft)
677 {
678 firstCount = 0;
679 secondCount = 0;
680 firstAvailablePosLeft = 0;
681 secondAvailablePosLeft = 0;
682 for (int i = 0; i < this.boardSize; i++)
683 {
684 for (int j = 0; j < this.boardSize; j++)
685 {
686 if (chessBoard[i, j] == ChessType.FIRST)
687 {
688 firstCount++;
689 }
690 else if (chessBoard[i, j] == ChessType.SECOND)
691 {
692 secondCount++;
693 }
694 else if (chessBoard[i, j] == ChessType.NONE) // 判断当前位置双方可否下子
695 {
696 int firstAffected = CountAffectedChess(i, j, ChessType.FIRST);
697 int secondAffected = CountAffectedChess(i, j, ChessType.SECOND);
698 if (firstAffected > 0)
699 {
700 ++firstAvailablePosLeft;
701 }
702 if (secondAffected > 0)
703 {
704 ++secondAvailablePosLeft;
705 }
706 }
707 }
708 }
709 }
710 #endregion
711 }
712 }
713
5. AIOthelloManager继承自OthelloManager,复用了所有黑白棋的规则。同时实现IAIChess接口,加入了AI功能。
1 using System;
3 using System.Linq;
4 using System.Text;
5 using System.Drawing;
6
7 namespace MyChess
8 {
9 public class AIOthelloManager : OthelloManager, IAIChess
10 {
11 #region IAIChess Members
12
13 /// <summary>
14 /// 贪心法 结合抢边角算法
15 /// 不是最优算法
16 /// </summary>
17 /// <returns></returns>
18 public Point getAIposition()
19 {
20 Point ret = new Point(-1, -1);
21
22 // TODO 改为胜者树更好
23 float maxAffected = 0;
24 for (int i = 0; i < this.boardSize; i++)
25 {
26 for (int j = 0; j < this.boardSize; j++)
27 {
28 if (chessBoard[i, j] != ChessType.NONE) // 无法落子
29 continue;
30 float curAffected = CountAffectedChess(i, j, this.currentType);
31 if (curAffected <= 0)
32 continue;
33
34 #region 计算加权
35 float factor = 1.0f;
36 if (i == 0 || i == this.boardSize - 1) // 2个边
37 {
38 if (j == i) // 4个角
39 factor = 40.0f;
40 else if (j == 1 || j == this.boardSize - 2)
41 factor = 0.01f;
42 else
43 factor = 20.0f; // 2个边(非角)
44 }
45 else if (j == 0 || j == this.boardSize - 1) // 另外2个边(非角)
46 {
47 factor = 20.0f;
48 }
49 else if ((i == j || i + j == this.boardSize - 1) && (i == 2 || i == this.boardSize - 2)) // 1~8*1~8 (2, 2)(7, 7)(2, 7)(7, 2)点位置不佳
50 {
51 factor = 0.01f;
52 }
53
54 #endregion
55 curAffected = curAffected * factor;
56
57 if (curAffected > maxAffected)
58 {
59 ret.X = i; ret.Y = j;
60 maxAffected = curAffected;
61 }
62 }
63 }
64 return ret;
65 // throw new NotImplementedException();
66 }
67
68 #endregion
69 }
70 }
71
6. 客户端调用方式:理论上ChessManager可以被任意种类客户端调用,如Web或WPF。作者选用Form作为客户端,Form类中包含一个ChessManager。
如下代码即体现了解耦:
2{
3 if (chessManager is IAIChess)
4 {
5 processAI();
6 this.Invalidate();
7 }
8}
通过判断chessManager is IAIChess,将AI类与非AI类区分开来,至于其内部处理流程均在各自类里面处理,如此程序的可扩展性就体现出来了。
完整代码如下:
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9
10 namespace MyChess
11 {
12 public partial class Form1 : Form
13 {
14 private Timer aiTimer = new Timer();
15
16 public Form1()
17 {
18 InitializeComponent();
19
20 aiTimer.Interval = 500;
21 aiTimer.Tick += new EventHandler(aiTimer_Tick);
22
23 //// 五子棋
24 //chessManager = new FiveChessManager();
25 //// 黑白棋
26 ////chessManager = new OthelloManager();
27 //chessManager.winning += new WinningHandler(chessManager_winning);
28 //chessManager.Init();
29 }
30
31 void aiTimer_Tick(object sender, EventArgs e)
32 {
33 #region AI
34 if (chessManager != null && chessManager.IsAlive)
35 {
36 if (chessManager is IAIChess)
37 {
38 processAI();
39 this.Invalidate();
40 }
41 }
42 #endregion
43 aiTimer.Stop();
44 }
45
46 void chessManager_winning(ChessType winner)
47 {
48 // 刷新以显示最后一个棋子
49 this.Invalidate();
50
51 string tipMsg = string.Empty;
52 if (winner == ChessType.NONE)
53 return;
54 else if (winner == ChessType.FIRST)
55 tipMsg = "First win!";
56 else if (winner == ChessType.SECOND)
57 tipMsg = "Second win!";
58 else if (winner == ChessType.BOTH)
59 tipMsg = "Draw game!";
60 MessageBox.Show(tipMsg, "Tip");
61 }
62
63 ChessManager chessManager;
64
65 private void Form1_Paint(object sender, PaintEventArgs e)
66 {
67 if (chessManager == null)
68 return;
69 chessManager.Render(e.Graphics);
70 }
71
72 private void Form1_MouseClick(object sender, MouseEventArgs e)
73 {
74 Point tmpLocation = e.Location;
75 if (aiTimer.Enabled) // AI正进下棋
76 return;
77 if (chessManager == null || !chessManager.IsAlive)
78 return;
79 #region 坐标映射
80 {
81 if (tmpLocation.X < Common.leftBound
82 || tmpLocation.X > Common.leftBound + Common.diameter * (chessManager.BoardSize + 0.5)
83 || tmpLocation.Y < Common.topBound
84 || tmpLocation.Y > Common.topBound + Common.diameter * (chessManager.BoardSize + 0.5)
85 )
86 {
87 return;
88 }
89
90 int tx, ty;
91 tx = (int)Math.Round((tmpLocation.Y - Common.topBound) / Common.diameter - 0.5);
92 ty = (int)Math.Round((tmpLocation.X - Common.leftBound) / Common.diameter - 0.5);
93
94 bool isSuccess; // 下子是否成功
95 isSuccess = chessManager.AddChess(tx, ty);
96
97 #region AI
98 if (isSuccess && chessManager is IAIChess)
99 {
100 aiTimer.Start();
101 }
102 #endregion
103 }
104 #endregion
105
106 this.Invalidate();
107 }
108
109 private void btnShuffle_Click(object sender, EventArgs e)
110 {
111 chessManager.Shuffle();
112 this.Invalidate();
113 }
114
115 private void btnClear_Click(object sender, EventArgs e)
116 {
117 if (chessManager == null)
118 return;
119 chessManager.Init();
120 this.Invalidate();
121 }
122
123 private void btnPass_Click(object sender, EventArgs e)
124 {
125 if (chessManager == null || !chessManager.IsAlive)
126 return;
127 if (!chessManager.Pass())
128 {
129 MessageBox.Show("当前形势下不能弃权");
130 return;
131 }
132 if (chessManager is IAIChess)
133 aiTimer.Start();
134 }
135
136 /// <summary>
137 /// 处理AI
138 /// </summary>
139 private void processAI()
140 {
141 Point aiChoice = ((IAIChess)chessManager).getAIposition();
142 if (aiChoice.X < 0 || aiChoice.Y < 0) // Computer Pass
143 {
144 bool canPass = chessManager.Pass();
145 if (canPass)
146 {
147 aiTimer.Stop();
148 MessageBox.Show("AI pass");
149 }
150 else
151 throw new Exception("AI算法失效");
152 }
153 else
154 {
155 chessManager.AddChess(aiChoice.X, aiChoice.Y);
156 aiTimer.Stop();
157 }
158 }
159
160 private void btnFive_Click(object sender, EventArgs e)
161 {
162 this.BackColor = Color.Orange;
163 // 五子棋
164 chessManager = new FiveChessManager();
165 chessManager.winning += new WinningHandler(chessManager_winning);
166 chessManager.Init();
167 this.Invalidate();
168 }
169
170 private void btnOthello_Click(object sender, EventArgs e)
171 {
172 this.BackColor = Color.Green;
173
174 // 黑白棋
175 chessManager = new OthelloManager();
176 chessManager.winning += new WinningHandler(chessManager_winning);
177 chessManager.Init();
178 this.Invalidate();
179 }
180
181 private void btnFiveAI_Click(object sender, EventArgs e)
182 {
183 this.BackColor = Color.Orange;
184
185 chessManager = new AIFiveChessManager();
186 chessManager.winning += new WinningHandler(chessManager_winning);
187 chessManager.Init();
188 this.Invalidate();
189 }
190
191 private void btnOthelloAI_Click(object sender, EventArgs e)
192 {
193 this.BackColor = Color.Green;
194
195 chessManager = new AIOthelloManager();
196 chessManager.winning += new WinningHandler(chessManager_winning);
197 chessManager.Init();
198 this.Invalidate();
199 }
200 }
201 }
202
三、总结
作者对本程序的思想都在类图中。遵循了具体依赖于抽象、高层依赖于抽象的原则。因作者水平有限,设计、编程存在诸多不足之处,望网友多多拍砖。