haihongyuan.com
海量文库 文档专家
全站搜索:
您现在的位置:首页 > 幼儿教育 > 幼儿读物幼儿读物

拼图游戏

发布时间:2013-12-06 10:01:00  

B类题——FUNCODE

———拼图游戏 学 院 计算机科学与技术 专 业 计算机科学与技术 学 号 110341202 学 生 姓 名 陈格 指导教师姓名 沈中林 题目来源及序号 拼图游戏 难 度 B类

2013 年 5月 27 日

一、题目

玩家通过鼠标单击相邻位有空位的方块来移动方块,从而最终将一副散乱的图片拼成完整的图片。

二、问题分析及求解基本思路

1.添加一个4x4的二维数组,将图案分成15份,随机摆放,剩下一个位置留空,用于移动方块。

2.游戏的原理:

在一个4 * 4的方块矩阵(二维数组),前15个的值按顺序从1-15依次递增,第16个留空,我们设置为一个名称为“NULL”的精灵。按照这个顺序排列的矩阵值代表游戏胜利。

精灵名称依次是PictureBlock1,PictureBlock2…PictureBlock15。因此,初始化的时候,用一个数组iRandData顺序保存1到15,对应表示15个方块精灵。再用一个二维数组m_iBlockState[4][4]表示这16个位置。

每次随机从iRandData中取一个值,赋给m_iBlockState,表示某个位置放置哪张方块图片。 为了保证同一张方块图片不会被重复使用,每次从iRandData随机取一个值以后,将该随机数后面的数组值往前移一位,并且数组大小减1。

三、问题求解的整体框架结构

实验内容

玩家通过鼠标单击相邻位有空位的方块来移动方块,从而最终将一副散乱的图片拼成完整的图片。要求如下:

1. 游戏的初始界面如图一,单击空格键进入游戏,进入游戏之后系统将完成的图片分成大小相同的15分并随机摆放成如图二。

图 一

图 二

2. 启动游戏,单击空格键进入游戏。通过鼠标单击周围有空格的方块来移动方块,直到全图

拼接成图二中右下角的图案样式,游戏结束,重新回到图一界面。

3. 游戏的原理是定义一个4 * 4的方块矩阵(二维数组),前15个的值按顺序从1-15依次递增,

第16个留空为0。按照这个顺序排列的矩阵值代表游戏胜利。初始化的时候,将该16个矩阵值随机排布即得到本局关卡。为0的空位代表附近上下左右的4个方块可以移动过来

4. 每次单击鼠标左键,方块移动一格。鼠标必须移动到方块范围内单击才能有效移动方块。

【实验任务】

步骤一、打开FunCode,创建一个的C++语言项目;

步骤二、导入Puzzle模板。

【实验指导】

1、 打开FunCode,点击“项目”菜单,选择“创建C++工程”

注意:工程名名称要求字母开头,只能包含字母和数字,且名字中间不能有空格。

2、 点击菜单“项目”中的“导入地图模块”,如图一。跳出一个对话框,选中“Puzzle”模板,

点击“导入到工程”按钮,如图二。

图 一 图 二

3、 导入成功后的,界面如下图所示:

步骤、启动游戏显示“空格开始”,单击空格键进入游戏初始界面。

【实验思路】

系统会自动响应OnKeyDown函数来响应键盘按下消息,这部分代码实现在main.cpp里。我们要做的就是通过在main.cpp的OnKeyDown函数里实现我们的代码。当用户单击键盘上的空格键之后,设置GameBegin即“空格开始”精灵不可见。

【实验指导】

1、 进入LessonX.h文件中添加“空格开始”精灵变量的声明以及我们自定义处理系统响应键

盘按下消息的函数OnKeyDown的声明:

CSprite* m_spGameBegin; //"空格开始"精灵

void OnKeyDown( const int iKey, const int iAltPress, const int iShiftPress, const int iCtrlPress );

