小项目,木板接小球游戏,(文件IO)实现计分、开始、暂停、退出等。多线程操作,注释里有详细解析和思路。

3 篇文章 0 订阅
3 篇文章 0 订阅
#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;
}


评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值