现代程序设计 homework-06
写代码爽还是读代码爽?
当然是写代码爽好吧...
读代码明显是读+写两倍的工作量好么...
本次作业要求:
1) 把程序编译通过, 跑起来。
读懂程序,在你觉得比较难懂的地方加上一些注释,这样大家就能比较容易地了解这些程序在干什么。
把正确的 playPrev(GoMove) 的方法给实现了。 如果大家不会下围棋,那就需要大家实地或者上网练习一下围棋的死活,提子是怎么回事。这个应该一个小时就能搞定。
注释的问题放到后面的问题有统一解决,这里先实现PlayPrev方法.
通过仔细研读代码(¥#@#¥@#¥!#¥&8),我们可以知道这个GoMove里面的DeadGroup存的是被吃掉的子,那么我们就需要把它们的位置都恢复,我们也可以知道上一步GoMove可以通过一个队列的gameTree.PeekPrev获得,辛苦这里命名还比较易懂,否则真的不知道要找到什么时候(如果不那么蛋疼的把注释全部编程Z相信会更好),我们还可以知道RepaintOneSpotNow是重绘方法,那么再加上一些细节问题就可以实现playPrev方法了
1 public void PlayPrev(GoMove gm) 2 { 3 if (gm == null) 4 { 5 throw new ArgumentNullException("gm"); 6 } 7 Point p = gm.Point;//获得当前要移除的点 8 m_colorToPlay = gm.Color;//要移除的点的颜色 9 ClearLabelsAndMarksOnBoard();//清除highlight信息和落子信息 10 Grid[p.X, p.Y].RemoveStone();//remove the current move from the board 11 bDrawMark = false;//also remove the "lastmove" highlight 12 RepaintOneSpotNow(p); 13 if (gm.DeadGroup != null)//恢复被吃掉的子 14 { 15 for (int i = 0; i <gm.DeadGroup.Count; i++) 16 { 17 Point pp = (Point)gm.DeadGroup[i]; 18 RepaintOneSpotNow(pp); 19 Grid[pp.X, pp.Y].SetStone(gm.DeadGroupColor); 20 } 21 22 } 23 m_gmLastMove = gameTree.PeekPrev(); 24 if (m_gmLastMove != null)//highlight the new "lastmove" 25 { 26 bDrawMark = true; 27 RepaintOneSpotNow(m_gmLastMove.Point); 28 SetLabelsOnBoard(m_gmLastMove); 29 SetMarksOnBoard(m_gmLastMove); 30 } 31 OptRepaint(); 32 }
机智的我制作了GIF动态图来展现playPrev方法的实现效果
关于围棋的死活,我用了一个多小时的事件来学习围棋的规则,主要是参照了必应搜索的第一篇百度文档http://wenku.baidu.com/view/a7d18903cc17552707220831.html,讲的很不错,针对本次作业主要有以下几点规则需要特别注意:
- 执黑现行
- 禁着点不能下子,所谓禁着点,是指周围没有气,又不能吃掉对方子的地方
2)根据你选择的教材 (三本之一或更多),点评一下这个程序设计方面的不足,例如:
编码风格,
程序架构,有哪些不符合良好的设计,这个程序的设计模式 (MVC等) 是高端大气国际化的么? 等等。
程序的错误处理,文件处理,UI 等等
编码风格:
很明显这个程序的命名规则不够明确,既有大小写混用的骆驼式命名法(的确很形象),也有下划线这种匈牙利命名法,所以看的时候很不协调。按照我的命名习惯,我个人比较倾向于骆驼式命名法,不过VS的代码分析是推荐我们使用帕斯卡命名法的,这个我们等会儿再说
程序架构:
说到这个程序的设计模式,的确是不那么高大上,MVC模式没有得到贯彻实施,一个文件中穿插了大量的游戏逻辑+文件操作+前端界面绘制,所以读代码的时候一会儿是UI一会儿是逻辑的看起来很难受,而我们的MVC设计模式强调的是业务逻辑和数据显示分离的方法,并且这样一个小的工程明显不适合用一个源文件来写,完全可以UI部分与游戏逻辑分开,将游戏逻辑的各个类单独开一个.cs,这样无论是读代码还是后期添加功能都会方便很多
程序的错误处理,文件处理,UI 等等
程序的文件处理明显是在赶工好么..比如打开一个棋谱文件这一段,
1 private void OpenFile() 2 { 3 OpenFileDialog openDlg = new OpenFileDialog(); 4 openDlg.Filter = "sgf files (*.sgf)|*.sgf|All Files (*.*)|*.*"; 5 openDlg.FileName = "" ; 6 openDlg.DefaultExt = ".sgf"; 7 openDlg.CheckFileExists = true; 8 openDlg.CheckPathExists = true; 9 10 DialogResult res = openDlg.ShowDialog (); 11 12 if(res == DialogResult.OK) 13 { 14 if( !(openDlg.FileName).EndsWith(".sgf") && !(openDlg.FileName).EndsWith(".SGF")) 15 MessageBox.Show("Unexpected file format","Super Go Format",MessageBoxButtons.OK); 16 else 17 { 18 FileStream f = new FileStream(openDlg.FileName, FileMode.Open); 19 StreamReader r = new StreamReader(f); 20 string s = r.ReadToEnd(); 21 gameTree = new GoTree(s); 22 gameTree.reset(); 23 resetBoard(); 24 r.Close(); 25 f.Close(); 26 } 27 } 28 }
根本没有检测文件路径是否正确好么。。。然后某人果断就将布尔量设为true了么...所以说这里用一个try-catch块来处理会好很多
还有saveFile这一块,原程序中并没有写,但就给出的接口来看,根本没有办法写貌似,尝试了多次也没有提取到程序中保存整个逻辑的信息段,而比如gm这种变量程序并没有作为全局变量出现,如果我要将它改成全局变量那么会牵扯许多代码的更改,得不偿失.
至于UI,感觉还是可以的..毕竟我也没有怎么玩过围棋游戏,绘成这个样子还是可以的,但是有一点问题就是UI的很多控件并没有设置Lock和Anchor属性,这导致了窗体大小改变的时候很违和,
而这个问题想要解决只需要简单设置一下Anchor属性就好,当然为了美观最好也重新改进一下左边棋盘的绘制,不要把尺寸写死,而应该根据窗体的大小来更改相应尺寸(工作量比较大先不进行了)
3)把Code Analysis 报告的所有问题给解决了。
不忍吐槽这个任务。。。
扫描之后需要改进的地方有150项左右。。。
细细分析一下大概有以下几种类型:
- CA1709:标识符的大小写应当正确.这种问题属于命名的问题,于是索性一股脑的全部采用了帕斯卡命名法
- CA1028:枚举存储应为 Int32.这个问题就不用改了吧...感觉好死板
- CA1814:与多维数组相比,首选使用交错的数组.这里如果浪费很多控件会建议使用交错数组,但源程序中明显数组覆盖率为100%,所以不需要修改
- CA1823:避免未使用的私有字段.许多没有用过的属性方法什么的果断注释掉
- CA1303:不要将文本作为本地化参数传递.本地化的问题,关于何时取消显示这一警告,MSDN的说法是:
-
如果将不会本地化代码库,或者如果字符串未公开给使用该代码库的最终用户或开发人员,则可以安全地禁止显示此规则发出的警告。
通过重命名已命名的参数或属性或者通过将这些项设置为条件项,用户可以消除不应传递本地化字符串的方法的影响。
果断[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:请不要将文本作为本地化参数传递", MessageId = "System.Windows.Forms.Control.set_Text(System.String)")]或者直接从文件中取消显示
- CA1707:标识符不应包含下划线.这个真的是很奇葩,由于控件的响应事件默认都是采用匈牙利命名规则的,所以改起来还是很麻烦的,将匈牙利命名改成帕斯卡命名即可
- CA2000:超出范围前释放对象.这个可以用using块来解决,其实由于很多地方确实马上释放了,所以也不妨取消显示这个错误
还有一些其他警告,采取各种方式消除它..最后效果如下:
4) 程序的注释
所有人都觉得注释很重要,写程序不写注释的同学真是RP 比较低。。。
那么,就请把这个程序中被标成 “zzzz” 的注释都恢复过来。 当然,你可以用中文写注释。
为什么。。。要把注释都搞成这个样子。。。而且这个逆向工程明显更像是在猜好么。。。不过。。。机智的我还是把他们改好了。。。
工程全部代码如下:
1 /** 2 * Go Applet 3 * 1996.11 xinz written in Java 4 * 2001.3 xinz port to C# 5 * 2001.5.10 xinz file parsing, back/forward 6 */ 7 8 using System; 9 using System.Drawing; 10 using System.Collections; 11 using System.ComponentModel; 12 using System.Windows.Forms; 13 using System.Data; 14 using System.IO; 15 using System.Diagnostics; 16 using System.Resources; 17 [assembly: CLSCompliant(true)] 18 namespace DesignLibrary { } 19 20 namespace GoWinApp 21 { 22 23 public enum StoneColor : int //黑子白子 24 { 25 Black = 0, White = 1 26 } 27 28 29 /** 30 * 呵呵呵 31 */ 32 public class GoBoard : System.Windows.Forms.Form 33 { 34 string [] strLabels; // {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T"}; 35 36 int nSize; //每行每列方格数 19 37 const int nBoardMargin = 10; //边线距离 10 38 int nCoodStart = 4; 39 const int nBoardOffset = 20; //棋盘与左边距离 20 40 int nEdgeLen = nBoardOffset + nBoardMargin; //棋盘右下角的横纵长度 41 int nTotalGridWidth = 360 + 36; //方格总大小 42 int nUnitGridWidth = 22; //每个小方格的大小 43 int nSeq = 0; 44 Rectangle rGrid; // rectangle for 整个棋盘 45 StoneColor m_colorToPlay; // 接下来要走的旗子颜色 46 GoMove m_gmLastMove; // 上一步动作 47 Boolean bDrawMark; // 是否highlight高亮显示(话说高亮做的好挫..) 48 Boolean m_fAnyKill; // 是否有吃子动作 49 Spot [,] Grid; // 记录棋盘状态的二维数组 50 Pen penGrid; //各种色笔用来画图 51 //被删掉了, penStoneW, penStoneB,penMarkW, penMarkB 52 Brush brStar, brBoard, brBlack, brWhite, m_brMark; //各种画刷用来画图 53 54 // >>Button <<Button 55 int nFFMove = 10; // 限制>>Button的最大操作数 56 // int nRewindMove = 10; // 限制<<Button的最大操作数,但1.这个变量没有使用过 2.<<Button click的时候直接就reset了,所以根本没用 57 58 GoTree gameTree; //游戏逻辑部分 59 60 /// 各种UI变量声明 61 // private System.ComponentModel.Container components; 62 private System.Windows.Forms.TextBox textBox1; 63 private System.Windows.Forms.Button Rewind; 64 private System.Windows.Forms.Button FForward; 65 private System.Windows.Forms.Button Save; 66 private System.Windows.Forms.Button Open; 67 private System.Windows.Forms.Button Back; 68 private System.Windows.Forms.Button Forward; 69 70 public GoBoard(int nSize) 71 { 72 // 73 // Form第一步,初始化各种组件 74 // 75 InitializeComponent(); 76 77 // 78 // 各种初始化操作 79 // 80 81 this.nSize = nSize; //设定棋盘大小 82 83 m_colorToPlay = StoneColor.Black; //执黑先行 84 85 Grid = new Spot[nSize,nSize]; //new一个Grid存状态 86 for (int i=0; i<nSize; i++) 87 for (int j=0; j<nSize; j++) 88 Grid[i,j] = new Spot(); 89 /*------以下各种初始化画笔画刷------*/ 90 penGrid = new Pen(Color.Brown, (float)0.5); 91 //penStoneW = new Pen(Color.WhiteSmoke, (float)1); 92 //penStoneB = new Pen(Color.Black,(float)1); 93 //penMarkW = new Pen(Color.Blue, (float) 1); 94 //penMarkB = new Pen(Color.Beige, (float) 1); 95 96 brStar = new SolidBrush(Color.Black); 97 brBoard = new SolidBrush(Color.Orange); 98 brBlack = new SolidBrush(Color.Black); 99 brWhite = new SolidBrush(Color.White); 100 m_brMark = new SolidBrush(Color.Red); 101 102 rGrid = new Rectangle(nEdgeLen, nEdgeLen,nTotalGridWidth, nTotalGridWidth); 103 strLabels = new string [] {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"}; 104 gameTree = new GoTree(); 105 } 106 /*------绘制UI------*/ 107 #region 108 /// 动态绘制各种组件,没什么好说的 109 /// 110 private void InitializeComponent() 111 { 112 this.Open = new System.Windows.Forms.Button(); 113 this.Save = new System.Windows.Forms.Button(); 114 this.Rewind = new System.Windows.Forms.Button(); 115 this.Forward = new System.Windows.Forms.Button(); 116 this.Back = new System.Windows.Forms.Button(); 117 this.FForward = new System.Windows.Forms.Button(); 118 this.textBox1 = new System.Windows.Forms.TextBox(); 119 this.SuspendLayout(); 120 // 121 // Open 122 // 123 this.Open.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 124 this.Open.Location = new System.Drawing.Point(534, 95); 125 this.Open.Name = "Open"; 126 this.Open.Size = new System.Drawing.Size(67, 25); 127 this.Open.TabIndex = 2; 128 this.Open.Text = "open"; 129 this.Open.Click += new System.EventHandler(this.OpenClick); 130 // 131 // Save 132 // 133 this.Save.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 134 this.Save.Location = new System.Drawing.Point(611, 95); 135 this.Save.Name = "Save"; 136 this.Save.Size = new System.Drawing.Size(67, 25); 137 this.Save.TabIndex = 3; 138 this.Save.Text = "save"; 139 this.Save.Click += new System.EventHandler(this.SaveClick); 140 // 141 // Rewind 142 // 143 this.Rewind.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 144 this.Rewind.Location = new System.Drawing.Point(611, 60); 145 this.Rewind.Name = "Rewind"; 146 this.Rewind.Size = new System.Drawing.Size(67, 25); 147 this.Rewind.TabIndex = 5; 148 this.Rewind.Text = "<<"; 149 this.Rewind.Click += new System.EventHandler(this.RewindClick); 150 // 151 // Forward 152 // 153 this.Forward.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 154 this.Forward.Location = new System.Drawing.Point(534, 26); 155 this.Forward.Name = "Forward"; 156 this.Forward.Size = new System.Drawing.Size(67, 25); 157 this.Forward.TabIndex = 0; 158 this.Forward.Text = ">"; 159 this.Forward.Click += new System.EventHandler(this.ForwardClick); 160 // 161 // Back 162 // 163 this.Back.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 164 this.Back.Location = new System.Drawing.Point(611, 26); 165 this.Back.Name = "Back"; 166 this.Back.Size = new System.Drawing.Size(67, 25); 167 this.Back.TabIndex = 1; 168 this.Back.Text = "<"; 169 this.Back.Click += new System.EventHandler(this.BackClick); 170 // 171 // FForward 172 // 173 this.FForward.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 174 this.FForward.Location = new System.Drawing.Point(534, 60); 175 this.FForward.Name = "FForward"; 176 this.FForward.Size = new System.Drawing.Size(67, 25); 177 this.FForward.TabIndex = 4; 178 this.FForward.Text = ">>"; 179 this.FForward.Click += new System.EventHandler(this.FForwardClick); 180 // 181 // textBox1 182 // 183 this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 184 this.textBox1.Location = new System.Drawing.Point(536, 138); 185 this.textBox1.Multiline = true; 186 this.textBox1.Name = "textBox1"; 187 this.textBox1.Size = new System.Drawing.Size(144, 335); 188 this.textBox1.TabIndex = 6; 189 this.textBox1.Text = "please open a .sgf file to view, or just play on the board"; 190 // 191 // GoBoard 192 // 193 this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); 194 this.AutoScroll = true; 195 this.ClientSize = new System.Drawing.Size(784, 561); 196 this.Controls.Add(this.textBox1); 197 this.Controls.Add(this.Rewind); 198 this.Controls.Add(this.FForward); 199 this.Controls.Add(this.Save); 200 this.Controls.Add(this.Open); 201 this.Controls.Add(this.Back); 202 this.Controls.Add(this.Forward); 203 this.Name = "GoBoard"; 204 this.Text = "Go_WinForm"; 205 this.Click += new System.EventHandler(this.GoBoardClick); 206 this.Paint += new System.Windows.Forms.PaintEventHandler(this.PaintHandler); 207 this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.MouseUpHandler); 208 this.ResumeLayout(false); 209 this.PerformLayout(); 210 211 } 212 #endregion 213 214 private void PaintHandler(Object sender, PaintEventArgs e) 215 { 216 UpdateGoBoard(e); //更新棋盘 217 } 218 /*------Save功能都没有实现?------*/ 219 protected void SaveClick(object sender, System.EventArgs e) 220 { 221 SaveFile(); 222 return; 223 } 224 /*------话说这些简单的button响应事件就不用多说了吧?------*/ 225 protected void OpenClick(object sender, System.EventArgs e) 226 { 227 OpenFile(); 228 ShowGameInfo();//棋谱文件可能带有自述信息 229 } 230 #region 231 protected void RewindClick(object sender, System.EventArgs e) 232 { 233 gameTree.Reset();//<<Button需要完成游戏逻辑重置,棋盘重置,并重新显示游戏信息 234 ResetBoard(); 235 ShowGameInfo(); 236 } 237 238 protected void FForwardClick(object sender, System.EventArgs e) 239 { 240 if (gameTree != null) 241 { 242 int i = 0; 243 GoMove gm = null; 244 for (gm = gameTree.DoNext(); gm != null; gm = gameTree.DoNext()) 245 { 246 PlayNext(ref gm); 247 if (i++ > nFFMove)//将棋盘状态恢复到允许恢复到的最新状态 248 break; 249 } 250 } 251 } 252 253 protected void ForwardClick(object sender, System.EventArgs e) 254 { 255 GoMove gm = gameTree.DoNext(); 256 if (null != gm)//前进到有历史记录的下一个操作 257 { 258 PlayNext(ref gm); 259 } 260 } 261 private void ShowGameInfo() 262 { 263 //显示游戏信息 264 textBox1.Clear(); 265 textBox1.AppendText(gameTree.Info); 266 } 267 268 protected void BackClick(object sender, System.EventArgs e) 269 { 270 GoMove gm = gameTree.DoPrev(); //游戏历史记录中的前一步 271 if (null != gm) 272 { 273 PlayPrev(gm); 274 } 275 else 276 { 277 ResetBoard(); 278 ShowGameInfo(); 279 } 280 } 281 282 Boolean OnBoard(int x, int y) //边界处理 283 { 284 return (x>=0 && x<nSize && y>=0 && y<nSize); 285 } 286 /*------又在没用可删的范畴内------*/ 287 protected void GoBoardClick(object sender, System.EventArgs e) 288 { 289 return; 290 } 291 /*------将坐标转换为棋盘中的图块------*/ 292 private Point PointToGrid(int x, int y) 293 { 294 Point p= new Point(0,0); 295 p.X = (x - rGrid.X + nUnitGridWidth/2) / nUnitGridWidth; 296 p.Y = (y - rGrid.Y + nUnitGridWidth/2) / nUnitGridWidth; 297 return p; 298 } 299 300 //设定了在相交点附近怎样的范围内松开鼠标就视为在此处落子 301 // 302 private Boolean CloseEnough(Point p, int x, int y) 303 { 304 if (x < rGrid.X+nUnitGridWidth*p.X-nUnitGridWidth/3 || 305 x > rGrid.X+nUnitGridWidth*p.X+nUnitGridWidth/3 || 306 y < rGrid.Y+nUnitGridWidth*p.Y-nUnitGridWidth/3 || 307 y > rGrid.Y+nUnitGridWidth*p.Y+nUnitGridWidth/3) 308 { 309 return false; 310 } 311 else 312 return true; 313 } 314 /// 鼠标松开事件,用来处理落子 315 private void MouseUpHandler(Object sender,MouseEventArgs e) 316 { 317 Point p; 318 GoMove gmThisMove; 319 320 p = PointToGrid(e.X,e.Y); 321 if (!OnBoard(p.X, p.Y) || !CloseEnough(p,e.X, e.Y)|| Grid[p.X,p.Y].HasStone()) 322 return; //不满足落子条件 323 324 //满足落子条件时,落子,并将thismove传到gametree中 325 gmThisMove = new GoMove(p.X, p.Y, m_colorToPlay, 0); 326 PlayNext(ref gmThisMove); 327 gameTree.AddMove(gmThisMove); 328 } 329 330 public void PlayNext(ref GoMove gm) 331 { 332 if (gm == null) 333 { 334 throw new ArgumentNullException("gm"); 335 } 336 Point p = gm.Point; //获得当前move点 337 m_colorToPlay = gm.Color; //接下来要下子的颜色 338 339 //清除highlight信息和落子信息 340 ClearLabelsAndMarksOnBoard(); 341 342 if (m_gmLastMove != null)//如果上一轮已经落子,那么取消该棋子的高亮显示 343 { 344 RepaintOneSpotNow(m_gmLastMove.Point); 345 } 346 bDrawMark = true; //高亮显示 347 Grid[p.X,p.Y].SetStone(gm.Color); //落子 348 m_gmLastMove = new GoMove(p.X, p.Y, gm.Color, nSeq++); //将本次操作记录在lastmove中 349 //棋盘显示所有的labels和mark 350 SetLabelsOnBoard(gm); 351 SetMarksOnBoard(gm); 352 353 DoDeadGroup(NextTurn(m_colorToPlay));//nextturn返回下一轮的行子颜色,这一操作即进行吃子动作,注意先判定本轮落子能否先吃对方 354 //如果有吃子的动作,那么就存入deadgroup中 355 if (m_fAnyKill) 356 { 357 AppendDeadGroup(ref gm, NextTurn(m_colorToPlay)); 358 } 359 else //否则要判是否会被吃 360 { 361 DoDeadGroup(m_colorToPlay); 362 if (m_fAnyKill) 363 { 364 AppendDeadGroup(ref gm, m_colorToPlay); 365 } 366 } 367 m_fAnyKill = false; 368 369 OptRepaint();//重绘棋盘 370 371 //更新下一轮落子颜色 372 m_colorToPlay = NextTurn(m_colorToPlay); 373 374 //......更新游戏信息,这里其实可以重写showgameinfo() 375 textBox1.Clear(); 376 textBox1.AppendText(gm.Comment); 377 } 378 /*------添加被吃的颜色为c的子------*/ 379 private void AppendDeadGroup(ref GoMove gm, StoneColor c) 380 { 381 ArrayList a = new ArrayList(); 382 for (int i=0; i<nSize; i++) 383 for (int j=0; j<nSize; j++) 384 if (Grid[i,j].IsKilled()) 385 { 386 Point pt = new Point(i,j); 387 a.Add(pt); 388 Grid[i,j].SetNoKilled();//这里...真的有必要封装成这个样子么.. 389 } 390 gm.DeadGroup = a;//存入本次动作gm中,gm.deadgroup就存放了本轮被吃的子,于是playprev可以用到 391 gm.DeadGroupColor = c; 392 } 393 /*------重绘棋盘------*/ 394 public void ResetBoard() 395 { 396 int i,j; 397 for (i=0; i<nSize; i++) 398 for (j=0; j<nSize; j++) 399 Grid[i,j].RemoveStone(); 400 m_gmLastMove = null; 401 Invalidate(null); 402 } 403 404 /* 405 * play the move so that the game situation is just BEFORE this move is played. 406 * what to do: 407 * 1. remove the current move from the board :removestone 408 * 1.1 also remove the "lastmov" highlight :bDrawMark=false; 409 * 2. store the stones got killed by current move 410 * 3. highlight the new "lastmove" :bDrawMark=true 411 */ 412 public void PlayPrev(GoMove gm) 413 { 414 if (gm == null) 415 { 416 throw new ArgumentNullException("gm"); 417 } 418 Point p = gm.Point;//获得当前要移除的点 419 m_colorToPlay = gm.Color;//要移除的点的颜色 420 ClearLabelsAndMarksOnBoard();//清除highlight信息和落子信息 421 Grid[p.X, p.Y].RemoveStone();//remove the current move from the board 422 bDrawMark = false;//also remove the "lastmove" highlight 423 RepaintOneSpotNow(p); 424 if (gm.DeadGroup != null)//恢复被吃掉的子 425 { 426 for (int i = 0; i <gm.DeadGroup.Count; i++) 427 { 428 Point pp = (Point)gm.DeadGroup[i]; 429 RepaintOneSpotNow(pp); 430 Grid[pp.X, pp.Y].SetStone(gm.DeadGroupColor); 431 } 432 433 } 434 m_gmLastMove = gameTree.PeekPrev(); 435 if (m_gmLastMove != null)//highlight the new "lastmove" 436 { 437 bDrawMark = true; 438 RepaintOneSpotNow(m_gmLastMove.Point); 439 SetLabelsOnBoard(m_gmLastMove); 440 SetMarksOnBoard(m_gmLastMove); 441 } 442 OptRepaint(); 443 } 444 445 446 447 Rectangle GetUpdatedArea(int i, int j) //返回需要更新重绘的区域 448 { 449 int x = rGrid.X + i * nUnitGridWidth - nUnitGridWidth/2; 450 int y = rGrid.Y + j * nUnitGridWidth - nUnitGridWidth/2; 451 return new Rectangle(x,y, nUnitGridWidth, nUnitGridWidth); 452 } 453 454 /** 455 * 重绘 456 */ 457 private void OptRepaint() 458 { 459 Rectangle r = new Rectangle(0,0,0,0); 460 Region re; 461 462 for (int i=0; i<nSize; i++) 463 for (int j=0; j<nSize; j++) 464 if (Grid[i,j].IsUpdated()) 465 { 466 r = GetUpdatedArea(i,j); 467 re = new Region(r); 468 Invalidate(re); 469 re.Dispose(); 470 } 471 } 472 473 /* 474 * 只重回一个交叉点,用在本轮已经落子需要进行>>或者<<操作的时候 475 */ 476 public void RepaintOneSpotNow(Point point) 477 { 478 Grid[point.X, point.Y].SetUpdated(); 479 bDrawMark = false; 480 Rectangle r = GetUpdatedArea(point.X, point.Y); 481 Region re=new Region(r); 482 Invalidate(re); 483 re.Dispose(); 484 Grid[point.X, point.Y].ResetUpdated(); 485 bDrawMark = true; 486 } 487 488 //字面意思是记录操作,但是这个函数没有用到过,很可疑 489 public void RecordMove(Point point, StoneColor colorToPlay) 490 { 491 Grid[point.X, point.Y].SetStone(colorToPlay); 492 // 将上一个操作置为该次操作 493 m_gmLastMove = new GoMove(point.X, point.Y, colorToPlay, nSeq++); 494 } 495 //返回下次落子的颜色 496 static StoneColor NextTurn(StoneColor c) 497 { 498 if (c == StoneColor.Black) 499 return StoneColor.White; 500 else 501 return StoneColor.Black; 502 } 503 504 /** 505 * bury the dead stones in a group (same color). 506 * if a stone in one group is dead, the whole group is dead. 507 * 说白了就是dfs消除相连的气为0的子 508 */ 509 void BuryTheDead(int i, int j, StoneColor c) 510 { 511 if (OnBoard(i,j) && Grid[i,j].HasStone() && 512 Grid[i,j].Color() == c) 513 { 514 Grid[i,j].Die(); 515 BuryTheDead(i-1, j, c); 516 BuryTheDead(i+1, j, c); 517 BuryTheDead(i, j-1, c); 518 BuryTheDead(i, j+1, c); 519 } 520 } 521 /*------清除扫描记录,就是清除visit数组的意思------*/ 522 void CleanScanStatus() 523 { 524 int i,j; 525 for (i=0; i<nSize; i++) 526 for (j=0; j<nSize; j++) 527 Grid[i,j].ClearScanned(); 528 } 529 530 /** 531 * 扫描整个棋盘,对当前颜色c,提掉所有气为0的子 532 */ 533 void DoDeadGroup(StoneColor c) 534 { 535 int i,j; 536 for (i=0; i<nSize; i++) 537 for (j=0; j<nSize; j++) 538 if (Grid[i,j].HasStone() && 539 Grid[i,j].Color() == c) 540 { 541 if (CalcLiberty(i,j,c) == 0) 542 { 543 BuryTheDead(i,j,c); 544 m_fAnyKill = true; 545 } 546 CleanScanStatus(); 547 } 548 } 549 /*------非递归BFS实现计算气------*/ 550 /* 551 int CalcLibertyBfs(int x, int y, StoneColor c) 552 { 553 int lib=0; 554 int[] dx = { 1, 0, -1, 0 }; 555 int[] dy = { 0, -1, 0, 1 }; 556 Queue q = new Queue(); 557 Point s=new Point(x,y); 558 Point next = new Point(); 559 q.Enqueue(s); 560 while (q.Count > 0) 561 { 562 s = (Point)q.Dequeue(); 563 for (int i = 0; i < 4; i++) 564 { 565 next.X = s.X + dx[i]; 566 next.Y = s.Y + dy[i]; 567 if (!OnBoard(next.X, next.Y)) 568 { 569 continue; 570 } 571 if (Grid[next.X, next.Y].IsScanned()) 572 { 573 continue; 574 } 575 if (!Grid[next.X, next.Y].HasStone()) 576 { 577 lib++; 578 } 579 if (Grid[next.X, next.Y].Color() == c) 580 { 581 q.Enqueue(next); 582 } 583 Grid[next.X, next.Y].SetScanned(); 584 } 585 } 586 return lib; 587 588 } 589 */ 590 /** 591 * dfs计算每个子(每个group)的气 592 */ 593 int CalcLiberty(int x, int y, StoneColor c) 594 { 595 int lib = 0; // 初始化 596 597 if (!OnBoard(x,y)) 598 return 0; 599 if (Grid[x,y].IsScanned()) 600 return 0; 601 602 if (Grid[x,y].HasStone()) 603 { 604 if (Grid[x,y].Color() == c) 605 { 606 //dfs深搜四个相邻的格子 607 Grid[x,y].SetScanned(); 608 lib += CalcLiberty(x-1, y, c); 609 lib += CalcLiberty(x+1, y, c); 610 lib += CalcLiberty(x, y-1, c); 611 lib += CalcLiberty(x, y+1, c); 612 } 613 else 614 return 0; 615 } 616 else 617 {// 周围没有棋子的话气+1 618 lib ++; 619 Grid[x,y].SetScanned(); 620 } 621 622 return lib; 623 } 624 625 626 /** 627 * 高亮显示上一次的落子 628 */ 629 void MarkLastMove(Graphics g) 630 { 631 Brush brMark; 632 if (m_gmLastMove.Color == StoneColor.White) 633 brMark = brBlack; 634 else 635 brMark = brWhite; 636 Point p = m_gmLastMove.Point; 637 g.FillRectangle( brMark, 638 rGrid.X + (p.X) * nUnitGridWidth - (nUnitGridWidth-1)/8, 639 rGrid.Y + (p.Y) * nUnitGridWidth - (nUnitGridWidth-1)/8, 640 3, 3); 641 } 642 /*------消除所有棋子和高亮标记------*/ 643 private void ClearLabelsAndMarksOnBoard() 644 { 645 for (int i=0; i<nSize; i++) 646 for (int j=0; j<nSize; j++) 647 { 648 if (Grid[i,j].HasLabel()) 649 Grid[i,j].ResetLabel(); 650 if (Grid[i,j].HasMark()) 651 Grid[i,j].ResetMark(); 652 } 653 654 } 655 /*------就是按照当前操作gm,将gm之前的labels全部落下------*/ 656 private void SetLabelsOnBoard(GoMove gm) 657 { 658 short nLabel = 0; 659 Point p; 660 if (null != gm.Labels) 661 { 662 System.Collections.IEnumerator myEnumerator = gm.Labels.GetEnumerator(); 663 while (myEnumerator.MoveNext()) 664 { 665 p = (Point)myEnumerator.Current; 666 Grid[p.X,p.Y].SetLabel(++nLabel); 667 } 668 } 669 } 670 /*------设置棋盘上的高亮显示------*/ 671 private void SetMarksOnBoard(GoMove gm) 672 { 673 Point p; 674 if (null != gm.Labels) 675 { 676 System.Collections.IEnumerator myEnumerator = gm.Marks.GetEnumerator(); 677 while ( myEnumerator.MoveNext() ) 678 { 679 p = (Point)myEnumerator.Current; 680 Grid[p.X,p.Y].SetMark(); 681 } 682 } 683 } 684 685 static private Point SwapXY(Point p)//额...交换两点坐标 686 { 687 Point pNew = new Point(p.Y,p.X); 688 return pNew; 689 } 690 /*------绘制棋盘------*/ 691 private void DrawBoard(Graphics g) 692 { 693 //绘制坐标 694 string[] strV= {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19"}; 695 string [] strH= {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T"}; 696 697 Point p1 = new Point(nEdgeLen,nEdgeLen); 698 Point p2 = new Point(nTotalGridWidth+nEdgeLen,nEdgeLen); 699 g.FillRectangle(brBoard,nBoardOffset,nBoardOffset,nTotalGridWidth+nBoardOffset,nTotalGridWidth+nBoardOffset); 700 for (int i=0;i<nSize; i++) 701 { 702 g.DrawString(strV[i],this.Font, brBlack, 0, nCoodStart+ nBoardOffset + nUnitGridWidth*i); 703 g.DrawString(strH[i],this.Font, brBlack, nBoardOffset + nCoodStart + nUnitGridWidth*i, 0); 704 g.DrawLine(penGrid, p1, p2); 705 g.DrawLine(penGrid, SwapXY(p1), SwapXY(p2)); 706 707 p1.Y += nUnitGridWidth; 708 p2.Y += nUnitGridWidth; 709 } 710 //绘制边线 711 Pen penHi = new Pen(Color.WhiteSmoke, (float)0.5); 712 Pen penLow = new Pen(Color.Gray, (float)0.5); 713 714 g.DrawLine(penHi, nBoardOffset, nBoardOffset, nTotalGridWidth+2*nBoardOffset, nBoardOffset); 715 g.DrawLine(penHi, nBoardOffset, nBoardOffset, nBoardOffset, nTotalGridWidth+2*nBoardOffset); 716 g.DrawLine(penLow, nTotalGridWidth+2*nBoardOffset,nTotalGridWidth+2*nBoardOffset, nBoardOffset+1, nTotalGridWidth+2*nBoardOffset); 717 g.DrawLine(penLow, nTotalGridWidth+2*nBoardOffset,nTotalGridWidth+2*nBoardOffset, nTotalGridWidth+2*nBoardOffset, nBoardOffset+1); 718 } 719 720 void UpdateGoBoard(PaintEventArgs e) 721 { 722 DrawBoard(e.Graphics); 723 724 //绘制天元 725 DrawStars(e.Graphics); 726 727 //绘制每一个区域 728 DrawEverySpot(e.Graphics); 729 } 730 731 //绘制天元 732 void DrawStar(Graphics g, int row, int col) 733 { 734 g.FillRectangle(brStar, 735 rGrid.X + (row-1) * nUnitGridWidth - 1, 736 rGrid.Y + (col-1) * nUnitGridWidth - 1, 737 3, 738 3); 739 } 740 741 //绘制9个天元 742 void DrawStars(Graphics g) 743 { 744 DrawStar(g, 4, 4); 745 DrawStar(g, 4, 10); 746 DrawStar(g, 4, 16); 747 DrawStar(g, 10, 4); 748 DrawStar(g, 10, 10); 749 DrawStar(g, 10, 16); 750 DrawStar(g, 16, 4); 751 DrawStar(g, 16, 10); 752 DrawStar(g, 16, 16); 753 } 754 755 /** 756 * 绘制落子 757 */ 758 void DrawStone(Graphics g, int row, int col, StoneColor c) 759 { 760 Brush br; 761 if (c == StoneColor.White) 762 br = brWhite; 763 else 764 br = brBlack; 765 766 Rectangle r = new Rectangle(rGrid.X+ (row) * nUnitGridWidth - (nUnitGridWidth-1)/2, 767 rGrid.Y + (col) * nUnitGridWidth - (nUnitGridWidth-1)/2, 768 nUnitGridWidth-1, 769 nUnitGridWidth-1); 770 771 g.FillEllipse(br, r); 772 } 773 /*------这个函数貌似没有调用过?------*/ 774 void DrawLabel(Graphics g, int x, int y, short nLabel) 775 { 776 if (nLabel ==0) 777 return; 778 nLabel --; 779 nLabel %= 18; //各种转换 780 781 //画Label 782 Rectangle r = new Rectangle(rGrid.X+ x * nUnitGridWidth - (nUnitGridWidth-1)/2, 783 rGrid.Y + y * nUnitGridWidth - (nUnitGridWidth-1)/2, 784 nUnitGridWidth-1, 785 nUnitGridWidth-1); 786 787 g.FillEllipse(brBoard, r); 788 g.DrawString(strLabels[nLabel], //填字符串 789 this.Font, 790 brBlack, 791 rGrid.X+ (x) * nUnitGridWidth - (nUnitGridWidth-1)/4, 792 rGrid.Y + (y) * nUnitGridWidth - (nUnitGridWidth-1)/2); 793 } 794 /*------绘制highlight点------*/ 795 void DrawMark(Graphics g, int x, int y) 796 { 797 g.FillRectangle( m_brMark, 798 rGrid.X + x* nUnitGridWidth - (nUnitGridWidth-1)/8, 799 rGrid.Y + y * nUnitGridWidth - (nUnitGridWidth-1)/8, 800 5, 5); 801 } 802 /*------绘制每一个落子点------*/ 803 void DrawEverySpot(Graphics g) 804 { 805 for (int i=0; i<nSize; i++) 806 for (int j=0; j<nSize; j++) 807 { 808 if (Grid[i,j].HasStone()) 809 DrawStone(g, i, j, Grid[i,j].Color()); 810 if (Grid[i,j].HasLabel()) 811 DrawLabel(g, i, j, Grid[i,j].GetLabel()); 812 if (Grid[i,j].HasMark()) 813 DrawMark(g, i, j); 814 } 815 //标记操作 816 if (bDrawMark && m_gmLastMove != null) 817 MarkLastMove(g); 818 } 819 #endregion 820 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:不要多次释放对象"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:请不要将文本作为本地化参数传递", MessageId = "System.Windows.Forms.MessageBox.Show(System.String,System.String,System.Windows.Forms.MessageBoxButtons)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:请不要将文本作为本地化参数传递", MessageId = "System.Windows.Forms.FileDialog.set_Filter(System.String)")] 821 private void SaveFile() 822 { 823 using (SaveFileDialog saveDlg = new SaveFileDialog()) 824 { 825 saveDlg.Filter = "sgf files (*.sgf)|*.sgf|All Files (*.*)|*.*"; 826 saveDlg.DefaultExt = ".sgf"; 827 DialogResult res = saveDlg.ShowDialog(); 828 if (res == DialogResult.OK) 829 { 830 if (!(saveDlg.FileName).EndsWith(".sgf") && !(saveDlg.FileName).EndsWith(".SGF")) 831 MessageBox.Show("Unexpected file format", "Super Go Format", MessageBoxButtons.OK); 832 else 833 { 834 using (StreamWriter w = new StreamWriter(saveDlg.FileName, false)) 835 { 836 string s = gameTree.Info;//这里应该递归掉gm的信息,但是gm目前不是全局变量. 837 w.WriteLine(s); 838 } 839 } 840 } 841 } 842 } 843 //打开棋谱文件 844 private void OpenFile() 845 { 846 OpenFileDialog openDlg = new OpenFileDialog(); 847 openDlg.Filter = "sgf files (*.sgf)|*.sgf|All Files (*.*)|*.*"; 848 openDlg.FileName = "" ; 849 openDlg.DefaultExt = ".sgf"; 850 openDlg.CheckFileExists = true; 851 openDlg.CheckPathExists = true; 852 853 DialogResult res = openDlg.ShowDialog (); 854 855 if(res == DialogResult.OK) 856 { 857 if (!(openDlg.FileName).EndsWith(".sgf") && !(openDlg.FileName).EndsWith(".SGF")) 858 MessageBox.Show("Unexpected file format", "Super Go Format", MessageBoxButtons.OK); 859 else 860 { 861 FileStream f = new FileStream(openDlg.FileName, FileMode.Open); 862 StreamReader r = new StreamReader(f); 863 string s = r.ReadToEnd(); 864 gameTree = new GoTree(s); 865 gameTree.Reset(); 866 ResetBoard(); 867 // r.Close(); 868 f.Close(); 869 } 870 } 871 } 872 } 873 874 public class GoTest 875 { 876 /// 877 /// main入口,单线程标记 878 /// 879 [STAThread] 880 public static void Main(string[] args) 881 { 882 Application.Run(new GoBoard(19)); 883 } 884 } 885 886 887 //Spot类代表每一个点 888 public class Spot 889 { 890 private Boolean bEmpty; //是否为空 891 private Boolean bKilled; //是否被吃 892 private Stone s; //落子属性 893 private short m_nLabel; 894 private Boolean m_bMark; //是否高亮 895 private Boolean bScanned; //是否被扫描过 896 private Boolean bUpdated; //是否已经被更新 897 /** 898 * 构造函数 899 */ 900 public Spot() 901 { 902 bEmpty = true; 903 bScanned = false; 904 bUpdated = false; 905 bKilled = false; 906 } 907 /*------简单各种方法------*/ 908 public Boolean HasStone() { return !bEmpty; } 909 public Boolean IsEmpty() { return bEmpty; } 910 public Stone ThisStone() { return s;} 911 public StoneColor Color() { return s.color;} 912 913 public Boolean HasLabel() {return m_nLabel>0;} 914 public Boolean HasMark() {return m_bMark;} 915 public void SetLabel(short label) {m_nLabel = label; bUpdated = true; } 916 public void SetMark() {m_bMark = true; bUpdated = true;} 917 public void ResetLabel() {m_nLabel = 0; bUpdated = true;} 918 public void ResetMark() {m_bMark = false; bUpdated = true;} 919 public short GetLabel() {return m_nLabel;} 920 921 public Boolean IsScanned() { return bScanned;} 922 public void SetScanned() { bScanned = true;} 923 public void ClearScanned() { bScanned = false; } 924 925 public void SetStone(StoneColor color) 926 { 927 if (bEmpty) 928 { 929 bEmpty = false; 930 s.color = color; 931 bUpdated = true; 932 } // 落子 933 } 934 935 /* 936 * 提子 937 */ 938 public void RemoveStone() 939 { //提子 940 bEmpty = true; 941 bUpdated = true; 942 } 943 944 //被吃 945 public void Die() 946 { 947 bKilled = true; 948 bEmpty = true; 949 bUpdated = true; 950 } 951 952 public Boolean IsKilled() { return bKilled;} 953 public void SetNoKilled() { bKilled = false;} 954 955 public void ResetUpdated() { bUpdated = false; bKilled = false;} 956 957 //是否被更新 958 public Boolean IsUpdated() 959 { 960 if (bUpdated) 961 { //如果已经被更新了,那么置更新状态为反 962 bUpdated = false; 963 return true; 964 } 965 else 966 return false; 967 } 968 969 // updated的set函数 970 public void SetUpdated() { bUpdated = true; } 971 } 972 973 /** 974 * 操作类 975 */ 976 public class GoMove 977 { 978 StoneColor m_c; //落子颜色 979 Point m_pos; //落子位置 980 int m_n; //操作数 981 String m_comment; // 每步操作的信息 982 MoveResult m_mr; //每步操作的结果 983 984 ArrayList m_alLabel; //所有的label 985 ArrayList m_alMark; //所有的mark 986 987 //所有的被吃点 988 //被吃子的颜色 989 ArrayList m_alDead; 990 StoneColor m_cDead; 991 /** 992 * 构造函数 993 */ 994 public GoMove(int posX, int posY, StoneColor sc, int seq) 995 { 996 m_pos = new Point(posX,posY); 997 m_c = sc; 998 m_n = seq; 999 m_mr = new MoveResult(); 1000 m_alLabel = new ArrayList(); 1001 m_alMark = new ArrayList(); 1002 } 1003 /*------这种构造函数用于棋谱文件------*/ 1004 public GoMove(String str, StoneColor color) 1005 { 1006 if (str == null) 1007 { 1008 throw new ArgumentNullException("str"); 1009 } 1010 char cx = str[0]; 1011 char cy = str[1]; 1012 m_pos = new Point(0,0); 1013 //转换为坐标 1014 m_pos.X = (int) ( (int)cx - (int)(char)'a'); 1015 m_pos.Y = (int) ( (int)cy - (int)(char)'a'); 1016 this.m_c = color; 1017 m_alLabel = new ArrayList(); 1018 m_alMark = new ArrayList(); 1019 } 1020 1021 /*------还是将游戏信息中的str转换为点坐标,所以说上面的构造函数代码覆盖率太差------*/ 1022 static private Point StrToPoint(String str) 1023 { 1024 Point p = new Point(0,0); 1025 char cx = str[0]; 1026 char cy = str[1]; 1027 //转换为坐标 1028 p.X = (int) ( (int)cx - (int)(char)'a'); 1029 p.Y = (int) ( (int)cy - (int)(char)'a'); 1030 return p; 1031 } 1032 /*------各种get/set------*/ 1033 //落子颜色 1034 public StoneColor Color 1035 { 1036 get { return m_c; } 1037 } 1038 //游戏信息 1039 public String Comment 1040 { 1041 get 1042 { 1043 if (m_comment == null) 1044 return string.Empty; 1045 else 1046 return m_comment; 1047 } 1048 set 1049 { 1050 m_comment = value; 1051 } 1052 } 1053 1054 public int Seq 1055 { 1056 get { return m_n; } 1057 set { m_n = value;} 1058 } 1059 1060 public Point Point 1061 { 1062 get { return m_pos; } 1063 } 1064 1065 public MoveResult Result 1066 { 1067 get { return m_mr; } 1068 set { m_mr = value; } 1069 } 1070 1071 public ArrayList DeadGroup 1072 { 1073 get { return m_alDead;} 1074 set {m_alDead = value;} 1075 } 1076 1077 public StoneColor DeadGroupColor 1078 { 1079 get { return m_cDead; } 1080 set { m_cDead = value; } 1081 } 1082 1083 public void AddLabel(String str) {m_alLabel.Add(StrToPoint(str));} 1084 1085 public void AddMark(String str) { m_alMark.Add(StrToPoint(str));} 1086 1087 public ArrayList Labels 1088 { 1089 get { return m_alLabel; } 1090 } 1091 1092 public ArrayList Marks 1093 { 1094 get { return m_alMark; } 1095 } 1096 } 1097 1098 1099 /** 1100 * 操作结果类 1101 * 1102 */ 1103 public class MoveResult 1104 { 1105 public StoneColor color; 1106 // 4个方向是否被吃子 1107 public Boolean bUpKilled; 1108 public Boolean bDownKilled; 1109 public Boolean bLeftKilled; 1110 public Boolean bRightKilled; 1111 public Boolean bSuicide; //是否还有气 1112 public MoveResult() 1113 { 1114 bUpKilled = false; 1115 bDownKilled = false; 1116 bLeftKilled = false; 1117 bRightKilled = false; 1118 bSuicide = false; 1119 } 1120 } 1121 1122 /** 1123 * 1124 * ...这是在搞什么..突然又出现一个只有一个成员的struct.. 1125 */ 1126 public struct Stone 1127 { 1128 public StoneColor color; 1129 } 1130 1131 /** 1132 * 操作变量类 1133 * 1134 */ 1135 public class GoVariation 1136 { 1137 // int m_id; //用不到删掉了 1138 // string m_name; //用不到删掉了 1139 //请直接无视我 //天知道你在说什么 1140 ArrayList m_moves; 1141 int m_seq; //这个东西应该是在读谱的时候才有用的 1142 int m_total; 1143 1144 //构造函数 1145 public GoVariation(int id) 1146 { 1147 // m_id = id; 1148 m_moves = new ArrayList(10); 1149 m_seq = 0; 1150 m_total = 0; 1151 } 1152 1153 public void AddAMove(GoMove gm) 1154 { 1155 if (gm == null) 1156 { 1157 throw new ArgumentNullException("gm"); 1158 } 1159 gm.Seq = m_total ++; 1160 m_seq++; 1161 m_moves.Add(gm); 1162 } 1163 1164 public void UpdateResult(GoMove gm) 1165 { 1166 } 1167 1168 public GoMove DoNext() 1169 { 1170 if (m_seq < m_total) 1171 { 1172 return (GoMove)m_moves[m_seq++]; 1173 } 1174 else 1175 return null; 1176 } 1177 1178 public GoMove DoPrev() 1179 { 1180 if (m_seq > 0) 1181 return (GoMove)(m_moves[--m_seq]); 1182 else 1183 return null; 1184 } 1185 1186 /* 1187 * 嗯下面这个确实很有用,它返回操作队列的当前操作的前一个 1188 */ 1189 public GoMove PeekPrev() 1190 { 1191 if (m_seq > 0) 1192 return (GoMove)(m_moves[m_seq-1]); 1193 else 1194 return null; 1195 } 1196 1197 public void Reset() {m_seq = 0;} 1198 } 1199 1200 1201 /** 1202 * 我想说下面的其实用不到 1203 * 下面的用不到的 1204 */ 1205 //struct VarStartPoint 1206 //{ 1207 // int m_id; 1208 // int m_seq; 1209 //} 1210 1211 struct GameInfo //这个GameInfo其实有好多用不到的地方 1212 { 1213 public string gameName; 1214 public string playerBlack; 1215 public string playerWhite; 1216 public string rankBlack; 1217 public string rankWhite; 1218 public string result; 1219 public string date; 1220 public string km; 1221 public string size; 1222 public string comment; 1223 public string handicap; 1224 public string gameEvent; 1225 public string location; 1226 public string time; // 时间 1227 public string unknown_ff; //谁能告诉我这是什么... 1228 public string unknown_gm; 1229 public string unknown_vw; 1230 } 1231 1232 /*------这一坨应该是解析棋谱类------*/ 1233 1234 public class KeyValuePair 1235 { 1236 public string k; public ArrayList alV; 1237 1238 static private string RemoveBackSlash(string strIn) 1239 { 1240 string strOut; 1241 int iSlash; 1242 1243 strOut = string.Copy(strIn); 1244 if (strOut.Length < 2) 1245 return strOut; 1246 for (iSlash = strOut.Length-2; iSlash>=0; iSlash--) 1247 { 1248 if (strOut[iSlash] == '\\') // && 转义字符首先,搜字符\ 1249 { 1250 strOut = strOut.Remove(iSlash,1); 1251 if (iSlash>0) 1252 iSlash --; // 就是专门解析棋谱文件的字符串操作...我们没必要花时间在这个上面 1253 } 1254 } 1255 return strOut; 1256 } 1257 public KeyValuePair(string k, string v) 1258 { 1259 if (k == null) 1260 { 1261 throw new ArgumentNullException("k"); 1262 } 1263 this.k = string.Copy(k); 1264 string strOneVal; 1265 int iBegin, iEnd; 1266 1267 // 将棋谱信息转换为X[]的形式 1268 alV = new ArrayList(1); 1269 1270 // Comment 1271 if (k.Equals("C")) 1272 { 1273 strOneVal = RemoveBackSlash(string.Copy(v)); 1274 // Comment 1275 alV.Add(strOneVal); 1276 return; 1277 } 1278 if (v == null) 1279 { 1280 throw new ArgumentNullException("v"); 1281 } 1282 iBegin = v.IndexOf("["); 1283 if (iBegin == -1) // 里面是坐标 1284 { 1285 alV.Add(v); 1286 return; 1287 } 1288 1289 iBegin = 0; 1290 while (iBegin < v.Length && iBegin>=0) 1291 { 1292 iEnd = v.IndexOf("]", iBegin); 1293 if (iEnd > 0) 1294 strOneVal = v.Substring(iBegin, iEnd-iBegin); 1295 else 1296 strOneVal = v.Substring(iBegin); // 里面是坐标 1297 alV.Add(strOneVal); 1298 iBegin = v.IndexOf("[", iBegin+1); 1299 if (iBegin > 0) 1300 iBegin++; // 循环继续 1301 } 1302 } 1303 } 1304 1305 //GoTree其实是操作队列 1306 1307 public class GoTree 1308 { 1309 GameInfo _gi; //_gi:GameInfo游戏信息 1310 ArrayList _vars; //forward的时候用 1311 int _currVarId; //当前操作ID 1312 // int _currVarNum; 1313 GoVariation _currVar; //当前操作 1314 string _stGameComment; 1315 1316 // 由棋谱产生的构造函数 1317 public GoTree(string str) 1318 { 1319 _vars = new ArrayList(10); 1320 // _currVarNum = 0; 1321 _currVarId = 0; 1322 _currVar = new GoVariation(_currVarId); 1323 _vars.Add(_currVar); 1324 ParseFile(str); // 棋谱信息转换 1325 } 1326 1327 // 平时用的构造函数 1328 public GoTree() 1329 { 1330 _vars = new ArrayList(10); 1331 // _currVarNum = 0; 1332 _currVarId = 0; 1333 _currVar = new GoVariation(_currVarId); 1334 _vars.Add(_currVar); 1335 } 1336 1337 public string Info 1338 { 1339 get 1340 { 1341 return _gi.comment == null? string.Empty : _gi.comment; 1342 } 1343 } 1344 1345 public void AddMove(GoMove gm) 1346 { 1347 _currVar.AddAMove(gm); 1348 } 1349 1350 /** 1351 * 顾名思义,棋谱文件转换用的 1352 */ 1353 Boolean ParseFile(String goStr) 1354 { 1355 int iBeg, iEnd=0; 1356 while (iEnd < goStr.Length) 1357 { 1358 if (iEnd > 0) 1359 iBeg = iEnd; 1360 else 1361 iBeg = goStr.IndexOf(";", iEnd);//从iEnd后第一个;的位置 1362 iEnd = goStr.IndexOf(";", iBeg+1);//indexof如果未找到会返回-1 1363 if (iEnd < 0) // 没找到 1364 iEnd = goStr.LastIndexOf(")", goStr.Length); //// 找最后一个) 1365 if (iBeg >= 0 && iEnd > iBeg) 1366 { 1367 string section = goStr.Substring(iBeg+1, iEnd-iBeg-1); 1368 ParseASection(section);//划分出棋谱主体部分开始搞 1369 } 1370 else 1371 break; 1372 } 1373 return true; 1374 } 1375 1376 /// 相当于词法分析提取一个单词 1377 static public int FindEndofValueStr(String sec) 1378 { 1379 if (sec == null) 1380 { 1381 throw new ArgumentNullException("sec"); 1382 } 1383 int i = 0; 1384 // 截到] 1385 while (i >= 0) 1386 { 1387 i = sec.IndexOf(']', i+1); 1388 if (i > 0 && sec[i - 1] != '\\') 1389 return i; // 返回单词位置 1390 } 1391 1392 // 没提到单词就返回总长度 1393 return sec.Length - 1; // 没提到单词就返回总长度 1394 } 1395 1396 static public int FindEndofValueStrOld(String sec)//老版,用不到 1397 { 1398 if (sec == null) 1399 { 1400 throw new ArgumentNullException("sec"); 1401 } 1402 int i = 0; 1403 // 就是专门解析棋谱文件的字符串操作...我们没必要花时间在这个上面 1404 bool fOutside = false; 1405 1406 for (i=0; i<sec.Length;i++) 1407 { 1408 if (sec[i] == ']') 1409 { 1410 if (i > 1 && sec[i - 1] != '\\') // 就是专门解析棋谱文件的字符串操作...我们没必要花时间在这个上面 1411 fOutside = true; 1412 } 1413 else if (char.IsLetter(sec[i]) && fOutside && i>0) 1414 return i-1; 1415 else if (fOutside && sec[i] == '[') 1416 fOutside = false; 1417 } 1418 return sec.Length - 1; // 就是专门解析棋谱文件的字符串操作...我们没必要花时间在这个上面 1419 } 1420 1421 static private string PurgeCRLFSuffix(string inStr) 1422 { 1423 int iLast = inStr.Length - 1; // 就像词法分析跳过没有意义的字符一样 1424 1425 if (iLast <= 0) 1426 return inStr; 1427 1428 while ((inStr[iLast] == '\r' || inStr[iLast] == '\n' || inStr[iLast] == ' ')) 1429 { 1430 iLast--; 1431 } 1432 if ((iLast+1) != inStr.Length) 1433 return inStr.Substring(0, iLast + 1); //就像词法分析跳过没有意义的字符一样 1434 else 1435 return inStr; 1436 } 1437 1438 1439 // 棋谱主体部分 1440 Boolean ParseASection(String sec) 1441 { 1442 int iKey = 0; 1443 int iValue = 0; 1444 int iLastValue = 0; 1445 KeyValuePair kv; 1446 ArrayList Section = new ArrayList(10); 1447 1448 try 1449 { 1450 iKey = sec.IndexOf("["); 1451 if (iKey < 0) 1452 { 1453 return false; 1454 } 1455 sec = PurgeCRLFSuffix(sec); 1456 1457 iValue = FindEndofValueStr(sec); // 提取一个[]操作 1458 iLastValue = sec.LastIndexOf("]"); 1459 if (iValue <= 0 || iLastValue <= 1) 1460 { 1461 return false; 1462 } 1463 sec = sec.Substring(0,iLastValue+1); 1464 while (iKey > 0 && iValue > iKey)// 正确提取了一个[] 1465 { 1466 string key = sec.Substring(0,iKey); 1467 int iNonLetter = 0; 1468 while (!char.IsLetter(key[iNonLetter]) && iNonLetter < key.Length) 1469 iNonLetter ++; 1470 key = key.Substring(iNonLetter); 1471 // X[] 1472 string strValue = sec.Substring(iKey+1, iValue-iKey-1); 1473 // 键值对 1474 kv = new KeyValuePair(key, strValue); 1475 Section.Add(kv); 1476 if (iValue >= sec.Length) 1477 break; 1478 sec = sec.Substring(iValue+1); 1479 iKey = sec.IndexOf("["); 1480 if (iKey > 0) 1481 { 1482 iValue = FindEndofValueStr(sec); // 循环继续 1483 } 1484 } 1485 } 1486 catch 1487 { 1488 return false; 1489 } 1490 1491 ProcessASection(Section); 1492 return true; 1493 } 1494 1495 1496 // 提取出操作后就要进行识别了 1497 Boolean ProcessASection(ArrayList arrKV) 1498 { 1499 Boolean fMultipleMoves = false; //单步操作 1500 GoMove gm = null; 1501 1502 string key, strValue; 1503 1504 for (int i = 0;i<arrKV.Count;i++) 1505 { 1506 key = ((KeyValuePair)(arrKV[i])).k; 1507 for (int j=0; j<((KeyValuePair)(arrKV[i])).alV.Count; j++) 1508 { 1509 strValue = (string)(((KeyValuePair)(arrKV[i])).alV[j]); 1510 1511 if (key.Equals("B")) // 黑 1512 { 1513 Debug.Assert(gm == null); 1514 gm = new GoMove(strValue, StoneColor.Black); 1515 } 1516 else if (key.Equals("W")) // 白 1517 { 1518 Debug.Assert(gm == null); 1519 gm = new GoMove(strValue, StoneColor.White); 1520 } 1521 else if (key.Equals("C")) // Comment,针对一些步数发表自己的看法。。 1522 { 1523 // 初始comment 1524 if (gm != null) 1525 gm.Comment = strValue; 1526 else // appent comment 1527 _gi.comment += strValue; 1528 } 1529 else if (key.Equals("L")) // 放子,就是一开始就有一些地方放了子 1530 { 1531 if (gm != null) 1532 gm.AddLabel(strValue); 1533 else // 中途放子是个什么逻辑 1534 _stGameComment += strValue; 1535 } 1536 1537 else if (key.Equals("M")) // 貌似是在开始之前就显示一些重要的操作? 1538 { 1539 if (gm != null) 1540 gm.AddMark(strValue); 1541 else // 游戏中途搞这个? 1542 _gi.comment += strValue; 1543 } 1544 else if (key.Equals("AW")) // 突然觉得好蛋疼,给这么一串英文标识符让我们来猜意思? 1545 { 1546 fMultipleMoves = true; 1547 gm = new GoMove(strValue, StoneColor.White); 1548 } 1549 else if (key.Equals("AB")) // 多步黑 1550 { 1551 fMultipleMoves = true; 1552 gm = new GoMove(strValue, StoneColor.Black); 1553 } 1554 else if (key.Equals("HA"))//这些键值对根本就没有被用过.. 1555 _gi.handicap = (strValue); 1556 else if (key.Equals("BR")) 1557 _gi.rankBlack = (strValue); 1558 else if (key.Equals("PB")) 1559 _gi.playerBlack = (strValue); 1560 else if (key.Equals("PW")) 1561 _gi.playerWhite = (strValue); 1562 else if (key.Equals("WR")) 1563 _gi.rankWhite = (strValue); 1564 else if (key.Equals("DT")) 1565 _gi.date = (strValue); 1566 else if (key.Equals("KM")) 1567 _gi.km = (strValue); 1568 else if (key.Equals("RE")) 1569 _gi.result = (strValue); 1570 else if (key.Equals("SZ")) 1571 _gi.size = (strValue); 1572 else if (key.Equals("EV")) 1573 _gi.gameEvent = (strValue); 1574 else if (key.Equals("PC")) 1575 _gi.location = (strValue); 1576 else if (key.Equals("TM")) 1577 _gi.time = (strValue); 1578 else if (key.Equals("GN")) 1579 _gi.gameName = strValue; 1580 1581 else if (key.Equals("FF")) 1582 _gi.unknown_ff = (strValue); 1583 else if (key.Equals("GM")) 1584 _gi.unknown_gm = (strValue); 1585 else if (key.Equals("VW")) 1586 _gi.unknown_vw = (strValue); 1587 else if (key.Equals("US")) 1588 _gi.unknown_vw = (strValue); 1589 1590 else if (key.Equals("BS")) 1591 _gi.unknown_vw = (strValue); 1592 else if (key.Equals("WS")) 1593 _gi.unknown_vw = (strValue); 1594 else if (key.Equals("ID")) 1595 _gi.unknown_vw = (strValue); 1596 else if (key.Equals("KI")) 1597 _gi.unknown_vw = (strValue); 1598 else if (key.Equals("SO")) 1599 _gi.unknown_vw = (strValue); 1600 else if (key.Equals("TR")) 1601 _gi.unknown_vw = (strValue); 1602 else if (key.Equals("LB")) 1603 _gi.unknown_vw = (strValue); 1604 else if (key.Equals("RO")) 1605 _gi.unknown_vw = (strValue); 1606 1607 1608 // 未定义的键值对 1609 else 1610 System.Diagnostics.Debug.Assert(false, "unhandle key: " + key + " "+ strValue); 1611 1612 // 如果是多步操作 1613 if (fMultipleMoves) 1614 { 1615 _currVar.AddAMove(gm); 1616 } 1617 } 1618 } 1619 1620 // 下一步 1621 if (!fMultipleMoves && gm != null) 1622 { 1623 _currVar.AddAMove(gm); 1624 } 1625 return true; 1626 } 1627 1628 public GoMove DoPrev() 1629 { 1630 return _currVar.DoPrev(); 1631 } 1632 1633 public GoMove PeekPrev() 1634 { 1635 return _currVar.PeekPrev(); 1636 } 1637 1638 public GoMove DoNext() 1639 { 1640 return _currVar.DoNext(); 1641 } 1642 1643 public void UpdateResult(GoMove gm) 1644 { 1645 _currVar.UpdateResult(gm); 1646 } 1647 1648 public void Reset() 1649 { 1650 // _currVarNum = 0; 1651 _currVarId = 0; 1652 _currVar.Reset(); 1653 } 1654 static public void RewindToStart() 1655 { 1656 1657 } 1658 } 1659 }
5) 选择题: (提示: 这个题目另外算分,满分10分,需要挣分的同学就可以考虑这个选择题)
对于功能上的小问题, 那么你怎么改进呢? 请选出 1-2个你想做的改进,然后运用你的各种编程技术和能力把这些改进给实现了(必须明确指出改进/增加了哪一个功能)。
把所有的改进都实现之后,把代码签入 GitHub, 经历了这一番改动,你的程序和别的同学的程序就很不一样了。
这里我做的改进主要是把SaveFile功能实现,但是...由于我提取不到整个游戏的逻辑string信息..所以其实是一个伪实现..
1 private void SaveFile() 2 { 3 SaveFileDialog saveDlg = new SaveFileDialog(); 4 saveDlg.Filter = "sgf files (*.sgf)|*.sgf|All Files (*.*)|*.*"; 5 saveDlg.DefaultExt = ".sgf"; 6 DialogResult res = saveDlg.ShowDialog(); 7 if (res == DialogResult.OK) 8 { 9 if (!(saveDlg.FileName).EndsWith(".sgf") && !(saveDlg.FileName).EndsWith(".SGF")) 10 MessageBox.Show("Unexpected file format", "Super Go Format", MessageBoxButtons.OK); 11 else 12 { 13 StreamWriter w = new StreamWriter(saveDlg.FileName, false); 14 string s = gameTree.Info;//这里应该递归掉gm的信息,但是gm目前不是全局变量. 15 w.WriteLine(s); 16 w.Close(); 17 } 18 } 19 }
后来我看到肖犇犇把计算气的非递归实现也当作一个改进,我想这也行,他是用栈实现的,机智的我用队列将它是实现了..
1 /*------非递归BFS实现计算气------*/ 2 /* 3 int CalcLibertyBfs(int x, int y, StoneColor c) 4 { 5 int lib=0; 6 int[] dx = { 1, 0, -1, 0 }; 7 int[] dy = { 0, -1, 0, 1 }; 8 Queue q = new Queue(); 9 Point s=new Point(x,y); 10 Point next = new Point(); 11 q.Enqueue(s); 12 while (q.Count > 0) 13 { 14 s = (Point)q.Dequeue(); 15 for (int i = 0; i < 4; i++) 16 { 17 next.X = s.X + dx[i]; 18 next.Y = s.Y + dy[i]; 19 if (!OnBoard(next.X, next.Y)) 20 { 21 continue; 22 } 23 if (Grid[next.X, next.Y].IsScanned()) 24 { 25 continue; 26 } 27 if (!Grid[next.X, next.Y].HasStone()) 28 { 29 lib++; 30 } 31 if (Grid[next.X, next.Y].Color() == c) 32 { 33 q.Enqueue(next); 34 } 35 Grid[next.X, next.Y].SetScanned(); 36 } 37 } 38 return lib; 39 40 } 41 */
如果大家有时间并有兴趣,可以做一些大的改进:
a) 如果我要把这个程序变成一个可以人机对战的小游戏 (假设你的AI 模块已经写好,这里我们就可以让一个函数返回一个合法的位置就可以), 那这个程序的架构应该怎么变化? 请把这个功能写出来。
b) 如果我想让这个程序变成两个用户可以通过网络对战,这个程序的架构要怎么变化?
网络对战还是比较简单的。。。上次我们刚刚做过网络编程,那么这个只需要每次传递下棋的坐标就好,架构的变化我的倾向是对于游戏中每次换人行子的这部分改掉,这个GoMove参数可以由本机传递,也可以由网络编程传递的坐标来传,同理AI这块也可以这样做,可以让AI学习比较多的棋谱之后,计算得到下子的坐标,然后传到游戏的下子逻辑中即可