2、 进入LessonX.cpp的构造函数里面初始化“空格开始”精灵成员变量:

m_spGameBegin = new CSprite("GameBegin");

在文件的最后面添加OnKeyDown函数的定义代码:

void CGameMain::OnKeyDown( const int iKey, const int iAltPress, const int iShiftPress, const int iCtrlPress )

{

}

3、 空格键的键值为KEY_SPACE,当系统判断玩家单击了空格键以及游戏的状态时未进行(即

m_iGameState的值0)则游戏开始设置m_iGameState的值为1,同时隐藏掉“空格开始”精灵。在步骤2的函数里面添加下面的代码:

if( KEY_SPACE == iKey && 0 == m_iGameState )

{

m_iGameState = 1;

} // 隐藏提示开始文字 m_spGameBegin->SetSpriteVisible(0);

4、 进入Main.cpp函数里面,将系统捕获到的键盘按下消息的参数传给我们自定义的函数,需

要在OnKeyDown函数里面添加下面一行代码:

g_GameMain.OnKeyDown(iKey,bAltPress,bShiftPress,bCtrlPress);

至此,本实验结束。

【实验内容】

步骤一、添加一个4x4的二维数组,将图案分成15份,随机摆放,剩下一个位置留空,用于移动方块。

【实验思路】

游戏的原理是在一个4 * 4的方块矩阵(二维数组),前15个的值按顺序从1-15依次递增,第16个留空,我们设置为一个名称为“NULL”的精灵。按照这个顺序排列的矩阵值代表游戏胜利。

精灵名称依次是PictureBlock1,PictureBlock2…PictureBlock15。因此,初始化的时候,用一个数组iRandData顺序保存1到15,对应表示15个方块精灵。再用一个二维数组m_iBlockState[4][4]表示这16个位置。

每次随机从iRandData中取一个值,赋给m_iBlockState,表示某个位置放置哪张方块图片。 为了保证同一张方块图片不会被重复使用,每次从iRandData随机取一个值以后,将该随机数后面的数组值往前移一位,并且数组大小减1。

【实验指导】

1、 进入LessonX.h里面,添加如下的变量声明:

1) 添加成员变量声明:

static const float m_fBlockStartX;// 按方块大小,在编辑器里摆放的第一块方块的起

始坐标

static const float m_fBlockStartY ;

static const float m_fBlockSize ; // 屏幕高度75 / 4块 = 18.75每块的大小.编辑

器里预先摆放好的方块宽和高必须与此值一致

int m_iBlockState[BLOCK_COUNT][BLOCK_COUNT]; // 二维数组,存储N*N的矩阵

方块信息

CSprite* m_spBlock[BLOCK_COUNT*BLOCK_COUNT];//方块精灵

注意,m_fBlockStartX,m_fBlockStartY,m_fBlockSize三个变量是不能改变的,因此我

们需要将它们设置为const类型,const在c++里面表示只读,即后面程序只能读取使

用它的值而不能对其进行赋值改变。C++类中的成员变量如果是const类型,则需要在

其前面加上一个static的声明,即静态变量的声明,这样,我们才能在cpp文件里面使

用这个变量。

2) 添加BLOCK_COUNT的#define声明:

在LessonX.h的#include后面添加下面一行:

#define BLOCK_COUNT 4 // N * N 的矩阵方块,一个N的大小

#define相当于告诉编译器,如果遇到BLOCK_COUNT则它相当与一个int类型的变量,

且值为4。使用#define可以方便改变我们需要的值。

2、 进入LessonX.cpp中在最后面添加下面的成员变量的声明:

const float CGameMain::m_fBlockSize=18.75f;

const float CGameMain::m_fBlockStartX=-40.625f;

const float CGameMain::m_fBlockStartY=-28.125f;

因为这三个变量的类型是const类型,因此我们不能在构造函数里面定义它,需要单独拿出来进行定义。相当于c里面的全局变量的定义。

3、 进入LessonX.cpp中的GameInit函数里面填写初始化代码。

