当前位置: 首页 > news >正文

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 改进/进阶逻辑

  1. 增加数字:4096、8192……65536
  2. 绘制方块时,绘制圆角
  3. 当格子满时,有的时候可以再次消除
  4. 玩家操作时,用GetAsynckeyState来代替case
  5. 将字符设得大一点
  6. 方块颜色,颜色应遵照原版
  7. 有的时候,颜色偏深,字体应该改成白色;有的时候,颜色偏浅,字体应该改成黑色
  8. 可循环游戏:游戏结束后弹窗,询问是否再来一局
  9. 将分数、最大方块和最高分显示。生成文件,每次开始游戏读入,游戏结束时将数据录入

1.3 完善/再次改进(目前未完成)

  1. 动画:合并方块时的动画及移动方块时的动画   已完成
  2. 文件:文件录入时可能会出bug                          已完成
  3. 最大方块:无法显示最大方块                            已完成
  4. 背景:自行选择背景色主题(白色或黑色)
  5. 出现方块时方块刷新可能有问题

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博客

哦对了,本系列为限时免费,如果你们点赞快的话,也许系列更完了都还是免费的,所以点赞冲冲冲!详见评论区

相关文章:

  • PHP 基础介绍
  • 使用Nuitka工具打包Python程序成exe
  • 【Java八股文】01-Java基础面试篇
  • 猜数字小游戏
  • 使用Hexo部署NexT主体网站
  • SQL SERVER的PARTITION BY应用场景
  • C# CountdownEvent 类 使用详解
  • 认识网络安全
  • 【css】width:100%;padding:20px;造成超出100%宽度的解决办法 - box-sizing的使用方法 - CSS布局
  • Android Studio:RxBus结合ICompositeSubscription使用
  • YOLO数据标注——LabelImg
  • PMP--冲刺--流程图
  • vue3+element-plus中的el-table表头和el-table-column内容全部一行显示完整(hook函数)
  • 【第3章:卷积神经网络(CNN)——3.8 迁移学习与微调策略】
  • 恩智浦:将开发文档迁移到DITA/XML
  • ASP.NET Core 使用 FileStream 将 FileResult 文件发送到浏览器后删除该文件
  • 趣味魔法项目 LinuxPDF —— 在 PDF 中启动一个 Linux 操作系统
  • jQuery UI 工作原理
  • C语言:指针详解
  • 深入了解 Oracle 正则表达式
  • 涉个人信息收集使用问题,15款App和16款SDK被通报
  • 娱见 | 为了撕番而脱粉,内娱粉丝为何如此在乎番位
  • 一季度全国30强城市出炉:谁能更进一步?谁掉队了?
  • 10家A股农商行去年年报:瑞丰银行营收增速领跑,常熟银行等4家净利增速超11%
  • 长三角铁路今日预计发送386万人次,沪宁、沪杭等方向尚有余票
  • 演员扎堆音乐节,是丰富了舞台还是流量自嗨?