一、前言
嵌入式 Linux 开发常接触 /dev/fb0 帧缓冲设备,很多初学者只懂基础画点画线,不知道如何搭建完整交互小游戏。本文不使用任何图形框架(SDL/GTK/Qt),仅通过原生 C、Linux 帧缓冲接口实现一套完整五子棋程序,包含棋盘绘制、WASD 光标移动、黑白交替落子、落子后四向胜负判定、BMP 胜利图片展示、一键重置棋盘功能。 项目覆盖:帧缓冲初始化、基础绘图封装、棋盘坐标换算、交互逻辑、五子棋核心算法,同时记录开发中踩坑的坐标颠倒、数组越界、胜负判定低效、棋手轮换等问题,适合嵌入式 Linux 图形入门学习。
二、开发环境与环境说明
1.开发环境
-
系统:Ubuntu / 嵌入式 Linux(ARM 开发板均可)
-
语言:标准 C 语言
-
显示设备:/dev/fb0 Framebuffer 帧缓冲
-
依赖:无第三方图形库,仅 Linux 系统调用、mmap 内存映射、基础文件 IO
-
资源:两张 BMP 图片(黑棋胜利 p111.bmp、白棋胜利 p222.bmp)
2.工程文件结构
├── main.c # 程序入口,初始化帧缓冲、启动游戏
├── framebuffer.c # fb底层绘图封装:画点/线/矩形/圆/清屏/绘制BMP
├── framebuffer.h # 绘图函数头文件
├── game.c # 五子棋核心逻辑:棋盘渲染、按键交互、落子、判赢、重置
├── game.h # 游戏模块头文件
├── p111.bmp # 黑棋胜利界面图片
├── p222.bmp # 白棋胜利界面图片
3.功能清单
-
Framebuffer 设备初始化,显存 mmap 映射,自动读取屏幕分辨率
-
封装基础绘图 API:点、水平线、竖线、实心矩形、圆形、全屏清屏
-
BMP 图片解析渲染,胜利弹窗展示
-
15×15 棋盘自动居中绘制,木纹棋盘底色 + 深色网格线
-
WASD 控制红色光标在棋盘交点移动,边界限制防止出界
-
空格键落子,基于计数器奇偶实现黑白棋手自动轮换
-
落子后以当前棋子为中心,四向遍历判断五子连线,胜利展示对应 BMP
-
E 键一键清空棋盘,重置对局
4.流程框图

