小白笔记
目录
前言
一、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总数,恒为 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 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 );
}
}
}
}
四、流程框图和界面功能描述
接下来就是在此基础上添加一些功能了,下面是流程图和界面功能介绍
文章来源:https://www.toymoban.com/news/detail-427309.html
2048游戏项目功能文章来源地址https://www.toymoban.com/news/detail-427309.html
到了这里,关于基于6818粤嵌开发板的2048游戏项目的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!