1) 填写下面几行变量的定义:

int iLoopX = 0, iLoopY = 0, iLoop = 0;

int iOneIndex = 0, iRandIndex = 0;

// 用做随机的数组,当随机抽取到此数组中的一个时,比如随机到第五个,则将第五个取出来用

// 第五个后面的数组都往前移动一位,将第五个覆盖掉,数组总数减一,下次再在这剩余的14个数值里随机抽取

int iDataCount = BLOCK_COUNT * BLOCK_COUNT - 1;

int iRandData[BLOCK_COUNT * BLOCK_COUNT - 1] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

iDataCount用于记录方块的数目,iRandData是一个二维数组,里面存放了1至15的int值,对应表示1-15个方块精灵,用于后面对方块进行随机生成。每次随机从此数组中抽取一个值,比如随机到第五个,则将第五个取出来用,第五个后面的数组都往前移动一位,将第五个覆盖掉,数组总数减一,下次再在这剩余的14个数值里随机抽取。这样做的好处是确保不会重复两次以上使用同一个方块。

2) 由于我们用于记录方块位置的成员变量是一个二维数组m_iBlockState,而我们用于保

存所有方块精灵的数组是一个一维数组,所以下面我们需要用到一些自定义的函数用于从二维数组转换到一维数组。其实二维数组的存放在内存里面也是连续存放的,我们在读取他们的值得时候当然可以使用一维数组的方法来读取它,只不过这里需要进行数组下标数值的相应改变。例:二维数组a[x][y]转换为一维数组的计算方法是:x* 二维数组中每行的元素数+y。

进入LessonX.h中添加我们自定义的二维数组索引转换成一维数组索引的函数

XYToOneIndex声明:

int XYToOneIndex( const int iIndexX, const int iIndexY );

进入LessonX.cpp中添加上面函数的定义:

int CGameMain::XYToOneIndex( const int iIndexX, const int iIndexY )

{

return (iIndexY * BLOCK_COUNT + iIndexX);

}

4、 我们使用两个for循环来遍历二维数组,第一个for循环遍历二维数组m_iBlockState的第

二个下标,第二个for循环遍历二维数组m_iBlockState的第一个下标。在GameIint函数里面添加下面的代码:

for( iLoopY = 0; iLoopY < BLOCK_COUNT; iLoopY++ )

{

for( iLoopX = 0; iLoopX < BLOCK_COUNT; iLoopX++ )

{

}

}

5、 我们首先用刚刚我们自定义的数组下标转换函数XYToOneIndex将二维数组下标转换成一

维数组的下标。在for( iLoopX = 0; iLoopX < BLOCK_COUNT; iLoopX++ )里面填写下面的代码: iOneIndex = XYToOneIndex( iLoopX, iLoopY );

iOneIndex即是二维数组在一维数组里面的下标值。

1) 如果遍历到数组的最后一个,我们就将其设定为空位精灵,它的名称为NULL”,后面我

们只要判断一个精灵的周围有没有名称为”NULL”的精灵就可以知道他周围是否有空位,它在二维数组中的值以0来代替。添加下面的代码:

// 数组的最后一个

if( BLOCK_COUNT - 1 == iLoopX && BLOCK_COUNT - 1 == iLoopY )

{

m_iBlockState[iLoopY][iLoopX] = 0;

m_spBlock[iOneIndex] = new CSprite("NULL");

}

2) 如果没有遍历到最后一个位置,我们随机从iRandData取一个值赋给m_iBlockState,并

且给对应名字的精灵数组m_spBlock初始化,同时将该精灵移动到对应的位置。添加下面代码:

else

{

// 在当前剩余未使用到的数值里随机一个出来,赋值给二维数组

iRandIndex = CSystem::RandomRange( 0, iDataCount - 1 );

m_iBlockState[iLoopY][iLoopX] = iRandData[iRandIndex];

char* tmpName=CSystem::MakeSpriteName("PictureBlock",m_iBlockState[iLoopY][iLoopX]);

m_spBlock[iOneIndex]=new CSprite(tmpName);

} // 将该精灵移动到对应的位置 MoveSpriteToBlock( m_spBlock[iOneIndex], iLoopX, iLoopY );