三、核心模块
1.Framebuffer底层图形封装原理
(1)帧缓冲核心原理:/dev/fb0 是屏幕显存抽象,通过mmap将内核显存映射到用户空间,直接修改内存像素实现绘图。
(2)关键函数:
-
init_fb():打开 fb 设备、ioctl 获取分辨率、内存映射显存
-
draw_point():基础像素绘制,区分 32 位 RGB888/16 位 RGB565
-
直线、矩形、圆形、清屏函数基于draw_point二次封装
-
draw_bmp():解析 24 位 BMP 文件头,逐行读取像素绘制到屏幕
#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 <math.h>
#include "framebuffer.h"
void *pmem = NULL;
int fb;
struct fb_var_screeninfo vinfo;
int fbx_g = 0;
int fby_g = 0;
int init_fb(char *devname)
{
//1. 打开显示设备(/dev/fb0)
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 error");
return -1;
}
printf("xres = %d, yres = %d\\n", vinfo.xres, vinfo.yres);
// printf("xres_virtual = %d, yres_virtual = %d\\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bits_per_pixel = %d\\n", vinfo.bits_per_pixel);
fbx_g = vinfo.xres;
fby_g = vinfo.yres;
//3. 建立显存空间和用户空间的内存映射
size_t len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel/8;
pmem = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fb, 0);
if (pmem == MAP_FAILED)
{
perror("mmap error");
return -1;
}
return 0;
}
void draw_point(int x, int y, unsigned int col)
{
if (x >= vinfo.xres || y >= vinfo.yres)
{
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;
}
}
void draw_h_line(int x, int y, int len, unsigned int col)
{
for (int i = x; i < x+len; ++i)
{
draw_point(i, y, col);
}
}
void draw_s_line(int x, int y, int len, unsigned int col)
{
for (int i = y; i < y+len; ++i)
{
draw_point(x, i, col);
}
}
void draw_clear(unsigned int col)
{
for (int i = 0; i < fby_g; ++i)
{
draw_h_line(0, i, fbx_g, col);
}
}
void draw_full_rect(int x, int y, int w, int h, unsigned int col)
{
for (int i = y; i < y+h; ++i)
{
draw_h_line(x, i, w, col);
}
}
void draw_circle(int x0, int y0, int r, unsigned int col)
{
int x = 0, y = 0;
int r0 = 0;
for (r0 = 0; r0 <= r; ++r0)
{
for (float si = 0; si <= 360; si += 0.01)
{
x = x0 + r0 * cos(2*3.1416/360 * si);
y = y0 + r0 * sin(2*3.1416/360 * si);
draw_point(x, y, col);
}
}
}
2.五子棋棋盘坐标体系设计
(1)宏定义统一管理棋盘参数
#define BROAD_X_START 125 // XY起点相等,棋盘正方形居中
#define BROAD_Y_START 125
#define BROAD_X_CNT 15 // 15*15棋盘
#define BROAD_Y_CNT 15
#define BROAD_W_ONE 25 // 单格像素宽度
(2)程序代码
#include "game.h"
#include "framebuffer.h"
#include <stdio.h>
#define RED 0x00FF0000
#define GREEN 0x0000FF00
#define BLUE 0x000000FF
#define BLACK 0x00000000
#define WHITE 0x00FFFFFF
#define YELLOW 0x00FFFF00
//棋盘横向起始位置的偏移量
#define BROAD_X_START 125
//棋盘纵向起始位置的偏移量
#define BROAD_Y_START 125
//棋盘横向位置个数
#define BROAD_X_CNT 15
//棋盘纵向位置个数
#define BROAD_Y_CNT 15
//棋盘单个格子的宽度
#define BROAD_W_ONE 25
#define WHITE_CHESS 1
#define BLACK_CHESS -1
//当前光标所在的位置
int posx_g = BROAD_X_START;
int posy_g = BROAD_Y_START;
int chess_g[BROAD_Y_CNT][BROAD_X_CNT] = {0};
int k_g = 1;
void show_chess_broad()
{
draw_clear(0x00A07257);
for (int i = 0; i < BROAD_Y_CNT; ++i)
{
draw_h_line(BROAD_X_START, BROAD_Y_START+i*BROAD_W_ONE, (BROAD_X_CNT-1)*BROAD_W_ONE,0X00141414);
}
for (int i = 0; i < BROAD_X_CNT; ++i)
{
draw_s_line(BROAD_X_START+i*BROAD_W_ONE, BROAD_Y_START, (BROAD_Y_CNT-1)*BROAD_W_ONE, 0X00141414);
}
draw_full_rect(posx_g-5, posy_g-5, 10, 10, RED);
for (int i = 0; i < BROAD_Y_CNT; ++i)
{
for (int j = 0; j < BROAD_X_CNT; ++j)
{
if (WHITE_CHESS == chess_g[i][j])
{
draw_circle(i*BROAD_W_ONE+BROAD_Y_START, j*BROAD_W_ONE + BROAD_X_START,10, WHITE);
}
else if (BLACK_CHESS == chess_g[i][j])
{
draw_circle(i*BROAD_W_ONE+BROAD_Y_START, j*BROAD_W_ONE + BROAD_X_START,10, BLACK);
}
}
}
}
3.交互逻辑实现
-
光标移动:w/a/s/d控制,增加边界判断,光标无法移出棋盘范围
-
黑白交替落子实现采用全局计数变量k_g奇偶区分:
-
初始k_g=1,奇数 = 白棋,偶数 = 黑棋
-
每次落子完成计数器自增,自动切换棋手
-
E 键重置时计数器归 1,清空棋盘二维数组
-
-
落子数组存储:chess_g[行][列],1 代表白棋,-1 代表黑棋
void begin_game()
{
char choose = 0;
while (1)
{
choose = getchar();
if ('w' == choose)
{
if (posy_g > BROAD_Y_START)
{
posy_g -= BROAD_W_ONE;
}
}
else if ('a' == choose)
{
if(posx_g > BROAD_X_START)
{
posx_g -= BROAD_W_ONE;
}
}
else if ('s' == choose)
{
if (posy_g < BROAD_Y_START + (BROAD_Y_CNT-1)*BROAD_W_ONE)
{
posy_g += BROAD_W_ONE;
}
}
else if ('d' == choose)
{
if(posx_g < BROAD_X_START + (BROAD_X_CNT-1)*BROAD_W_ONE)
{
posx_g += BROAD_W_ONE;
}
}
else if (' ' == choose)
{
if(0 == k_g % 2)
{
chess_g[(posx_g-BROAD_X_START)/BROAD_W_ONE][(posy_g-BROAD_Y_START)/BROAD_W_ONE] = -1;
++k_g;
}
else if(0 != k_g % 2)
{
chess_g[(posx_g-BROAD_X_START)/BROAD_W_ONE][(posy_g-BROAD_Y_START)/BROAD_W_ONE] = 1;
++k_g;
}
}
else if('e' == choose)
{
for(int i = 0 ; i < BROAD_X_CNT ; ++i)
{
int j = 0;
chess_g[i][j] = 0;
++j;
}
draw_clear(0x00A07257);;
}
show_chess_broad();
win_game();
}
}
4.五子棋胜负判定算法
-
判定规则:每个方向向正反两侧遍历,统计连续同色棋子,碰到边界 / 空 / 异色棋子停止,连续≥5 则判定胜利;win_game()函数逻辑,黑白两套判定分支,胜利后绘制对应 BMP 图片。
void win_game()
{
int x = (posx_g – BROAD_X_START)/BROAD_W_ONE;
int y = (posy_g – BROAD_Y_START)/BROAD_W_ONE;
int cnt = 1;
int dx = 0;
int dy = 0;
dx = x;
dy = y;
if(1 == chess_g[x][y])
{
while(1 == chess_g[x][y+1])
{
cnt = cnt + 1;
++y;
}
y = dy;
while(1 == chess_g[x][y-1])
{
cnt = cnt + 1;
–y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p222.bmp");
return ;
}
y = dy;
cnt = 1;
while(1 == chess_g[x+1][y])
{
cnt = cnt + 1;
++x;
}
x = dx;
while(1 == chess_g[x-1][y])
{
cnt = cnt + 1;
–x;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p222.bmp");
return ;
}
x = dx;
cnt = 1;
while(1 == chess_g[x+1][y+1])
{
cnt = cnt +1;
++x;
++y;
}
x = dx;
y = dy;
while(1 == chess_g[x-1][y-1])
{
cnt = cnt +1;
–x;
–y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p222.bmp");
return ;
}
x= dx;
y= dy;
cnt = 1;
while(1 == chess_g[x+1][y-1])
{
cnt = cnt +1;
++x;
–y;
}
x = dx;
y = dy;
while(1 == chess_g[x-1][y+1])
{
cnt = cnt +1;
–x;
++y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p222.bmp");
return ;
}
x = dx;
y = dy;
cnt = 1;
}
if(-1 == chess_g[x][y])
{
while(-1 == chess_g[x][y+1])
{
cnt = cnt + 1;
++y;
}
y = dy;
while(-1 == chess_g[x][y-1])
{
cnt = cnt + 1;
–y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p111.bmp");
return ;
}
y = dy;
cnt = 1;
while(-1 == chess_g[x+1][y])
{
cnt = cnt + 1;
++x;
}
x = dx;
while(-1 == chess_g[x-1][y])
{
cnt = cnt + 1;
–x;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p111.bmp");
return ;
}
x = dx;
cnt = 1;
while(-1 == chess_g[x+1][y+1])
{
cnt = cnt +1;
++x;
++y;
}
x = dx;
y = dy;
while(-1 == chess_g[x-1][y-1])
{
cnt = cnt +1;
–x;
–y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p111.bmp");
return ;
}
x= dx;
y= dy;
cnt = 1;
while(-1 == chess_g[x+1][y-1])
{
cnt = cnt +1;
++x;
–y;
}
x = dx;
y = dy;
while(-1 == chess_g[x-1][y+1])
{
cnt = cnt +1;
–x;
++y;
}
if(cnt >= 5)
{
draw_bmp(200,200,"./p111.bmp");
return ;
}
x = dx;
y = dy;
cnt = 1;
}
}
5.主程序
#include <stdio.h>
#include "framebuffer.h"
#include "game.h"
int main(void)
{
init_fb("/dev/fb0");
show_chess_broad();
begin_game();
return 0;
}
6.Makefile编译脚本
OBJ = a.out
SRC = main.c game.c framebuffer.c
CC = gcc
$(OBJ):$(SRC)
$(CC) $^ -o $@ -lm
clean:
rm $(OBJ)
四、总结
本项目不依赖复杂图形库,纯粹基于 Linux 原生 Framebuffer 设备完成小型交互游戏,覆盖嵌入式图形开发基础技能:显存映射、像素绘图、图片解析、屏幕坐标算法、游戏交互逻辑、五子棋核心算法。适合 Linux 嵌入式入门者练习,文中记录的坐标错位、胜负判定、棋手轮换等踩坑点,能解决大部分初学者开发同类小游戏时遇到的共性问题,代码模块化拆分清晰,可在此基础上拓展俄罗斯方块、贪吃蛇等其他图形小游戏。
网硕互联帮助中心





评论前必须登录!
注册