基于【C语言】和【freebuffer】的五子棋游戏项目
文章目录
- 前言
- 一、项目要求及意义
- 二、项目功能描述及效果
-
- 2.1 项目功能描述
- 2.2 项目效果展示
- 三、项目具体实现
-
- 3.1 项目流程图
- 3.2 项目具体解决方案
- 3.3 核心技术
-
- 3.3.1 Framebuffer 显示模块
- 3.3.2 游戏逻辑模块之数据结构设计
- 3.3.3 游戏逻辑模块之坐标映射机制
- 3.3.4 游戏逻辑模块之落子算法
- 3.3.5 游戏逻辑模块之胜负判定算法
- 四、项目总结
-
- 4.1 项目收获
- 4.2 项目中遇到的问题及解决办法
- 4.3 项目可优化点

前言
在进行此项目之前,我们需要涉及到C语言相关的知识以及linux系统的基本知识和freebuffer帧缓存技术,此项目旨在对C语言知识的巩固以及对模块化编程思想的加深,所以按时不需要考虑freebuffer技术,后文会直接提供。以下三篇文章是对C语言和linux基本知识的总结:
- 从0基础开始到熟练掌握C语言
- Linux入门指南
- linux系统中项目工程中的多文件管理(makefile)
一、项目要求及意义
要求:编写C语言程序,通过Linux系统的伪终端构建五子棋棋盘和落子位置等相关内容。
意义:通过实现五子棋游戏,掌握 Linux 系统编程、内存映射,了解底层图形渲染的核心方法;以经典益智游戏为载体,练习模块化代码设计、游戏逻辑实现与边界问题处理。
二、项目功能描述及效果
2.1 项目功能描述
-
功能总览:
在Linux环境下、基于帧缓冲驱动的双人对战五子棋游戏,完整覆盖从界面显示到对局逻辑的全流程。 -
详细功能说明
(1)界面显示功能
支持黑白棋子绘制、红色选择光标显示。对局结束自动弹出胜负结果提示图;
支持BMP背景图加载,绘制木色棋盘与20×20标准网。
(2)操作交互功能
键盘w/a/s/d控制光标上下左右移动,选择落子位置;
空格键执行落子,黑白双方自动交替落子。r键实现单步悔棋,回退上一步落子状态。
(3)游戏规则功能
自动拦截重复落子,仅允许在空位落子;
实时胜负判定,横、竖、斜向连成五子即判胜;
右侧提示区显示当前轮到哪一方落子。
2.2 项目效果展示
五子棋项目实现效果视频
三、项目具体实现
3.1 项目流程图

