00简介:
0.1:演示:
0.2:游戏说明:
玩家通过滑动屏幕来将相同数字合并并生成新的方块,并努力生成2048方块。
1.项目说明:
1.1项目主题:
本项目的主题是创建一个基于C语言的2048游戏,运用二维数组来管理2048棋盘,实现棋盘的灵活移动。同时,通过相同方块合并机制,让各个方块进行滑动合并生成新的方块,实现游戏的核心逻辑。
1.2技术实现:
项目采用C语言作为开发语言,整个游戏的设计主要包含以下几个方面的知识和技术实现:
二维数组的运用:
使用二维数组来存储棋盘的每个格子,每个格子包含着产生的格子信息。这种结构能够方便地实现2048滑动合并等操作。
枚举类型的设计:
通过定义枚举类型touchEvent,touchType;,管理方块滑动的方向,允许程序在不同情况下执行不同的逻辑,使代码更加清晰和易于管理。
游戏逻辑的实现:
实现了方块的滑动逻辑、方块的生成与合并逻辑等。特别是通过方块滑动函数来判断是否有相同的方块可以合并。
用户交互:
采用触摸屏滑动方式,用户通过滑动屏幕来控制方块的滑动方向。结合动态刷新界面和动画效果,增强了用户的游戏沉浸感。
1.3项目框架:
项目的整体框架分为几个模块,包括:
棋盘和方块模块:负责定义整个二维数组棋盘和每个方块的值,以及相关的方块合并生成函数。
游戏逻辑模块:处理棋盘状态的更新,包括方块的滑动、合并逻辑、方块生成、以及游戏得分的计算。
用户交互模块:管理用户的滑动,响应触摸事件,并实时更新棋盘。
初始化与资源管理模块:负责初始化游戏设置,例如棋盘的初始状态和速度,并管理游戏过程中的资源
1.4项目开发工具:
本项目在Ubuntu Linux环境下进行开发,主要使用以下工具和技术:
开发环境:
代码编辑器:使用 Visual Studio Code (VSCode) 作为主要的代码编辑器。VSCode 提供了丰富的插件和调试支持,极大提高了开发效率。
编译工具链:使用 arm-linux-gcc 编译器针对 ARM 架构进行编译。此编译器适用于嵌入式系统,可以将 C 代码编译为适合 ARM 处理器的可执行文件。
文件管理:
项目文件通过 NFS (网络文件系统) 共享到目标开发板。这一方式允许开发者在本地机器上进行开发与测试,同时能够方便地将代码同步到开发板进行实际运行。
在 NFS 的配置中,确保设置合适的权限,以便于无障碍地访问和修改共享的项目文件。
2.项目实现:
2.1项目整体架构:
void _2048Play(void)
{
// 显示2048棋盘图片
showRandBmp();
// 初始化2048 把4*4的数组全部赋值为0
memset(_2048ChessBoard, 0, sizeof(_2048ChessBoard));
// 初始化_2048GameOverFlag为false
_2048GameOverFlag = false;
// 重置棋盘 重置分数 重置change dir _2048GameScore _2048AddScore 重新播放音乐
_2048Restart(_2048ChessBoard);
//判断用户是否触摸了棋盘
bool isTouchFlag = 0;
while (1)
{
// 规定触摸范围在2048以内
if ()
{
isTouchFlag = true;
}
//当产生触摸事件时,dir赋值
if (isTouchFlag)
{
isTouchFlag = false;
dir = touchResult.touchEventResult;
}
// 判断上下左右
switch (dir)
{
case up_to_down:
memset(&touchResult, 0, sizeof(touchType));
dir = no_event; // 置零
// 从上往下滑
_2048_Up_To_Down(_2048ChessBoard);
break;
case 2:
memset(&touchResult, 0, sizeof(touchType));
dir = 0; // 置零
// 从下往上滑
_2048_Down_To_Up(_2048ChessBoard);
break;
case 3:
memset(&touchResult, 0, sizeof(touchType));
dir = 0; // 置零
// 从左往右滑
_2048_Left_To_Right(_2048ChessBoard);
break;
case 4:
memset(&touchResult, 0, sizeof(touchType));
dir = 0; // 置零
// 从右往左滑
_2048_Right_To_Left(_2048ChessBoard);
break;
default:
break;
}
// 打印棋盘
_2048PrintfChess(_2048ChessBoard);
// 当游戏结束时赋值为1 并且图片显示GameOver 用户再次点击重置时再刷新
_2048GameOver(_2048ChessBoard);
// 判断是否点了重置
if ()
{
// 置零触摸事件
memset(&touchResult, 0, sizeof(touchType));
// 2048重置函数
_2048Restart(_2048ChessBoard);
}
// 返回检测 时间变量掐掉 音乐变量掐掉 界面标识符设置为3
if ()
{
// 触摸事件置零
memset(&touchResult, 0, sizeof(touchType))
// 2048重置以便下次进入游戏是刷新游戏的状态
_2048Restart(_2048ChessBoard);
break;
}
}
}
2.2 主要功能模块及API
2.2.1棋盘与数据结构:
// 触摸滑屏事件enum
typedef enum touchEvent
{
no_event = 0,
up_to_down = 1, // 从上往下滑
down_to_up = 2, // 从下往上滑
left_to_right = 3, // 从左往右滑
right_to_left = 4, // 从右往左滑
} touchEvent;
// 触摸事件
typedef struct touchType
{
int touchX; // 触摸事件X
int touchY; // 触摸事件Y
touchEvent touchEventResult; // 滑屏事件
} touchType;
// 初始化2048 把4*4的数组全部赋值为0
int _2048ChessBoard[4][4] = {0};
2.2.2游戏初始化
全局变量设置:
// _2048ChessBoardChangeFlag记录数组变化,变化了就打印数组
// _2048GameOverFlag 游戏结束标志,当游戏结束时赋值为1 并且图片显示GameOver 用户再次点击重置时再刷新
bool _2048ChessBoardChangeFlag = false, _2048GameOverFlag = false;
// dir 记录滑屏事件 switch判断dir来调用上下左右函数
int dir = no_event;
// _2048GameScore记录总分 在每次两个for循环跳出来后加上_2048AddScore 有变化时,刷新字库 退出或者新游戏重置为0
// _2048AddScore 记录每次的分数 在每次棋子变化加上新棋子
int _2048GameScore = 0, _2048AddScore = 0;
游戏初始化:
// 显示2048游戏界面
showRandBmp(display(interface_List_head, 5), 0, 0);
// 显示用户名
writeFontProMax(currentUser.name, 575, 15, 100, 40, 0, 0);
// 初始化2048 把4*4的数组全部赋值为0
memset(_2048ChessBoard, 0, sizeof(_2048ChessBoard));
// 初始化_2048GameOverFlag为false
_2048GameOverFlag = false;
// 重置棋盘 重置分数 重置change dir _2048GameScore _2048AddScore 重新播放音乐
_2048Restart(_2048ChessBoard);
//判断用户是否触摸了棋盘
bool isTouchFlag = 0;
重置棋盘_2048Restart()函数API:
/// @brief 重置棋盘
/// @param _2048ChessBoard 棋盘
void _2048Restart(int _2048ChessBoard[][4])
{
// 重置棋盘 重置分数 重置change dir _2048GameScore _2048AddScore 重新播放音乐
// 重置棋盘, 初始化2048 把4*4的数组全部赋值为0
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
_2048ChessBoard[i][j] = 0;
}
}
// 初始化2048 把4*4的数组全部赋值为0
memset(_2048ChessBoard, 0, sizeof(&_2048ChessBoard));
// 重新打印空棋盘
showRandBmp(display(interface_List_head, 501), 0, 0);
// 重置滑动方向
dir = 0;
// 重置累加分数
_2048AddScore = 0;
// 重置用户得分
writeFontProMax("0", 615, 225, 80, 40, 0, 0);
_2048GameScore = 0;
_2048GameOverFlag = false;
_2048ChessBoardChangeFlag = true;
// 随机刷新一个棋子
_2048RandChess(_2048ChessBoard);
// 这里总共要刷新两个棋子,但是这里注释掉这句原因是_2048PrintfChess还有再调用一次随机刷新棋子
// _2048RandChess(_2048ChessBoard);
// 打印重置后的棋盘
_2048PrintfChess(_2048ChessBoard);
// 棋盘刷新标志置为false
_2048ChessBoardChangeFlag = false;
}
2.2.3 2048方块滑动合并逻辑
1.触摸线程函数不断返回触摸事件:
/// @brief 触摸线程,不断扫描屏幕触摸信息,并返回触摸事件
/// @param arg 线程号
/// @return 无
void *touchThread(void *arg)
{
int endX = 0, endY = 0, startX = 0, startY = 0;
struct input_event ts;
memset(&ts, 0, sizeof(struct input_event));
while (1)
{
// 读触摸屏
read(touch_fd, &ts, sizeof(struct input_event));
// 决断是否为相对坐标类型
if (ts.type == EV_ABS && ts.code == ABS_X)
{
endX = ts.value;
touchResult.touchX = ts.value * (800.0 / 1024.0);
}
// 决断是否为相对坐标类型
if (ts.type == EV_ABS && ts.code == ABS_Y)
{
endY = ts.value;
touchResult.touchY = (int)(ts.value * (480.0 / 600.0));
}
// 表示手指按下
if (ts.type == EV_KEY && ts.code == BTN_TOUCH && ts.value == 1)
{
// 记录起点坐标
startX = endX;
startY = endY;
}
// 表示手指松开
if (ts.type == EV_KEY && ts.code == BTN_TOUCH && ts.value == 0)
{
// 坐标换算 起点坐标
startX = startX * (800.0 / 1024.0);
startY = startY * (480.0 / 600.0);
// 坐标换算 结束坐标
endX = endX * (800.0 / 1024.0);
endY = endY * (480.0 / 600.0);
// 判断是左右滑还是上下滑
if (abs(startX - endX) > abs(startY - endY))
{
if ((startX - endX) > 100)
{
touchResult.touchEventResult = right_to_left;
}
if ((endX - startX) >= 100)
{
touchResult.touchEventResult = left_to_right;
}
}
else
{
if ((startY - endY) > 50)
{
touchResult.touchEventResult = down_to_up;
}
if ((endY - startY) >= 50)
{
touchResult.touchEventResult = up_to_down;
}
}
}
}
return 0;
}
2.判断滑动方向: