当前位置:文档之家› 基于C#的五子棋游戏的设计与实现综述

基于C#的五子棋游戏的设计与实现综述

基于C#的五子棋游戏的设计与实现综述
基于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.doczj.com/doc/565611444.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.doczj.com/doc/565611444.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))

{

相关主题
文本预览
相关文档 最新文档