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

小白笔记


前言

板子是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。

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

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

在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,我们就可以将其进行赋值,就可以完成我们对该点显示颜色。

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

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

 关闭lcd屏幕

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

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

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

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

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

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

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

// 关闭屏幕 
int lcd_close()
{
	// 关闭文件 
	close(lcd_fd);
	// 解除映射
    // Maxsize = 800 * 480
	int res = munmap( plcd , MaxSize*4 );
	if( res == -1 ){
		perror("Removal failed!");
		return -1;
	}
	return 0;
}

lcd.h

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

#ifndef __LCD_H__
#define __LCD_H__


// lcd屏幕的初始化
int lcd_init();

// 在任意的点上 显示任意的一个颜色
void display_point(int x, int y, int color);

// 关闭屏幕 
int lcd_close();

#endif

二、bmp.c代码

bmp格式

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

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

bmp图片读取

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

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

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

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

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

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

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

// 显示图片
void show_picture(char * pathname ,int x ,int y)
{
	int fd = open(pathname,O_RDONLY);
	if(fd == -1)
	{
		perror("open errorn");
		return ;
	}

	int width,height;
	short depth;
	unsigned char buf[4] ;
	//读取宽度
	lseek(fd,0x12,SEEK_SET);
	read(fd,buf,4);
	width = buf[3]<<24 | buf[2]<< 16 | buf[1] << 8 | buf[0];
	//读取高度
	read(fd,buf,4);
	height  = buf[3]<<24 | buf[2]<< 16 | buf[1] << 8 | buf[0];
	//读取色深
	lseek(fd,0x1c,SEEK_SET);
	read(fd,buf,2);
	depth = buf[1] << 8  | buf[0];
	//像素数组 
	int line_valid_bytes = abs(width) * depth / 8 ; //一行本有的有效字节
	int laizi = 0; //填充字节, 文件大小为 54 + wedth*height + height*n 为4的倍数 
	if( (line_valid_bytes % 4) !=0   ) laizi =  4 - line_valid_bytes%4;
	int line_bytes = line_valid_bytes + laizi; //一行所有的字节数
	int total_bytes = line_bytes * abs(height); //整个像素数组的大小
	unsigned char * p1  = malloc(total_bytes); // 获取动态数组
	
	// 像素为54字节之后,所以调到54读完
	lseek(fd,54,SEEK_SET);
	read(fd,p1,total_bytes);
	
	// 画点,画图
	unsigned char a ,r ,g, b ;
	int i = 0;//用来做指针运动的
	int x0=0,y0=0; //用来循环计数
	int color;
	for( y0 = 0 ; y0 < abs(height) ; y0 ++ ) { // 列 
		for( x0 = 0 ; x0 < abs(width) ; x0 ++ ) { // 行 
			//一字节一字节读入RGBA
			// 读取后,图片顺序会反过来,需要调整
			b = p1[i++];
			g = p1[i++];
			r = p1[i++];
			if(depth == 32)
			{
				a=p1[i++];
			}
			if(depth == 24)
			{
				a = 0;
			}
			color = a << 24 | r << 16 | g << 8 | b ;
            // 描绘该点
			display_point(width>0?x+x0:abs(width)+x-1-x0, height>0? y+height-1-y0 : y+y0,color);
			
			
		}
		// 一行弄完需要进行填充过滤 
		i = i +laizi;
	}
	// 释放指针 
	free(p1);
	close(fd);
}

bmp.h

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

#ifndef __BMP_H__
#define __BMP_H__

// 显示图片
void show_picture( char* pathname , int x , int y );


#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。

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

下面是触摸屏代码:

