news 2026/8/14 14:06:12

五子棋项目

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
五子棋项目

Linux FrameBuffer 实现嵌入式五子棋(完整源码 + 解析)

一、项目概述

本项目基于 Linux 帧缓冲FrameBuffer(fb)手写一套极简 2D 绘图引擎,不依赖 Qt、LVGL 等第三方 UI 库,纯 C 实现 20×20 五子棋。

功能清单

  1. FrameBuffer 底层封装:点、直线、矩形、圆形、区域清屏、24 位 BMP 图片渲染
  2. 20×20 棋盘绘制,WASD 红色光标移动选中落子点
  3. 空格落子,黑白棋子自动交替,已有棋子位置禁止重复落子
  4. 四向胜负判断(横、竖、正对角线、反对角线),五子连珠弹出胜利图片

环境说明

  • 系统:Linux(开发板 / 虚拟机均可)
  • 显示设备:/dev/fb0
  • 颜色格式:RGB888 (32 位色)、RGB565 (16 位色) 双兼容
  • 编译工具:GCC + Makefile

二、项目文件结构

├── main.c # 程序入口,初始化fb启动游戏 ├── game.c # 五子棋业务逻辑:棋盘绘制、操控、胜负判断 ├── game.h # 游戏函数声明头文件 ├── framebuffer.c # FrameBuffer底层绘图驱动封装 ├── framebuffer.h # 绘图API头文件 ├── black.bmp.bmp # 黑棋胜利贴图 ├── white.bmp.bmp # 白棋胜利贴图 └── Makefile # 一键编译脚本

三、源码分模块解析

模块 1:FrameBuffer 底层绘图驱动

1.1 framebuffer.h 头文件(函数对外声明)
#ifndef __FRAMEBUFFER_H__ #define __FRAMEBUFFER_H__ #define RGB_FMT_888 32 #define RGB_FMT_565 16 //屏幕全局分辨率 extern int fbx_g; extern int fby_g; //初始化帧缓冲设备 extern int init_fb(char *devname); //基础绘图接口 extern void draw_point(int x, int y, unsigned int col); extern void draw_h_line(int x, int y, int len, unsigned int col); extern void draw_s_line(int x,int y,int len,unsigned int col); extern void clear_ping(int x,int y,int hang,int lie,unsigned int col); extern void draw_square(int x,int y,int len,unsigned int col); extern void draw_circle(int x,int y,int r,unsigned int col); extern void draw_bmp(int x, int y, char *bmpname); #endif
1.2 framebuffer.c 底层实现

核心原理:open打开 fb 设备 →ioctl获取屏幕参数 →mmap显存映射,直接操作像素内存。

