深圳幻海软件技术有限公司 欢迎您!

基于6818粤嵌开发板的2048游戏项目

2023-06-29

小白笔记目录前言一、lcd.c代码lcd屏幕初始化在lcd屏幕上任意一点显示颜色 关闭lcd屏幕lcd.h二、bmp.c代码bmp格式bmp图片读取bmp.h三、work.c部分源码触摸屏2048方块移动算法在随机位置上生成2或者4绘出4*4矩阵四、流程框图和界面功能描述前言板子是6818

小白笔记

目录

前言

一、lcd.c代码

lcd屏幕初始化

在lcd屏幕上任意一点显示颜色

 关闭lcd屏幕

lcd.h

二、bmp.c代码

bmp格式

bmp图片读取

bmp.h

三、work.c部分源码

触摸屏

2048方块移动算法

在随机位置上生成2或者4

绘出4*4矩阵

四、流程框图和界面功能描述


前言

板子是6818粤嵌开发板,为800*480,触摸屏幕大小为1024*600

需要自己配好相应数字图片,将其图片命名为数字.bmp的形式

在VMware虚拟机下进行arm-linux-gcc bla进行编译,然后传输可执行文件和图片至开发板中,最后在开发板上运行。 


一、lcd.c代码

lcd屏幕初始化

在linux系统中打开文件为open("文件名/位置",权限),其中权限包括只读,只写还有可读可写。open打开会得到一个返回值,打开失败则返回-1,成功打开则返回正数,所以判断是否打开成功,我们需要进行判断。

打开屏幕后,我们需要用write来进行写入像素(rgb),但是用write来进行操作的话,中间会浪费太多时间,导致数据无法一次性快速地完整被写入到屏幕中(多次运行可解决)。

所以我们需要用到mmap函数,功能为将地址映射出来,就比write函数省去了很多中间过程,可以让数据一次性完整迅速地写入到屏幕中。

其中函数原型为mmap( void* address , size_t length , int port , int flag , int fd , off_t off_set )。

其参数分别为

映射区首地址,一般存放NULL

映射区大小,系统会自动调整为4的整数倍,不能为0,一般文件多大就填多大。

映射权限,映射区必须要有权限,我们要可读也要可写,所以为PROT_READ | PROT_WRITE

标志位参数,判断是否为私有或者公有,修改内存数据的话,share的会同步要硬盘中,而private不会。

要映射的文件,为我们打开文件的返回值。

映射文件的偏移量,表示从该文件的哪里开始进行映射。必须为4的整数倍,一般设置成0。

将其用函数来封装,代码如下:

  1. // lcd屏幕的初始化
  2. // 打开屏幕
  3. int lcd_init(){
  4. lcd_fd = open( "/dev/fb0" , O_RDWR );
  5. // 判断打开是否成功
  6. if( lcd_fd == -1 ){
  7. // 返回打开失败的原因
  8. perror("open failed!");
  9. return -1;
  10. }
  11. // 内存映射
  12. // MaxSize = 800 * 480
  13. plcd = mmap( NULL , MaxSize*4 , PROT_READ | PROT_WRITE , MAP_SHARED , lcd_fd , 0 );
  14. return 0;
  15. }

在lcd屏幕上任意一点显示颜色

我们要在lcd屏幕上显示颜色,首先需要知道其坐标

        屏幕左上角为( 0 , 0 ),右下角为( lcd_width-1 , lcd_height-1 )。

        // 因为是从0开始的,所以最后一个是n-1

        其中屏幕上我们用到的是指针,所以随机一点位置为( x , y )的话,那么其地址就为

        首地址+x+y*lcd_width.

        首地址为我们映射所得得返回值plcd,所以随机一点地址为plcd+x+y*lcd_width.

然后我们需要知道将该点显示的颜色color,我们就可以将其进行赋值,就可以完成我们对该点显示颜色。

将其用函数封装,代码如下:

  1. // 在任意的点上 显示任意的一个颜色
  2. void display_point(int x, int y, int color)
  3. {
  4. if( x >= 0 && x < 800 && y >= 0 && y < 480 )
  5. *(plcd + x + y*800) = color;
  6. }

 关闭lcd屏幕

在结束操作后我们需要记得关闭文件。

关闭文件需要用到我们open的返回值lcd_fd,来确定文件,然后我们用close函数来进行关闭文件。

当然我们还用到了映射,我们也要取消掉映射。取消映射为munmap函数

函数原型为munmap( void* addr , size_t length )

        第一个为文件,第二个为字节大小。

如果返回值为-1,则代表关闭失败,则关闭成功。

将其用函数封装,代码如下:

  1. // 关闭屏幕
  2. int lcd_close()
  3. {
  4. // 关闭文件
  5. close(lcd_fd);
  6. // 解除映射
  7. // Maxsize = 800 * 480
  8. int res = munmap( plcd , MaxSize*4 );
  9. if( res == -1 ){
  10. perror("Removal failed!");
  11. return -1;
  12. }
  13. return 0;
  14. }

lcd.h

最后用.c文件保存,然后创建.h文件,我们就可以需要打开屏幕的时候直接调用头文件和函数即可。

  1. #ifndef __LCD_H__
  2. #define __LCD_H__
  3. // lcd屏幕的初始化
  4. int lcd_init();
  5. // 在任意的点上 显示任意的一个颜色
  6. void display_point(int x, int y, int color);
  7. // 关闭屏幕
  8. int lcd_close();
  9. #endif

二、bmp.c代码

bmp格式

bmp文件时有固定格式的,我们需要了解其格式,才能准确的读取到图片的数据

  1. /*
  2. * 00-01 文件标识,为字母ASCII码"BM"2byte
  3. * 02-05 文件大小 4byte
  4. * 06-09 位图文件保留字,必须为0 4byte
  5. * 0A-0D 文件开始到位图数据开始之间的偏移量 4byte
  6. * 0E-11 图像描述信息块的大小,常为28H 4byte
  7. * 12-15 图片高度 4byte
  8. * 16-19 图片宽度 4byte
  9. * 1A-1B 图像plane总数,恒为 12byte
  10. * 1C-1D 记录颜色的位数2byte
  11. * 1E-21 数据压缩方式 2byte
  12. * 22-25 图像区数据大小,必须为4的倍数 4byte
  13. * 26-29 水平像素点个数(在设备无关位图中,00H)4byte
  14. * 2A-2D 垂直像素点个数 (在设备无关位图中,00H) 4byte
  15. * 2E-31 图像所用颜色数(不用,固定为0)4byte
  16. * 32-35 重要颜色数(不用,固定为0)4byte
  17. *
  18. */

bmp图片读取

首先打开图片,我们需要知道图片的文件名或者文件路径,当然我们一般需要跟代码存放到一起,直接给文件名来进行打开。然后我们需要知道图片打开的起始位置。

所以我们图片读取需要知道三个参数,文件名,起始坐标x,起始坐标y

我们再根据bmp的格式来进行一一读取,我们就可以得到图片的宽度,高度,色深和像素数组等数据,最后运用lcd屏幕画点的函数来进行描绘图片。

我们读取的时候需要读取到特定的一些位置的数据,我们这个时候可以用lseek函数来改变我们光标位置,用read函数来选择我们读取数据的大小。

我们得到的图片宽度高度有的时候可能是正值也有可能是负值。

比如高度,正值就是从上到下,负值就是从下到上的像素点,所以需要进行正负的判断来改变描绘。

