【C语言】2048游戏完整实现|控制台版 ( 附完整源码)

引言

在C语言学习阶段,通过实战项目巩固知识点是最高效的方式。本文分享一个基于C语言的2048游戏,实现了四方向滑动、数字合并、胜负判定等核心功能,涵盖结构体设计、二维数组操作、随机数生成、控制台绘图等关键技术点,代码精简不到200行,非常适合作为入门级项目练手。

一、项目介绍

  1. 开发背景

2048是一款风靡全球的数字益智游戏,玩家通过滑动方块使相同数字合并,最终目标是合成2048。本文使用纯C语言在控制台环境下实现该游戏,无需任何第三方库,帮助初学者理解游戏逻辑的设计思路。

  1. 技术栈

编程语言:C语言(C99标准)

核心库:stdio.h(输入输出)、stdlib.h(随机数与系统调用)、stdbool.h(布尔类型)、conio.h(控制台按键输入)、time.h(时间种子)

运行环境:Windows控制台

  1. 核心功能
功能编号功能名称功能描述
1游戏初始化清空棋盘,随机生成两个初始数字
2四方向滑动支持↑↓←→四个方向移动方块
3数字合并相邻相同数字自动合并(2+2=4)
4随机生成每次移动后随机生成2或4
5胜负判定达到2048胜利,无法移动则失败
6退出游戏按Q键安全退出

二、核心技术要点

  1. 结构体设计

使用结构体封装游戏状态,将棋盘数据和游戏状态绑定在一起,便于函数间传递数据:

typedef struct { int board[4][4]; // 4x4游戏棋盘,0表示空格 bool isWin; // 是否胜利(达到2048) bool isOver; // 是否游戏结束(无法移动) } GameState;
  1. 移动算法设计

以左移为核心,其他方向通过变换复用左移逻辑:

  • 右移 = 反转行 + 左移 + 反转行
  • 上移 = 矩阵转置 + 左移 + 矩阵转置
  • 下移 = 矩阵转置 + 右移 + 矩阵转置
  1. 控制台绘图

使用Unicode制表符绘制棋盘边框:

  • ┌ ┬ ┐ 表示上边框
  • ├ ┼ ┤ 表示中间分隔线
  • └ ┴ ┘ 表示下边框
  • │ 表示竖线分隔
  1. 按键处理

使用conio.h库的_getch()函数捕获方向键:

  • 72 = 上箭头
  • 80 = 下箭头
  • 75 = 左箭头
  • 77 = 右箭头
  1. 随机数生成

使用srand(time(NULL))初始化随机种子,保证每次游戏随机性不同:

  • 90%概率生成数字2
  • 10%概率生成数字4

三、核心功能解析

  1. 游戏初始化(initGame函数)

核心逻辑:

  • 清空棋盘所有格子为0
  • 初始化游戏状态标志
  • 设置随机数种子
  • 生成两个初始数字

代码部分:

void initGame(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = 0; game->isWin = false; game->isOver = false; srand((unsigned int)time(NULL)); generateNewNumber(game); generateNewNumber(game); }
  1. 棋盘打印(printBoard函数)

核心逻辑:

  • 清屏后打印标题和操作提示
  • 使用Unicode字符绘制4x4网格
  • 根据数字位数自动调整对齐
  • 显示游戏胜利或失败状态

代码部分:

void printBoard(GameState* game) { system("cls"); printf("================= 2048 =================\n"); printf("操作: ↑ ↓ ← → 退出:Q\n\n"); printf("┌─────────┬─────────┬─────────┬─────────┐\n"); for (int i = 0; i < 4; i++) { printf("│"); for (int j = 0; j < 4; j++) { if (game->board[i][j] == 0) printf(" │"); else if (game->board[i][j] < 10) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 100) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 1000) printf(" %d │", game->board[i][j]); else printf(" %d │", game->board[i][j]); } printf("\n"); if (i < 3) printf("├─────────┼─────────┼─────────┼─────────┤\n"); else printf("└─────────┴─────────┴─────────┴─────────┘\n"); } if (game->isWin) printf("\n恭喜!你赢了!\n"); else if (game->isOver) printf("\n游戏结束!\n"); }
  1. 左移操作(moveLeft函数)

核心逻辑:

  • 遍历每一行,将非零数字靠左存储
  • 合并相邻相同数字(每个数字每轮只能合并一次)
  • 合并后再次靠左,更新棋盘并返回是否发生移动

代码部分:

bool moveLeft(GameState* game) { bool moved = false; int temp[4]; for (int i = 0; i < 4; i++) { int idx = 0; for (int j = 0; j < 4; j++) if (game->board[i][j] != 0) temp[idx++] = game->board[i][j]; while (idx < 4) temp[idx++] = 0; for (int j = 0; j < 3; j++) if (temp[j] != 0 && temp[j] == temp[j+1]) { temp[j] *= 2; temp[j+1] = 0; moved = true; } idx = 0; int newRow[4] = {0}; for (int j = 0; j < 4; j++) if (temp[j] != 0) newRow[idx++] = temp[j]; for (int j = 0; j < 4; j++) if (game->board[i][j] != newRow[j]) { moved = true; game->board[i][j] = newRow[j]; } } return moved; }
  1. 其他方向移动(复用左移)

核心逻辑:

  • 右移:先反转每行,再左移,再反转恢复
  • 上移:先转置矩阵,再左移,再转置恢复
  • 下移:先转置矩阵,再右移,再转置恢复

代码部分:

bool moveRight(GameState* game) { bool moved = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } return moved; } bool moveUp(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool moveDown(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveRight(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; }
  1. 随机生成数字(generateNewNumber函数)

核心逻辑:

  • 检查棋盘是否已满
  • 随机选择一个空位置
  • 90%概率生成2,10%概率生成4

代码部分:

void generateNewNumber(GameState* game) { if (isBoardFull(game)) return; int x, y; do { x = rand() % 4; y = rand() % 4; } while (game->board[x][y] != 0); game->board[x][y] = (rand() % 10 == 0) ? 4 : 2; }
  1. 胜负判定(checkGameState函数)

核心逻辑:

  • 遍历棋盘检查是否有2048,有则胜利
  • 调用canMove检查是否还能继续移动,不能则失败

代码部分:

void checkGameState(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 2048) { game->isWin = true; return; } if (!canMove(game)) game->isOver = true; } bool canMove(GameState* game) { if (!isBoardFull(game)) return true; for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) if (game->board[i][j] == game->board[i][j+1]) return true; for (int j = 0; j < 4; j++) for (int i = 0; i < 3; i++) if (game->board[i][j] == game->board[i+1][j]) return true; return false; }
  1. 主函数(main)

核心逻辑:

  • 初始化游戏
  • 循环:打印棋盘 → 获取按键 → 执行移动 → 生成新数字 → 检查状态
  • 游戏结束时等待用户按键退出

代码部分:

int main() { GameState game; initGame(&game); while (true) { printBoard(&game); if (game.isWin || game.isOver) break; char input = _getch(); bool moved = false; switch (input) { case 72: moved = moveUp(&game); break; case 80: moved = moveDown(&game); break; case 75: moved = moveLeft(&game); break; case 77: moved = moveRight(&game); break; case 'q': case 'Q': return 0; default: continue; } if (moved) { generateNewNumber(&game); checkGameState(&game); } } printf("\n按任意键退出\n"); _getch(); return 0; }

四、运行效果演示

示例 1:游戏初始界面

================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ │ 2 │ │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ │ │ │ └─────────┴─────────┴─────────┴─────────┘

示例 2:游戏中界面

================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ │ 2 │ │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 8 │ 16 │ 4 │ │ ├─────────┼─────────┼─────────┼─────────┤ │ │ 2 │ 8 │ 2 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ │ 4 │ 8 │ └─────────┴─────────┴─────────┴─────────┘

示例 3:游戏胜利

================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ 2 │ 4 │ 8 │ 16 │ ├─────────┼─────────┼─────────┼─────────┤ │ 64 │ 32 │ 16 │ 8 │ ├─────────┼─────────┼─────────┼─────────┤ │ 256 │ 128 │ 64 │ 32 │ ├─────────┼─────────┼─────────┼─────────┤ │ 16 │ 1024 │ 512 │ 2048 │ └─────────┴─────────┴─────────┴─────────┘ 恭喜!你赢了!

示例 4:游戏失败

================= 2048 ================= 操作: ↑ ↓ ← → 退出:Q ┌─────────┬─────────┬─────────┬─────────┐ │ 2 │ 4 │ 2 │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ 2 │ 4 │ 2 │ ├─────────┼─────────┼─────────┼─────────┤ │ 2 │ 4 │ 2 │ 4 │ ├─────────┼─────────┼─────────┼─────────┤ │ 4 │ 2 │ 4 │ 2 │ └─────────┴─────────┴─────────┴─────────┘ 游戏结束!

