#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<error.h>
#include<linux/input.h>
#include<unistd.h>
#include<sys/mman.h>
#include<pthread.h>
#include<fcntl.h>
/*
顺序:1、先打开所有文件,映射图片;
2、画小球;使小球自由移动
3、画出小木板
4、写控制小木板移动
5、处理各种图片
6、用flag_x和y(标志位)里面判断小球和木板是否接触;
7、判断放加分的图片,也是用flag_x和y(标志位)
8、写开始、暂停、退出,都是用很简单的判断语句,一个标志位,加上是否按到屏幕就可以了;
*/
#define Lcd_Path "/dev/fb0"
#define Touch_Dev_Path "/dev/input/event0"
#define w 50
#define h 40
//下面全是函数声明。
//这个函数用来打开图片文件路径、映射文件路径等;
int Init_DR();
//这个函数里面的顺序是1、画小球;2、使小球自由移动;3、在flag_x和y(标志位)里面判断小球和木板是否接触;
void* Move_Ball(void *arg);
//这个函数是用来画出小木板的,
int Draw_Plate();
//触控屏控制小木板移动的函数(画小木板和控制小木板不是同一个函数)顺序是1、先把画面都变成白色;2、给图片左边的开始、结束等;3、移动木板的位置*/
void* Touch_Plate(void *arg);
//这个函数是用来处理各种图片的,只要将图片传进来这个函数,他就会自动根据你调整的宽和高调整,比如在游戏开始时的左边Show_ui("./MI.bmp",-1,-1); 在游戏结束时的gameover Show_ui("./over.bmp",-1,-1); 想在什么时候放一张图片就写一个Show_ui("路径",-1,-1);*/
int Show_ui(char * bmp_path,int zs_x,int zs_y);
//这个函数是用来判断放加分的图片,加一分就切一张图片
int Grade_ball(int grade);
//释放内存
int Draw_free();
//上面全是函数声明。
char * picture[10] = {"./0.bmp","./1.bmp","./2.bmp",
"./3.bmp","./4.bmp","./5.bmp",
"./6.bmp","./7.bmp","./8.bmp",
"./9.bmp"}; //加分的图片,每加一分,切换一张图片,实现显示加一分;
/*结构体,画图*/
struct Drow_text
{
int lcd_fd; //映射和图片的文件路径
int *mmap_fd;
int plate_w; //木板长和宽
int plate_h;
struct input_event touch; //触控屏的结构体,里面存放了触控屏的四个数据type、code、value(压力系数,可以通过压力系数是否为0或1来判断松手)、time;
int touch_fd;
int t_x; //用来存放手点到开发板的位置的变量
int t_y;
int x0; //圆的中心点
int y0; //圆的中心点
int x_min; //用来判断木板的边界的变量
int x_max; //用来判断木板的边界的变量
pthread_t ball_id; //创建线程的ID,只有创建线程ID号才能用ID号调用这个线程!
pthread_t plate_id;
int flag_x; //存放小球自由移动的标志位 和 用于小球是否退出边界的标志位 的变量
int flag_y; //存放小球自由移动的标志位 和 用于小球是否退出边界的标志位 的变量
int grade; //存放分数的变量
int star_game; //存放使用标志位1,这样可以防止重复按到开始按钮,防止开启多个线程,导致bug的变量;
}RG;
/*主函数,调用函数*/
int main()
{
Init_DR(); //这个函数用来打开图片文件路径、映射文件路径等;
Show_ui("./MI.bmp",-1,-1); //屏幕左边的开始、暂停、结束
RG.star_game = 1; //使用标志位1,这样可以防止重复按到开始按钮,防止开启多个线程,导致bug;
while (1)
{
read(RG.touch_fd,&RG.touch,sizeof(RG.touch));
if(RG.touch.type == EV_ABS && RG.touch.code == ABS_X) RG.t_x = RG.touch.value*800/1024; // RG.t_x 是手触碰的x轴坐标!
if(RG.touch.type == EV_ABS && RG.touch.code == ABS_Y) RG.t_y = RG.touch.value*480/600; // RG.t_y 是手触碰的y轴坐标!
if (RG.t_y > 50 && RG.t_y < 150 && RG.t_x > 700 && RG.t_x < 800 && RG.star_game == 1) //开始游戏按钮
{
RG.star_game = 0; //当按下开始按扭,就会令RG.star_game标志位变成0,这样&& RG.star_game == 1在上面判断就无法执行,就不会重复开启多个线程;
//并且使 RG.star_game 变成0,这样下面 && RG.star_game == 0暂停按钮就可以运行;
pthread_create(&RG.plate_id,NULL,Touch_Plate,NULL); //线程创建
pthread_create(&RG.ball_id,NULL,Move_Ball,NULL);
printf("Game star!\n");
}
if (RG.t_y > 155 && RG.t_y < 280 && RG.t_x > 700 && RG.t_x < 800 && RG.star_game == 0) //暂停游戏按钮
{
RG.star_game = 1; //当按下暂停按钮,就会令RG.star_game标志位变成1,&& RG.star_game == 1这句就成立了,这样就可以按开始按钮啦;
/*杀死线程老师还没教,不用看懂,知道这个语句是杀死线程就可以*/
pthread_cancel(RG.plate_id); //杀死木板线程
pthread_cancel(RG.ball_id); //杀死小球线程
printf("Game stop!\n");
}
if (RG.t_y > 300 && RG.t_y < 480 && RG.t_x > 700 && RG.t_x < 800 ) //游戏退出按钮,点下去弹出游戏game over
{
printf("Game back!\n");
Show_ui("./over.bmp",-1,-1);
break;
}
}
Draw_free(); //游戏结束往下走到这里,释放内存。
return 0;
}
int Init_DR() //这个函数用来打开图片文件路径、映射文件路径等;
{
RG.lcd_fd=open(Lcd_Path,O_RDWR); //打开文件
RG.touch_fd=open(Touch_Dev_Path,O_RDONLY);
if (RG.lcd_fd == -1 || RG.touch_fd == -1)
{
perror("OPEN");
return -1;
}
RG.mmap_fd = (int *)mmap(NULL,
800*480*4,
PROT_READ | PROT_WRITE,
MAP_SHARED,
RG.lcd_fd,
0); //映射图片
if (RG.mmap_fd == MAP_FAILED) //映射打开失败
{
perror("mmap");
return -1;
}
RG.plate_h = 30; //plate_h木板,高;(这个高后面用不到,只是定义给人看的)
RG.plate_w = 100; //plate_w木板,宽;
RG.x0=400;
RG.y0=240;
return 0;
}
/*这个函数里面的顺序是1、画小球;2、使小球自由移动;3、在flag_x和y(标志位)里面判断小球和木板是否接触;*/
void* Move_Ball(void *arg)
{
/*杀死线程老师还没教,不用看懂,知道这些语句是杀死线程就可以*/
// 设置自我分离
pthread_detach(pthread_self());
// 设置取消状态为可以取消
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置取消类型-->立即取消
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
RG.flag_x=0;
RG.flag_y=0;
int R = 50;
while(1) //1、画小球
{
for (int i = RG.y0 - R; i <= RG.y0 + R ; i++)
{
for (int j= RG.x0 - R; j <= RG.x0 + R; j++)
{
if ((j-RG.x0)*(j-RG.x0)+(i-RG.y0)*(i-RG.y0) < R*R)
{
*(RG.mmap_fd+800*i+j) = 0xfaff72;
}
else
{
*(RG.mmap_fd+800*i+j) = 0xffffff;
}
}
}
/*这行注释下面的所有if都是标志位判断,使小球自由移动和加分等,2、使小球自由移动*/
if (RG.y0-R==0)
{
RG.flag_y = 1;
}
/*判断木板有没有跟小最下方接触,有就加一分(++(RG.grade)),3、在flag_x和y(标志位)里面判断小球和木板是否接触;*/
if (RG.x0 >RG.x_min && RG.x0 < RG.x_max && RG.y0 + R == 399) //RG.x_min在画木板的函数Draw_Plate()里面已经处理,这是木板最左边。
{ //RG.x_max在画木板的函数Draw_Plate()里面已经处理,这是木板最右边。
RG.flag_y = 0;
printf("加一分!\n");
Grade_ball(++(RG.grade)); //加一分,(RG.grade)在 Grade_ball(int grade)函数里面使用,用来判断图片是否要切换
}
if (RG.y0+R == 470) //判断小球是否出了边界.
{
printf("Game over!");
Show_ui("./over.bmp",-1,-1);
Draw_free(); //游戏结束往下走到这里,释放内存。
break;
}
if (RG.x0-R==0)
{
RG.flag_x = 1;
}
if (RG.x0+R==696)
{
RG.flag_x = 0;
}
if(RG.flag_y==1) RG.y0++;
if(RG.flag_y==0) RG.y0--;
if(RG.flag_x==1) RG.x0++;
if(RG.flag_x==0) RG.x0--;
usleep(3000);
}
return (void *)0;
}
/*这个函数是用来画出小木板的,*/
int Draw_Plate()
{
// RG.t_x 是手触碰的x轴坐标! RG.plate_w/2是小板宽度的一半,比如x坐标为240,小板宽度的一半为50,相减就是190,就是小木板最左边啦
RG.x_min = RG.t_x-RG.plate_w/2-80;
//同上,RG.x_max是木板的最右边
RG.x_max = RG.t_x+RG.plate_w/2+80;
for(int y=400; y<430; y++) //画出小木板
{
for(int x=0; x<698; x++)
{
if(x>RG.t_x-RG.plate_w/2 && x<RG.t_x+RG.plate_w/2)
*(RG.mmap_fd +800*y+x) = 0xfaff72; //小木板为鸭黄
else
*(RG.mmap_fd +800*y+x) = 0xffffff; //其他地方为白色
}
}
return 0;
}
/*触控屏控制小木板移动的函数(画小木板和控制小木板不是同一个函数)顺序是1、先把画面都变成白色;2、给图片左边的开始、结束等;3、移动木板的位置*/
void* Touch_Plate(void *arg)
{
/*杀死线程老师还没教,不用看懂,知道这些语句是杀死线程就可以*/
// 设置自我分离
pthread_detach(pthread_self());
// 设置取消状态为可以取消
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置取消类型-->立即取消
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
Show_ui("./MI.bmp",-1,-1); //2、给图片左边的开始、结束等;
while(1) //3、移动木板的位置
{
read(RG.touch_fd,&RG.touch,sizeof(RG.touch));
if(RG.touch.type == EV_ABS && RG.touch.code == ABS_X) RG.t_x = RG.touch.value*800/1024; // RG.t_x 是手触碰的x轴坐标!
if(RG.touch.type == EV_ABS && RG.touch.code == ABS_Y) RG.t_y = RG.touch.value*480/600; // RG.t_y 是手触碰的y轴坐标!
if (RG.t_y > 400 && RG.t_y < 430 && RG.t_x != -1) //小木板移动的范围,这个 RG.t_x != -1 是用来判断你手有没有碰到屏幕,如果没有触控到屏幕当然不等于1啦。
{
Draw_Plate(); //画小木板
}
}
return (void *)0;
}
/*这个函数是用来处理各种图片的,只要将图片传进来这个函数,他就会自动根据你调整的宽和高调整,比如在游戏开始时的左边Show_ui("./MI.bmp",-1,-1); 在游戏结束时的gameover Show_ui("./over.bmp",-1,-1); 想在什么时候放一张图片就写一个Show_ui("路径",-1,-1);*/
int Show_ui(char * bmp_path,int zs_x,int zs_y)
{
//里面是处理图片的过程,不会的话去看看视频,自己敲一敲放图片的代码(就Show_ui()这一个函数里面的内容),我也有点不太能解释
int bmp_fd = open(bmp_path,O_RDONLY);
if(bmp_fd == -1)
{
perror("open");
return -1;
}
int bmp_w,bmp_h,skip;
/*获取图片的宽和高*/
lseek(bmp_fd,18,SEEK_SET);
read(bmp_fd,&bmp_w,4);
read(bmp_fd,&bmp_h,4);
//printf("w:%d---h:%d\n",bmp_w,bmp_h);
/*判断宽度是不是4的倍数*/
if(bmp_w*3 % 4 == 0)
{
skip = 0; //图片宽高符合4的倍数
}
else
{
skip = 4 - (bmp_w*3 % 4); //图片宽高不符合4的倍数,就可以在下面用这个skip来将这个图片处理成合适的大小
}
int * new_p; //新的铲子,用来处理这个图片
if(zs_x == -1 && zs_y == -1)
{
new_p = RG.mmap_fd+ 800*(240-bmp_h/2)+(400-bmp_w/2);
}
else
{
new_p = RG.mmap_fd+ 800*zs_y+zs_x;
}
/*获取图片像素点,用skip来处理这个图片的大小*/
char TQ[bmp_w*bmp_h*3 + skip*bmp_h];
lseek(bmp_fd,54,SEEK_SET);
read(bmp_fd,TQ,bmp_w*bmp_h*3 + skip*bmp_h);
int x,y,n;
for(y=0,n=0; y<bmp_h; y++)
{
for(x=0; x<bmp_w; x++,n+=3)
{
*(new_p + 800*(bmp_h-1-y)+x) = TQ[n] << 0 | TQ[n+1] << 8 | TQ[n+2] << 16; //将铲子处理后的图片映射到屏幕上面
}
}
close(bmp_fd);
return 0;
}
/*这个函数是用来判断放加分的图片,加一分就切一张图片*/
int Grade_ball(int grade)
{
//判断成绩,映射图片。
int sum=0; //一开始是个位数,在第一个位置各位
while(1)
{
if(grade / 10 != 0) //判断成绩是不是个位数(是不是大于10了) ,大于10, 比如 19/10 = 9 它不等于 0
{
Show_ui(picture[grade%10],762-(sum*30),5); // 19%10 = 9 就放第九张图片,在屏幕上的第一张图片显示的是9。
grade /= 10;
sum++; //当判断分数超过10,十位要放图片,就往左边移动30像素,分数超100就加三个位置sum*30;
}
else //是个位数的时候,比如9%10 = 9,就显示第九张图片1,在屏幕上的第一张图片显示的是9。
{
Show_ui(picture[grade%10],762-(sum*50),5);
break;
}
}
return 0;
}
int Draw_free() //释放所有用到的文件和映射。
{
close(RG.lcd_fd);
close(RG.touch_fd);
munmap(RG.mmap_fd,480*800*4); //从头到尾映射用一个就可以放全部图片了。
return 0;
}
小项目,木板接小球游戏,(文件IO)实现计分、开始、暂停、退出等。多线程操作,注释里有详细解析和思路。
最新推荐文章于 2024-09-05 23:29:08 发布