百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

C语言实现2048小游戏(c语言2048游戏课程总结)

xsobi 2025-04-06 00:04 6 浏览

以下是使用C语言实现的2048小游戏代码:

Bash
#include 
#include 
#include 
#include  // 用于Windows平台的_getch()

#define SIZE 4
#define TARGET 2048

// 游戏板结构体
typedef struct
{
    int board[SIZE][SIZE];
    int score;
    int game_over;
    int win;
} Game;

// 方向枚举
enum Direction
{
    UP,
    DOWN,
    LEFT,
    RIGHT
};

void display(const Game *game);
int check_game_over(const Game *game);
void add_new_tile(Game *game);
void move(Game *game, enum Direction dir);

// 初始化游戏
void init_game(Game *game)
{
    // 清空棋盘
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < size j game->board[i][j] = 0;
        }
    }
    game->score = 0;
    game->game_over = 0;
    game->win = 0;

    // 生成两个初始数字
    add_new_tile(game);
    add_new_tile(game);
}

// 添加新数字(90%概率生成2,10%生成4)
void add_new_tile(Game *game)
{
    int empty_cells[SIZE * SIZE][2];
    int count = 0;

    // 查找所有空格子
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < size j if game->board[i][j] == 0)
            {
                empty_cells[count][0] = i;
                empty_cells[count][1] = j;
                count++;
            }
        }
    }

    if (count > 0)
    {
        // 随机选择一个空格子
        int index = rand() % count;
        int value = (rand() % 10 < 9 2 : 4 game->board[empty_cells[index][0]][empty_cells[index][1]] = value;
    }
}

// 打印游戏界面
void display(const Game *game)
{
    system("cls"); // 清屏(Windows)

    printf("Score: %d\n", game->score);
    printf("-------------------------\n");

    for (int i = 0; i < SIZE; i++)
    {
        printf("|");
        for (int j = 0; j < size j if game->board[i][j] == 0)
            {
                printf("    |");
            }
            else
            {
                printf("%4d|", game->board[i][j]);
            }
        }
        printf("\n-------------------------\n");
    }
}

// 检查游戏是否结束
int check_game_over(const Game *game)
{
    // 检查是否有空格子
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < size j if game->board[i][j] == 0)
                return 0;
        }
    }

    // 检查是否有相邻相同数字
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < size j int current='game-'>board[i][j];
            // 检查右侧和下侧
            if (j < size - 1 current='= game-'>board[i][j + 1])
                return 0;
            if (i < size - 1 current='= game-'>board[i + 1][j])
                return 0;
        }
    }

    return 1;
}