#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; //fb设备文件描述符 struct fb_var_screeninfo vinfo; //屏幕可变参数 int fbx_g = 0; int fby_g = 0; //初始化帧缓冲 int init_fb(char *devname) { fb = open(devname, O_RDWR); if (-1 == fb) { perror("open fb error"); return -1; } //获取屏幕分辨率、色深 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("bits_per_pixel = %d\n", vinfo.bits_per_pixel); fbx_g = vinfo.xres; fby_g = vinfo.yres; //计算显存总长度,建立内存映射 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; } //绘制单个像素点,适配RGB888/RGB565 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 clear_ping(int x,int y,int hang,int lie,unsigned int col) { for(int i=x;i<hang;++i) for(int j=y;j<hang;++j) draw_point(i,j,col); } //实心正方形 void draw_square(int x,int y,int len,unsigned int col) { for(int i=x;i<x+len;++i) for(int j=y;j<y+len;++j) draw_point(i,j,col); } //实心圆形(圆方程判定像素) void draw_circle(int x,int y,int r,unsigned int col) { for(int i=y-r;i<=y+r;++i) for(int j=x-r;j<=x+r;++j) if((j-x)*(j-x)+(i-y)*(i-y)<=r*r) draw_point(j,i,col); } //加载并绘制24位BMP图片 void draw_bmp(int x, int y, char *bmpname) { #pragma pack(1) //BMP文件头 typedef struct tagBITMAPFILEHEADER { short bfType; int bfSize; short bfReserved1; short bfReserved2; int bfOffBits; }Bmp_file_head_t; //BMP信息头 typedef struct tagBITMAPINFOHEADER { int biSize; int biWidth; int biHeight; short biPlanes; short biBitCount; int biCompression; int biSizeImage; int biXPelsPerMeter; int biYPelsPerMeter; int biClrUsed; int biClrImportant; }Bmp_info_t; #pragma pack() Bmp_file_head_t headinfo; Bmp_info_t bmpinfo; int fd = open(bmpname, O_RDONLY); if (-1 == fd) { perror("open bmp error"); return ; } read(fd, &headinfo, sizeof(headinfo)); read(fd, &bmpinfo, sizeof(bmpinfo)); unsigned char *buff = malloc(bmpinfo.biHeight*bmpinfo.biWidth*bmpinfo.biBitCount/8); read(fd, buff, bmpinfo.biHeight*bmpinfo.biWidth*bmpinfo.biBitCount/8); close(fd); unsigned char *p = buff; unsigned char r, g, b; //BMP像素上下倒置,逆序遍历 for (int j = bmpinfo.biHeight-1; j >= 0; j--) for (int i = 0; i < bmpinfo.biWidth; i++) { b = *p;++p; g = *p;++p; r = *p;++p; unsigned int col = (r << 16) | (g << 8) | b; draw_point(i+x, j+y, col); } free(buff); }

模块 2:五子棋游戏业务层