// 移动 
int get_movement(){
	int fd = open("/dev/input/event0", O_RDONLY);
	int res;
	if (fd == -1) {
		printf("open /dev/event0 failedn");
		return -1; 
	}
	int x1 = -1, y1 = -1; // 接触时候的坐标点
	int x2, y2; // 离开后的坐标点
	struct input_event ev;
	while( 1 ){
		res = read( fd , &ev, sizeof(ev) );
		if( res != sizeof(ev) ) continue;	
		if( ev.type == EV_ABS && ev.code == ABS_X ) {
			if( x1 == -1 ) x1 = ev.value;
			x2 = ev.value;
		} 
		if (ev.type == EV_ABS && ev.code == ABS_Y) {
			if (y1 == -1) y1 = ev.value;
			y2 = ev.value;
		}
		if( (ev.type == EV_ABS && ev.code == ABS_PRESSURE && ev.value == 0 ) || 
		    ( ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0 ) ) {
		    
		    
		    printf("x2 = %dty2 = %dn",x2,y2);
			printf("flag = %dn",flag);
			printf("nnnn");
			if( flag ) return 0;
			printf("x1 = %dty1 = %dnx2 = %dty2 = %dn",x1,y1,x2,y2);
			int opposite_x = abs(x2-x1); // 左右 
			int opposite_y = abs(y2-y1); // 上下 
			printf("opposite_x = %dtopposite_y = %dn",opposite_x,opposite_y);
			
		    if( x2 >= 830 && y2 < 80 && opposite_x <= 40 && opposite_y <= 32 ) {
		    	flag = 1;
		    	return 0;
			}
			if( opposite_x <= 40 && opposite_y <= 32 ) {
				x1 = -1;
				y1 = -1;
				continue;
			}
			if( opposite_x > 2 * opposite_y ){ // 左右移动 
				if( x2 > x1 ){ // 方块向右边移动 
					close(fd);
					return MOVE_RIGHT;
				}
				else{ // 方块向左边移动 
					close(fd);
					return MOVE_LEFT;
				}
			} 
			else if( opposite_x < 2* opposite_y ){ // 上下移动 
				if( y2 > y1 ){ // 方块向下移动 
					close(fd);
					return MOVE_DOWN;
				}
				else{ // 方块向上移动 
					close(fd);
					return MOVE_UP;
				}
			}
			else x1 = -1, y1 = -1;
		}
	}
	close(fd);
}

2048方块移动算法

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

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

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

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

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

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

// 向上移动
void move_up(){
	int i, j;
	int x, y;
	for (i = 0; i < ITEM_NUM; i++) {
		for (x = 0; x < ITEM_NUM; ) {
			if (matrix_2048[x][i] != 0) {
				for (y = x + 1; y < ITEM_NUM; y++) {
					if (matrix_2048[y][i] != 0) {
						if (matrix_2048[x][i] == matrix_2048[y][i]) {
							matrix_2048[x][i] += matrix_2048[y][i];
							matrix_2048[y][i] = 0;
							x = y + 1;
							break;
						}
						else x = y;
					}
				}
				if (y >= ITEM_NUM) break;
			}
			else x++;
		}       
		x = 0;
		for (y = 0; y < ITEM_NUM; y++) {
			if (matrix_2048[y][i] != 0) {       
				if (x != y) {
					matrix_2048[x][i] = matrix_2048[y][i];
					matrix_2048[y][i] = 0;
				}
				x++;
			}
		}
	}
}

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

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

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

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

代码如下:

int get_zeronum(){
	int i, j, n = 0;
	for( i = 0 ; i < ITEM_NUM ; i ++ )
		for( j = 0 ; j < ITEM_NUM ; j ++ )
			if( matrix_2048[i][j] == 0 )
				n ++;
	return n;
}

void set_rand_num(){
	int zero_Num = get_zeronum();
	int n = 0;
	int pos = rand() % zero_Num; // pos为随机出现2的位置   pos -> [0,zero_Num) 整数
	int i, j;
	for( i = 0 ; i < ITEM_NUM ; i ++ )
		for( j = 0 ; j < ITEM_NUM ; j ++ )
			sum += matrix_2048[i][j];

	for( i = 0 ; i < ITEM_NUM ; i ++ )
		for( j = 0 ; j < ITEM_NUM ; j ++ )
			if( matrix_2048[i][j] == 0 ){
				if( n == pos ) {
					if( sum >=    2200 ) {
						int res = rand()%100000;
						if( res == 99999 ) {
							matrix_2048[i][j] = 16384;
							return;
						}
					}
					int res = rand()%3;
					if( res < 2 ) matrix_2048[i][j] = 2;
					else matrix_2048[i][j] = 4;
					return;
				}
				else n++;
			}
}

绘出4*4矩阵

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

代码如下:

void LCD_draw_matrix(){
	int i, j;
	int x0, y0;
	for( i = 0 ; i < ITEM_NUM ; i ++ ){
		for( j = 0 ; j < ITEM_NUM ; j ++ ){
			x0 = MATRIX_X0 + ( ITEM_WIDTH + BLACK_LINE ) * j + 5;
			y0 = MATRIX_Y0 + ( ITEM_HEIGHT + BLACK_LINE ) * i + 5;
			if( matrix_2048[i][j] == 0 ) {
				int k, z;
				for( k = 0 ; k < ITEM_WIDTH ; k ++ )
					for( z = 0 ; z < ITEM_HEIGHT ; z ++ )
						display_point( x0 + k , y0 + z , 0x4682b4 );
			}
			else{
				// 加载那个数字的图片 
				char pathname[32];
				sprintf( pathname , "%d.bmp" , matrix_2048[i][j] );
				show_picture( pathname , x0 , y0 );
			}
		}
	}
}

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

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

2048游戏项目功能


本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>