// 移动并合并数字(核心逻辑)
void move(Game *game, enum Direction dir)
{
    int moved = 0;
    int temp_board[SIZE][SIZE];

    // 复制当前棋盘状态
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < size j temp_boardij='game-'>board[i][j];
        }
    }

    // 根据方向处理移动
    switch (dir)
    {
    case LEFT:
        for (int i = 0; i < SIZE; i++)
        {
            int pos = 0;
            int last_merged = -1;

            for (int j = 0; j < size j if temp_boardij if pos> 0 && temp_board[i][pos - 1] == temp_board[i][j] && last_merged != pos - 1)
                    {
                        // 合并
                        temp_board[i][pos - 1] *= 2;
                        game->score += temp_board[i][pos - 1];
                        temp_board[i][j] = 0;
                        last_merged = pos - 1;
                        moved = 1;
                    }
                    else
                    {
                        if (pos != j)
                            moved = 1;
                        temp_board[i][pos] = temp_board[i][j];
                        pos++;
                    }
                }
            }

            // 填充右侧空白
            while (pos < SIZE)
            {
                if (temp_board[i][pos] != 0)
                    moved = 1;
                temp_board[i][pos++] = 0;
            }
        }
        break;
        
    case DOWN:
        // 按列处理,从下往上扫描
        for (int j = 0; j < size j int pos='SIZE' - 1 int last_merged='SIZE;' for int i='SIZE' - 1 i>= 0; i--)
            { // 从下往上扫描
                if (temp_board[i][j] != 0)
                { // 发现非空单元格
                    // 检查是否可以合并
                    if (pos < size - 1 temp_boardpos 1j='= temp_board[i][j]' last_merged 1 temp_boardpos 1j game->score += temp_board[pos + 1][j]; // 更新分数
                        temp_board[i][j] = 0;                  // 清空当前位置
                        last_merged = pos + 1;                 // 记录合并位置
                        moved = 1;                             // 标记发生移动
                    }
                    else
                    {
                        // 无法合并则移动元素
                        if (pos != i)
                        { // 位置发生变化
                            temp_board[pos][j] = temp_board[i][j];
                            temp_board[i][j] = 0; // 清空原位置
                            moved = 1;
                        }
                        pos--; // 更新填充位置
                    }
                }
            }

            // 填充上方空白(非必需但更安全)
            while (pos >= 0)
            {
                if (temp_board[pos][j] != 0)
                    moved = 1;
                temp_board[pos--][j] = 0;
            }
        }
        break;
        
    case RIGHT:
        // 按行处理,从右往左扫描
        for (int i = 0; i < size i int pos='SIZE' - 1 int last_merged='SIZE;' for int j='SIZE' - 1 j>= 0; j--)
            { // 从右往左扫描
                if (temp_board[i][j] != 0)
                { // 发现非空单元格
                    // 检查是否可以合并
                    if (pos < size - 1 temp_boardipos 1='= temp_board[i][j]' last_merged 1 temp_boardipos 1 game->score += temp_board[i][pos + 1]; // 更新分数
                        temp_board[i][j] = 0;                  // 清空当前位置
                        last_merged = pos + 1;                 // 记录合并位置
                        moved = 1;                             // 标记发生移动
                    }
                    else
                    {
                        // 无法合并则移动元素
                        if (pos != j)
                        { // 位置发生变化
                            temp_board[i][pos] = temp_board[i][j];
                            temp_board[i][j] = 0; // 清空原位置
                            moved = 1;
                        }
                        pos--; // 更新填充位置
                    }
                }
            }

            // 填充左侧空白(非必需但更安全)
            while (pos >= 0)
            {
                if (temp_board[i][pos] != 0)
                    moved = 1;
                temp_board[i][pos--] = 0;
            }
        }
        break;

    case UP:
        // 按列处理,从上往下扫描
        for (int j = 0; j < SIZE; j++)
        {                         // 遍历每一列
            int pos = 0;          // 从最顶行开始填充
            int last_merged = -1; // 初始化最后合并位置(无效位置)

            for (int i = 0; i < size i if temp_boardij if pos> 0 &&                                    // 确保不是第一个元素
                        temp_board[pos - 1][j] == temp_board[i][j] && // 值相同
                        last_merged != pos - 1)
                    { // 防止重复合并

                        // 执行合并
                        temp_board[pos - 1][j] *= 2;
                        game->score += temp_board[pos - 1][j]; // 更新分数
                        temp_board[i][j] = 0;                  // 清空当前位置
                        last_merged = pos - 1;                 // 记录合并位置
                        moved = 1;                             // 标记发生移动
                    }
                    else
                    {
                        // 无法合并则移动元素
                        if (pos != i)
                        { // 位置发生变化
                            temp_board[pos][j] = temp_board[i][j];
                            temp_board[i][j] = 0; // 清空原位置
                            moved = 1;
                        }
                        pos++; // 更新填充位置
                    }
                }
            }

            // 填充下方空白(非必需但更安全)
            while (pos < SIZE)
            {
                if (temp_board[pos][j] != 0)
                    moved = 1;
                temp_board[pos++][j] = 0;
            }
        }
        break;
        // 其他方向处理类似(代码略)
        // ...
    }

    // 更新棋盘状态
    if (moved)
    {
        for (int i = 0; i < SIZE; i++)
        {
            for (int j = 0; j < size j game->board[i][j] = temp_board[i][j];
            }
        }
        add_new_tile(game);
        game->game_over = check_game_over(game);
    }
}

// 获取用户输入
enum Direction get_input()
{
    int c = _getch();
    switch (c)
    {
    case 72:
        return UP; // 方向键上
    case 80:
        return DOWN; // 方向键下
    case 75:
        return LEFT; // 方向键左
    case 77:
        return RIGHT; // 方向键右
    case 'q':
        exit(0); // 退出游戏
    default:
        return get_input(); // 无效输入重新获取
    }
}

int main()
{
    srand(time(NULL)); // 初始化随机种子

    Game game;
    init_game(&game);

    while (!game.game_over && !game.win)
    {
        display(&game);
        enum Direction dir = get_input();
        move(&game, dir);

        // 检查是否达成2048
        for (int i = 0; i < SIZE; i++)
        {
            for (int j = 0; j < SIZE; j++)
            {
                if (game.board[i][j] == TARGET)
                {
                    game.win = 1;
                    break;
                }
            }
        }
    }

    display(&game);
    if (game.win)
    {
        printf("Congratulations! You've reached %d!\n", TARGET);
    }
    else
    {
        printf("Game Over! Final score: %d\n", game.score);
    }

    return 0;
}