2.1 game.h 函数声明
#ifndef _GAME_H #define _GAME_H extern void show_chess_board(); extern void begin_game(); extern int check_win(int x,int y,int col); #endif
2.2 game.c 棋盘、操控、胜负逻辑
#include<stdio.h> #include"game.h" #include"framebuffer.h" //RGB888颜色宏 格式0x00RRGGBB #define RED 0x00FF0000 #define GREEN 0x0000FF00 #define BLUE 0x000000FF #define BLACK 0x00000000 #define WHITE 0x00FFFFFF #define YELLOW 0x00FFFF00 #define BACKGROUND 0x00FFDAB9 //棋盘布局参数 #define BEGIN_X 100 #define BEGIN_Y 100 #define CNT_X 20 #define CNT_Y 20 #define ONE_W 25 #define WHITE_CHESS 1 #define BLACK_CHESS -1 //全局变量 int gb_x=BEGIN_X; //光标屏幕坐标X int gb_y=BEGIN_Y; //光标屏幕坐标Y int chess_g[CNT_Y][CNT_X]={0}; //棋盘数组 0空 1白 -1黑 int cishu=1; //落子次数,控制黑白交替 int chess_col=0; //本次落子棋子颜色 //绘制完整棋盘:背景、网格、光标、所有棋子 void show_chess_board() { clear_ping(0,0,800,600,BACKGROUND); //绘制横线 for(int i=0;i<CNT_Y;++i) draw_h_line(BEGIN_X, BEGIN_Y+i*ONE_W, (CNT_X-1)*ONE_W, BLACK); //绘制竖线 for(int i=0;i<CNT_X;++i) draw_s_line(BEGIN_X+i*ONE_W,BEGIN_Y,(CNT_Y-1)*ONE_W,BLACK); //红色光标方块 draw_square(gb_x-5,gb_y-5,10,RED); //遍历棋盘绘制黑白棋子 for(int i=0;i<CNT_Y;++i) for(int j=0;j<CNT_X;++j) { if(WHITE_CHESS==chess_g[i][j]) draw_circle(BEGIN_X+j*ONE_W,BEGIN_Y+i*ONE_W,10,WHITE); else if(BLACK_CHESS==chess_g[i][j]) draw_circle(BEGIN_X+j*ONE_W,BEGIN_Y+i*ONE_W,10,BLACK); } } //游戏主循环:键盘WASD移动,空格落子 void begin_game() { char choose=0; while(1) { choose=getchar(); //上W if('w'==choose) if(gb_y>BEGIN_Y) gb_y-=ONE_W; //下S else if('s'==choose) if(gb_y<BEGIN_Y+(CNT_Y-1)*ONE_W) gb_y+=ONE_W; //左A else if('a'==choose) if(gb_x>BEGIN_X) gb_x-=ONE_W; //右D else if('d'==choose) if(gb_x<BEGIN_X+(CNT_X-1)*ONE_W) gb_x+=ONE_W; //空格落子 else if(' '==choose) { //当前位置已有棋子,禁止落子 if(chess_g[(gb_y-BEGIN_Y)/ONE_W][(gb_x-BEGIN_X)/ONE_W]!=0) { show_chess_board(); continue; } //奇偶次数切换黑白棋 if(1==cishu%2) { chess_g[(gb_y-BEGIN_Y)/ONE_W][(gb_x-BEGIN_X)/ONE_W]=WHITE_CHESS; chess_col=WHITE_CHESS; } else { chess_g[(gb_y-BEGIN_Y)/ONE_W][(gb_x-BEGIN_X)/ONE_W]=BLACK_CHESS; chess_col=BLACK_CHESS; } ++cishu; } //刷新界面 show_chess_board(); //判断胜利,显示对应胜利图片 if(check_win((gb_x-BEGIN_Y)/ONE_W,(gb_y-BEGIN_X)/ONE_W,chess_col)) { if(chess_col==BLACK_CHESS) draw_bmp(600,200,"black.bmp.bmp"); else if(chess_col==WHITE_CHESS) draw_bmp(600,200,"white.bmp.bmp"); } } } //胜负检测:检测刚落下的棋子四向是否五子连珠 int check_win(int x,int y,int col) { int num=1; //1. 水平左右 for(int i=1;i<5;++i) if(col==chess_g[y][x+i] && (x+i)<CNT_X) num++; else break; for(int i=1;i<5;++i) if(col==chess_g[y][x-i] && (x-i)>=0) num++; else break; if(num>=5) return 1; //2. 垂直上下 num=1; for(int i=1;i<5;++i) if(col==chess_g[y+i][x] && (y+i)<CNT_Y) num++; else break; for(int i=1;i<5;++i) if(col==chess_g[y-i][x] && (y-i)>=0) num++; else break; if(num>=5) return 1; //3. 正对角线 \ num=1; for(int i=1;i<5;++i) if(col==chess_g[y+i][x+i] && (y+i)<CNT_Y && (x+i)<CNT_X) num++; else break; for(int i=1;i<5;++i) if(col==chess_g[y-i][x-i] && (y-i)>=0 && (x-i)>=0) num++; else break; if(num>=5) return 1; //4. 反对角线 / num=1; for(int i=1;i<5;++i) if(col==chess_g[y+i][x-i] && (y+i)<CNT_Y && (x-i)>=0) num++; else break; for(int i=1;i<5;++i) if(col==chess_g[y-i][x+i] && (y-i)>=0 && (x+i)<CNT_X) num++; else break; if(num>=5) return 1; return 0; }
模块 3:程序入口 main.c
#include <stdio.h> #include "framebuffer.h" #include"game.h" int main(void) { init_fb("/dev/fb0"); show_chess_board(); begin_game(); return 0; }
模块 4:Makefile 编译脚本
CC = gcc TARGET = gobang SRCS = main.c game.c framebuffer.c OBJS = $(SRCS:.c=.o) CFLAGS = -Wall -g $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -rf $(OBJS) $(TARGET) .PHONY: clean

四、关键知识点讲解

