小白笔记
目录
前言
一、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。
将其用函数来封装,代码如下:
- // 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总数,恒为 12byte
- * 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 error\n");
- 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 failed\n");
- 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 = %d\ty2 = %d\n",x2,y2);
- printf("flag = %d\n",flag);
- printf("\n\n\n\n");
- if( flag ) return 0;
- printf("x1 = %d\ty1 = %d\nx2 = %d\ty2 = %d\n",x1,y1,x2,y2);
- int opposite_x = abs(x2-x1); // 左右
- int opposite_y = abs(y2-y1); // 上下
- printf("opposite_x = %d\topposite_y = %d\n",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游戏项目功能