基于C#的五子棋游戏的设计与实现综述
郑州科技学院课程设计论
文
基于C#的五子棋游戏的设计与实现
学生姓名:王新年
学号:201015066
年级专业:10级计科二班
指导老师:王玉萍
学院:信息工程学院
1 引言 (1)
1.1 五子棋介绍 (1)
2 软件架构 (1)
3 五子棋设计说明 (2)
3.1 主要成员变量说明 (2)
3.2 回溯栈元素类——StackElement (3)
3.3 棋子点属性类——qzdianshuxing (3)
3.4 主要成员函数说明 (3)
3.5 实现人机对弈的主要函数 (7)
3.6 实现菜单功能的函数 (26)
3.6 程序运行界面 (30)
4 心得体会 (311)
1 引言
1.1 五子棋介绍
五子棋是起源于中国古代的传统黑白棋种之一。现代五子棋日文称之为“連珠”,英译为“Renju ”,英文称之为“Gobang ”或“FIR ”(Five in a Row 的缩写),亦有“连五子”、“五子连”、“串珠”、“五目”、“五目碰”、“五格”等多种称谓。
五子棋不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。五子棋既有现代休闲的明显特征“短、平、快”,又有古典哲学的高深学问“阴阳易理”;它既有简单易学的特性,为人民群众所喜闻乐见,又有深奥的技巧和高水平的国际性比赛;它的棋文化源渊流长,具有东方的神秘和西方的直观;既有“场”的概念,亦有“点”的连接。它是中西文化的交流点,是古今哲理的结晶。
2 软件架构
软件的总体架构如图2.1:
人机对战 人人对战 主界面 游戏控制 游戏模式 重
新开
始 退出 声音控制 悔棋 开始
图2.1 软件架构
3 五子棋设计说明
3.1 主要成员变量说明
1)选择游戏模式标志——m_renren
用来表示当前玩家选择游戏的情况,当m_renren为false时,表示人机对战;为true时,表示人人对弈。
2)游戏开始标志——begin
用来判断当前游戏是否开始
3)音效标志——sound
在下棋过程中,判断是否需要声音,当sound为true时,表示玩家需要声音,否则的话,玩家不需要声音。
4)谁先下的标志——first
这个标志只对人机对弈时有效。当first为true时,表示人先下,否则,电脑先下。
5)棋盘数据——points
points为棋盘情况数组,是用一个15*15的二维数组来表示的。points[i,j]=2表示此处无子,points[i,j]=1表示此处为黑子 points[i,j]=0表示此处为白子。
6)棋子颜色标志——qzcolor
用来表示当前棋子的颜色,qzcolor=1时表示黑棋,qzcolor=0时表示百棋。
7)棋子数据——qz
表示棋子所放的位子,是用一个15*15的PictureBox类型的二维数组来表示。它还可以用来显示当前棋子的图片。
8)oldMovePoint
用来记录鼠标经过后点的位置。
9)backStack
用于悔棋的栈。
10)backTrackStack
用于回溯的栈
11)结局——result
用枚举类型来表示结局。如:
public enum result : int//结局
{
lose = -1,
equal,
win
}
3.2 回溯栈元素类——StackElement
成员变量:
1)qzColor 棋子的颜色
2)bestFivePoints 最好点的位置
3)pointsCount 计算最好点的数目
4)pointNumber 点的数目
5)Theresult 结局
6)stepNumber 预测的步数
3.3 棋子点属性类——qzdianshuxing
成员变量:
1)blackConnect 黑棋子i个(包括活棋)的连接条数2)blackActive 黑活棋i个的连接条数
3)whiteConnect 白棋子i个(包括活棋)的连接条数4)whiteActive 白活棋i个的连接条数
5)tempActive3 活棋数为3的连接条数
3.4 主要成员函数说明
1)初始化棋盘——Initializeqp
初始化操作包括以下几个步骤:
●设置棋子所在的位置
●设置棋子的大小
●初始化棋子的背景颜色
●将棋子的sizemode设置为 CenterImage
●将棋子的可见性设置为false
●将棋子添加到form上。
2)绘制棋盘——Form1_Paint
其主要是画出以40*40的大小为每一小格,代码如下:
for (i = 0; i < 15; i++)
{
g.DrawLine(myPen, 30 + i * 40, 50, 30 + i * 40, 610);
g.DrawLine(myPen, 30, 50 + i * 40, 590,
50 + i * 40);
}
3)绘制光标——Form1_MouseMove
当鼠标在棋盘上移动时,当前的显示画红方框,过去的显示和背景一样颜色的方框。
当前的红方框代码如下:
if (10 < e.X && 10< e.Y &&e.X { x = ((e.X - 10) / 40) * 40 + 30; y = ((e.Y - 10) / 40) * 40 + 50; g.DrawLine(newpen, x - 15, y - 15, x - 15, y - 5); g.DrawLine(newpen, x - 15, y - 15, x - 5, y - 15); g.DrawLine(newpen, x + 15, y - 15, x + 5, y - 15); g.DrawLine(newpen, x + 15, y - 15, x + 15, y -5); g.DrawLine(newpen, x - 15, y + 15, x - 15, y + 5); g.DrawLine(newpen, x - 15, y + 15, x - 5, y + 15); g.DrawLine(newpen, x + 15, y + 15, x + 15, y + 5); g.DrawLine(newpen, x + 15, y + 15, x + 5, y + 15); oldMovePoint.X = x; oldMovePoint.Y = y; } 过去的方框代码如下: if (oldMovePoint.X != -1) { g.DrawLine(oldpen, oldMovePoint.X - 15, oldMovePoint.Y - 15, oldMovePoint.X - 15, oldMovePoint.Y - 5); g.DrawLine(oldpen, oldMovePoint.X - 15, oldMovePoint.Y - 15, oldMovePoint.X - 5, oldMovePoint.Y - 15); g.DrawLine(oldpen, oldMovePoint.X + 15, oldMovePoint.Y - 15, oldMovePoint.X + 5, oldMovePoint.Y - 15); g.DrawLine(oldpen, oldMovePoint.X + 15, oldMovePoint.Y - 15, oldMovePoint.X + 15, oldMovePoint.Y - 5); g.DrawLine(oldpen, oldMovePoint.X - 15, oldMovePoint.Y + 15, oldMovePoint.X - 15, oldMovePoint.Y + 5); g.DrawLine(oldpen, oldMovePoint.X - 15, oldMovePoint.Y + 15, oldMovePoint.X - 5, oldMovePoint.Y + 15); g.DrawLine(oldpen, oldMovePoint.X + 15, oldMovePoint.Y + 15, oldMovePoint.X + 15, oldMovePoint.Y + 5); g.DrawLine(oldpen, oldMovePoint.X + 15, oldMovePoint.Y + 15, oldMovePoint.X + 5, oldMovePoint.Y + 15); } 4)下棋子——putqz 下棋子有两种可能性,一是知道一个点的横纵坐标;二是知道一个点。 下面我就说一说知道x,y坐标的情况,第二种情况只要调用第一种情况就行了。 假如下的是一个黑棋子,将qz的背景图设置为blackstone,并将此处标记为已下黑棋,并将此棋子标记为最后落子指示。 如果悔棋的栈不为空,将其弹出栈,并将qz的图像设置为什么都没有,再将其压入栈。同理,白旗也跟这一样做。代码 如下: if (qzcolor==1) { qz[x, y].BackgroundImage = global::五子棋.Properties.Resources.blackstone; points[x, y] = 1; qz[x, y].Image = global::五子 棋https://www.360docs.net/doc/5c5611444.html,stblackstone; if (backStack.Count > 0) { temp = (Point)backStack.Pop(); qz[temp.X, temp.Y].Image = global::五子棋.Properties.Resources.nullll; backStack.Push(temp); } } else { qz[x, y].BackgroundImage = global::五子棋.Properties.Resources.whitestone; points[x, y] = 0; qz[x, y].Image = global::五子 棋https://www.360docs.net/doc/5c5611444.html,stwhitestone; if (backStack.Count > 0) { temp = (Point)backStack.Pop(); qz[temp.X, temp.Y].Image = global::五子棋.Properties.Resources.nullll; backStack.Push(temp); } } 最后将其可见性设置为true。 5)开始函数——start 当棋局开始时,就应将棋盘初始化,使棋盘上没有棋子。如果有悔棋,就要将悔棋栈清空。代码如下: if (!begin) { begin = true; for (x = 0; x < 15; x++) for (y = 0; y < 15; y++) { qz[x, y].Visible = false; points[x, y] = 2; } while (backStack.Count > 0) backStack.Pop(); } 3.5 实现人机对弈的主要函数 6)察看两点之间的棋子数函数——ConnectqpCount 这个函数主要求两点之间可能形成五连子的qzcolor色棋的连子数(包括活期)。首先,求出两点之间总共的棋子数,并判断棋子所在哪个方向。沿着这个方向每个点的坐标,并察看这几个点中有没有反色的棋子。如果有,棋子数设为0,否则的话,棋子数自加1。代码如下: int x, y, i, j, length, xPlus = 0, yPlus = 0, sum, maxSum = 0; length = Math.Max(Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y)) + 1; if (point1.X != point2.X) xPlus = 1; if (point1.Y != point2.Y) yPlus = (point2.Y - point1.Y)/Math.Abs(po int2.Y - point1.Y); for (i = 0; i < length - 4; i++) { x = point1.X + i * xPlus; y = point1.Y + i * yPlus; sum = 0; for (j = 0; j < 5; j++) {//察看两点之间当中有没有反色 if(points[x + j * xPlus, y + j * yPlus] == qzcolor) sum++; else if (points[x + j * xPlus, y + j * yPlus] == -qzcolor+1) { sum = 0; break; } } if (maxSum < sum) maxSum = sum; } return maxSum; 7)察看两点之间是否存在活棋的函数——ActiveConnectqp 这个函数主要求两点之间qzcolor色棋是否存在活棋。temp1变量表示在一直线上,比如,一条向下的直线,则表示点point1上方可下的个数;而temp2表示点point2下方可下的个数。代码表示为: temp1 = Math.Min(Math.Min(Math.Min(5 - count, point1.X), point1.Y), 14 - point1.Y); temp2 = Math.Min(Math.Min(Math.Min(5 - count, 14 - point2.X), 14 - point2.Y), point2.Y); 则长度表示为: length = Math.Max(Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y)) + 1 + temp1 + temp2; 先求两点之间qzcolor色棋的棋子个数,做法和函数ConnectqpCount一样。再判断它是否是活棋。当参数count 和所得两点之间qzcolor色棋的棋子个数相等,并且两头都没下棋子时,它为活棋。否则,反之。代码如下: if (point1.X != point2.X) xPlus = 1; if(point1.Y != point2.Y) yPlus = (point2.Y - point1.Y) / Math.Abs(point2.Y - point1.Y); for (i = 0; i < length - 4; i++) { x = point1.X - temp1 * xPlus + i * xPlus; y = point1.Y - temp1 * yPlus + i * yPlus; if (x + 4 * xPlus > 14 || y + 4 * yPlus > 14) break; sum = 0; for (j = 0; j < 4; j++) { if(points[x + j * xPlus, y + j * yPlus] == qzcolor) sum++; else if (points[x + j * xPlus, y + j * yPlus] == -qzcolor+1) { sum = 0; break; } } if (0 < x && 0 <= y - yPlus && y - yPlus <= 14) { if (sum == count && points[x - xPlus, y - yPlus] == 2 && points[x + 4 * xPlus, y + 4 * yPlus] == 2) return true; } } 8)查看是否被破坏活期——BreakActiveConnectqp 在(x,y)处放qzcolor色棋后形成活count,且放一反色棋后破坏棋形成活count。代码如下: if (!ActiveConnectqp(qzcolor, count, point1, point2)) return false; if (count == 5) return false; else if (count == 4) return true; else { bool blnFlag; points[x, y] = -qzcolor+1; blnFlag = !ActiveConnectqp(qzcolor, count - 1, point1, point2); points[x, y] = qzcolor; return blnFlag; } 9)查看是否是最好的点——FindBestPoint 首先,查看有没有最佳点,并形成栈元素。如果没有,返回false;否则,将这栈元素压入回溯栈中。当栈非空时,将栈元素弹出,如果栈中的pointNumber小于pointCount时,在棋盘上下一棋。如果赢棋,不再继续探测,并在棋盘上退一棋。如果和棋的话,也不再继续探测,并在棋盘上退一棋。 否则,继续下棋并探测。如果栈顶元素无点,弹出后栈必非空,并在棋盘上退一棋。如果栈顶元素中点均已试过,则寻找栈顶元素中点的最好结局,并寻找最佳步数。实现的代码如下: result totalresult = result.lose; int i, bestStepNumber = 0; StackElement tempStackElement = new StackElement(); if (first) { qzcolor = 0; if (!FindBestFivePointsAndFormAStackElement(qzcolor, ref tempStackElement)) return false; } else { qzcolor = 1; if (!FindBestFivePointsAndFormAStackElement(qzcolor, ref tempStackElement)) return false; } backTrackStack.Push(tempStackElement); while (backTrackStack.Count > 0)//栈非空 { tempStackElement = (StackElement)backTrackStack.Pop(); if (tempStackElement.pointNumber < tempStackElement.pointsCount) { //在棋盘上下一棋 points[tempStackElement.bestFivePoints[tempStackElement .pointNumber].X, tempStackElement.bestFivePoints[tempStackElement.pointN umber].Y] = tempStackElement.qzColor; if (Win(tempStackElement.qzColor, tempStackElement.bestFivePoints[tempStackElement.pointN umber])) {//赢棋,不在继续探测 tempStackElement.theresult[tempStackElement.pointNumber] = result.win; tempStackElement.stepNumber[tempStackElement.pointNumbe r] = backTrackStack.Count + 1; //在棋盘上退一棋 points[tempStackElement.bestFivePoints[tempStackElement .pointNumber].X, tempStackElement.bestFivePoints[tempStackElement.pointN umber].Y] = 2; tempStackElement.pointNumber++; backTrackStack.Push(tempStackElement); } else if (backTrackStack.Count == M - 1) {//将此元素压入栈后栈满,不在继续探测 tempStackElement.theresult[tempStackElement.pointNumber] = result.equal; tempStackElement.stepNumber[tempStackElement.pointNumbe r] = M; //在棋盘上退一棋 points[tempStackElement.bestFivePoints[tempStackElement .pointNumber].X, tempStackElement.bestFivePoints[tempStackElement.pointN umber].Y] = 2; tempStackElement.pointNumber++; backTrackStack.Push(tempStackElement); } else {//另一方继续下棋向下探测 tempStackElement.pointNumber++; backTrackStack.Push(tempStackElement); FindBestFivePointsAndFormAStackElement(-tempStackElemen t.qzColor+1, ref tempStackElement); backTrackStack.Push(tempStackElement); } }//end if else//栈顶元素无点或点均已试过 { if (tempStackElement.pointsCount == 0)//栈顶元素无点,且弹出后栈必非空 { tempStackElement = (StackElement)backTrackStack.Pop(); tempStackElement.theresult[tempStackElement.pointNumber - 1] = result.win; tempStackElement.stepNumber[tempStackElement.pointNumbe r - 1] = backTrackStack.Count + 1; //在棋盘上退一棋 points[tempStackElement.bestFivePoints[tempStackElement .pointNumber - 1].X, tempStackElement.bestFivePoints[tempStackElement.pointN umber - 1].Y] = 2; backTrackStack.Push(tempStackElement); } else//栈顶元素中点均已试过 { //寻找栈顶元素中点的最好结局 totalresult = tempStackElement.theresult[0]; for (i = 0; i < tempStackElement.pointsCount; i++) if (totalresult < tempStackElement.theresult[i]) totalresult = tempStackElement.theresult[i]; //寻找最佳步数 if (totalresult == result.win) { bestStepNumber = M + 2; for (i = 0; i < tempStackElement.pointsCount; i++) if (totalresult == tempStackElement.theresult[i] && bestStepNumber > tempStackElement.stepNumber[i]) bestStepNumber = tempStackElement.stepNumber[i]; } else//totalresult==result.equal 或lose { bestStepNumber = 0; for (i = 0; i < tempStackElement.pointsCount; i++) if (totalresult == tempStackElement.theresult[i] && bestStepNumber < tempStackElement.stepNumber[i]) bestStepNumber = tempStackElement.stepNumber[i]; } if (backTrackStack.Count > 0)//栈非空 { tempStackElement = (StackElement)backTrackStack.Pop(); tempStackElement.theresult[tempStackElement.pointNumber - 1] = (result)(0 - totalresult); tempStackElement.stepNumber[tempStackElement.pointNumbe r - 1] = bestStepNumber; //在棋盘上退一棋 points[tempStackElement.bestFivePoints[tempStackElement .pointNumber - 1].X, tempStackElement.bestFivePoints[tempStackElement.pointN umber - 1].Y] = 2; backTrackStack.Push(tempStackElement); } } } } for (i = 0; i < tempStackElement.pointsCount; i++) if (totalresult == tempStackElement.theresult[i] && bestStepNumber == tempStackElement.stepNumber[i]) break; bestPoint = tempStackElement.bestFivePoints[i]; return true; 10)寻找最佳的五个点,并形成栈元素——FindBestFivePointsAndFormAStackElement 函数主要是找最佳点,并形成栈元素。如果找到,返回true;否则。返回false。要找 最佳点,就是找权值最大的点。首先,计算出棋盘上每一个点的权值,并找出最大的一个。代码如下: int[,] qpPower = new int[15, 15]; bool blnHaveFound; int x, y, i, max; tempStackElement.pointsCount = 0; for (x = 0; x < 15; x++) for (y = 0; y < 15; y++) qpPower[x, y] = GetqpPower(qzcolor, x, y); for (i = 0; i < 5; i++) {//求第i个最佳点 max = 0; for (x = 0; x < 15; x++) for (y = 0; y < 15; y++) if (max < qpPower[x, y]) max = qpPower[x, y]; for (x = 0; x < 15; x++) { blnHaveFound = false; for (y = 0; y < 15; y++) if (max == qpPower[x, y]) { tempStackElement.bestFivePoints[i] = new Point(x, y); tempStackElement.pointsCount++; qpPower[x, y] = -1; blnHaveFound = true; break; } if (blnHaveFound) break; } } if (tempStackElement.pointsCount == 0) return false; else { tempStackElement.qzColor = qzcolor; tempStackElement.pointNumber = 0; return true; } 11)求权值——GetqpPower 定义八个方向:左,右,上,下,左上,右下,左下,右上。 代码如下: left = new Point(Math.Max(0, x - 4), y); right = new Point(Math.Min(14, x + 4), y); top = new Point(x, Math.Max(0, y - 4)); down = new Point(x, Math.Min(14, y + 4)); temp = Math.Min(x - left.X, y - top.Y); leftTop = new Point(x - temp, y - temp); temp = Math.Min(x - left.X, down.Y - y); leftDown = new Point(x - temp, y + temp); temp = Math.Min(right.X - x, y - top.Y); rightTop = new Point(x + temp, y - temp); temp = Math.Min(right.X - x, down.Y - y); rightDown = new Point(x + temp, y + temp); 如果颜色是黑色,处理黑棋连子情况:如果这个点没有下棋子,则在此置为1,表示这点下的是黑棋。然后看这个点各个方向黑棋子的数目是多少,并看是否是活棋,再作出处理。最后,将此点处置为2。表示这点没有下棋子。处理白棋连子情况:在此点处置为0,表示这点下的是白棋。然后看这个点各个方向白棋子的数目是多少,并在此放一白棋破坏黑棋,再做出相应的处理。如果各个方向上出现3个的活棋。就将tempActive3自加1。最后,将此点处置为2。表示这点没有下棋子。设置权值:形成黑棋五个的权值设为150000;形成白棋五个的权值设为140000;形成黑活棋个数为4或形成两条以上黑棋个数为4的权值设为130000;形成一条黑棋个数为4并且一条为黑活棋个数为3的权值为120000;形成一条黑棋个数为4或一条 以上黑棋个数为3的权值110000;形成白活棋个数为4或形成两条以上白棋个数为4的权值设为100000;形成一条黑棋个数为4并且一条为黑活棋个数为3的权值为120000;形成一条白棋个数为4并且一条为白活棋个数为3的权值为90000;两条以上白活棋个数为3的权值为80000;形成一条白棋个数为4或一条以上白棋个数为3的权值为70000;同样,如果颜色为白色,做法和以上的差不多。代码如下: if (qzcolor == 1) { if (points[x, y] != 2) return -2; else { /// ///处理黑棋连子情况 /// points[x, y] = 1; //左右方向 connectCount = ConnectqpCount(1, left, right); qzdsx.blackConnect[connectCount]++; if (ActiveConnectqp(1, connectCount, left, right)) { qzdsx.blackConnect[connectCount]--; qzdsx.blackActive[connectCount]++; } //上下方向 connectCount = ConnectqpCount(1, top, down); qzdsx.blackConnect[connectCount]++; if (ActiveConnectqp(1, connectCount, top, down)) {