五、项目总结与改进方向

  1. 项目收获
  • 掌握C语言结构体的定义与使用
  • 理解二维数组的操作技巧
  • 学会矩阵转置、反转等算法应用
  • 掌握随机数生成与种子初始化
  • 学会使用控制台字符绘制简单图形
  1. 改进方向
  • 添加分数统计功能
  • 实现撤销功能
  • 添加最高分记录
  • 跨平台支持(Linux/macOS)
  • 图形界面(SDL/OpenGL)

六、完整源码

#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <conio.h> #include <time.h> typedef struct { int board[4][4]; bool isWin; bool isOver; } GameState; void initGame(GameState* game); void printBoard(GameState* game); void generateNewNumber(GameState* game); bool moveLeft(GameState* game); bool moveRight(GameState* game); bool moveUp(GameState* game); bool moveDown(GameState* game); void checkGameState(GameState* game); bool isBoardFull(GameState* game); bool canMove(GameState* game); void initGame(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = 0; game->isWin = false; game->isOver = false; srand((unsigned int)time(NULL)); generateNewNumber(game); generateNewNumber(game); } void printBoard(GameState* game) { system("cls"); printf("================= 2048 =================\n"); printf("操作: ↑ ↓ ← → 退出:Q\n\n"); printf("┌─────────┬─────────┬─────────┬─────────┐\n"); for (int i = 0; i < 4; i++) { printf("│"); for (int j = 0; j < 4; j++) { if (game->board[i][j] == 0) printf(" │"); else if (game->board[i][j] < 10) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 100) printf(" %d │", game->board[i][j]); else if (game->board[i][j] < 1000) printf(" %d │", game->board[i][j]); else printf(" %d │", game->board[i][j]); } printf("\n"); if (i < 3) printf("├─────────┼─────────┼─────────┼─────────┤\n"); else printf("└─────────┴─────────┴─────────┴─────────┘\n"); } if (game->isWin) printf("\n恭喜!你赢了!\n"); else if (game->isOver) printf("\n游戏结束!\n"); } void generateNewNumber(GameState* game) { if (isBoardFull(game)) return; int x, y; do { x = rand() % 4; y = rand() % 4; } while (game->board[x][y] != 0); game->board[x][y] = (rand() % 10 == 0) ? 4 : 2; } bool isBoardFull(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 0) return false; return true; } bool moveLeft(GameState* game) { bool moved = false; int temp[4]; for (int i = 0; i < 4; i++) { int idx = 0; for (int j = 0; j < 4; j++) if (game->board[i][j] != 0) temp[idx++] = game->board[i][j]; while (idx < 4) temp[idx++] = 0; for (int j = 0; j < 3; j++) if (temp[j] != 0 && temp[j] == temp[j+1]) { temp[j] *= 2; temp[j+1] = 0; moved = true; } idx = 0; int newRow[4] = {0}; for (int j = 0; j < 4; j++) if (temp[j] != 0) newRow[idx++] = temp[j]; for (int j = 0; j < 4; j++) if (game->board[i][j] != newRow[j]) { moved = true; game->board[i][j] = newRow[j]; } } return moved; } bool moveRight(GameState* game) { bool moved = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) { int t = game->board[i][j]; game->board[i][j] = game->board[i][3-j]; game->board[i][3-j] = t; } return moved; } bool moveUp(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveLeft(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool moveDown(GameState* game) { bool moved = false; int temp[4][4]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; moved = moveRight(game); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) temp[i][j] = game->board[i][j]; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) game->board[i][j] = temp[j][i]; return moved; } bool canMove(GameState* game) { if (!isBoardFull(game)) return true; for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) if (game->board[i][j] == game->board[i][j+1]) return true; for (int j = 0; j < 4; j++) for (int i = 0; i < 3; i++) if (game->board[i][j] == game->board[i+1][j]) return true; return false; } void checkGameState(GameState* game) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (game->board[i][j] == 2048) { game->isWin = true; return; } if (!canMove(game)) game->isOver = true; } int main() { GameState game; initGame(&game); while (true) { printBoard(&game); if (game.isWin || game.isOver) break; char input = _getch(); bool moved = false; switch (input) { case 72: moved = moveUp(&game); break; case 80: moved = moveDown(&game); break; case 75: moved = moveLeft(&game); break; case 77: moved = moveRight(&game); break; case 'q': case 'Q': printf("游戏退出\n"); return 0; default: continue; } if (moved) { generateNewNumber(&game); checkGameState(&game); } } printf("\n按任意键退出\n"); _getch(); return 0; }