在这里我们需要添加一个移动精灵到特定位置的函数MoveSpriteToBlock。进入

LessonX.h中添加函数的声明:

void MoveSpriteToBlock( CSprite *tmpSprite, const int iIndexX, const int iIndexY );

进入LessonX.cpp中添加该函数的定义:

void CGameMain::MoveSpriteToBlock( CSprite *tmpSprite, const int iIndexX, const int

iIndexY )

{

float fPosX = m_fBlockStartX + iIndexX * m_fBlockSize;

float fPosY = m_fBlockStartY + iIndexY * m_fBlockSize;

tmpSprite->SetSpritePosition(fPosX, fPosY );

}

3) 由于是随机从iRandData数组里面取一个数赋给m_iBlockState,所以我们每次赋一个需

要将iRandData数组后面的值往前面移动覆盖掉改值。因此我们需要用for循环,将抽取到的索引iRandIndex后面的数组值依次往前移动一位,同时方块总数目减一。代码如下:

for( iLoop = iRandIndex; iLoop < iDataCount - 1; iLoop++ )

{

iRandData[iLoop] = iRandData[iLoop + 1]; } // 剩余有效值总数减一 iDataCount--; 至此,本实验结束。

【实验内容】

步骤一、获取鼠标单击消息

步骤二、判断鼠标点击的方块

步骤三、判断周围是否有空位,移动方块

【实验思路】

遍历一维数组m_spBlock,使用IsPointInSprite 函数判断当前鼠标坐标是否位于某个名字的精灵内部。如果找到某个名字的精灵被点击中,请将当前循环变量iLoop赋值给iClickIndex。再判断该方块精灵周围有没有名称为“NULL”的精灵,有的有的话移动到该位置。

【实验指导】

1、 进入LessonX.h中添加我们自定义的处理鼠标单击的函数声明:

void OnMouseClick( const int iMouseType, const float fMouseX, const float fMouseY );

2、 进入LessonX.cpp中添加该函数的定义:

void CGameMain::OnMouseClick( const int iMouseType, const float fMouseX, const float fMouseY )

{

}

3、 判断游戏是否正在进行,在上面的函数里面添加下面的代码:

// 只处理游戏进行中的鼠标响应

if( 2 != m_iGameState )

return;

4、 获取鼠标点击的坐标,使用一个for循环遍历存储所以方块精灵的一维数组m_spBlock,判

断该坐标是否在某一个方块精灵中,是的话得到该精灵的下标值。

添加下面的代码:

int iClickIndex = -1;

int iLoop = 0;

for( iLoop = 0; iLoop < BLOCK_COUNT * BLOCK_COUNT; iLoop++ )

{

if( "NULL" == m_spBlock[iLoop]->GetName() )

continue;

// 使用API dIsPointInSprite 判断指定坐标是否位于某个名字的精灵内部

if(m_spBlock[iLoop]->IsPointInSprite(fMouseX, fMouseY))

{

iClickIndex = iLoop;

break;

}

}

5、 这里我们需要用到将一维数组转换为二维数组,原理与我们前面二维数组转换为一维数组

的方面相反。只要将一维数组下标值对二维数组中每行的元素数进行求余就能得到二维数组的X下标值,将一维数组对二维数组中的每行的元素数进行求商就能得到二维数组的Y下标值。为简化操作,我们分别将这两个操作定义为函数形式直接调用。在LessonX.h中添加这两个函数的声明:

int OneIndexToX( const int iIndex );

int OneIndexToY( const int iIndex );

在LessonX.cpp中添加这两个函数的定义:

int CGameMain::OneIndexToX( const int iIndex )

{

return (iIndex % BLOCK_COUNT);

}

int CGameMain::OneIndexToY( const int iIndex )

{

return (iIndex / BLOCK_COUNT);

}

6、 我们将步骤4得到的一维数组下标值转换为二维数组m_iBlockState的下标值。这样我们就

可以在m_iBlockState中查找该鼠标点击到的方块精灵周围是否有空位。在二维数组里查找鼠标点击的方块上下左右4个方向上是否有空位。注意边界判断,否则数组访问会越界。比如判断左边时,需要判断是否已经是最左边的索引(iIndexX == 0)。如果有空位(值为0),则将该空位的索引赋值给下面这2个变量iEmptyIndexX和iEmptyIndexY保存。在

OnMouseClick中添加下面代码:

// 判断鼠标是否点中方块

if( -1 == iClickIndex )

return;

// 将该一维数组的Index转换成二维数组的X,Y

int iIndexX = OneIndexToX( iClickIndex );

int iIndexY = OneIndexToY( iClickIndex );

int iEmptyIndexX = -1, iEmptyIndexY = -1;

// X 左方向(4个方向均需要判断是否是位于边缘,iIndexX > 0 即起此作用)

if( iIndexX > 0 )

{

if( 0 == m_iBlockState[iIndexY][iIndexX - 1] )

{

iEmptyIndexX = iIndexX - 1;

iEmptyIndexY = iIndexY;

}

}

// X 右方向

if( -1 == iEmptyIndexX && iIndexX < BLOCK_COUNT - 1 )

{

if( 0 == m_iBlockState[iIndexY][iIndexX + 1] )

{

iEmptyIndexX = iIndexX + 1;

iEmptyIndexY = iIndexY;

}

}

// Y 上方向

if( -1 == iEmptyIndexY && iIndexY > 0 )

{

if( 0 == m_iBlockState[iIndexY - 1][iIndexX] )

{

iEmptyIndexX = iIndexX;

iEmptyIndexY = iIndexY - 1;

}

}

// Y 下方向

if( -1 == iEmptyIndexY && iIndexY < BLOCK_COUNT - 1 )

{

if( 0 == m_iBlockState[iIndexY + 1][iIndexX] )

{

iEmptyIndexX = iIndexX;

iEmptyIndexY = iIndexY + 1;

}

}

7、 如果找到空位,则将鼠标点击的方块精灵移动到该空位上。在二维数组里,将该索引对应

的值进行交换以及将两个方块精灵的名称交换,然后再将鼠标点击的精灵移动到新的位置上。添加下面代码:

// 判断是否找到空位

if( -1 == iEmptyIndexX || -1 == iEmptyIndexY )

return;

// 有空位,在二维数组里,将该索引对应的值进行交换

m_iBlockState[iEmptyIndexY][iEmptyIndexX] = m_iBlockState[iIndexY][iIndexX];

m_iBlockState[iIndexY][iIndexX] = 0;

// 对应的名字也进行交换

int iOneIndex = XYToOneIndex( iEmptyIndexX, iEmptyIndexY );

const char* tmpName=m_spBlock[iOneIndex]->GetName();

m_spBlock[iOneIndex]=new CSprite(m_spBlock[iClickIndex]->GetName());

m_spBlock[iClickIndex]=new CSprite("NULL");

// 将该精灵移动到对应的位置

MoveSpriteToBlock( m_spBlock[iOneIndex], iEmptyIndexX, iEmptyIndexY );

8、 最后我们需要在Main.cpp中的OnMouseClick函数里面添加对鼠标点击消息的响应代码:

g_GameMain.OnMouseClick(iMouseType,fMouseX,fMouseY);

至此,本实验结束。

【实验内容】

步骤一、循环遍历判断是否胜利

步骤二、重新开始游戏

【实验思路】

判断游戏是否胜利主要是看所有方块的排列顺序是否为1至15,并且第16个位置的值为0。判断是否胜利的函数需要程序每次调用主函数的时候检测一次。

