const float g_fBlockStartX = -40.625f; const float g_fBlockStartY = -28.125f;
// 屏幕高度75 / 4块 = 18.75每块的大小.编辑器里预先摆放好的方块宽和高 // 必须与此值一致 const float g_fBlockSize = 18.75f;
3、 进入Main.cpp中填写初始化代码。 1) 填写下面几行变量的定义: /* 游戏原理:4 * 4的方块矩阵(二维数组),前15个的值按顺序从1-15依次递增, * 第16个留空为0。按照这个顺序排列的矩阵值代表游戏胜利。初始化的时候, * 将该16个矩阵值随机排布即得到本局关卡。为0的空位代表附近上下左右的 * 4个方块可以移动过来。 一维数组g_szBlockName存储精灵名字与二维数组 * 的值一一对应,当移动二维数组的值的时候,此名字数组的值也需要跟着移动
*/
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}; 2) 由于我们用于记录方块位置的成员变量是一个二维数组g_iBlockState,而我们
用于保存所有方块精灵的数组是一个一维数组,所以下面我们需要用到一些自定义的函数用于从二维数组转换到一维数组。其实二维数组的存放在内存里面也是连续存放的,我们在读取他们的值得时候当然可以使用一维数组的方法来读取它,只不过这里需要进行数组下标数值的相应改变。例:二维数组a[x][y]转换为一维数组的计算方法是:x* 二维数组中每行的元素数+y。
在Main.cpp中添加我们自定义的二维数组索引转换成一维数组索引的函数XYToOneIndex声明并且定义:
int XYToOneIndex( const int iIndexX, const int iIndexY ) { return (iIndexY * BLOCK_COUNT + iIndexX); }
我们使用两个for循环来遍历二维数组,第一个for循环遍历二维数g_iBlockState的第二个下标,第二个for循环遍历二维数组g_iBlockState的第一个下标。在Main.cpp函数里面添加下面的代码:
for( iLoopY = 0; iLoopY < BLOCK_COUNT; iLoopY++ ) { for( iLoopX = 0; iLoopX < BLOCK_COUNT; iLoopX++ ) { } }
4、 我们首先用刚刚我们自定义的数组下标转换函数XYToOneIndex将二维数组下标转
换成一维数组的下标。在for( iLoopX = 0; iLoopX < BLOCK_COUNT; iLoopX++ )里面填写下面的代码: iOneIndex = XYToOneIndex( iLoopX, iLoopY ); iOneIndex即是二维数组在一维数组里面的下标值。
1) 如果遍历到数组的最后一个,我们就将其设定为空位精灵,它的名称为‘\\0’,
后面我们只要判断一个精灵的周围有没有名称为”\\0”的精灵就可以知道他周围是否有空位,它在二维数组中的值以0来代替。添加下面的代码: // 数组的最后一个 if( BLOCK_COUNT - 1 == iLoopX && BLOCK_COUNT - 1 == iLoopY ) {
g_iBlockState[iLoopY][iLoopX] = 0;
g_szBlockName[iOneIndex][0] = '\\0'; } 2) 如果没有遍历到最后一个位置,我们随机从iRandData取一个值赋给
g_iBlockState,并且给对应名字的精灵数组g_spBlock初始化,同时将该精灵移动到对应的位置。添加下面代码: else { // 在当前剩余未使用到的数值里随机一个出来,赋值给二维数组 iRandIndex = dRandomRange( 0, iDataCount - 1 ); g_iBlockState[iLoopY][iLoopX] = iRandData[iRandIndex]; /* 给对应的名字数组赋值。该名字的方块已经预先在地图里摆放好,因 此只需要生成对应的名字即可,不用创建精灵 */ strcpy( g_szBlockName[iOneIndex], dMakeSpriteName( \ g_iBlockState[iLoopY][iLoopX] ) ); // 将该精灵移动到对应的位置 MoveSpriteToBlock( g_szBlockName[iOneIndex], iLoopX, iLoopY ); }
3) 在这里我们需要添加一个移动精灵到特定位置的函数MoveSpriteToBlock。进入
Main.cpp中添加函数的声明和定义:
void MoveSpriteToBlock( const char *szName, const int iIndexX, const int iIndexY ) { float fPosX = g_fBlockStartX + iIndexX * g_fBlockSize; float fPosY = g_fBlockStartY + iIndexY * g_fBlockSize; dSetSpritePosition( szName, fPosX, fPosY ); }
4) 由于是随机从iRandData数组里面取一个数赋给g_iBlockState,所以我们每次赋
一个需要将iRandData数组后面的值往前面移动覆盖掉改值。因此我们需要用for循环,将抽取到的索引iRandIndex后面的数组值依次往前移动一位,同时方块总数目减一。在两个for循环的else语句的最后代码如下: for( iLoop = iRandIndex; iLoop < iDataCount - 1; iLoop++ ) { iRandData[iLoop] = iRandData[iLoop + 1]; }
// 剩余有效值总数减一 iDataCount--;
至此,本实验结束。
实验四 移动方块
【实验内容】
步骤一、获取鼠标单击消息 步骤二、判断鼠标点击的方块
步骤三、判断周围是否有空位,移动方块 【实验思路】
遍历一维数组g_szBlockName,使用dIsPointInSprite 函数判断当前鼠标坐标是否位于某个名字的精灵内部。如果找到某个名字的精灵被点击中,请将当前循环变量iLoop赋值给iClickIndex。再判断该方块精灵周围有没有名称为“\\0”的精灵,有的有的话移动到该位置。 【实验指导】
1、 在Main.cpp的dOnMouseClick函数中去实现本实验的功能: 2、 判断游戏是否正在进行,在上面的函数里面添加下面的代码:
// 只处理游戏进行中的鼠标响应 if( 2 != m_iGameState ) return;
3、 获取鼠标点击的坐标,使用一个for循环遍历存储所以方块精灵的一维数组
g_szBlockName,判断该坐标是否在某一个方块精灵中,是的话得到该精灵的下标值。添加下面的代码: int iClickIndex = -1; int iLoop = 0;
for( iLoop = 0; iLoop < BLOCK_COUNT * BLOCK_COUNT; iLoop++ ) { if( '\\0' == g_szBlockName[iLoop][0] ) continue; // 使用API dIsPointInSprite 判断指定坐标是否位于某个名字的精灵内部 if( dIsPointInSprite( g_szBlockName[iLoop], fMouseX, fMouseY ) ) { iClickIndex = iLoop; break; } }
4、 这里我们需要用到将一维数组转换为二维数组,原理与我们前面二维数组转换为一
维数组的方面相反。只要将一维数组下标值对二维数组中每行的元素数进行求余就能得到二维数组的X下标值,将一维数组对二维数组中的每行的元素数进行求商就能得到二维数组的Y下标值。为简化操作,我们分别将这两个操作定义为函数形式直接调用。在Main.cpp中添加这两个函数的定义:
// 一维数组索引转换到二维数组索引X,注意这2个数组大小必须一致 int OneIndexToX( const int iIndex ) { return (iIndex % BLOCK_COUNT);
}
// 一维数组索引转换到二维数组索引Y,注意这2个数组大小必须一致 int OneIndexToY( const int iIndex ) { return (iIndex / BLOCK_COUNT); }
5、 我们将步骤4得到的一维数组下标值转换为二维数组g_iBlockState的下标值。这样
我们就可以在g_iBlockState中查找该鼠标点击到的方块精灵周围是否有空位。在二维数组里查找鼠标点击的方块上下左右4个方向上是否有空位。注意边界判断,否则数组访问会越界。比如判断左边时,需要判断是否已经是最左边的索引(iIndexX == 0)。如果有空位(值为0),则将该空位的索引赋值给下面这2个变量iEmptyIndexX和iEmptyIndexY保存。在OnMouseClick中添加下面代码: // 判断鼠标是否点中方块 if( -1 == iClickIndex ) return; // 将该一维数组的Index转换成二维数组的X,Y int iIndexX = OneIndexToX( iClickIndex ); int iIndexY = OneIndexToY( iClickIndex );
// TODO 在二维数组里查找鼠标点击的方块上下左右4个方向上是否有空位:
// 注意边界判断,否则数组访问会越界。比如判断左边时,需要判断是否已经是 //最左边的索引(iIndexX == 0)
// 如果有空位(值为0),则将该空位的索引赋值给下面这2个变量 int iEmptyIndexX = -1, iEmptyIndexY = -1; // X 左方向(4个方向均需要判断是否是位于边缘,iIndexX > 0 即起此作用) if( iIndexX > 0 ) { if( 0 == g_iBlockState[iIndexY][iIndexX - 1] ) { iEmptyIndexX = iIndexX - 1; iEmptyIndexY = iIndexY; } } // X 右方向 if( -1 == iEmptyIndexX && iIndexX < BLOCK_COUNT - 1 )
{ if( 0 == g_iBlockState[iIndexY][iIndexX + 1] ) { iEmptyIndexX = iIndexX + 1; iEmptyIndexY = iIndexY; } } // Y 上方向 if( -1 == iEmptyIndexY && iIndexY > 0 )