基于【C语言】和【freebuffer】的五子棋游戏项目

AI1周前发布 beixibaobao
8 0 0

文章目录

  • 前言
  • 一、项目要求及意义
  • 二、项目功能描述及效果
    • 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基本知识的总结:

  1. 从0基础开始到熟练掌握C语言
  2. Linux入门指南
  3. linux系统中项目工程中的多文件管理(makefile)

一、项目要求及意义

要求:编写C语言程序,通过Linux系统的伪终端构建五子棋棋盘和落子位置等相关内容。
意义:通过实现五子棋游戏,掌握 Linux 系统编程、内存映射,了解底层图形渲染的核心方法;以经典益智游戏为载体,练习模块化代码设计、游戏逻辑实现与边界问题处理。

二、项目功能描述及效果

2.1 项目功能描述

  1. 功能总览:
    在Linux环境下、基于帧缓冲驱动的双人对战五子棋游戏,完整覆盖从界面显示到对局逻辑的全流程。

  2. 详细功能说明
    (1)界面显示功能
    支持黑白棋子绘制、红色选择光标显示。对局结束自动弹出胜负结果提示图;
    支持BMP背景图加载,绘制木色棋盘与20×20标准网。
    (2)操作交互功能
    键盘w/a/s/d控制光标上下左右移动,选择落子位置;
    空格键执行落子,黑白双方自动交替落子。r键实现单步悔棋,回退上一步落子状态。
    (3)游戏规则功能
    自动拦截重复落子,仅允许在空位落子;
    实时胜负判定,横、竖、斜向连成五子即判胜;
    右侧提示区显示当前轮到哪一方落子。

2.2 项目效果展示

五子棋项目实现效果视频

三、项目具体实现

3.1 项目流程图

基于【C语言】和【freebuffer】的五子棋游戏项目

此为五子棋项目的流程图

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);
    #endif
    
    freebuffer.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 项目收获

  1. 初步了解了Linux Framebuffer;
  2. 在绘制元素点的基础上掌握了基础图形绘制等底层图形编程技能;
  3. 提升了模块化代码设计能力,巩固了分层封装、数据结构与业务逻辑分离的开发思路
  4. 积累了嵌入式环境下轻量级图形界面的开发与调试经验

4.2 项目中遇到的问题及解决办法

  • 悔棋功能状态回滚异常
    增加二级历史记录结构体,保存最近两步的落子位置与棋子类型,实现状态完整回退
  • 在边界的胜负判定错误问题
    对判断数组递加的下标进行边界判定。
  • 项目陷入一些不明问题
    通过在伪终端打印一个大圆,定位程序结束位置,从而进行代码修改

4.3 项目可优化点

  • 人机对战功能
    增加人机对战 AI 算法,实现单人模式;增加对局计时、比分统计功能
  • 时间限制功能
    在上次落子后就开始计时,限定落子时间,提高游戏体验。
  • 悔棋功能完善
    当前悔棋仅支持返回上一步,增加悔棋算法,使其没有次数限制。

好啦,以上就是本次基于C语言基础和linux系统freebuffer技术的五子棋项目,希望对各位在C语言的学习上起到一定的作业,相信各位在完成之后一定也会有成就感的,一起加油!!!

© 版权声明

相关文章