最后将打开图片封装成函数,代码如下:

  1. // 显示图片
  2. void show_picture(char * pathname ,int x ,int y)
  3. {
  4. int fd = open(pathname,O_RDONLY);
  5. if(fd == -1)
  6. {
  7. perror("open error\n");
  8. return ;
  9. }
  10. int width,height;
  11. short depth;
  12. unsigned char buf[4] ;
  13. //读取宽度
  14. lseek(fd,0x12,SEEK_SET);
  15. read(fd,buf,4);
  16. width = buf[3]<<24 | buf[2]<< 16 | buf[1] << 8 | buf[0];
  17. //读取高度
  18. read(fd,buf,4);
  19. height = buf[3]<<24 | buf[2]<< 16 | buf[1] << 8 | buf[0];
  20. //读取色深
  21. lseek(fd,0x1c,SEEK_SET);
  22. read(fd,buf,2);
  23. depth = buf[1] << 8 | buf[0];
  24. //像素数组
  25. int line_valid_bytes = abs(width) * depth / 8 ; //一行本有的有效字节
  26. int laizi = 0; //填充字节, 文件大小为 54 + wedth*height + height*n 为4的倍数
  27. if( (line_valid_bytes % 4) !=0 ) laizi = 4 - line_valid_bytes%4;
  28. int line_bytes = line_valid_bytes + laizi; //一行所有的字节数
  29. int total_bytes = line_bytes * abs(height); //整个像素数组的大小
  30. unsigned char * p1 = malloc(total_bytes); // 获取动态数组
  31. // 像素为54字节之后,所以调到54读完
  32. lseek(fd,54,SEEK_SET);
  33. read(fd,p1,total_bytes);
  34. // 画点,画图
  35. unsigned char a ,r ,g, b ;
  36. int i = 0;//用来做指针运动的
  37. int x0=0,y0=0; //用来循环计数
  38. int color;
  39. for( y0 = 0 ; y0 < abs(height) ; y0 ++ ) { // 列
  40. for( x0 = 0 ; x0 < abs(width) ; x0 ++ ) { // 行
  41. //一字节一字节读入RGBA
  42. // 读取后,图片顺序会反过来,需要调整
  43. b = p1[i++];
  44. g = p1[i++];
  45. r = p1[i++];
  46. if(depth == 32)
  47. {
  48. a=p1[i++];
  49. }
  50. if(depth == 24)
  51. {
  52. a = 0;
  53. }
  54. color = a << 24 | r << 16 | g << 8 | b ;
  55. // 描绘该点
  56. display_point(width>0?x+x0:abs(width)+x-1-x0, height>0? y+height-1-y0 : y+y0,color);
  57. }
  58. // 一行弄完需要进行填充过滤
  59. i = i +laizi;
  60. }
  61. // 释放指针
  62. free(p1);
  63. close(fd);
  64. }

bmp.h

最后用.c文件保存,然后创建.h文件,我们就可以需要显示图片的时候直接调用头文件和函数即可。

  1. #ifndef __BMP_H__
  2. #define __BMP_H__
  3. // 显示图片
  4. void show_picture( char* pathname , int x , int y );
  5. #endif

三、work.c部分源码

触摸屏

我们需要知道触摸屏的文件为"/dev/input/event0",然后我们无需更改,所以我们就就可以权限赋为只读。然后会有一个结构体struct input_event

其中有type种类,判断接触,有code值判断类型,有value值,根据type和code类型有不同的意思,如果是按压,则是压力值,滑动则是坐标值。

因为滑动过程中的坐标是不断在改变的,所以我们需要一直读入,设置x1,y1表示起始点坐标,x2,y2表示滑动的给出的坐标。如果没有读全则一直读入数据,然后我们不滑动离开的话,我们需要对结构体的值进行判断,滑动则更新数值,如果这个时候type类型为EV_ABS并且code为ABS_PRESSURE时,为按压,并且压力值(value)为0时,则可以视作离开,或者是type类型为EV_KEY并且code为BTN_TOUCH时,为滑动,且value为0时,也可以视作离开,这个时候我们就需要来进行判断我们滑动的方向了。

用相对位置来进行判定,若在x方向走的路程比在y方向走的路程两倍多,则是x方向上的移动,若在y方向走的路程比在x方向走的路程两倍多,则是y方向上的移动。

这个时候就可能会有误差,触击得到的相对位置也会在两倍以上,所以我们需要增加一个误差判定,我设置的x方向至少40,y方向上至少32。

判断滑动方向我们就可以关闭触屏文件,进行滑动操作的功能了。

下面是触摸屏代码:

  1. // 移动
  2. int get_movement(){
  3. int fd = open("/dev/input/event0", O_RDONLY);
  4. int res;
  5. if (fd == -1) {
  6. printf("open /dev/event0 failed\n");
  7. return -1;
  8. }
  9. int x1 = -1, y1 = -1; // 接触时候的坐标点
  10. int x2, y2; // 离开后的坐标点
  11. struct input_event ev;
  12. while( 1 ){
  13. res = read( fd , &ev, sizeof(ev) );
  14. if( res != sizeof(ev) ) continue;
  15. if( ev.type == EV_ABS && ev.code == ABS_X ) {
  16. if( x1 == -1 ) x1 = ev.value;
  17. x2 = ev.value;
  18. }
  19. if (ev.type == EV_ABS && ev.code == ABS_Y) {
  20. if (y1 == -1) y1 = ev.value;
  21. y2 = ev.value;
  22. }
  23. if( (ev.type == EV_ABS && ev.code == ABS_PRESSURE && ev.value == 0 ) ||
  24. ( ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0 ) ) {
  25. printf("x2 = %d\ty2 = %d\n",x2,y2);
  26. printf("flag = %d\n",flag);
  27. printf("\n\n\n\n");
  28. if( flag ) return 0;
  29. printf("x1 = %d\ty1 = %d\nx2 = %d\ty2 = %d\n",x1,y1,x2,y2);
  30. int opposite_x = abs(x2-x1); // 左右
  31. int opposite_y = abs(y2-y1); // 上下
  32. printf("opposite_x = %d\topposite_y = %d\n",opposite_x,opposite_y);
  33. if( x2 >= 830 && y2 < 80 && opposite_x <= 40 && opposite_y <= 32 ) {
  34. flag = 1;
  35. return 0;
  36. }
  37. if( opposite_x <= 40 && opposite_y <= 32 ) {
  38. x1 = -1;
  39. y1 = -1;
  40. continue;
  41. }
  42. if( opposite_x > 2 * opposite_y ){ // 左右移动
  43. if( x2 > x1 ){ // 方块向右边移动
  44. close(fd);
  45. return MOVE_RIGHT;
  46. }
  47. else{ // 方块向左边移动
  48. close(fd);
  49. return MOVE_LEFT;
  50. }
  51. }
  52. else if( opposite_x < 2* opposite_y ){ // 上下移动
  53. if( y2 > y1 ){ // 方块向下移动
  54. close(fd);
  55. return MOVE_DOWN;
  56. }
  57. else{ // 方块向上移动
  58. close(fd);
  59. return MOVE_UP;
  60. }
  61. }
  62. else x1 = -1, y1 = -1;
  63. }
  64. }
  65. close(fd);
  66. }

2048方块移动算法

2048是一个4*4的矩阵,滑动可以将方块移动并且合并,然后结束一次滑动后可以生成一个新的数字,可能是2或者4。

我们将方块移动,是需要先进行相加,再最后移动。

以向上滑动为例子,需要在每一列进行算(如果向右,则在每一行进行计算)

首先我们设置pos1和pos2,我们需要再这一列找到一个数字,将其位置赋值给pos1,然后再在这数字之后进行寻找下一个数字,第一个有数的数字如果跟pos1位置数字相同,则将pos1位置的数翻倍,且让pos2位置的数变为0。然后pos1变为pos2+1。这样我们就可以将两个数字相加。

最后移动,我们需要在顶端为pos1,然后让pos2进行寻找数字,如果pos1 != pos2的话,则交换两位置的数字,然后pos1向下移动一个位置,pos2继续按顺序寻找,这样我们就可以完成数字的移动了。