代码结构解析

  1. 数据结构设计
  • Game结构体:包含游戏板(4x4二维数组)、当前分数、游戏结束标志和胜利标志
  • 方向枚举:使用枚举类型定义四个移动方向
  1. 核心函数说明
  • init_game():初始化游戏状态,生成初始数字
  • add_new_tile():在随机空白位置生成新数字
  • display():控制台界面显示
  • check_game_over():检查游戏结束条件
  • move():处理移动和合并的核心逻辑
  • get_input():获取键盘输入
  1. 关键算法实现--移动合并算法(以左移为例):
  • 按行处理,从左到右扫描
  • 将非零数字向左紧凑排列
  • 检查相邻相同数字进行合并
  • 合并后再次紧凑排列
  • 记录移动状态用于判断是否需要生成新数字
  1. 用户输入处理
  • 使用_getch()获取方向键输入
  • 处理Windows平台方向键的特殊编码(72/80/75/77)
  1. 游戏流程控制
  • 主循环:显示界面 → 获取输入 → 处理移动 → 检查状态
  • 结束条件:达成2048或无法继续移动

游戏特色功能

  1. 智能合并机制
Bash
if (pos > 0 && temp_board[i][pos-1] == temp_board[i][j] && last_merged != pos-1)
  • 使用last_merged标记防止同一回合重复合并
  • 例如:[2, 2, 2, 2] → [4, 4, 0, 0]而非[8, 0, 0, 0]
  1. 动态分数计算
game->score += temp_board[i][pos-1];
  • 每次合并后累加合并后的值到总分
  1. 概率生成新数字
int value = (rand() % 10 < 9) ? 2 : 4;
  • 90%概率生成2,10%概率生成4

代码详解:move()函数中的DOWN分支:

核心逻辑详解(以列j=0为例)

假设当前列初始状态为:

[2]   <- i=0
[ ]
[2]   <- i=2 
[4]   <- i=3 (最下方)
  1. 初始化参数
  • pos = 3(最底行索引)
  • last_merged = 4(无效位置)
  1. 第一次循环 i=3:
  • 发现数字4,直接放入pos=3位置
  • pos-- → 2
  • 结果:[ ][ ][ ][4]
  1. 第二次循环 i=2:
  • 发现数字2
  • 检查下方位置pos+1=3的4 ≠ 2 → 无法合并
  • 移动到pos=2位置
  • pos-- → 1
  • 结果:[ ][ ][2][4]

4.第三次循环 i=1:

  • 空单元格 → 跳过

5.第四次循环 i=0:

  • 发现数字2
  • 检查下方位置pos+1=1的单元格为空
  • 移动到pos=1位置
  • pos-- → 0
  • 最终结果:[ ][2][2][4]

6.填充空白

  • 此时pos=0,将0-pos位置置0(本例无变化)

复杂情况处理示例

案例1:连续合并
输入列:[2][2][2][2]

处理流程:

  1. i=3发现2 → 放置pos=3
  2. i=2发现2 → 与pos+1=3合并 → [0][0][0][4]
  3. i=1发现2 → 移动到pos=2 → [0][0][2][4]
  4. i=0发现2 → 与pos+1=2合并 → [0][0][0][4+2=6]
    最终结果:[0][0][0][8]

案例2:阻挡合并
输入列:[2][4][4][2]
处理流程:

  1. i=3发现2 → 放置pos=3
  2. i=2发现4 → 无法合并,移动到pos=2
  3. i=1发现4 → 与pos+1=2合并 → [0][0][8][2]
  4. i=0发现2 → 移动到pos=1
    最终结果:[0][2][8][2]

关键设计亮点

  1. 逆向扫描机制
for (int i = SIZE-1; i >= 0; i--)  // 从下往上扫描
  • 符合向下移动时"沉底"的物理直觉
  • 确保合并操作的正确顺序

2.防重复合并保护

last_merged != pos+1
  • 防止同一位置多次合并
  • 例如序列[4][4][8][8]应合并为[0][0][8][16]而不是[0][0][0][32]

3.双重移动检测

if (pos != i) moved = 1; // 位置变化检测 while (pos >= 0) { ... } // 显式填充检测
  • 确保所有移动情况都被记录
  • 处理类似[2][ ][ ][ ] → [ ][ ][ ][2]的情况

方向处理统一模式

其他方向的实现可通过调整以下参数实现:

  1. 遍历顺序
  • 上下方向:列循环在外层
  • 左右方向:行循环在外层
  1. 扫描方向
  • 下/右:逆向扫描(i/j从大到小)
  • 上/左:正向扫描(i/j从小到大)
  1. 位置指针
  • 下/右:从最大索引开始递减
  • 上/左:从0开始递增

