涉及内容:Unbuffered I/O函数
open
close
read / write
ioctl
mmap
程序实现过程:
1. 在屏幕上打印出一点
2. 在屏幕上打印出基本图形
3. 实现用按键w、s、a、d(上下左右)控制蛇的移动方向
4. 在可吃的区域内,按规律随机产生食物。(时间到或是被吃了,立即产生食物)
5. 吃食物
6. 吃完食物,增加蛇身长度
7. game over情况:撞倒墙壁或是撞倒自己
准备工作:
1. 为提高效率,需将要操作的文件的内容映射到内存,使用mmap:
mmap(地址, 长度, 可操作性, 共享性 , 文件描述符, 偏移量)
未知量:长度、文件
要操作的屏幕文件: /dev/fb0 (通常都需要修改权限,才能操作)
屏幕文件的长度(字符点阵): 要知道屏幕的大小和分辨率
屏幕的宽:水平方向多少个像素
屏幕的高:竖直方向多少个像素
分辨率:一个像素有多少位
屏幕的宽、高和分辨率:在/usr/include/linux/fb.h 中获得
注意:内存中数据的存储形式是一位数组形式
2. 打印点,相当于向屏幕文件进行写操作
注意:必须在文本模式下进行运行操作
- #Makefile
- src = $(wildcard *.c)
- CC = gcc
- CFLAGS = -Wall -g
- main:$(src)
- $(CC) $(CFLAGS) ./frame.h $^ -o $@
- clean:
- rm main *.o
- .PHONY:clean
- //frame.h
- #ifndef _FRAME_H_
- #define _FRAME_H_
- #define RED 0x00ff0000
- #define GREEN 0x0000ff00
- #define BLUE 0x000000ff
- #define WHITE 0x00ffffff
- #define BLACK 0x00000000
- typedef unsigned char u8_t;
- typedef unsigned int u32_t;
- typedef struct
- {
- int wide;
- int high;
- int bpp;
- void *mem;
- }fbsrc_t;
- int init_data(void); //初始化:完成映射
- int fb_one_pixel(int x, int y, u32_t color); //画点
- int square(int x, int y,int len, u32_t color); //画正方形方块
- int rectangle(int x1, int y1, int x2, int y2, u32_t color); //画矩形框
- int circle(int x, int y, int radius, u32_t color); //画圆形:已填充的
- int snake_move(void); //完成蛇的移动
- #endif //_FRAME_H_
- //init_data.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <linux/fb.h>
- #include <sys/mman.h>
- #include <string.h>
- #include "frame.h"
- fbsrc_t fb_v;
- int init_data(void)
- {
- int fd, len;
- struct fb_var_screeninfo fb_var;
- //读写屏幕文件:/dev/fb0
- //运行程序时,可能会出现:permission denied
- //此时序更改/dev/fb0文件的权限 sudo chmod 666 /dev/fb0
- fd = open("/dev/fb0", O_RDWR);
- if(fd < 0)
- {
- perror("/dev/fb0");
- exit(1);
- }
- //ioctl函数:用于向设备发控制和配置命令
- //ioctl函数的相关定义:在/usr/include/fb.h文件中
- //查询可知,ioctl函数第二和第三个参数的相关内容
- if(ioctl(fd, FBIOGET_VSCREENINFO, &fb_var) < 0)
- {
- perror("ioctl");
- exit(1);
- }
- fb_v.wide = fb_var.xres; //屏幕的宽:水平方向有多少个像素
- fb_v.high = fb_var.yres; //屏幕的高: 竖直方向有多少个像素
- fb_v.bpp = fb_var.bits_per_pixel; //屏幕的分辨率:一个像素有多少个位组成
- len = (fb_v.wide * fb_v.high * fb_v.bpp) / 8; //屏幕的大小共占的字节数
- //使用mmap函数把磁盘文件的一部分直接映射到内存
- fb_v.mem = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if(fb_v.mem == MAP_FAILED)
- {
- perror("map");
- exit(1);
- }
- memset(fb_v.mem, 0, len);
- //清屏
- close(fd);
- //关闭文件
-
- return 0;
- }
- //basic_shapes.c
- #include <stdio.h>
- #include "frame.h"
- fbsrc_t fb_v;
- //画点:在(x, y)点上画点
- int fb_one_pixel(int x, int y, u32_t color)
- {
- *((u32_t *)fb_v.mem + x + y * fb_v.wide) = color;
- return 0;
- }
- //画正方形:以(x, y)为左上角点,len为边长画方块
- int square(int x, int y, int len , u32_t color)
- {
- int i, j;
- for(i = 0; i < len; i ++)
- {
- for(j = 0; j < len; j ++)
- {
- fb_one_pixel(x + i, y + j, color);
- }
- }
- return 0;
- }
- //画圆:以(x, y)为圆心,radius为半径画圆
- int circle(int x, int y, int radius, u32_t color)
- {
- int i, j;
- printf("x");
- for(i = x -radius; i <= x + radius; i ++)
- {
- for(j = y - radius; j <= y + radius; j ++)
- {
- if((((i - x) * (i - x) + (j - y) * (j - y)) <= (radius * radius)) && i >= 0 && i < 1024 && j >= 0 && j < 768)
- {
- fb_one_pixel(i, j, color);
- }
- }
- }
- return 0;
- }
- //画矩形框:以左上角点(x1, y1)和右上角(x2, y2)为基准
- int rectangle(int x1, int y1, int x2, int y2, u32_t color)
- {
- int i, j;
-
- for(i = x1; i < x2; i ++)
- {
- for(j = y1; j < y2; j ++)
- {
- if(i == x1 || i == x2 - 1 || j == y1 || j == y2 - 1)
- {
- fb_one_pixel(i, j, color);
- }
- }
- }
- return 0;
- }
- //snake.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include "frame.h"
- fbsrc_t fb_v;
- int rand_food[2];
- int move_one_step(int *sx, int *sy, int x_offset, int y_offset, int s_len)
- {
- int i;
-
- // 判断贪吃蛇是否碰到左右墙:碰到墙返回-1
- if(((sx[0] + x_offset) < 100) || ((sx[0] + x_offset + 50) > 900))
- {
- return -1;
- }
- // 判断贪吃蛇是否碰到上下墙:碰到墙返回-1
- if(((sy[0] + y_offset) < 50) || ((sy[0] + y_offset + 50) > 700))
- {
- return -1;
- }
- //覆盖掉蛇身的最后一节
- square(sx[s_len - 1], sy[s_len- 1], 50, BLACK);
- //将因消掉最后一节而覆盖掉的倒数第二个节点的边界框,重新加上
- rectangle(sx[s_len -2], sy[s_len - 2], sx[s_len - 2] + 50, sy[s_len - 2] + 50, RED);
- //消掉蛇尾,添加蛇头,更新存储蛇的数组
- for(i = s_len - 1; i > 0; i --)
- {
- sx[i] = sx[i - 1];
- sy[i] = sy[i - 1];
- }
- sx[0] += x_offset;
- sy[0] += y_offset;
- //在原来蛇头的基础上,添加蛇头
- square(sx[0], sy[0], 50, GREEN);
- rectangle(sx[0], sy[0], sx[0] + 50, sy[0] + 50, RED);
- //此时原来的蛇头变为蛇身的一部分,将其颜色改为蛇身的颜色
- square(sx[1], sy[1], 50, BLUE);
- rectangle(sx[1], sy[1], sx[1] + 50, sy[1] + 50, RED);
- usleep(1000 * 300);
- return 0;
- }
- int keyhit(void)
- {
- int i = 0;
- //将从标准输入读到的数存储到i中。若读到数:返回非0的数;若读不到:返回0
- ioctl(STDIN_FILENO, FIONREAD, &i);
- return i;
- }
- char get_key(void)
- {
- //标准输入中有数:用getchar获取
- if(keyhit() > 0)
- return getchar();
- return 0;
- }
- //打印贪吃蛇
- int print_snake(int *sx, int *sy, int s_len)
- {
- int i;
- for(i = 0; i < s_len; i ++)
- {
- if( i == 0)
- {
- square(sx[i], sy[i], 50, GREEN);
- //打印贪吃蛇的头
- }
- else
- {
- square(sx[i], sy[i], 50, BLUE);
- //打印贪吃蛇的蛇身
- }
- rectangle(sx[i], sy[i], sx[i] + 50, sy[i] + 50, RED);
- //给贪吃蛇的每节加边框
- }
- return 0;
- }
- //擦除贪吃蛇
- int erase_snake(int *sx, int *sy, int s_len)
- {
- int i;
-
- for(i = 0; i < s_len; i ++)
- {
- square(sx[i], sy[i], 50, BLACK);
- //擦除贪吃蛇的每个节点
- }
- return 0;
- }
- //重启游戏
- int restart_game(int *sx, int *sy, int s_len)
- {
- int i = 5;
- //实现贪吃蛇的闪烁
- while(i -- >= 0)
- {
- print_snake(sx, sy, s_len);
- usleep(1000 * 200);
- erase_snake(sx, sy, s_len);
- usleep(1000 * 200);
- }
- memset(fb_v.mem, 0, (fb_v.wide * fb_v.high * fb_v.bpp) / 8);
- //清屏,将mmap映射得到的内存区域清0,即在屏幕上所有的像素点打印黑色
- rectangle(100 - 1, 50 - 1, 900 + 1, 700 + 1, GREEN);
- //给贪吃蛇划定活动区域
- //初始化贪吃蛇的蛇身各节点的坐标
- for(i = 0; i < s_len; i ++)
- {
- sx[i] = 250 - i * 50;
- sy[i] = 300;
- }
- return 0;
- }
- //判断坐标为(x, y)的点,在不在蛇身上
- //若在,返回1;否则,返回0
- int is_covery(int *sx, int *sy, int s_len, int x, int y)
- {
- int i;
- for(i = 0; i < s_len; i ++)
- {
- if((sx[i] == x) && (sy[i] == y))
- {
- return 1;
- }
- }
- return 0;
- }
- //随机产生一个食物
- int show_food(int *sx, int *sy, int s_len)
- {
- int x, y;
- //若为游戏刚启动,第一个产生的食物,不覆盖
- //否则擦除上一个产生的食物,在生成下一个食物
- if(rand_food[0] != 0)
- {
- square(rand_food[0], rand_food[1], 50, BLACK);
- }
- //随机生成一个符合条件的食物坐标
- while(1)
- {
- x = (rand() % 14 + 3) * 50;
- y = (rand() % 11 + 2) * 50;
-
- if(is_covery(sx, sy, s_len, x, y) == 0)
- {
- break;
- }
- }
- //以该生成的坐标,画食物
- rand_food[0] = x;
- rand_food[1] = y;
- square(x, y, 50, GREEN);
- return 0;
- }
- //贪吃蛇吃食物
- int eat_node(int *sx, int *sy, int *s_len)
- {
- //若贪吃蛇的头结点的坐标,恰好等于食物坐标,吃食物
- //扩充贪吃蛇的长度,s_len加1(s_len为传入和传出参数),将新扩充的节点附加在贪吃蛇的尾巴处
- if(sx[0] == rand_food[0] && sy[0] == rand_food[1])
- {
- sx[*s_len] = sx[*s_len - 1];
- sy[*s_len] = sy[*s_len - 1];
- //画新扩充的节点
- square(sx[*s_len], sy[*s_len], 50, BLUE);
- rectangle(sx[*s_len], sy[*s_len], sx[*s_len] + 50, sy[*s_len] + 50, RED);
- //贪吃蛇蛇身的长度加1
- *s_len = *s_len + 1;
- return 1;
- }
- return 0;
- }
- int snake_move(void)
- {
- //初始化蛇身各节点的坐标,各节点为宽度为50的正方形
- int sx[20] = {300, 250, 200, 150};
- int sy[20] = {300, 300, 300, 300};
- int s_len = 4;
-
- int step = 50, flag = 0, rand_flag = 0;
- //step为贪吃蛇每次移动的长度,即为蛇身各正方形节点的边长
- //flag为退出标志,按下q时:将flag = 1,退出循环
- //rand_flag为开始游戏时,第一次产生食物的标志。
- //若是刚开始游戏,则立即产生一个食物;否则,间隔产生
- int x_offset = step, y_offset = 0;
- //贪吃蛇每次移动的偏移量,初始化为向左移动
- int rand_space = 40;
- //随机产生食物的间隔,以贪吃蛇移动的次数为间隔
-
- char dict = 'd';
- //贪吃蛇的移动方向
- char new_dict = 0;
- //贪吃蛇的新的移动方向
- long time = 0;
- //贪吃蛇到吃到食物的移动次数
-
- rectangle(100 - 1, 50 - 1, 900 + 1, 700 + 1, GREEN);
- //create a rectangle: by two dot (100, 50) (900, 700)
- system("stty raw -echo");
- //调用此函数:相当于在命令行中输入该命令
- //注意:此时程序的退出不能再按Ctrl + c
- //否则会有麻烦,将不能恢复到标准输入输出的正常状态(必须重启),
- //所以该程序的退出一定要设置退出标志
- //this function is equal to entering the command in the command line.
- //program :q for quit: can not press Ctrl + c
- // the computer will have trouble (restart), if you press Ctrl + c
-
- while(flag == 0)
- {
- time ++;
- if(s_len >= 20)
- {
- break;
- }
- //若为刚启动游戏,则立即产生一个食物,并修改刚启动游戏的标志
- //或若吃到食物,则立即产生一个食物
- if(eat_node(sx, sy, &s_len) == 1 || rand_flag == 0)
- {
- show_food(sx, sy, s_len);
- rand_flag = 1;
- }
- //控制产生食物的间隔
- if(time == rand_space)
- {
- show_food(sx, sy, s_len);
- time = 0;
- }
- //判断是否撞倒墙
- if(move_one_step(sx, sy, x_offset, y_offset, s_len) == -1 || is_covery(sx + 1, sy + 1, s_len - 1, sx[0], sy[0]) == 1)
- {
- /*
- restart_game(sx, sy, s_len);
- dict = 'd';
- new_dict = 0;
- x_offset = step;
- y_offset = 0;
- s_len = 4;
- */
- break;
- }
- //获得新的方向控制
- new_dict = get_key();
- //控制贪吃蛇不能在其方向的反方向上移动
- switch(new_dict)
- {
- case 'a':{ if(dict != 'd') dict = new_dict; break; }
- case 'd':{ if(dict != 'a') dict = new_dict; break; }
- case 'w':{ if(dict != 's') dict = new_dict; break; }
- case 's':{ if(dict != 'w') dict = new_dict; break; }
- case 'q': dict = new_dict;
- default: break;
- }
- //贪吃蛇的实际移动方向
- switch(dict)
- {
- case 'a':{ x_offset = -step; y_offset = 0; break; }
- case 'd':{ x_offset = step; y_offset = 0; break; }
- case 'w':{ x_offset = 0; y_offset = -step; break; }
- case 's':{ x_offset = 0; y_offset = step; break; }
- case 'q': flag = 1; break;
- default: break;
- }
- }
- system("stty cooked echo");
- //恢复正常设置
- return 0;
- }
- //main.c
- #include <stdio.h>
- #include <unistd.h>
- #include <unistd.h>
- #include "frame.h"
- int main(int argc, const char *argv[])
- {
- init_data();
- snake_move();
- return 0;
- }
阅读(1057) | 评论(0) | 转发(0) |