C++之2048小游戏 第二期
不是,一天点赞就到15了?!好吧,那我更新一下
1. 逻辑
(真的有人会看吗?)
注:本文1.1章为AI生成,如有错误欢迎在评论其指出!
1.1 普通/最初逻辑
这里我们首先考虑最初的逻辑
通常,游戏会有初始化、绘制界面、处理输入、更新游戏状态、判断胜负等部分。所以,可能需要一个二维数组来表示4x4的网格,存储每个格子的数值。然后,需要处理键盘输入,尤其是方向键的控制,这可能需要使用EasyX的键盘事件处理函数。
然后,关于图形绘制部分,每个格子的大小、颜色、数字的显示都需要设计。不同的数字对应不同的颜色,比如2是浅色,4稍深,以此类推。可能需要一个函数根据数字的值返回对应的颜色,然后在绘制时调用。
接下来,处理数字的移动和合并逻辑。这是比较复杂的部分。例如,当用户按下右方向键时,所有数字向右移动,并且相邻相同的数字合并。这个过程需要分步骤处理:先移动,再合并,再移动。每个方向的处理逻辑可能不同,需要分别处理。比如,向右移动的话,每一行从右往左处理,合并相同的数字,并累加分值。这里可能需要四个不同的函数来处理四个方向,或者找到一种统一的方式处理。
另外,每次操作后,需要在空白的位置随机生成一个2或4。需要记录所有空位的位置,随机选择一个位置,并生成数字。这一步需要每次操作后检查是否有空位,没有的话游戏结束。
游戏结束的条件有两个:一是所有格子填满且无法合并,二是有一个格子达到2048,即胜利。需要编写函数来检测这两种情况。
然后,测试各个部分的功能是否正确。比如,移动和合并是否正常,分数是否正确计算,界面是否正常刷新,游戏结束的判断是否准确等。
可能的难点包括正确处理不同方向的移动和合并逻辑,避免重复合并(例如,在一次移动中,合并过的块不能再合并),以及高效地检测游戏是否结束。
另外,用户体验方面,每次操作后需要刷新界面,可能需要添加动画效果,但考虑到EasyX的简单性,可能只做简单的重绘。同时,显示当前分数和最高分数,可能需要全局变量来记录这些信息。
现在,考虑代码的大致结构:
1. 包含头文件:graphics.h用于EasyX,conio.h可能用于键盘输入,time.h和stdlib.h用于随机数生成。
2. 定义常量,如格子大小、窗口尺寸、颜色数组等。
3. 全局变量:网格数组、分数、是否结束等。
4. 初始化函数:初始化窗口,设置随机种子,初始化网格数组,生成初始数字。
5. 绘制函数:根据网格数组绘制每个格子,显示分数。
6. 处理输入的函数:使用GetAsyncKeyState检测方向键,或者使用EasyX的消息循环处理按键。
7. 移动和合并逻辑:四个方向的函数,或者一个通用函数处理不同方向。
8. 生成新数字的函数:随机选择空白位置,生成2或4。
9. 判断游戏结束的函数:检查是否还有移动的可能,即是否有相邻相同的数字或者空位。
10. 主循环:处理输入,更新游戏状态,重绘界面,判断结束条件。
在具体实现时,如何处理方向移动是关键。例如,向右移动时,对于每一行,从右到左遍历,将非零数字移到右边,遇到相同的合并。例如,对于行[2,2,2,2],右移后变成[0,0,4,4]。需要确保每次合并只发生一次,比如四个2在右移时,右边的两个先合并成4,左边的两个再合并成4,所以结果应该是0,0,4,4。
另外,生成新数字的逻辑:每次移动有效(即网格有变化)后才生成新数字,否则不生成。例如,如果用户按下右方向键但所有数字无法移动或合并,那么不生成新数字。
测试案例:比如,当一行是2,2,2,2时,右移后应为0,0,4,4;左移应为4,4,0,0。当有4,4,8,8时,右移合并成0,8,0,16。
在编写移动函数时,可能需要针对每个方向分别处理。例如,右移时,对每一行进行处理,从最右边的列开始,依次向左检查是否有可合并的块。另一种方法是将处理逻辑通用化,比如将行提取出来,处理合并后,再放回去,这样四个方向的处理可以复用同一段代码,只需要调整行列的顺序。
比如,处理一行或一列的函数,参数可以是行的指针或数组,处理完合并后返回新的行和是否有变化。这样四个方向的处理都可以调用同一个函数,但需要调整如何提取行和写回行。
例如,对于右移,每一行处理为从右到左合并;左移则处理为从左到右;上移和下移则需要处理列,可能需要将列转换为行数组处理后再转回去。
这可能需要更复杂的代码结构,但可以减少重复代码。
现在,具体到代码:
定义网格数组:int grid[4][4] = {0};
初始化时,生成两个初始数字,通常是两个2,或者一个2和一个4?
可能首先生成两个2。在初始化函数中,调用生成新数字的函数两次。
生成新数字的函数需要找到所有空位,随机选一个,并随机生成2或4(通常是2的概率更高,比如90%的2,10%的4)。可以使用一个列表保存所有空位的坐标,然后随机选择一个。
绘制函数需要遍历每个格子,根据数字的值绘制不同颜色的矩形,并在中间显示数字。如果数字为0,则不显示。数字的位置可能需要调整,比如居中显示,使用合适的字体大小。
处理方向键输入,比如使用GetAsyncKeyState检测VK_UP、VK_DOWN、VK_LEFT、VK_RIGHT。但需要注意按键的持续检测,可能需要处理为每次按键触发一次移动,而不是按住不放连续移动。或者设置一个延时,避免按键处理过快。
移动处理函数的大致步骤:
对于每个方向,比如右移:
遍历每一行:
将当前行中的非零元素移到右侧,合并相邻相同的数字,并记录是否有移动或合并发生。
例如,处理一行数据:
原始行:2,0,2,4 → 移动后:0,0,4,4,并加分8分(合并两次,2+2=4,4+4=8)。
处理逻辑:
创建一个临时数组,保存处理后的行,初始为0。
从右往左遍历原行,记录上一个非零的值的位置。如果当前元素与上一个相同,则合并,分数增加,并标记合并,然后继续处理剩下的元素。
或者,另一种方法是,先将所有非零元素紧凑排列到右侧,然后遍历合并相邻相同的元素。
比如,步骤1:将行中的非零元素移到右边,如[2,0,2,4] → [0,2,2,4]
然后,从右到左检查相邻元素是否相同:
4和2不同,跳过。2和2相同,合并为4,左边的元素变为0 → [0,0,4,4]。此时,再紧凑一次,但可能不需要,因为合并后的元素已经靠右了。但可能会有中间的空隙,比如[2,2,2,2] → 合并成0,0,4,4,之后不需要再移动。或者,在合并之后,需要再次紧凑元素?
可能需要分步骤处理:
例如,处理右移:
对于每一行:
1. 移除空格:将非零元素移到右边,保持相对顺序。例如,行[2,0,2,4]变为[0,2,2,4]。
2. 合并相邻相同的元素,从右到左:检查当前元素和右边的元素是否相同,如果相同则合并,右边的元素置0,并加分。例如,处理后的行可能变为[0,2, 0, 8],然后再移除空格,变为[0,0,2,8]。
或者,可能需要先紧凑,再合并,再紧凑。
例如,步骤:
原始行:2,0,2,4 → 紧凑后:2,2,4,0 → 从右向左检查合并:
检查位置3(4)和位置2(2),不同;位置2(2)和位置1(2)相同,合并,变为4,位置1置0 → 行变为2,0,4,0 → 再次紧凑到右边:0,0,2,4。这显然不正确。
可能我的处理逻辑有问题。正确的右移合并应该是将两个相邻的相同数字合并一次,比如右边的两个先合并?
或者正确的处理方式应该是这样的:
比如,对于行 [2, 2, 2, 2],右移:
紧凑到右边:四个2 → 没有空格。然后从右往左合并,第三个和第四个合并为4,第三个位置变为4,第四个变为0。然后第二个和第三个合并为4,第二个变为4,第三个变为0。第一个和第二个合并为4,第一个变为4,第二个变为0。结果变成 [0,0,0,8],这显然不对,正确的结果应为 [0,0,4,4]。
所以,正确的合并逻辑应该是在紧凑之后,从右往左或从左往右合并,但每个数字只能合并一次。这可能需要更细致的处理。
正确的处理方式可能是这样的:
在处理一行的时候,先紧凑,将非零元素移到右侧(对于右移的情况),然后从右到左检查相邻元素是否可以合并。例如:
原始行:2,2,2,2 → 紧凑后:2,2,2,2 → 合并:第三个和第四个合并为4,得到2,2,0,4 → 再次紧凑,得到2,2,4,0 → 再合并第二个和第三个?或者可能我的思路错了。
另一种思路是,处理每个元素,只合并一次。例如,对于右移,处理顺序是从右到左:
比如,对于行 [2,2,2,2]:
索引3和2比较,相同,合并为4,索引3为4,索引2置0 → [2,2,0,4]
然后索引1和0比较,相同,合并为4,索引1为4,索引0置0 → [0,4,0,4]
然后再次紧凑,得到 [4,4,0,0],这显然不对。
看来这种方法有问题。正确的做法可能是在处理的时候,每次合并后跳过下一个元素,因为它们已经被处理过了。例如,在合并索引i和i-1之后,i的位置已经被处理,所以下一次处理i-2的位置。
正确的处理步骤可能需要这样的逻辑:
在紧凑之后的行中,从右往左遍历:
例如,处理右移后的紧凑行 [2,2,2,2]:
i从3到0:
- i=3:比较i和i-1的位置(3和2),相同,合并,分数增加,i-1的位置置0,然后i减一,跳过下一个元素。
这样,合并3和2后,i变成2,接着处理i=1:
比较i=1和i=0,合并,得到4,分数增加,i=0。
最后,再次紧凑,得到 [0,0,4,4]。
这可能需要分步骤:
紧凑行 → 合并 → 再次紧凑。
所以,处理行的时候,步骤如下:
1. 紧凑:将非零元素移到右边,中间无空格。
2. 合并相邻的相同元素,从右向左,每个元素只能合并一次。
3. 再次紧凑,填补合并后产生的空格。
例如:
原行:2,0,2,4 → 紧凑后:2,2,4,0 → 合并:检查位置1和2(2和4)不同;位置0和1(2和2)合并为4,位置1置0 → 变为4,0,4,0 → 再次紧凑:4,4,0,0 → 结果为4,4在右侧,即行变成0,0,4,4?
或者,可能我的步骤还是有问题。这时候,可能需要更详细的例子。
比如,原行是 [2, 2, 2, 2],处理右移:
步骤1:紧凑,得到 [2,2,2,2]
步骤2:合并,从右向左:
i=3,比较i=3和i=2,都2,合并为4,i=2置0 → 现在行变为 [2,2,0,4]
然后,i=1,比较i=1和i=0,都2,合并为4,i=0置0 → 行变为 [0,4,0,4]
步骤3:再次紧凑,得到 [4,4,0,0]
这显然不是正确的右移结果。正确的右移结果应该是 [0,0,4,4]。
所以,这说明我的合并逻辑不正确。正确的合并应该是在紧凑之后,从右往左检查,如果相邻元素相同则合并,然后将右边的元素翻倍,左边的元素置0,然后紧凑一次。
例如,正确的处理:
原行:2,2,2,2 → 紧凑后:2,2,2,2 → 合并:
从右往左检查:
i=3和i=2都是2 → 合并,得到4,i=2置0 → 行变为2,2,0,4 → 现在,i=1和i=0都是2 → 合并,得到4,i=0置0 → 行变为0,4,0,4 → 再次紧凑,得到4,4,0,0 → 但这不是正确的结果。正确的结果应该是合并后两次合并,得到0,0,4,4。
哦,问题可能出在合并后的再次紧凑方向。在合并后,可能需要再次向右紧凑,这样在例子中,行变为0,4,0,4 → 再次向右紧凑,得到0,0,4,4。
哦,对的,合并后的步骤是再次向右紧凑。这样,在合并之后,再次将非零元素紧凑到右边。
所以,处理行的步骤是:
1. 向右紧凑(移除空格)
2. 合并相邻的相同元素(从右到左)
3. 再次向右紧凑
这样,在例子中:
原行:2,2,2,2 → 步骤1后:2,2,2,2 → 步骤2:合并i=3和i=2 → 4,i=2=0 → 行变成2,2,0,4 → 合并i=1和i=0 → 4,i=0=0 → 行变成0,4,0,4 → 步骤3:向右紧凑 → 0,0,4,4 → 正确。
所以,在处理每个方向时,都需要进行这三个步骤:紧凑、合并、再紧凑。
现在,如何将这个逻辑应用到每个方向?
例如,处理右移时,每一行都要经历这三个步骤。处理左移时,每一行需要向左紧凑,合并时从左到右,然后再紧凑。处理上下方向时,需要处理列,将其转换为行,处理,再转回去。
这可能需要编写通用函数来处理行或列的合并,根据不同的方向进行调整。
可能的实现步骤:
编写一个函数,处理一个数组(行或列),进行紧凑、合并、再紧凑的操作,并返回是否有任何移动或合并发生。例如:
bool ProcessLine(int line[4], bool reverse) {
// 紧凑:如果reverse为true,则从右往左紧凑,否则从左往右?
// 例如,对于右移,reverse为true,紧凑到右边;左移则reverse为false,紧凑到左边。
// 合并的方向也需要根据reverse来处理。
}
或者,可能需要更明确的参数,如处理方向。
或者,可以将每个行或列转换为一个一维数组,处理完成后再放回去。例如,对于右移和左移,处理行;对于上下移,处理列。
具体来说:
处理右移:
对每一行:
- 创建一个临时数组,将非零元素紧凑到右侧。
- 合并相邻相同的元素(从右到左)。
- 再次紧凑到右侧。
处理左移:
对每一行:
- 紧凑到左侧。
- 合并相邻相同的元素(从左到右)。
- 再次紧凑到左侧。
处理上移:
对每一列:
- 紧凑到上侧。
- 合并相邻相同的元素(从上到下)。
- 再次紧凑到上侧。
处理下移:
对每一列:
- 紧凑到下侧。
- 合并相邻相同的元素(从下到上)。
- 再次紧凑到下侧。
这样,每个方向的逻辑类似,但处理的行或列不同,紧凑的方向不同,合并的方向不同。
为了复用代码,可以编写一个处理一维数组的函数,根据参数决定紧凑的方向和合并的方向。
例如:
// direction参数决定紧凑的方向:0为左,1为右,2为上,3为下?或者用布尔值表示是否反向处理?
或者,更通用地,将数组视为行或列,并指定处理顺序。
可能比较复杂。或者,针对行和列分别处理。
关于主循环的结构:
初始化窗口,初始化网格,生成初始数字。
然后,循环:
- 绘制界面。
- 处理输入。
- 如果有有效的移动,生成新数字。
- 检查游戏是否结束。
在EasyX中,可能需要使用BeginBatchDraw和EndBatchDraw来减少闪烁。
处理输入时,可以使用GetAsyncKeyState来检测按键,但需要注意不要多次触发同一个按键。例如,在用户按下方向键时,只处理一次,直到释放后再按下。
或者,使用EasyX的ExMessage结构来处理消息循环,例如:
ExMessage msg;
while (true) {
if (peekmessage(&msg, EX_KEY)) {
if (msg.message == WM_KEYDOWN) {
switch (msg.vkcode) {
case VK_LEFT:
// 处理左移
break;
case VK_RIGHT:
// 处理右移
break;
case VK_UP:
// 处理上移
break;
case VK_DOWN:
// 处理下移
break;
case VK_ESCAPE:
exit(0);
break;
}
}
}
}
但这种方法可能无法检测到持续按下的键,或者需要处理成每次按键触发一次移动。或者,在每次循环中检测按键状态,而不是通过消息队列。
另一种方法是使用GetAsyncKeyState函数,例如:
if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
// 处理左移
}
同样的问题是如何防止多次触发。可以设置一个标志,表示键是否已经被处理,在键释放后重置。
或者,每次处理完移动后,等待按键释放。这可能不太友好,因为玩家可能需要快速操作。
可能更简单的方式是在每次循环中处理输入,但设置一个延时,比如每次移动后等待100毫秒,避免过于频繁的移动。
但这样可能影响游戏的响应速度。另一种方法是记录上一次处理的时间,只有当时间间隔超过一定值时才处理新的输入。
这可能比较复杂,对于简单的2048游戏,可以接受每次按键触发一次移动,即使按住不放也会有一定间隔。
回到代码,当处理方向键时,调用对应的处理函数,例如:
处理左移:
bool moved = false;
for (int i=0; i<4; i++) {
int originalRow[4];
memcpy(originalRow, grid[i], sizeof(originalRow));
ProcessRowForLeft(grid[i], score);
if (memcmp(originalRow, grid[i], sizeof(originalRow)) !=0) {
moved = true;
}
}
if (moved) {
GenerateNewNumber();
if (CheckGameOver()) {
// 游戏结束
}
}
类似地,其他方向的处理。
现在,生成新数字的函数:
void GenerateNewNumber() {
vector<pair<int, int>> emptyCells;
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
if (grid[i][j] ==0) {
emptyCells.emplace_back(i, j);
}
}
}
if (emptyCells.empty()) {
return;
}
int index = rand() % emptyCells.size();
int value = (rand() %10 ==0) ?4 :2; // 10%的概率生成4
grid[emptyCells[index].first][emptyCells[index].second] = value;
}
检查游戏是否结束:
bool CheckGameOver() {
// 检查是否有空位
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
if (grid[i][j] ==0) {
return false;
}
}
}
// 检查是否有相邻相同的数字
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
int current = grid[i][j];
// 检查右边
if (j <3 && grid[i][j+1] == current) {
return false;
}
// 检查下边
if (i <3 && grid[i+1][j] == current) {
return false;
}
}
}
return true;
}
另外,胜利条件需要检查是否有2048出现:
在每次生成新数字后,检查是否达到2048,如果有,则提示胜利,但游戏可能继续,直到无法移动。或者根据游戏规则,一旦有2048即胜利,可以选择继续游戏。这里可能需要根据用户需求来处理,但通常2048游戏允许玩家继续合并直到无法移动。
所以,在生成新数字后,可以检查是否有2048,并标记游戏胜利。
现在,关于绘制函数:
void Draw() {
cleardevice();
setbkcolor(RGB(250, 248, 239)); // 设置背景色
cleardevice();
int cellSize = 100;
int margin = 10;
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
int x = j * (cellSize + margin) + margin;
int y = i * (cellSize + margin) + margin;
int value = grid[i][j];
COLORREF color = GetColor(value);
setfillcolor(color);
fillrectangle(x, y, x + cellSize, y + cellSize);
if (value !=0) {
TCHAR str[10];
_stprintf(str, _T("%d"), value);
settextstyle(40, 0, _T("Arial"));
setbkmode(TRANSPARENT);
int textWidth = textwidth(str);
int textHeight = textheight(str);
int tx = x + (cellSize - textWidth)/2;
int ty = y + (cellSize - textHeight)/2;
outtextxy(tx, ty, str);
}
}
}
// 显示分数
TCHAR scoreStr[50]
_stprintf(scoreStr, _T("Score: %d"), score);
settextstyle(20, 0, _T("Arial"));
outtextxy(10, 460, scoreStr);
// 如果游戏结束
if (gameOver) {
settextstyle(40, 0, _T("Arial"));
outtextxy(100, 200, _T("Game Over!"));
}
FlushBatchDraw();
}
其中,GetColor函数根据数值返回颜色:
COLORREF GetColor(int value) {
switch (value) {
case 0: return RGB(205, 193, 180);
case 2: return RGB(238, 228, 218);
case 4: return RGB(237, 224, 200);
case 8: return RGB(242, 177, 121);
case 16: return RGB(245, 149, 99);
case 32: return RGB(246, 124, 95);
case 64: return RGB(246, 94, 59);
case 128: return RGB(237, 207, 114);
case 256: return RGB(237, 204, 97);
case 512: return RGB(237, 200, 80);
case 1024: return RGB(237, 197, 63);
case 2048: return RGB(237, 194, 46);
default: return RGB(60, 58, 50);
}
}
现在,将这些部分整合起来,形成完整的代码。
测试过程中需要注意的问题:
1. 是否正确处理移动和合并逻辑?
2. 分数是否正确累加?
3. 是否在移动后生成新数字?
4. 游戏结束条件是否准确?
5. 颜色和显示是否正确?
可能的错误点:
- 在合并时,分数累加是否正确?例如,合并两个2得到4,应加4分,而不是2分?
根据2048的规则,合并后的数字即为所得的分数。例如,2+2=4,得分+4;4+4=8,得分+8,等等。所以在代码中,当合并时,应将合并后的值加到分数中。
例如,在ProcessRowForRight函数中:
当合并两个2时,生成4,分数 +=4;
在代码中的score += temp[i]; 因为temp[i]在合并后被设置为原来的两倍,所以是正确的。
例如,原来的两个2合并为4,此时temp[i] =4,所以score +=4,是正确的。
另一个可能的问题是,在合并时,是否跳过了下一个元素以避免重复合并。例如,在合并i和i-1后,i--,使得循环中的i递减,从而跳过下一个元素。
在ProcessRowForRight的合并循环中:
for (int i =3; i >0; i--) {
if (temp[i] == temp[i-1] && temp[i] !=0) {
temp[i] *=2;
score += temp[i];
temp[i-1] =0;
merged = true;
i--; // 跳过下一个,避免重复合并
}
}
这样,当i=3时,如果合并了i=3和i=2,那么i会减到2,然后循环继续时i变成1,处理i=1的情况。这样避免了重复合并。
这可能正确。
现在,编写主函数:
int main() {
initgraph(500, 600); // 窗口大小500x600,下面显示分数
srand((unsigned)time(NULL));
InitializeGrid();
score =0;
gameOver = false;
bool win = false;
BeginBatchDraw();
while (true) {
Draw();
// 处理输入
if (_kbhit()) {
char ch = _getch();
if (ch == 27) { // ESC退出
break;
}
}
if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
bool moved = false;
for (int i=0; i<4; i++) {
int original[4];
memcpy(original, grid[i], sizeof(original));
ProcessRowForLeft(grid[i], score);
if (memcmp(original, grid[i], sizeof(original)) !=0) {
moved = true;
}
}
if (moved) {
GenerateNewNumber();
if (CheckGameOver()) {
gameOver = true;
}
}
Sleep(100); // 防止连续按键处理过快
}
else if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
bool moved = false;
for (int i=0; i<4; i++) {
int original[4];
memcpy(original, grid[i], sizeof(original));
ProcessRowForRight(grid[i], score);
if (memcmp(original, grid[i], sizeof(original)) !=0) {
moved = true;
}
}
if (moved) {
GenerateNewNumber();
if (CheckGameOver()) {
gameOver = true;
}
}
Sleep(100);
}
else if (GetAsyncKeyState(VK_UP) & 0x8000) {
bool moved = false;
for (int j=0; j<4; j++) {
int col[4];
for (int i=0; i<4; i++) {
col[i] = grid[i][j];
}
int original[4];
memcpy(original, col, sizeof(original));
ProcessColumnForUp(col, score);
if (memcmp(original, col, sizeof(original)) !=0) {
moved = true;
for (int i=0; i<4; i++) {
grid[i][j] = col[i];
}
}
}
if (moved) {
GenerateNewNumber();
if (CheckGameOver()) {
gameOver = true;
}
}
Sleep(100);
}
else if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
bool moved = false;
for (int j=0; j<4; j++) {
int col[4];
for (int i=0; i<4; i++) {
col[i] = grid[i][j];
}
int original[4];
memcpy(original, col, sizeof(original));
ProcessColumnForDown(col, score);
if (memcmp(original, col, sizeof(original)) !=0) {
moved = true;
for (int i=0; i<4; i++) {
grid[i][j] = col[i];
}
}
}
if (moved) {
GenerateNewNumber();
if (CheckGameOver()) {
gameOver = true;
}
}
Sleep(100);
}
if (gameOver) {
// 可以添加重新开始或退出逻辑
// 例如,按空格键重新开始
if (GetAsyncKeyState(VK_SPACE) & 0x8000) {
InitializeGrid();
score =0;
gameOver = false;
}
}
另外,初始化函数:
void InitializeGrid() {
for (int i=0; i<4; i++) {
for (int j=0; j<4; j++) {
grid[i][j] =0;
}
}
// 生成两个初始数字
GenerateNewNumber();
GenerateNewNumber();
}
可能存在的问题:
在上下移动的处理中,ProcessColumnForUp和ProcessColumnForDown是否正确处理了列数据,并在移动后写回grid?
例如,对于上移:
原列是 [2,2,0,0],处理后应变为 [4,0,0,0],所以将修改后的col写回grid的列。
代码中,当处理列j时:
for (int j=0; j<4; j++) {
int col[4];
for (int i=0; i<4; i++) {
col[i] = grid[i][j];
}
ProcessColumnForUp(col, score);
// 检查是否变化,如果变化则写回
if (memcmp(original, col, ...) {
for (int i=0; i<4; i++) {
grid[i][j] = col[i];
}
moved = true;
}
}
这是正确的。
综上,将这些函数整合起来,应该可以实现2048的基本逻辑。可能还需要调整颜色、字体大小、窗口尺寸等细节,以及处理胜利条件的显示。
最后,测试各个方向移动是否正常,合并是否正确,分数是否正确,游戏结束条件是否触发。
1.2 改进/进阶逻辑
- 增加数字:4096、8192……65536
- 绘制方块时,绘制圆角
- 当格子满时,有的时候可以再次消除
- 玩家操作时,用GetAsynckeyState来代替case
- 将字符设得大一点
- 方块颜色,颜色应遵照原版
- 有的时候,颜色偏深,字体应该改成白色;有的时候,颜色偏浅,字体应该改成黑色
- 可循环游戏:游戏结束后弹窗,询问是否再来一局
- 将分数、最大方块和最高分显示。生成文件,每次开始游戏读入,游戏结束时将数据录入
1.3 完善/再次改进(目前未完成)
动画:合并方块时的动画及移动方块时的动画已完成文件:文件录入时可能会出bug已完成最大方块:无法显示最大方块已完成- 背景:自行选择背景色主题(白色或黑色)
- 出现方块时方块刷新可能有问题
2. 代码
2.1 整体代码
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <stdlib.h>
#include <vector>
#include <windows.h>
#include <fstream>
#include <sstream>
using namespace std;
// 常量定义
const int CELL_SIZE = 120;
const int MARGIN = 15;
const int WINDOW_WIDTH = 600;
const int WINDOW_HEIGHT = 700;
const int CORNER_RADIUS = 20;
const int INFO_HEIGHT = 150;
// 颜色定义
const COLORREF SCORE_BG = RGB(158, 174, 187); // 0x9eaebb
const COLORREF TEXT_LIGHT = RGB(219, 230, 238); // 0xdbe6ee
const COLORREF TEXT_DARK = RGB(112, 123, 131); // 0x707b83
// 全局变量
int grid[4][4] = { 0 };
int score = 0;
int maxTile = 0;
int highScore = 0;
bool gameOver = false;
// 文件操作
void SaveRecord() {
ofstream file("2048.save");
if (file) {
file << highScore << endl;
file << maxTile << endl;
}
}
void LoadRecord() {
ifstream file("2048.save");
if (file) {
file >> highScore;
file >> maxTile;
}
else {
highScore = 0;
maxTile = 0;
}
}
// 根据数字获取颜色
COLORREF GetColor(int value) {
switch (value) {
case 0: return RGB(205, 193, 180);
case 2: return RGB(238, 228, 218);
case 4: return RGB(237, 224, 200);
case 8: return RGB(242, 177, 121);
case 16: return RGB(245, 149, 99);
case 32: return RGB(246, 124, 95);
case 64: return RGB(246, 94, 59);
case 128: return RGB(237, 207, 114);
case 256: return RGB(237, 204, 97);
case 512: return RGB(237, 200, 80);
case 1024: return RGB(237, 197, 63);
case 2048: return RGB(237, 194, 46);
case 4096: return RGB(180, 220, 90);
case 8192: return RGB(150, 200, 80);
case 16384:return RGB(120, 180, 70);
case 32768:return RGB(90, 160, 60);
case 65536:return RGB(60, 140, 50);
default: return RGB(60, 58, 50);
}
}
// 初始化网格
void InitializeGrid() {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
grid[i][j] = 0;
score = 0;
maxTile = 2;
gameOver = false;
vector<pair<int, int>> emptyCells;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) {
emptyCells.emplace_back(i, j);
}
}
}
if (!emptyCells.empty()) {
int index = rand() % emptyCells.size();
grid[emptyCells[index].first][emptyCells[index].second] = 2;
index = rand() % emptyCells.size();
grid[emptyCells[index].first][emptyCells[index].second] = 2;
}
}
// 生成新数字
void GenerateNewNumber() {
vector<pair<int, int>> emptyCells;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) {
emptyCells.emplace_back(i, j);
}
}
}
if (emptyCells.empty()) return;
int index = rand() % emptyCells.size();
int value = (rand() % 10 == 0) ? 4 : 2;
grid[emptyCells[index].first][emptyCells[index].second] = value;
}
// 检查游戏是否结束
bool CheckGameOver() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) return false;
if (j < 3 && grid[i][j] == grid[i][j + 1]) return false;
if (i < 3 && grid[i][j] == grid[i + 1][j]) return false;
}
}
return true;
}
// 文字居中绘制辅助函数
void DrawCenteredText(int x, int y, int width, int height, const wchar_t* str) {
int textWidth = textwidth(str);
int textHeight = textheight(str);
int tx = x + (width - textWidth) / 2;
int ty = y + (height - textHeight) / 2;
outtextxy(tx, ty, str);
}
// 绘制界面
void Draw() {
cleardevice();
setbkcolor(RGB(250, 248, 239));
cleardevice();
// ==== 分数面板 ====
const int scorePanelWidth = 152;
const int scorePanelHeight = 89;
// 当前分数
setfillcolor(SCORE_BG);
solidroundrect(MARGIN, MARGIN, MARGIN + scorePanelWidth, MARGIN + scorePanelHeight, 10, 10);
settextstyle(28, 0, _T("Arial"));
settextcolor(TEXT_LIGHT);
DrawCenteredText(MARGIN, MARGIN + 10, scorePanelWidth, 30, _T("SCORE"));
wchar_t scoreStr[20];
swprintf_s(scoreStr, L"%d", score);
settextstyle(44, 0, _T("Arial"));
settextcolor(WHITE);
DrawCenteredText(MARGIN, MARGIN + 45, scorePanelWidth, 44, scoreStr);
// 最高分数
setfillcolor(SCORE_BG);
solidroundrect(MARGIN * 2 + scorePanelWidth, MARGIN,
MARGIN * 2 + scorePanelWidth * 2, MARGIN + scorePanelHeight, 10, 10);
settextstyle(28, 0, _T("Arial"));
settextcolor(TEXT_LIGHT);
DrawCenteredText(MARGIN * 2 + scorePanelWidth, MARGIN + 10, scorePanelWidth, 30, _T("BEST"));
wchar_t bestStr[20];
swprintf_s(bestStr, L"%d", highScore);
settextstyle(44, 0, _T("Arial"));
settextcolor(WHITE);
DrawCenteredText(MARGIN * 2 + scorePanelWidth, MARGIN + 45, scorePanelWidth, 44, bestStr);
// 提示信息
settextstyle(24, 0, _T("Arial"));
settextcolor(TEXT_DARK);
wchar_t tipStr[50];
swprintf_s(tipStr, L"目标:合成 %d 方块!", maxTile * 2);
DrawCenteredText(0, INFO_HEIGHT - 40, WINDOW_WIDTH, 40, tipStr);
// ==== 游戏网格 ====
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int x = j * (CELL_SIZE + MARGIN) + MARGIN;
int y = i * (CELL_SIZE + MARGIN) + MARGIN + INFO_HEIGHT;
int value = grid[i][j];
setfillcolor(GetColor(value));
fillroundrect(x, y, x + CELL_SIZE, y + CELL_SIZE,
CORNER_RADIUS, CORNER_RADIUS);
if (value != 0) {
wchar_t str[10];
swprintf_s(str, L"%d", value);
int fontSize = 75;
if (value >= 10000) fontSize = 60;
else if (value >= 1000) fontSize = 65;
else if (value >= 100) fontSize = 70;
settextstyle(fontSize, 0, _T("Arial"));
setbkmode(TRANSPARENT);
int textWidth = textwidth(str);
int textHeight = textheight(str);
int tx = x + (CELL_SIZE - textWidth) / 2;
int ty = y + (CELL_SIZE - textHeight) / 2;
settextcolor(RGB(150, 150, 150));
outtextxy(tx + 3, ty + 3, str);
settextcolor(WHITE);
outtextxy(tx, ty, str);
}
if (value > maxTile) {
maxTile = value;
}
}
}
// 游戏结束提示
if (gameOver) {
settextstyle(48, 0, _T("微软雅黑"));
settextcolor(RGB(255, 100, 100));
outtextxy(150, 300, _T("游戏结束!"));
}
FlushBatchDraw();
}
// 移动处理函数
bool MoveLeft() {
bool moved = false;
for (int i = 0; i < 4; i++) {
int temp[4] = { 0 };
int index = 0;
for (int j = 0; j < 4; j++) {
if (grid[i][j] != 0) {
temp[index++] = grid[i][j];
}
}
for (int j = 0; j < 3; j++) {
if (temp[j] == temp[j + 1] && temp[j] != 0) {
temp[j] *= 2;
score += temp[j];
temp[j + 1] = 0;
j++;
}
}
int newRow[4] = { 0 };
index = 0;
for (int j = 0; j < 4; j++) {
if (temp[j] != 0) {
newRow[index++] = temp[j];
}
}
if (memcmp(grid[i], newRow, sizeof(newRow))) {
memcpy(grid[i], newRow, sizeof(newRow));
moved = true;
}
}
return moved;
}
bool MoveRight() {
bool moved = false;
for (int i = 0; i < 4; i++) {
int temp[4] = { 0 };
int index = 3; // 从右向左填充
// 紧凑到右侧
for (int j = 3; j >= 0; j--) {
if (grid[i][j] != 0) {
temp[index--] = grid[i][j];
}
}
// 合并相同数字(从右向左合并)
for (int j = 3; j > 0; j--) {
if (temp[j] == temp[j - 1] && temp[j] != 0) {
temp[j] *= 2;
score += temp[j];
temp[j - 1] = 0;
j--; // 跳过已合并的位置
}
}
// 再次紧凑到右侧
int newRow[4] = { 0 };
index = 3;
for (int j = 3; j >= 0; j--) {
if (temp[j] != 0) {
newRow[index--] = temp[j];
}
}
// 检查是否变化
if (memcmp(grid[i], newRow, sizeof(newRow)) != 0) {
memcpy(grid[i], newRow, sizeof(newRow));
moved = true;
}
}
return moved;
}
bool MoveDown() {
bool moved = false;
for (int j = 0; j < 4; j++) { // 按列处理
int temp[4] = { 0 };
int index = 3; // 从下向上填充
// 紧凑到下方
for (int i = 3; i >= 0; i--) {
if (grid[i][j] != 0) {
temp[index--] = grid[i][j];
}
}
// 合并相同数字(从下向上合并)
for (int i = 3; i > 0; i--) {
if (temp[i] == temp[i - 1] && temp[i] != 0) {
temp[i] *= 2;
score += temp[i];
temp[i - 1] = 0;
i--; // 跳过已合并的位置
}
}
// 再次紧凑到下方
int newCol[4] = { 0 };
index = 3;
for (int i = 3; i >= 0; i--) {
if (temp[i] != 0) {
newCol[index--] = temp[i];
}
}
// 检查是否变化
for (int i = 0; i < 4; i++) {
if (grid[i][j] != newCol[i]) {
moved = true;
break;
}
}
if (moved) {
for (int i = 0; i < 4; i++) {
grid[i][j] = newCol[i];
}
}
}
return moved;
}
bool MoveUp() {
bool moved = false;
for (int j = 0; j < 4; j++) { // 按列处理
int temp[4] = { 0 };
int index = 0;
// 紧凑到上方
for (int i = 0; i < 4; i++) {
if (grid[i][j] != 0) {
temp[index++] = grid[i][j];
}
}
// 合并相同数字
for (int i = 0; i < 3; i++) {
if (temp[i] == temp[i + 1] && temp[i] != 0) {
temp[i] *= 2;
score += temp[i];
temp[i + 1] = 0;
i++; // 跳过已合并的位置
}
}
// 再次紧凑
int newCol[4] = { 0 };
index = 0;
for (int i = 0; i < 4; i++) {
if (temp[i] != 0) {
newCol[index++] = temp[i];
}
}
// 检查是否变化
for (int i = 0; i < 4; i++) {
if (grid[i][j] != newCol[i]) {
moved = true;
break;
}
}
if (moved) {
for (int i = 0; i < 4; i++) {
grid[i][j] = newCol[i];
}
}
}
return moved;
}
int main() {
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
srand((unsigned)time(NULL));
LoadRecord();
InitializeGrid();
BeginBatchDraw();
while (true) {
while (!gameOver) {
Draw();
bool moved = false;
if (GetAsyncKeyState(VK_LEFT) & 0x8000 || GetAsyncKeyState('A') & 0x8000) {
moved = MoveLeft();
}
else if (GetAsyncKeyState(VK_RIGHT) & 0x8000 || GetAsyncKeyState('D') & 0x8000) {
moved = MoveRight();
}
else if (GetAsyncKeyState(VK_UP) & 0x8000 || GetAsyncKeyState('W') & 0x8000) {
moved = MoveUp();
}
else if (GetAsyncKeyState(VK_DOWN) & 0x8000 || GetAsyncKeyState('S') & 0x8000) {
moved = MoveDown();
}
if (moved) {
GenerateNewNumber();
gameOver = CheckGameOver();
Sleep(150);
}
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) break;
Sleep(10);
}
if (score > highScore) {
highScore = score;
SaveRecord();
}
Draw();
int ret = MessageBox(GetHWnd(), L"再来一局?", L"游戏结束", MB_YESNO);
if (ret == IDYES) {
InitializeGrid();
}
else {
break;
}
}
EndBatchDraw();
closegraph();
return 0;
}
2.2 代码解释
含注释版代码:
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <stdlib.h>
#include <vector>
#include <windows.h>
#include <fstream>
#include <sstream>
using namespace std;
// 常量定义
const int CELL_SIZE = 120; // 每个方格的大小
const int MARGIN = 15; // 方格之间的边距
const int WINDOW_WIDTH = 600; // 窗口宽度
const int WINDOW_HEIGHT = 700;// 窗口高度
const int CORNER_RADIUS = 15; // 方格圆角半径
const int INFO_HEIGHT = 150; // 显示分数等信息的高度
// 颜色定义
const COLORREF SCORE_BG = RGB(158, 174, 187); // 分数背景颜色
const COLORREF TEXT_LIGHT = RGB(219, 230, 238); // 浅色文字颜色
const COLORREF TEXT_DARK = RGB(112, 123, 131); // 深色文字颜色
// 全局变量
int grid[4][4] = { 0 }; // 游戏网格,初始值为0
int score = 0; // 当前得分
int maxTile = 0; // 最大数字
int highScore = 0; // 最高分
bool gameOver = false; // 游戏是否结束标志
// 文件操作:保存记录
void SaveRecord() {
ofstream file("2048.save");
if (file) {
file << highScore << endl; // 写入最高分
file << maxTile << endl; // 写入最大数字
}
}
// 文件操作:加载记录
void LoadRecord() {
ifstream file("2048.save");
if (file) {
file >> highScore; // 读取最高分
file >> maxTile; // 读取最大数字
}
else {
highScore = 0; // 默认最高分为0
maxTile = 2; // 默认最大数字为2
}
}
// 根据数字获取对应的颜色
COLORREF GetColor(int value) {
switch (value) {
case 0: return RGB(205, 193, 180);
case 2: return RGB(238, 228, 218);
case 4: return RGB(237, 224, 200);
case 8: return RGB(242, 177, 121);
case 16: return RGB(245, 149, 99);
case 32: return RGB(246, 124, 95);
case 64: return RGB(246, 94, 59);
case 128: return RGB(237, 207, 114);
case 256: return RGB(237, 204, 97);
case 512: return RGB(237, 200, 80);
case 1024: return RGB(237, 197, 63);
case 2048: return RGB(237, 194, 46);
case 4096: return RGB(180, 220, 90);
case 8192: return RGB(150, 200, 80);
case 16384:return RGB(120, 180, 70);
case 32768:return RGB(90, 160, 60);
case 65536:return RGB(60, 140, 50);
default: return RGB(60, 58, 50);
}
}
// 初始化游戏网格
void InitializeGrid() {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
grid[i][j] = 0; // 将所有方格初始化为0
score = 0; // 分数清零
gameOver = false; // 游戏状态设为未结束
vector<pair<int, int>> emptyCells;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) {
emptyCells.emplace_back(i, j); // 记录空方格位置
}
}
}
if (!emptyCells.empty()) {
int index = rand() % emptyCells.size();
grid[emptyCells[index].first][emptyCells[index].second] = 2; // 在随机空方格中放置一个2
index = rand() % emptyCells.size();
grid[emptyCells[index].first][emptyCells[index].second] = 2; // 再放置一个2
}
}
// 生成新数字(2或4)
void GenerateNewNumber() {
vector<pair<int, int>> emptyCells;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) {
emptyCells.emplace_back(i, j); // 记录空方格位置
}
}
}
if (emptyCells.empty()) return; // 如果没有空方格则返回
int index = rand() % emptyCells.size();
int value = (rand() % 10 == 0) ? 4 : 2; // 有10%的概率生成4,否则生成2
grid[emptyCells[index].first][emptyCells[index].second] = value; // 在随机空方格中放置新数字
}
// 检查游戏是否结束
bool CheckGameOver() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (grid[i][j] == 0) return false; // 存在空方格则游戏未结束
if (j < 3 && grid[i][j] == grid[i][j + 1]) return false; // 相邻方格相同则游戏未结束
if (i < 3 && grid[i][j] == grid[i + 1][j]) return false; // 相邻方格相同则游戏未结束
}
}
return true; // 所有条件都不满足则游戏结束
}
// 辅助函数:在指定区域内居中文本绘制
void DrawCenteredText(int x, int y, int width, int height, const wchar_t* str) {
int textWidth = textwidth(str); // 获取文本宽度
int textHeight = textheight(str);// 获取文本高度
int tx = x + (width - textWidth) / 2; // 计算文本左上角x坐标
int ty = y + (height - textHeight) / 2; // 计算文本左上角y坐标
outtextxy(tx, ty, str); // 绘制文本
}
void Draw() {
cleardevice(); // 清除设备上的所有内容
setbkcolor(RGB(250, 248, 239)); // 设置背景颜色
cleardevice(); // 再次清除设备上的所有内容
// ==== 分数面板 ====
const int scorePanelWidth = 152; // 分数面板宽度
const int scorePanelHeight = 89; // 分数面板高度
// 当前分数
setfillcolor(SCORE_BG); // 设置填充颜色为分数背景色
solidroundrect(MARGIN, MARGIN, MARGIN + scorePanelWidth, MARGIN + scorePanelHeight, 10, 10); // 绘制圆角矩形
settextstyle(28, 0, _T("Arial")); // 设置文本样式
settextcolor(TEXT_LIGHT); // 设置文本颜色为浅色
DrawCenteredText(MARGIN, MARGIN + 10, scorePanelWidth, 30, _T("SCORE")); // 居中绘制“SCORE”
wchar_t scoreStr[20]; // 定义存储当前分数的字符串
swprintf_s(scoreStr, L"%d", score); // 将当前分数转换为字符串
settextstyle(44, 0, _T("Arial")); // 设置文本样式
settextcolor(WHITE); // 设置文本颜色为白色
DrawCenteredText(MARGIN, MARGIN + 45, scorePanelWidth, 44, scoreStr); // 居中绘制当前分数
// 最高分数
setfillcolor(SCORE_BG); // 设置填充颜色为分数背景色
solidroundrect(MARGIN * 2 + scorePanelWidth, MARGIN,
MARGIN * 2 + scorePanelWidth * 2, MARGIN + scorePanelHeight, 10, 10); // 绘制圆角矩形
settextstyle(28, 0, _T("Arial")); // 设置文本样式
settextcolor(TEXT_LIGHT); // 设置文本颜色为浅色
DrawCenteredText(MARGIN * 2 + scorePanelWidth, MARGIN + 10, scorePanelWidth, 30, _T("BEST")); // 居中绘制“BEST”
wchar_t bestStr[20]; // 定义存储最高分的字符串
swprintf_s(bestStr, L"%d", highScore); // 将最高分转换为字符串
settextstyle(44, 0, _T("Arial")); // 设置文本样式
settextcolor(WHITE); // 设置文本颜色为白色
DrawCenteredText(MARGIN * 2 + scorePanelWidth, MARGIN + 45, scorePanelWidth, 44, bestStr); // 居中绘制最高分
// 最大方块
setfillcolor(GetColor(maxTile)); // 设置填充颜色为最大方块的颜色
fillroundrect(349, 15, 349 + 90, 15 + 90, 10, 10); // 绘制圆角矩形
wchar_t str[10]; // 定义存储最大方块数值的字符串
swprintf_s(str, L"%d", maxTile); // 将最大方块数值转换为字符串
int fontSize = 60; // 初始字体大小
if (maxTile >= 10000) fontSize = 35; // 根据数值调整字体大小
else if (maxTile >= 1000) fontSize = 42;
else if (maxTile >= 100) fontSize = 55;
settextstyle(fontSize, 0, _T("Arial")); // 设置文本样式
int textWidth = textwidth(str); // 获取文本宽度
int textHeight = textheight(str); // 获取文本高度
int tx = 349 + (90 - textWidth) / 2; // 计算文本左上角x坐标
int ty = 15 + (90 - textHeight) / 2; // 计算文本左上角y坐标
settextcolor(RGB(150, 150, 150)); // 设置文本颜色为灰色阴影
outtextxy(tx + 2, ty + 2, str); // 绘制带阴影的文字
settextcolor(WHITE); // 设置文本颜色为白色
outtextxy(tx, ty, str); // 绘制文字
// 提示信息
settextstyle(24, 0, _T("Arial")); // 设置文本样式
settextcolor(TEXT_DARK); // 设置文本颜色为深色
wchar_t tipStr[50]; // 定义存储提示信息的字符串
swprintf_s(tipStr, L"目标:合成 %d 方块!", maxTile * 2); // 构造提示信息
DrawCenteredText(0, INFO_HEIGHT - 40, WINDOW_WIDTH, 40, tipStr); // 居中绘制提示信息
// ==== 游戏网格 ====
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int x = j * (CELL_SIZE + MARGIN) + MARGIN; // 计算方格左上角x坐标
int y = i * (CELL_SIZE + MARGIN) + MARGIN + INFO_HEIGHT; // 计算方格左上角y坐标
int value = grid[i][j]; // 获取方格中的数值
setfillcolor(GetColor(value)); // 设置填充颜色为方格数值对应的颜色
fillroundrect(x, y, x + CELL_SIZE, y + CELL_SIZE, CORNER_RADIUS, CORNER_RADIUS); // 绘制圆角矩形
if (value != 0) { // 如果方格中有数字
wchar_t str[10]; // 定义存储方格数值的字符串
swprintf_s(str, L"%d", value); // 将方格数值转换为字符串
int fontSize = 75; // 初始字体大小
if (value >= 10000) fontSize = 50; // 根据数值调整字体大小
else if (value >= 1000) fontSize = 60;
else if (value >= 100) fontSize = 70;
settextstyle(fontSize, 0, _T("Arial")); // 设置文本样式
setbkmode(TRANSPARENT); // 设置背景模式为透明
int textWidth = textwidth(str); // 获取文本宽度
int textHeight = textheight(str); // 获取文本高度
int tx = x + (CELL_SIZE - textWidth) / 2; // 计算文本左上角x坐标
int ty = y + (CELL_SIZE - textHeight) / 2; // 计算文本左上角y坐标
settextcolor(RGB(150, 150, 150)); // 设置文本颜色为灰色阴影
outtextxy(tx + 3, ty + 3, str); // 绘制带阴影的文字
settextcolor(WHITE); // 设置文本颜色为白色
outtextxy(tx, ty, str); // 绘制文字
}
if (value > maxTile) {
maxTile = value; // 更新最大方块数值
}
}
}
// 游戏结束提示
if (gameOver) {
settextstyle(48, 0, _T("微软雅黑")); // 设置文本样式
settextcolor(RGB(255, 100, 100)); // 设置文本颜色为红色
outtextxy(150, 300, _T("游戏结束!")); // 绘制“游戏结束!”
}
FlushBatchDraw(); // 刷新绘图缓冲区
}
bool MoveLeft() {
bool moved = false;
for (int i = 0; i < 4; i++) {
int temp[4] = { 0 }; // 临时数组用于存储当前行的非零值
int index = 0; // 临时数组的索引
// 将当前行的非零值紧凑到左侧
for (int j = 0; j < 4; j++) {
if (grid[i][j] != 0) {
temp[index++] = grid[i][j];
}
}
// 合并相邻相同的数字(从左向右合并)
for (int j = 0; j < 3; j++) {
if (temp[j] == temp[j + 1] && temp[j] != 0) {
temp[j] *= 2; // 合并相同数字
score += temp[j]; // 更新分数
temp[j + 1] = 0; // 清空合并后的单元格
j++; // 跳过已合并的位置
}
}
// 再次将合并后的结果紧凑到左侧
int newRow[4] = { 0 };
index = 0;
for (int j = 0; j < 4; j++) {
if (temp[j] != 0) {
newRow[index++] = temp[j];
}
}
// 检查是否发生了移动
if (memcmp(grid[i], newRow, sizeof(newRow))) {
memcpy(grid[i], newRow, sizeof(newRow)); // 更新网格
moved = true;
}
}
return moved;
}
bool MoveRight() {
bool moved = false;
for (int i = 0; i < 4; i++) {
int temp[4] = { 0 }; // 临时数组用于存储当前行的非零值
int index = 3; // 临时数组的索引,从右向左填充
// 将当前行的非零值紧凑到右侧
for (int j = 3; j >= 0; j--) {
if (grid[i][j] != 0) {
temp[index--] = grid[i][j];
}
}
// 合并相邻相同的数字(从右向左合并)
for (int j = 3; j > 0; j--) {
if (temp[j] == temp[j - 1] && temp[j] != 0) {
temp[j] *= 2; // 合并相同数字
score += temp[j]; // 更新分数
temp[j - 1] = 0; // 清空合并后的单元格
j--; // 跳过已合并的位置
}
}
// 再次将合并后的结果紧凑到右侧
int newRow[4] = { 0 };
index = 3;
for (int j = 3; j >= 0; j--) {
if (temp[j] != 0) {
newRow[index--] = temp[j];
}
}
// 检查是否发生了移动
if (memcmp(grid[i], newRow, sizeof(newRow)) != 0) {
memcpy(grid[i], newRow, sizeof(newRow)); // 更新网格
moved = true;
}
}
return moved;
}
bool MoveDown() {
bool moved = false;
for (int j = 0; j < 4; j++) { // 按列处理
int temp[4] = { 0 }; // 临时数组用于存储当前列的非零值
int index = 3; // 临时数组的索引,从下向上填充
// 将当前列的非零值紧凑到下方
for (int i = 3; i >= 0; i--) {
if (grid[i][j] != 0) {
temp[index--] = grid[i][j];
}
}
// 合并相邻相同的数字(从下向上合并)
for (int i = 3; i > 0; i--) {
if (temp[i] == temp[i - 1] && temp[i] != 0) {
temp[i] *= 2; // 合并相同数字
score += temp[i]; // 更新分数
temp[i - 1] = 0; // 清空合并后的单元格
i--; // 跳过已合并的位置
}
}
// 再次将合并后的结果紧凑到下方
int newCol[4] = { 0 };
index = 3;
for (int i = 3; i >= 0; i--) {
if (temp[i] != 0) {
newCol[index--] = temp[i];
}
}
// 检查是否发生了移动
for (int i = 0; i < 4; i++) {
if (grid[i][j] != newCol[i]) {
moved = true;
break;
}
}
if (moved) {
for (int i = 0; i < 4; i++) {
grid[i][j] = newCol[i]; // 更新网格
}
}
}
return moved;
}
bool MoveUp() {
bool moved = false;
for (int j = 0; j < 4; j++) { // 按列处理
int temp[4] = { 0 }; // 临时数组用于存储当前列的非零值
int index = 0; // 临时数组的索引
// 将当前列的非零值紧凑到上方
for (int i = 0; i < 4; i++) {
if (grid[i][j] != 0) {
temp[index++] = grid[i][j];
}
}
// 合并相邻相同的数字(从上向下合并)
for (int i = 0; i < 3; i++) {
if (temp[i] == temp[i + 1] && temp[i] != 0) {
temp[i] *= 2; // 合并相同数字
score += temp[i]; // 更新分数
temp[i + 1] = 0; // 清空合并后的单元格
i++; // 跳过已合并的位置
}
}
// 再次将合并后的结果紧凑到上方
int newCol[4] = { 0 };
index = 0;
for (int i = 0; i < 4; i++) {
if (temp[i] != 0) {
newCol[index++] = temp[i];
}
}
// 检查是否发生了移动
for (int i = 0; i < 4; i++) {
if (grid[i][j] != newCol[i]) {
moved = true;
break;
}
}
if (moved) {
for (int i = 0; i < 4; i++) {
grid[i][j] = newCol[i]; // 更新网格
}
}
}
return moved;
}
int main() {
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT); // 初始化图形窗口
srand((unsigned)time(NULL)); // 设置随机种子
LoadRecord(); // 加载记录
InitializeGrid(); // 初始化游戏网格
BeginBatchDraw(); // 开始批量绘图
while (true) {
while (!gameOver) {
Draw(); // 绘制游戏界面
bool moved = false;
// 处理键盘输入
if (GetAsyncKeyState(VK_LEFT) & 0x8000 || GetAsyncKeyState('A') & 0x8000) {
moved = MoveLeft(); // 左移
}
else if (GetAsyncKeyState(VK_RIGHT) & 0x8000 || GetAsyncKeyState('D') & 0x8000) {
moved = MoveRight(); // 右移
}
else if (GetAsyncKeyState(VK_UP) & 0x8000 || GetAsyncKeyState('W') & 0x8000) {
moved = MoveUp(); // 上移
}
else if (GetAsyncKeyState(VK_DOWN) & 0x8000 || GetAsyncKeyState('S') & 0x8000) {
moved = MoveDown(); // 下移
}
if (moved) {
GenerateNewNumber(); // 生成新数字
gameOver = CheckGameOver(); // 检查游戏是否结束
Sleep(150); // 延迟一段时间
}
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) break; // 按Esc键退出
Sleep(10); // 延迟一段时间
}
if (score > highScore) {
highScore = score; // 更新最高分
SaveRecord(); // 保存记录
}
Draw(); // 最后一次绘制游戏界面
int ret = MessageBox(GetHWnd(), L"再来一局?", L"游戏结束", MB_YESNO); // 弹出确认对话框
if (ret == IDYES) {
InitializeGrid(); // 重新初始化游戏网格
}
else {
break; // 退出游戏
}
}
EndBatchDraw(); // 结束批量绘图
closegraph(); // 关闭图形窗口
return 0;
}
3.后记
点赞破30火速更下一期。第一期点赞量:15
目前为第二期,本期更新内容:见本文1.3
去我的主页查看更多小游戏,链接:EasyX游戏合集【最新版】_easyx小游戏-CSDN博客
哦对了,本系列为限时免费,如果你们点赞快的话,也许系列更完了都还是免费的,所以点赞冲冲冲!详见评论区