这种模块化设计使得各方向处理代码保持高度一致性,只需调整循环顺序和方向参数即可实现完整移动逻辑。


扩展建议

  1. 增加存档功能
void save_game(const Game* game) {
    FILE* fp = fopen("save.dat", "wb");
    fwrite(game, sizeof(Game), 1, fp);
    fclose(fp);
}

2. 实现动画效果

// 使用Windows API实现简单动画
#include 
void animate_move(int from_x, int from_y, int to_x, int to_y) {
    // 实现移动动画
}
  1. 添加音效
#include 
void play_sound(int type) {
    switch(type) {
        case 1: PlaySound("merge.wav", NULL, SND_ASYNC); break;
        case 2: PlaySound("win.wav", NULL, SND_ASYNC); break;
    }
}

该代码完整展现了2048游戏的核心机制,通过模块化设计方便扩展,使用控制台界面确保跨平台兼容性,适合作为学习C语言项目开发的典型案例。

相关推荐

我把家搬进了NAS?家庭资产数字化革命,现在连袜子都有编号了!

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:羊刀仙家里东西一多,总有一种“我记得我有这个,但我不知道放哪了”的错觉。...

Mega Run第3关通关攻略-跳上高空(mega运动)

错过火炮等于失败,这是MegaRun在上一关中给玩家的一点小挑战,那在后面的关卡中,这中挑战会给你造成什么困难呢?具体请看MegaRun第3关通关攻略-跳上高空。MegaRun第3关在一开始我...

Axure高保真教程:中继器表格自动合计模板

编辑导语:合计作为日常使用频率比较高的一个功能,但在Axure里面传统的表格如果做合计是很麻烦的,遇到数据多的时候很耗费时间,那么该如何优化,提高工作效率?本文以中继器表格为核心,教大家如何制作一个自...

多角色登录原型(分角色登录)

编辑导读:多角色登录是很多系统都需要具备的功能,例如在招聘网站里,登录的角色包括、求职者、招聘企业的hr、猎头、内部员工等等。本文作者分享了如何在Axure里面制作多角色登录的原型模板,希望对你有帮助...

利用Axure+js创建可配置地图页面(axure地址选择)

编辑导语:如何利用Axure实现自定义地图展示?本篇文章里,作者结合Axure与高德地图,对如何在Axure中设置相应参数、进而预览时实现自定义地图展示效果的操作流程进行了示范和总结,一起来看一下。本...

纬地智能模板的一些基础与概念(纬地智能模板的一些基础与概念是什么)

对于公路及相关土木工程的勘测设计工作,虽然基础的理论和方法是基本相同的,但是受到各国家、地区文化、制图习惯等方面因素的影响,不同地域公路勘测设计的习惯和表达方式有着很大的差异,当然语言方面的不同和差...

WPF界面开发第三方控件入门指南——菜单项

点击“了解更多”获取工具DevExpressWPFSubscription拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强...

鸿蒙开发:自定义一个搜索模版(鸿蒙系统怎么添加百度搜索条)

前言代码案例基于Api13。...

简单介绍一下前端各框架中的模板标签

在各大前端框架、小程序中,此类标签的作用主要是用来帮助我们包裹多个元素。在浏览器实际渲染中会将其移除只渲染其包裹的DOM元素,所以说不会增加额外的DOM节点...

法媒:简单的几个步骤让你的运动鞋更加炫酷

【环球网综合报道】有没有觉得自己的运动鞋样式单调,希望它变得更加炫酷?法媒《ELLE》推荐了一种简单易行的装饰方法,让您的运动鞋更加时尚。制作前,您需要准备一张细毡子、一张闪光热胶合布、胶水、两个银夹...

Blazor 代码隐藏(blazor 操作dom)

Razor组件通常是在单个.razor文件中创作的,这样就存在页面中包含html代码和后台代码(@code标记指定的代码)。如下...

DevExpress WPF入门指南:DXBars, DXRibbon中使用MVVM的两种方式

你也可以下载Universal安装包或者到DevExpress中文网查看更多示例和教程哦本文档介绍在DXBars,DXRibbon和GalleryControl这三个控件中使用MVVM框架的...

干货,Uploadfive插件上传,Python接收存储

在Web开发工作中,经常遇到上传需求,上传照片,文件等,网上的上传插件有很多,我使用的是一款H5上传插件Uploadfive,与之对应的是Uploadify,后者是基于Flash的,不过现在越来越多的...

用DevExpress实现基于HTML&amp;CSS的桌面应用程序的UI(二)

DevExpressWinForm拥有180+组件和UI库,能为WindowsForms...

Avalonia:一个开源的跨平台UI选项

...