以向上滑动的代码为例子:

  1. // 向上移动
  2. void move_up(){
  3. int i, j;
  4. int x, y;
  5. for (i = 0; i < ITEM_NUM; i++) {
  6. for (x = 0; x < ITEM_NUM; ) {
  7. if (matrix_2048[x][i] != 0) {
  8. for (y = x + 1; y < ITEM_NUM; y++) {
  9. if (matrix_2048[y][i] != 0) {
  10. if (matrix_2048[x][i] == matrix_2048[y][i]) {
  11. matrix_2048[x][i] += matrix_2048[y][i];
  12. matrix_2048[y][i] = 0;
  13. x = y + 1;
  14. break;
  15. }
  16. else x = y;
  17. }
  18. }
  19. if (y >= ITEM_NUM) break;
  20. }
  21. else x++;
  22. }
  23. x = 0;
  24. for (y = 0; y < ITEM_NUM; y++) {
  25. if (matrix_2048[y][i] != 0) {
  26. if (x != y) {
  27. matrix_2048[x][i] = matrix_2048[y][i];
  28. matrix_2048[y][i] = 0;
  29. }
  30. x++;
  31. }
  32. }
  33. }
  34. }

在随机位置上生成2或者4

滑动完之后,方格会随机生成2或者4,我们需要判断滑动完之后哪些空格是没有数字的,我们需要进行统计,然后用rand函数来随机到一个位置上。

我们生成2或者4的话就用res = rand() % 2,0为2,1为4就可以了。

我们使用rand()函数需要生成随机数种子,即需要在最开始写上srand(time(NULL))

代码如下:

  1. int get_zeronum(){
  2. int i, j, n = 0;
  3. for( i = 0 ; i < ITEM_NUM ; i ++ )
  4. for( j = 0 ; j < ITEM_NUM ; j ++ )
  5. if( matrix_2048[i][j] == 0 )
  6. n ++;
  7. return n;
  8. }
  9. void set_rand_num(){
  10. int zero_Num = get_zeronum();
  11. int n = 0;
  12. int pos = rand() % zero_Num; // pos为随机出现2的位置 pos -> [0,zero_Num) 整数
  13. int i, j;
  14. for( i = 0 ; i < ITEM_NUM ; i ++ )
  15. for( j = 0 ; j < ITEM_NUM ; j ++ )
  16. sum += matrix_2048[i][j];
  17. for( i = 0 ; i < ITEM_NUM ; i ++ )
  18. for( j = 0 ; j < ITEM_NUM ; j ++ )
  19. if( matrix_2048[i][j] == 0 ){
  20. if( n == pos ) {
  21. if( sum >= 2200 ) {
  22. int res = rand()%100000;
  23. if( res == 99999 ) {
  24. matrix_2048[i][j] = 16384;
  25. return;
  26. }
  27. }
  28. int res = rand()%3;
  29. if( res < 2 ) matrix_2048[i][j] = 2;
  30. else matrix_2048[i][j] = 4;
  31. return;
  32. }
  33. else n++;
  34. }
  35. }

绘出4*4矩阵

这个比较简单,就用数字图片来进行绘画就可以了。

代码如下:

  1. void LCD_draw_matrix(){
  2. int i, j;
  3. int x0, y0;
  4. for( i = 0 ; i < ITEM_NUM ; i ++ ){
  5. for( j = 0 ; j < ITEM_NUM ; j ++ ){
  6. x0 = MATRIX_X0 + ( ITEM_WIDTH + BLACK_LINE ) * j + 5;
  7. y0 = MATRIX_Y0 + ( ITEM_HEIGHT + BLACK_LINE ) * i + 5;
  8. if( matrix_2048[i][j] == 0 ) {
  9. int k, z;
  10. for( k = 0 ; k < ITEM_WIDTH ; k ++ )
  11. for( z = 0 ; z < ITEM_HEIGHT ; z ++ )
  12. display_point( x0 + k , y0 + z , 0x4682b4 );
  13. }
  14. else{
  15. // 加载那个数字的图片
  16. char pathname[32];
  17. sprintf( pathname , "%d.bmp" , matrix_2048[i][j] );
  18. show_picture( pathname , x0 , y0 );
  19. }
  20. }
  21. }
  22. }

四、流程框图和界面功能描述

接下来就是在此基础上添加一些功能了,下面是流程图和界面功能介绍

2048游戏项目功能


文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览48550 人正在系统学习中