4.1 FrameBuffer 核心原理

  1. /dev/fb0是 Linux 显卡抽象设备,操作文件等价操作屏幕显存;
  2. mmap将内核显存映射到用户进程虚拟内存,直接写内存即可改像素,无系统调用开销;
  3. RGB888:32 位无符号整型存储颜色0x00RRGGBB,单通道 0~255,千万级色彩;
  4. RGB565:16 位低成本色深,R5 G6 B5,嵌入式老屏幕常用。

4.2 BMP 图片渲染要点

  1. BMP 像素存储上下颠倒,绘图需要从最后一行往第一行遍历;
  2. BMP 原生像素顺序为 BGR,代码内手动转换成 RGB 格式写入显存;
  3. 使用#pragma pack(1)取消结构体内存对齐,防止读取文件头错位。

4.3 五子棋胜负算法

落子后只检测当前棋子四个方向: 水平、垂直、左上 - 右下、右上 - 左下; 每个方向分别向两侧统计连续同色棋子,总数≥5 判定胜利。

五、操作说明

  • W:光标上移
  • S:光标下移
  • A:光标左移
  • D:光标右移
  • 空格:落子

六、代码缺陷与优化方向

  1. 全屏重绘:每次按键清空整个屏幕,刷新闪烁,可增加局部刷新 / 双缓冲;
  2. 无触摸、鼠标支持,仅键盘操控;
  3. 无文字绘制接口,胜利仅贴图无文字提示;
  4. 无游戏重启逻辑,分出胜负后无法新开一局;
  5. clear_ping参数变量名混乱,存在语义 bug;
  6. 未封装通用窗口、按钮等 UI 控件,仅适用于单棋盘场景。

七、适用场景

  1. Linux 嵌入式 FrameBuffer 底层显示驱动学习;
  2. 手写 2D 简易 UI 绘图引擎入门;
  3. C 语言文件 IO、内存映射、位图解析实战;
  4. 五子棋游戏逻辑、五子连珠算法练习。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 14:05:44

告别WiFi热点:几步搞定Mac与Android的USB共享网络

告别WiFi热点&#xff1a;几步搞定Mac与Android的USB共享网络 【免费下载链接】HoRNDIS Android USB tethering driver for Mac OS X 项目地址: https://gitcode.com/gh_mirrors/ho/HoRNDIS 咖啡厅的WiFi时断时续&#xff0c;酒店的网络要登录半天&#xff0c;手机热点一…

作者头像 李华
网站建设 2026/8/14 14:04:27

奇点大会的回流数据议题,对内容平台算法有什么启发

对奇点智能大会&#xff08;2026&#xff09;的完整技术议题感兴趣&#xff0c;可前往奇点大会官方渠道免费获取PPT详细资料。 从“奇点”到日常&#xff1a;回流数据议题对内容平台的真实启发 2026 奇点智能大会把“回流数据”推到了舞台中央。对内容平台的技术团队来说&…

作者头像 李华
网站建设 2026/8/14 14:03:45

从奇点大会看技术会议的价值,普通开发者怎么选会省钱

技术会议每年成百上千&#xff0c;门票从免费到上万元不等。对普通开发者来说&#xff0c;时间有限、预算有限&#xff0c;选错一场会意味着浪费的不只是钱&#xff0c;更是周末两天本可以写代码或陪家人的时间。这篇文章想提供一个可复用的决策框架&#xff0c;并以奇点智能大…

作者头像 李华
网站建设 2026/8/14 14:02:51

没有官方驱动的 Linux 打印机,foo2zjs 免费搞定 100+ 型号兼容

没有官方驱动的 Linux 打印机&#xff0c;foo2zjs 免费搞定 100 型号兼容 【免费下载链接】foo2zjs A linux printer driver for QPDL protocol - copy of http://foo2zjs.rkkda.com/ 项目地址: https://gitcode.com/gh_mirrors/fo/foo2zjs 朋友送了我一台退役的 HP Las…

作者头像 李华