)
Linux Framebuffer 简易五子棋项目项目简介基于Linux帧缓冲/dev/fb0C语言实现五子棋。不依赖GUI库直接操作显存进行绘图32位RGBA8888像素格式键盘w/a/s/d移动光标空格落子支持24位BMP图片弹窗显示胜利画面。 文件组成framebuffer.h头文件、framebuffer.c功能实现、main.c程序入口一、framebuffer.h 头文件摘要定义像素色深宏RGB_FMT_888(32位)、RGB_FMT_565(16位)声明全局屏幕分辨率fbx_g/fby_g对外声明全部API帧缓冲初始化、画点、画线、画棋盘、画圆棋子、BMP图片解析绘制、界面刷新show_ui、游戏主循环game_start、胜负判定w_b。对外接口简要说明函数功能返回值init_fb()打开/dev/fb0ioctl获取屏幕参数mmap映射显存0成功‑1失败draw_point()绘制单个像素点输入0x00RRGGBB颜色voiddraw_h_line()/draw_l_line()绘制棋盘横向、纵向网格线voiddraw_qipan()绘制棋盘底色矩形voiddraw_beijing()全屏填充背景色voiddraw_yuan()利用圆方程绘制圆形棋子voiddraw_bmp()解析绘制24位无压缩BMP胜利弹窗处理BMP行padding、正负高度voidshow_ui()整体界面刷新背景、棋盘、光标、全部棋子voidgame_start()游戏主循环处理键盘w/s/a/d移动光标空格落子voidw_b()全盘扫描4方向判断五子连珠1胜利0无胜利二、framebuffer.c源文件模块1帧缓冲初始化 init_fb逻辑流程open()打开字符设备/dev/fb0ioctl(FBIOGET_VSCREENINFO)获取屏幕可变信息分辨率、色深赋值全局fbx_g/fby_g计算显存字节大小mmap将物理显存映射到用户空间pmem程序直接操作该虚拟地址即可修改屏幕像素要点mmapMAP_SHARED用户态修改内存显卡硬件自动刷新屏幕模块2BMP图片绘制 draw_bmp使用#pragma pack(1)取消结构体对齐正确读取14字节文件头、40字节信息头校验BMP标识0x4D42仅支持24位无压缩BMP处理biHeight正负正数图片原点在左下角需要行逆序绘制负数原点在左上角BMP每行存在4字节对齐padding读取时完整读入padding绘图丢弃填充字节BMP像素存储顺序是B‑G‑R转换为本项目0x00RRGGBB格式单行缓冲区不一次性加载整张图片节省内存绘制前做屏幕越界判断。模块3基础绘图函数draw_point核心底层绘图函数根据色深888/565向映射显存地址写入颜色做xy越界判断。draw_h_line / draw_l_line双重循环调用draw_point绘制棋盘横竖网格。draw_beijing双重循环全屏填充背景色。draw_qipan双重循环绘制棋盘底色矩形。draw_yuan圆方程 $(i‑x)^2(j‑y)^2 \le R^2$遍历圆包围盒满足条件就画点绘制圆形棋子。draw_mouse绘制小方块作为落子光标颜色随黑白棋切换。模块4游戏业务全局变量int X0,Y0; //光标屏幕像素坐标 int a[21][21]; //棋盘逻辑数组0空1白棋‑1黑棋 int flag; //落子计数奇数白棋、偶数黑棋定义颜色宏0x00RRGGBB棋盘格子间距GAP棋子半径R。模块5胜负判定 w_b()方向向量4组右{0,1}、下{1,0}、右下斜{1,1}、左下斜{1,-1}遍历21×21棋盘遇到非空棋子向4个方向最多延伸4格统计同色连续棋子一旦cnt5调用draw_bmp弹出胜利图片return 1否则返回0。模块6界面刷新 show_ui()绘制顺序全屏背景 →棋盘底色 →网格线 →光标方块 →遍历棋盘数组绘制所有黑白棋子。每次按键落子之后调用完全重绘整个画面。模块7游戏主交互 game_start()getchar()读取键盘字符w/s/a/d移动光标做边界限制不能移出棋盘空格落子屏幕像素坐标换算为棋盘数组下标向a[i][j]写入1或‑1flag切换棋手每次输入后调用show_ui()刷新界面调用w_b()做胜负检测。三、main.c主函数流程调用init_fb(/dev/fb0)初始化帧缓冲初始化失败直接退出程序调用show_ui()渲染初始游戏画面调用game_start()进入死循环键盘交互游戏。运行前提程序需要访问帧缓冲设备需要root权限或者用户加入video用户组四、整体架构总结底层层framebuffer设备操作、mmap显存映射、基础绘图原语draw_point所有绘图全部建立在画点之上画线、画圆、填充矩形都是多次调用draw_point。工具层BMP图片解析绘制函数。游戏逻辑层棋盘数组、胜负判断w_b()。UI层show_ui()统一刷新界面。交互层game_start()键盘处理。入口main初始化启动游戏。运行逻辑时序main → init_fb 映射显存 → show_ui绘制初始界面 → game_start死循环 键盘输入(w/a/s/d/空格) → 更新光标/更新棋盘数组 → show_ui重绘全部画面 → w_b判断五子连珠五、代码展示头文件#ifndef __FRAMEBUFFER_H__ #define __FRAMEBUFFER_H__ /* 像素格式宏 */ #define RGB_FMT_888 32 // RGBA8888 32位色深 #define RGB_FMT_565 16 // RGB565 16位色深 /* 屏幕全局分辨率变量 */ extern int fbx_g; // 屏幕横向总像素 extern int fby_g; // 屏幕纵向总像素 /** * init_fb - 初始化帧缓冲设备/dev/fb0映射显存 * devname: 帧缓冲设备文件名 * return: 0成功-1失败 */ extern int init_fb(char *devname); /** * draw_point - 在屏幕(x,y)绘制单个像素点 * x: 横坐标 * y: 纵坐标 * col: 32位RGB888颜色值 0x00RRGGBB */ extern void draw_point(int x, int y, unsigned int col); /** * draw_h_line - 绘制棋盘横向网格线 */ extern void draw_h_line(int x,int y, unsigned int col); /** * draw_l_line - 绘制棋盘纵向网格线 */ extern void draw_l_line(int x,int y,unsigned int col); /** * draw_qipan - 填充棋盘底色矩形 */ extern void draw_qipan(int x,int y,unsigned int col); /** * draw_beijing - 全屏填充背景色 */ extern void draw_beijing(unsigned int col); /** * draw_yuan - 绘制圆形棋子 */ extern void draw_yuan(int x,int y,unsigned int col); /** * draw_bmp - 解析并绘制24位无压缩BMP图片胜利弹窗 * x: 图片左上角X * y: 图片左上角Y * bmpname: BMP文件路径 */ extern void draw_bmp(int x, int y, char *bmpname); /** * show_ui - 刷新整套游戏界面背景、棋盘、光标、所有棋子 */ extern void show_ui(); /** * game_start - 游戏主交互循环处理键盘落子、光标移动 */ extern void game_start(); /** * w_b - 胜负判定函数全盘扫描判断是否存在五子连珠 * return: 1胜利0无胜利 */ extern int w_b(); #endif源文件#include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include stdlib.h #include sys/ioctl.h #include linux/fb.h #include sys/mman.h #include framebuffer.h /* 帧缓冲全局资源 */ void *pmem NULL; // 显存映射内存首地址 int fb; // /dev/fb0 文件描述符 struct fb_var_screeninfo vinfo; // 屏幕可变参数分辨率、色深 int fbx_g 0; // 屏幕横向分辨率 int fby_g 0; // 屏幕纵向分辨率 /** * init_fb - 初始化帧缓冲mmap映射显存 * devname: 帧缓冲设备路径 * return: 0成功-1打开/映射失败 */ int init_fb(char *devname) { // 1. 打开帧缓冲字符设备 fb open(devname, O_RDWR); if (-1 fb) { perror(open fb error); return -1; } // 2. 获取屏幕分辨率、色深等参数 int ret ioctl(fb, FBIOGET_VSCREENINFO, vinfo); if (ret 0) { perror(ioctl get vinfo error); close(fb); return -1; } printf(屏幕分辨率xres%d, yres%d\n, vinfo.xres, vinfo.yres); printf(单像素位数bits_per_pixel%d\n, vinfo.bits_per_pixel); fbx_g vinfo.xres; fby_g vinfo.yres; // 3. 计算显存总字节建立用户态内存映射 size_t vmem_size vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8; pmem mmap(NULL, vmem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fb, 0); if (pmem MAP_FAILED) { perror(mmap fb error); close(fb); return -1; } return 0; } /** * draw_bmp - 绘制24位无压缩BMP图片胜利弹窗专用 * 修复问题行padding对齐、像素偏移lseek、兼容正负高度BMP * x: 图片左上角横坐标 * y: 图片左上角纵坐标 * bmpname: BMP图片文件路径 */ void draw_bmp(int x, int y, char *bmpname) { #pragma pack(1) // BMP文件头 固定14字节 typedef struct tagBITMAPFILEHEADER { short bfType; // 文件标识 固定0x4D42(BM) int bfSize; // 整个bmp文件字节大小 short bfReserved1; // 保留位 short bfReserved2; // 保留位 int bfOffBits; // 像素数据起始偏移字节 } Bmp_file_head_t; // BMP信息头 固定40字节 typedef struct tagBITMAPINFOHEADER { int biSize; int biWidth; // 图片宽度像素 int biHeight; // 图片高度像素正数原点左下负数左上 short biPlanes; short biBitCount; // 单像素位数仅支持24 int biCompression; // 压缩类型仅支持无压缩0 int biSizeImage; int biXPelsPerMeter; int biYPelsPerMeter; int biClrUsed; int biClrImportant; } Bmp_info_t; #pragma pack() Bmp_file_head_t file_h; Bmp_info_t info_h; // 只读打开图片 int fd open(bmpname, O_RDONLY); if (fd 0) { perror(open bmp file fail); return; } // 读取并校验文件头 read(fd, file_h, sizeof(file_h)); if (file_h.bfType ! 0x4D42) { printf(文件非标准BMP\n); close(fd); return; } // 读取图像信息头 read(fd, info_h, sizeof(info_h)); // 仅支持24位无压缩位图 if (info_h.biBitCount ! 24 || info_h.biCompression ! 0) { printf(仅支持24位无压缩BMP图片\n); close(fd); return; } int w info_h.biWidth; int h_raw info_h.biHeight; int img_h; int reverse_line; // 标记是否逆序绘制行原点在左下 // 判断图片存储方向 if (h_raw 0) { img_h h_raw; reverse_line 1; } else { img_h -h_raw; reverse_line 0; } // 计算每行像素字节 4字节对齐填充padding int row_real_byte w * 3; int row_pad_byte (row_real_byte 3) (~3); int pad row_pad_byte - row_real_byte; // 偏移到像素数据真正起始位置修复图片左右错位核心 lseek(fd, file_h.bfOffBits, SEEK_SET); // 单行缓存不一次性分配整张图片内存节省堆空间 unsigned char *row_buf malloc(row_pad_byte); if (row_buf NULL) { printf(BMP行缓存分配失败\n); close(fd); return; } // 逐行读取渲染 for (int line 0; line img_h; line) { // 计算当前行对应屏幕Y坐标 int dst_y; if (reverse_line) dst_y y (img_h - 1 - line); else dst_y y line; // 读取一整行包含末尾填充padding字节 read(fd, row_buf, row_pad_byte); // 遍历本行所有像素绘制 for (int px 0; px w; px) { int dst_x x px; // 屏幕越界直接跳过不绘制 if (dst_x 0 || dst_x vinfo.xres || dst_y 0 || dst_y vinfo.yres) continue; // BMP像素存储顺序 B G R转换为程序RGB888格式 R16 | G8 | B unsigned char b row_buf[px * 3 0]; unsigned char g row_buf[px * 3 1]; unsigned char r row_buf[px * 3 2]; unsigned int color (r 16) | (g 8) | b; draw_point(dst_x, dst_y, color); } } // 释放资源 free(row_buf); close(fd); } /* 游戏全局常量定义 */ #define MAX_X 800 // 屏幕最大横向像素 #define MAX_Y 600 // 屏幕最大纵向像素 #define X_0 100 // 棋盘左上角X起点 #define Y_0 100 // 棋盘左上角Y起点 #define GAP 20 // 棋盘格子间距20像素 #define R 10 // 棋子绘制半径 /* 游戏全局变量 */ int X0 100; // 光标当前屏幕X坐标 int Y0 100; // 光标当前屏幕Y坐标 int a[21][21] {0}; // 21*21棋盘数组0空1白子-1黑子 int flag 1; // 落子标记奇数白棋偶数黑棋 /* 颜色宏 0x00RRGGBB 适配32位RGBA8888帧缓冲 */ #define RED 0x00FF0000 #define GREEN 0x0000FF00 #define BLUE 0x000000FF #define BLACK 0x00000000 #define WHITE 0x00FFFFFF #define YELLOW 0x00FFFF00 #define QIPAN 0x00cc9966 // 棋盘底色 #define WANGGE 0x00FF0000 // 网格红线 #define HEIQI 0x00000000 // 黑棋颜色 #define BAIQI 0x00FFFFFF // 白棋颜色 #define BEIJING 0x00ccffcc // 全局背景浅绿色 /** * draw_point - 向显存写入单个像素点 * x: 像素横坐标 * y: 像素纵坐标 * col: RGB888 32位颜色 */ void draw_point(int x, int y, unsigned int col) { // 越界直接放弃绘制 if (x vinfo.xres || y vinfo.yres || x 0 || y 0) return ; if (vinfo.bits_per_pixel RGB_FMT_888) { unsigned int *p pmem; *(p vinfo.xres_virtual * y x) col; } else if (vinfo.bits_per_pixel RGB_FMT_565) { unsigned short *p pmem; *(p vinfo.xres_virtual * y x) col; } } /** * draw_h_line - 绘制棋盘横向网格 */ void draw_h_line(int x,int y,unsigned int col) { for (int i X_0; i x X_0; i) { // 纵向按格子间隔绘制点形成横线 for(int j Y_0; j y Y_0; j GAP) { draw_point(i, j, col); } } } /** * draw_l_line - 绘制棋盘纵向网格 */ void draw_l_line(int x,int y,unsigned int col) { for (int i X_0; i x X_0; i GAP) { for(int j Y_0; j y Y_0; j) { draw_point(i, j, col); } } } /** * draw_beijing - 全屏填充背景色 */ void draw_beijing(unsigned int col) { for(int i 0; i MAX_X; i) { for(int j 0; j MAX_Y; j) { draw_point(i, j, col); } } } /** * draw_qipan - 填充棋盘底色矩形 */ void draw_qipan(int x,int y,unsigned int col) { for(int i X_0; i x X_0; i) { for(int j Y_0; j y Y_0; j) { draw_point(i, j, col); } } } /** * draw_yuan - 圆形棋子绘制圆方程判定像素 * x,y: 圆心坐标 * col: 棋子颜色 */ void draw_yuan(int x,int y,unsigned int col) { for(int i x - R; i x R; i) { for(int j y - R; j y R; j) { // 圆标准方程 (x-a)^2 (y-b)^2 r² if((i - x)*(i - x) (j - y)*(j - y) R * R) { draw_point(i, j, col); } } } } /** * draw_mouse - 绘制落子光标方块颜色随当前落子方切换 */ void draw_mouse(unsigned int col) { for(int i X0 - 5; i X0 5; i) { for(int j Y0 - 5; j Y0 5; j) { draw_point(i, j, col); } } } /** * w_b - 本人独立编写核心函数五子连珠胜负判断 * 遍历21*21棋盘四个方向检测连续5颗同色棋子 * 方向右、下、右下斜、左下斜 * return: 1 存在五子连线胜利0 无胜利 */ int w_b() { // 四组方向向量 {dx, dy} int dir[4][2] {{0,1},{1,0},{1,1},{1,-1}}; // 遍历棋盘所有点位 for(int i 0; i 21; i) { for(int j 0; j 21; j) { int key a[i][j]; if(key 0) // 无棋子跳过 continue; // 分别检测四个直线方向 for(int d 0; d 4; d) { int dx dir[d][0]; int dy dir[d][1]; int cnt 1; // 当前棋子自身计数为1 // 向该方向延伸最多4格凑5子 for(int k 1; k 4; k) { int ni i dx * k; int nj j dy * k; // 超出棋盘边界直接终止该方向检测 if(ni 0 || ni 21 || nj 0 || nj 21) break; // 同色棋子计数1异色中断 if(a[ni][nj] key) cnt; else break; // 累计5颗判定胜利弹出对应BMP弹窗 if(cnt 5) { draw_bmp(200, 200, key 1 ? W_V.bmp : B_V.bmp); return 1; } } } } } // 全盘无五子连线 return 0; } /** * show_ui - 界面刷新总函数 * 依次绘制背景 - 棋盘底色 - 网格线 - 光标 - 所有已落棋子 */ void show_ui() { draw_beijing(BEIJING); draw_qipan(400, 400, QIPAN); draw_h_line(400, 400, WANGGE); draw_l_line(400, 400, WANGGE); // 光标颜色随落子方切换 unsigned int cur_color (flag % 2 1) ? BAIQI : HEIQI; draw_mouse(cur_color); // 遍历棋盘数组绘制所有黑白棋子 for(int i 0; i 21; i) { for(int j 0; j 21; j) { int pix_x (i 5) * GAP; int pix_y (j 5) * GAP; if(a[i][j] 1) draw_yuan(pix_x, pix_y, BAIQI); else if(a[i][j] -1) draw_yuan(pix_x, pix_y, HEIQI); } } } /** * game_start - 游戏主循环键盘交互逻辑 * w/s/a/d移动光标空格落子切换黑白棋 */ void game_start() { char choose 0; while(1) { choose getchar(); switch(choose) { case w: // 光标上移 if(Y0 - GAP Y_0) Y0 - GAP; break; case s: // 光标下移 if(Y0 GAP Y_0 400) Y0 GAP; break; case a: // 光标左移 if(X0 - GAP X_0) X0 - GAP; break; case d: // 光标右移 if(X0 GAP X_0 400) X0 GAP; break; case : // 空格键落子切换黑白方 { // 屏幕光标坐标转棋盘数组下标 int arr_i X0 / GAP - 5; int arr_j Y0 / GAP - 5; if(flag % 2 ! 0) a[arr_i][arr_j] 1; else a[arr_i][arr_j] -1; flag; break; } default: break; } // 更新界面后执行胜负判断 show_ui(); w_b(); } }主函数#include stdio.h #include framebuffer.h /** * 程序入口 * 流程初始化帧缓冲 - 绘制初始界面 - 进入游戏交互循环 */ int main(void) { // 打开并映射屏幕帧缓冲 if(init_fb(/dev/fb0) 0) { printf(帧缓冲初始化失败程序退出\n); return -1; } // 绘制初始游戏界面 show_ui(); // 启动键盘交互游戏循环 game_start(); return 0; }