【实验指导】

1、 进入LessonX.h中添加我们自定义的判断是否胜利的函数IsGameWin的声明:

int IsGameWin();

2、 进入LessonX.cpp中添加该函数的定义,在文件的最后添加如下代码:

int CGameMain::IsGameWin()

{

}

3、 判断游戏是否胜利的算法主要是:使用2个for循环遍历二维数组m_iBlockState,当数组

里边的值为如下排列的时候,游戏胜利:第一个值是 1,第二个是 2,... 第15个是15。注意第16个(二维数组的最后一个)是空位,不用判断第16个(或者判断第16个的值为0)游戏胜利返回1,否则返回0. 如果在循环判断前15个的时候,只要其中一个值不符,不需要再往下执行,直接返回0即可。

在上面的函数定义里面添加如下的变量声明:

int iLoopX

int iResult = 0, iLoopY = 0; = 1;

利用两个for循环遍历m_iBlockState里面的数值,如果循环到数组的最后一个,说明该数组所有值符合游戏胜利的条件,则跳出循环,函数返回成功。如果有一个值不符合,则代表游戏没有胜利。实现代码如下:

} return 1;

4、 判断游戏是否胜利的函数以及完成。但是系统需要定时检测当前游戏是否是胜利的状态,

因此需要在主循环里面添加这个函数的if判断,如果游戏没有胜利,则执行GameRun函数,如果胜利,则执行GameEnd函数。因此在GameMainLoop将GameRun函数的if(true) { for( iLoopX = 0; iLoopX < BLOCK_COUNT; iLoopX++ ) { } // 数组的最后一个 if( BLOCK_COUNT - 1 == iLoopX && BLOCK_COUNT - 1 == iLoopY ) break; for( iLoopY = 0; iLoopY < BLOCK_COUNT; iLoopY++ ) // 其中一个值不等于,那么就没有胜利 if( m_iBlockState[iLoopY][iLoopX] != iResult ) return 0; iResult++;

改为if(!IsGameWin())。

5、 如果游戏成功需要执行GameEnd函数,重新开始游戏。进入GameEnd函数,添加下面一

行:

// 显示提示开始文字

m_spGameBegin->SetSpriteVisible(1);

至此,本实验结束。

五、总结

这是一个综合性较强,很实际的一个编程实例。刚学funcode时候,感觉设计游戏还挺有意思的,刚接触到这个题目时,觉得应该没什么大问题吧,因为,我自认为一些基本知识点都已经掌握了。可是,真的当我动手编程的时候,才发现并没有想象中的那么简单。单个知识点是弄清了,但是,将所有的知识点综合起来运用时,就碰到了各种各样的问题了。有时候,一个错误得找好久,才能发现。

本系统的重点就是要会建立精灵以及怎样随机分配精灵。初始化的时候,用一个数组iRandData顺序保存1到15,对应表示15个方块精灵。再用一个二维数组m_iBlockState[4][4]表示这16个位置。

每次随机从iRandData中取一个值,赋给m_iBlockState,表示某个位置放置哪张方块图片。 为了保证同一张方块图片不会被重复使用,每次从iRandData随机取一个值以后,将该随机数后面的数组值往前移一位,并且数组大小减1。

最后想说的是,调试程序真的很关键。调试程序需要较大的耐心。可能调试程序的时间比自己编写整个程序的时间还要长,下的工夫还要深,但这是一个程序员必须了解,也必须接受的事实。有好几次,几个错误让我很是头疼啊,很想删了,重新来过,可是最后发现,还是得有耐心去调试程序。

通过这个实验的练习,我也学到了很多东西,特别是加深了对类和对象理解和应用方面,也很谢谢老师给我的帮助。

上一篇:little match girl
下一篇:小卷
网站首页网站地图 站长统计
All rights reserved Powered by 海文库
copyright ©right 2010-2011。
文档资料库内容来自网络,如有侵犯请联系客服。zhit326@126.com