此为五子棋项目的流程图
3.2 项目具体解决方案
在Linux环境下、基于帧缓冲驱动的双人对战五子棋游戏,完整覆盖从界面显示到对局逻辑的全流程。
3.3 核心技术
3.3.1 Framebuffer 显示模块
-
基础绘图函数封装
通过draw_point()像素点绘制,封装水平线、竖直线、矩形、实心圆、斜线、全屏清屏函数。
以下代码为直接提供的freebuffer相关的代码,我们直接使用,暂时不做了解:freebuffer.h #ifndef __FRAMEBUFFER_H__ #define __FRAMEBUFFER_H__ #define RGB_FMT_888 32 #define RGB_FMT_565 16 #define RED 0x00FF0000 #define GREEN 0x0000FF00 #define BLUE 0x000000FF #define BLACK 0x00000000 #define WHITE 0x00FFFFFF #define YELLOW 0x00FFFF00 #define MUZHI 0x00DEB887 //#define WORD_D 5 //#define SUM_D d1+d2+d3+d4+d5+d6 // //当前显示设备可视化界面的分辨率 extern int fbx_g; //800 横向分辨率 extern int fby_g; //600 纵向分辨率 //初始化显示设备:/dev/fb0 extern int init_fb(char *devname); //绘制一个像素点 //x : 横向坐标 //y : 纵向坐标 //col :RGB888颜色值 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_clean(unsigned int col); extern void draw_s_line(int x, int y, int len, unsigned int col); extern void draw_rect(int x, int y, int h, int k, unsigned int col); extern void draw_circle(int x0, int y0, int r, unsigned int col); extern void draw_zxie_line(int x, int y, int size, unsigned int col); extern void draw_yxie_line(int x, int y, int size, unsigned int col); //extern void draw_win(int x, int y , int size, int thick, int col); extern void draw_bmp(int x, int y, char *bmpname); #endiffreebuffer.c #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 = %dn", vinfo.xres, vinfo.yres); // printf("xres_virtual = %d, yres_virtual = %dn", vinfo.xres_virtual, vinfo.yres_virtual); printf("bits_per_pixel = %dn", 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_clean(unsigned int col) { for(int i = 0; i < fby_g; ++i) { draw_h_line(0, i, fbx_g, 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_rect(int x, int y, int h, int k, unsigned int col) { for (int i = y; i < y+k; ++i) { for (int j = x; j < x+k; ++j) { draw_point(j, i, col); } } } void draw_circle(int x0, int y0, int r, unsigned int col) { int x = 0; int y = 0; for(int r0 = 0; r0 < r; ++r0) { for(double si = 0; si < 360; si += 1) { x = x0 + r0 * cos(2 * 3.1415926 / 360 * si); y = y0 + r0 * sin(2 * 3.1415926 / 360 * si); draw_point(x, y, col); } } } void draw_zxie_line(int x, int y, int size, unsigned int col) { for(int i = 0; i < size; ++i) { draw_point(x+i, y+2*i, col); } } void draw_yxie_line(int x, int y, int size, unsigned int col) { for(int i = 0; i < size; ++i) { draw_point(x-i, y+2*i, col); } } -
BMP 图片解析绘制
此函数直接提供,会用即可,我们暂时不做了解。/********************************************* *draw_bmp :绘制bmp格式的图片 *参数: * x : 图像显示的起始横坐标 * y :图像显示的起始从坐标 * bmpname :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; // BITMAPINFOHEADER的大小,单位为字节 int biWidth; // 位图的宽度,单位为像素 int biHeight; // 位图的高度,单位为像素 short biPlanes; // 目标设备的位平面数,必须为1 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; 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 << 0); draw_point(i+x, j+y, col); } } free(buff); }
3.3.2 游戏逻辑模块之数据结构设计
因为需要记录棋盘上每个位置的状态,所以定义了一个枚举类型(其中有空EMPTY、白子WHITE_P、黑子BLACK_P三种状态)。还定义了两个结构体,用来记录光标和棋子的位置。
光标结构体:保存光标当前像素坐标
历史记录结构体:保存前两步落子位置,支持悔棋回滚。

二维数组record[21][21]:记录每个棋盘交叉点状态(空 / 黑棋 / 白棋)
由于我定义的是每个格子的宽度,并且格子数为20,所以可以落子的位置就为21×21,因此定义一个21×21的数组,以此来记录所有的落子位置。

3.3.3 游戏逻辑模块之坐标映射机制

通过格子间距,通过定义这个宏来实现棋子与数组元素之间的对应关系,使得数组就能表现棋盘状态,方便记录棋子落子位置。
3.3.4 游戏逻辑模块之落子算法

在检测到空格键按下时执行落子逻辑:若棋盘上当前落点为空且无上一步棋子记录,则落下白棋并保存该棋子类型与坐标;若上一步落下的是白棋,且当前格子为空,则落下黑棋,同时记录黑棋状态、保存前一步白棋的位置作为倒数第二步点位;若上一步落下的是黑棋且当前格子为空,则落下白棋,记录白棋状态、保存前一步黑棋坐标,以此交替切换黑白双方轮流落子,保证双人交替下棋,并且只允许在空白棋盘位置落子,与此同时,保存前两步的落子位置,以此来支撑我们的悔棋操作。
3.3.5 游戏逻辑模块之胜负判定算法

这里以斜线方向为例。

以最后落子点为中心,向横向、纵向、左斜、右斜 4 个方向双向延伸
每个方向独立统计连续同色棋子数量,任意方向达到 5 子即判定获胜
仅检测落子点周边,无需遍历整个棋盘。
四、项目总结
4.1 项目收获
- 初步了解了Linux Framebuffer;
- 在绘制元素点的基础上掌握了基础图形绘制等底层图形编程技能;
- 提升了模块化代码设计能力,巩固了分层封装、数据结构与业务逻辑分离的开发思路
- 积累了嵌入式环境下轻量级图形界面的开发与调试经验
4.2 项目中遇到的问题及解决办法
- 悔棋功能状态回滚异常
增加二级历史记录结构体,保存最近两步的落子位置与棋子类型,实现状态完整回退 - 在边界的胜负判定错误问题
对判断数组递加的下标进行边界判定。 - 项目陷入一些不明问题
通过在伪终端打印一个大圆,定位程序结束位置,从而进行代码修改
4.3 项目可优化点
- 人机对战功能
增加人机对战 AI 算法,实现单人模式;增加对局计时、比分统计功能 - 时间限制功能
在上次落子后就开始计时,限定落子时间,提高游戏体验。 - 悔棋功能完善
当前悔棋仅支持返回上一步,增加悔棋算法,使其没有次数限制。
好啦,以上就是本次基于C语言基础和linux系统freebuffer技术的五子棋项目,希望对各位在C语言的学习上起到一定的作业,相信各位在完成之后一定也会有成就感的,一起加油!!!