连连看游戏设计
连连看游戏设计
连连看游戏虽然功能不强大,但设计到C#程序开发的许多方面。包括功能的设计、数据结构的设计、程序的资源管理等等。
1.1游戏功能设计
连连游戏的规则很简单,当你点击的两个图片相同且符合相应的寻路算法则图片会消失,直到所有的图片都消失为止。一开始只是相同图片的消失,一关之后每次消失图片后剩下的图片会向左、右、上、下四个方向移动,当所有图片都消失后,你就赢了。游戏的主要功能点包括:
①游戏的图片分为8行8列,16种图片。每次随机分布在窗体上,鼠标点击选中,选中之后图片显示为蓝色,当满足寻路算法时图片就消失。
②游戏的生命值为500,设置定时器,每两秒生命值减一,每次游戏的洗牌次数为4次,提示次数为4次,当生命值为0时,游戏失败,否则游戏胜利,比较每次所剩生命值的多少。
③游戏界面分为启动界面、操作界面组成。启动界面显示一张图片,提示玩家有所准备,并且提示相关的帮助。按Enter 键进入操作界面,所有图片都显示在这区域,每个图片的大小为25× 26,显示8行8列。有洗牌、提示按钮,并会显示相应的次数和生命值。游戏的界面设计如图1.1
④游戏中采用全触控操作,所有功能均通过手写笔触控完成,不必按任何键。
游戏的运行效果如图
1.2
图1.1 游戏的界面设计 图1.2连连看的运行效果
1.2类和对象的设计
通过游戏的功能分析,该游戏由以下类组成。如表1.1所示
表1.1 连连看游戏类的设计
1.3数据结构的设计
游戏中共有16种图片,每种图片是每次显示4张,分为8行8列,程序中如何存储和表示这些信息对于游戏开发是至关重要的。这16种图片存储在imagelist 控件上,用数组GameDate[,]表示,用Status[,]表示其状态,没有图片值为0,有图片值为1。游戏中的所有图片如图1.3。
图1.3游戏中的所有图片
1.4路径算法
1.4.1无折点的寻路算法
无折点意思是两个图片直接相连。如图1.4
图1.4
程序中分别写出了垂直和水平两种直线相连情况的函数。
private bool CheckVertical(Point P1, Point P2, bool AddPath) //垂直相连
{
Point Px, Py;
Px = P1.X
Py = P1.X
for (int i = (Px.X + 1); i
{
if (Status[i,P1.Y]!=1) //由上面的图向下遍历,如果两图中间有图片则算法结束,返回false ,表示没有找到路径。
return false ;
}
if (AddPath)
{
MyPath.Add(P1); //由上面的图向下遍历,如果两图中间没有图片,向动态数据添加数据,把两图的坐标加进数组,返回true ,表示找到路径
MyPath.Add(P2);
}
return true ;
}
同理以下是水平连线的情况
private bool CheckHorizontal(Point P1, Point P2, bool AddPath)
{
Point Px, Py;
Px = P1.Y
Py = P1.Y
for (int i = (Px.Y + 1); i
{
if (Status[P1.X,i]!=1)
return false ;
}
if (AddPath)
{
MyPath.Add(Px);
MyPath.Add(Py);
}
1.4.2一个折点的寻路算法
一个折点的情况如图1.5所示
图 1.5
当两图之间存在一个折点时,两图有两种情况,一种是先横线后竖线,一种是先竖线后横线。这两种情况都是先找出折点(图中的黄点),将两图间的连线由折点分成两个直线相连的情况,再调用直线相连的寻路函数进行查找,如果查找成功,则返回true ,把起点、折点、终点存入动态数组。
private bool CheckOneCorner(Point p1, Point p2, bool isAddPath, bool ClearPath)
{
Point MinRow, MaxRow;
Point CrossX, CrossY;
MinRow = p1.X
MaxRow = p1.X
CrossX = new Point (MinRow.X, MaxRow.Y);
CrossY = new Point (MaxRow.X, MinRow.Y);
if (Status[CrossX.X, CrossX.Y] != 0)
{
if (CheckHorizontal(MinRow, CrossX, false )) //先横后竖的情况
{
if (CheckVertical(CrossX, MaxRow, false )) //先判断水平路径是否可行,再判断垂直路径是否可行。
{
if (isAddPath) //如果上面的判断都可行,则路径合理,吧起点、折点、折点、终点四个坐标存入数组。
{
MyPath.Add(MinRow);
MyPath.Add(CrossX);
MyPath.Add(CrossX);
MyPath.Add(CrossY);
}
return true ;
}
}
}
if (isAddPath && ClearPath) //如果上面的判断中有不可行的,则进行另一种情况的判断,看是否可以通过一折找到路径。再进行两一种情况之前要清空动态数组。
{
MyPath.Clear();
}
if (Status[CrossY.X, CrossY.Y] != 0) //先竖后横情况的寻路算法。
{
if (CheckVertical(MinRow, CrossY, false ))
{
if (CheckHorizontal(CrossY, MaxRow, false ))
{
if (isAddPath)
{
MyPath.Add(MinRow);
MyPath.Add(CrossY);
MyPath.Add(CrossY);
MyPath.Add(MaxRow);
}
return true ;
}
}
}
return false ; //通过两种情况的判断,得出一折是否可行。
}
1.4.3两个折点的寻路算法
图1.6
两个折点时要由起点位置到终点位置连线的第一条线的画线方向分成四中情况来处理(如图所示)。思路是由起始图的位置依次向四个方向搜索查找。搜索方法是每次向一个方向移动一个位置,每移动一次进行判断一次这个位置是否是第一个折点。判断方法用的是假设法,假设此点是第一个折点,那剩下的就是一折的情况了,用一折的算法进行一次寻路查找。若能连通,则找到路径,退出算法,连不通,则假设失败,再用下一个位置判断。到达游戏图片的边缘后还没找到,则代表此方向不可行,再在起始点向另一个方向逐步假设判断。四个方向的都判断完后,要是没找到路径,就代表两个折点的情况找不到路径,返回false ,结束两个折点的算法。
private bool CheckTwoCorner(Point P1, Point P2, bool isAddPath)
{ //对上下左右方向的搜索函数的调用,当一个函数找到路径后,返回true ,退出此算法,否则清空动态数组,对其他方向的搜索函数进行调用。
if (CheckTwoCornerUP(P1, P2, isAddPath))
{
return true ;
}
MyPath.Clear();
if (CheckTwoCornerDown(P1, P2, isAddPath))
{
return true ;
}
MyPath.Clear();
if (CheckTwoCornerLeft(P1, P2, isAddPath))
{
return true ;
}
MyPath.Clear();
if (CheckTwoCornerRight(P1, P2, isAddPath))
{
return true ;
}
return false ; //四个方向的搜索都没找到,表示两个折点的情况下找不到路径。 }
private bool CheckTwoCornerUP(Point P1, Point P2, bool isAddPath)//想上搜索函数
{
图1.7
for (int i = P1.X - 1; i > -1; i--) //从起始点位置的上面开始到程序数组的上边缘。 {
Point StartPoint = new Point (i, P1.Y);
if (Status[StartPoint.X, StartPoint.Y]== 1)
{
if (isAddPath)
{
MyPath.Clear();
MyPath.Add(P1);
MyPath.Add(StartPoint);
}
if (CheckOneCorner(StartPoint, P2, isAddPath, false ))
{
return true ; //逐步搜索的“第一折点”为StartPoint 点,没搜索一步进行一次由“第一折点”和终点间的一折点的算法,如果判断返回true ,则第一折点找到,寻路成功。跳出此算法。若未找到,则返回false ,进行其他方向的搜索查找。
}
}
else
{
return false ;
}
}
return false ; //此方向全部搜索完,没找到路径,再搜索其他方向。
}
private bool CheckTwoCornerRight(Point P1, Point P2, bool isAddPath) //向右的搜索函数
{
图1.8
for (int i = P1.Y + 1; i
{
Point StartPoint = new Point (P1.X, i);
if (Status[StartPoint.X, StartPoint.Y] ==1)
{
if (isAddPath)
{
MyPath.Clear();
MyPath.Add(P1);
MyPath.Add(StartPoint);
}
if (CheckOneCorner(StartPoint, P2, isAddPath, false ))
{
return true ;
}
}
else
{
return false ;
}
}
return false ; //此方向搜索完,没找到路径,再搜索其他方向。
}
private bool CheckTwoCornerLeft(Point P1, Point P2, bool isAddPath) //向左的搜索函数
{
图1.9
for (int i = P1.Y - 1; i > -1; i--)
{
Point StartPoint = new Point (P1.X, i);
if (Status[StartPoint.X, StartPoint.Y] ==1)
{
if (isAddPath)
{
MyPath.Clear();
MyPath.Add(P1);
MyPath.Add(StartPoint);
}
if (CheckOneCorner(StartPoint, P2, isAddPath, false ))
{
return true ;
}
}
else
{
return false ;
}
}
return false ;
}
private bool CheckTwoCornerDown(Point P1, Point P2, bool isAddPath) //向下的搜索函数
{
图1.10
for (int i = P1.X + 1; i
{
Point StartPoint = new Point (i, P1.Y);
if (Status[StartPoint.X, StartPoint.Y]==1)
{
if (isAddPath)
{
MyPath.Clear();
MyPath.Add(P1);
MyPath.Add(StartPoint);
}
if (CheckOneCorner(StartPoint, P2, isAddPath, false ))
{
return true ;
}
}
else
{
return false ;
}
}
return false ;
}
1.5Game 类的实现
Game 包括了游戏中所有的基本功能:图片路径算法、游戏的提示共功能、洗牌功能等等。
public bool Thishi(Graphics g) //游戏的提示功能
{
int k = 1;
int i = 1;
int j = 1;
bool Right = false ; ;
ArrayList TishiArray = new ArrayList ();
for (k = 1; k
{
for (i = 1; i
for (j = 1; j
{
if (GameData[i, j] == k)
{
TishiArray.Add(i);
TishiArray.Add(j);
}
}
if (TishiArray.Count 0)
{
Point p1 = new Point ();
Point p2 = new Point ();
p1.X = (int )TishiArray[0];
p1.Y = (int )TishiArray[1];
p2.X = (int )TishiArray[2];
p2.Y = (int )TishiArray[3];
if (CheckPath(p1, p2, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p1.Y * 55 + 2), (p1.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p2.Y * 55 + 2), (p2.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
TishiArray.Clear();
}
else if (TishiArray.Count >= 5 && TishiArray.Count
{
Point p1 = new Point ();
Point p2 = new Point ();
Point p3 = new Point ();
Point p4 = new Point ();
p1.X = (int )TishiArray[0];
p1.Y = (int )TishiArray[1];
p2.X = (int )TishiArray[2];
p2.Y = (int )TishiArray[3];
p3.X = (int )TishiArray[4];
p3.Y = (int )TishiArray[5];
p4.X = (int )TishiArray[6];
p4.Y = (int )TishiArray[7];
if (CheckPath(p1, p2, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p1.Y * 55 + 2), (p1.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p2.Y * 55 + 2), (p2.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
else if (CheckPath(p1, p3, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p1.Y * 55 + 2), (p1.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p3.Y * 55 + 2), (p3.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
else if (CheckPath(p1, p4, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p1.Y * 55 + 2), (p1.X * 47 + 2), 55 - 4, 47 - 4);
g.DrawRectangle(b1, (p4.Y * 55 + 2), (p4.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
else if (CheckPath(p2, p3, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p2.Y * 55 + 2), (p2.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p3.Y * 55 + 2), (p3.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
else if (CheckPath(p2, p4, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p2.Y * 55 + 2), (p2.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p4.Y * 55 + 2), (p4.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
else if (CheckPath(p3, p4, true ) == true )
{
Pen b1 = new Pen (Color .Red, 4);
g.DrawRectangle(b1, (p3.Y * 55 + 2), (p3.X * 47 + 2), 55 - 4, 47 - 4); g.DrawRectangle(b1, (p4.Y * 55 + 2), (p4.X * 47 + 2), 55 - 4, 47 - 4); Right = true ;
}
TishiArray.Clear();
}
if (Right == true )
break ;
}
g.Dispose();
return Right;
}
}
public void Fresh() //提示功能
{ ArrayList Fresh = new ArrayList ();
for (int i = 1; i
for (int j = 1; j
{ Fresh.Add(GameData[i, j]); GameData[i, j] = -1;
}
}
Random Ran = new Random ();
int temp;
for (int k = 0; k
{int p1 = Ran.Next(0, Fresh.Count); int p2 = Ran.Next(0, Fresh.Count); if (p1 != p2)
{temp = (int )Fresh[p1];
Fresh[p1] = Fresh[p2];
Fresh[p2] = temp;
}
}
for (int i = 1; i
for (int j = 1; j
{ GameData[i, j] = (int )Fresh[0]; Fresh.RemoveAt(0);
}
} }