C语言GUI编程之数字记忆游戏——项目目录结构和初步的窗口布局

目录

项目目录结构

创建项目

命名规范

目录结构

编程模式

MVC

实例

初步的窗口布局

效果

EasyX的使用

总结


项目目录结构

创建项目

在Visual Studio 2022中创建一个空白的项目,起名为:NumberMemoryGame。

命名规范

常规的变量、函数以及文件名等命名规范不再赘述,这里主要说一下项目中变量和函数的命名规范。

由于C语言没有命名空间的概念,所以在定义变量、函数的时候要避免与系统、第三方类库以及自身项目里的变量、函数重名,否则会出现难以预料的bug,本项目采取了增加前缀的措施来确保不会重名。

前缀构成:项目名首字母+文件名+表意函数名,中间用下划线连接,比如:NMG_view_init()。

目录结构

根据上一篇文章中提到的模块拆分,我们建立具体的源文件、头文件与之对应。

下面简单介绍一下每个文件的作用:

  • main.cpp是项目的入口文件,主函数main()就定义在这里

  • 两个头文件:config.h定义一些常量,models.h定义项目里用到的数据结构模型

  • data.cpp,文件读写模块,就是项目里用到的文件数据库

  • game.cpp,游戏模块,控制整个游戏进程和交互

  • rank.cpp,排行榜模块,排行榜的展示以及更新

  • settings.cpp,设置模块,游戏设置中心

  • timer.cpp,计时器模块,计时和展示,作为排行榜的依据

  • views.cpp,窗口显示模块,控制整个项目的UI初始化、显示、更新等

编程模式

MVC

以上文件目录结构创建好后,我们就往面向对象的编程思路上去靠拢,首选是常用的MVC模式:

  • Model,定义在models.h中,管理所有抽象化对象的数据结构模型

  • View,视图,相当于浏览器,只管展示视图,尽量不要参与业务逻辑的编码

  • Controller,控制器,控制视图的数据展示以及用户和视图交互背后的逻辑处理

笔者GUI开发经验不多,再加上C语言自身的一些问题,很难去按照严格意义上的MVC模式去开发,但是我们的编程思想要向MVC看齐。

如果不理清关系、把逻辑分层、模块拆分开来,直接上手开发的话肯定会无从下手,而且过程会出现难以控制的bug,日后维护也很困难。

实例

下面来简单看一个实例,我们来实现排行榜这个模块的功能。

首先定义好数据模型,前期先加两个字段,后期实际开发中再去扩展。

/*******************************************************
* 项目中的数据模型
* 数据模型前缀:NMG_MODEL_
* 
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/

/*
* 排行榜数据模型
*/

//struct NMG_model_rank {
//    char nickername[20];
//    int take_time;
//};
// 使用typedef来定义,使用时更加便捷
typedef struct{
    char nickername[20];
    int take_time;
} NMG_MODEL_RANK;

/*
* 游戏相关数据模型
*/
// 棋盘格子
typedef struct{
    int left;
    int top;
    int right;
    int bottom;
    int width;
    int height;
} NMG_MODEL_CELL;

/*
* 设置选项数据模型
*/
typedef struct{
    int game_sound;     // 游戏音效开关
    int bgm_sound;      // 背景音乐开关
    char *bg_image_src; // 背景图地址
} NMG_MODEL_SETTINGS;

// 设置项
typedef enum {
    nmg_settings_game_sound,
    nmg_settings_bgm_sound,
    nmg_settings_bg_image_src
} NMG_MODEL_SETTINGS_OPTION;

// 设置项的值有多种数据类型,采用union构造类型
typedef union{
    int int_value;      // 整数类型的值
    char *str_value;    // 字符串类型的值
} NMG_MODEL_SETTINGS_VALUE;

/*
* 语言种类
*/
typedef struct{
    char name[30];
    int code;
} NMG_MODEL_SETTINGS_LANGUAGE_TYPE;

然后,在控制器中实现业务逻辑——做底层数据和外层视图的桥梁。排行榜模块功能很简单,前期就做两个功能:展示和更新。

/*******************************************************
* 排行榜模块函数
* 函数前缀:NMG_rank_
*
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/

#include <stdlib.h>
#include "models.h"

extern NMG_MODEL_RANK *NMG_data_rank_query();
extern int NMG_data_rank_update(NMG_MODEL_RANK *list);

/*
* 读取文件 && 获取排行榜TOP10
* 
* 返回排行榜数组指针
*/
NMG_MODEL_RANK *NMG_rank_top_10() {
    return NMG_data_rank_query();
}

/*
* 排行榜插入一条数据 && 写入文件
* 
* 返回新的排行榜数组指针
*/
NMG_MODEL_RANK *NMG_rank_insert(NMG_MODEL_RANK rank) {
    NMG_MODEL_RANK *list = (NMG_MODEL_RANK *) calloc(sizeof(NMG_MODEL_RANK), 10);
    list = NMG_rank_top_10();
    // todo: insert && update

    int res = NMG_data_rank_update(list);
    if (res){
        return list;
    }

    return nullptr;
}

不过,在控制器中,并没有直接去数据库(当前项目的文件系统)去直接读写数据,而是又加了一个文件data.cpp专门去读写数据库,这样做能让底层的数据分离更加彻底,使得程序逻辑更加清晰。

/*******************************************************
* 文件读写模块函数
* 函数前缀:NMG_data_
*
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/

#include <stdlib.h>
#include "config.h"
#include "models.h"

/*
* 排行榜数据读写
*/
// 读取排行榜
NMG_MODEL_RANK *NMG_data_rank_query() {
    NMG_MODEL_RANK *list = (NMG_MODEL_RANK *) calloc(sizeof(NMG_MODEL_RANK), 10);
    // todo: use fread to query

    return list;
}

// 更新排行榜
int NMG_data_rank_update(NMG_MODEL_RANK *list) {
    // todo: use fwrite to update

    return 1;
}

/*
* 用户设置数据读写
*/
// 读取用户设置
NMG_MODEL_SETTINGS NMG_data_settings_query() {
    NMG_MODEL_SETTINGS settings = {};
    // todo: use fread to query
    return settings;
}

// 更新用户设置
int NMG_data_settings_update(NMG_MODEL_SETTINGS settings) {
    // todo: use fwrite to update

    return 1;
}

注意到,这里有些函数只是做了定义(输入和输出),这点上一篇文章提到过:先把逻辑走通,回头再完善具体的功能性的函数。

初步的窗口布局

效果

由于要整理开发教程的步骤,所以整体进度上受点影响,今天先把初步的窗口布局实现以下,效果如下。

左侧就是游戏区了,目前已经划分好了棋盘,接下来就是把数字随机到棋盘格子里去,然后再接收玩家的鼠标点击事件,挑战成功则开启下一关,直至全部通关,基本上游戏的闭环就完成了。

右侧是功能区,主要是计时、排行榜、功能设置三块。

EasyX的使用

咱们项目的重点是通过EasyX来完成GUI开发,下面就来简单看一下EasyX的使用方法。

/*******************************************************
* 项目主函数文件
*
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/
#include <stdio.h>

#include "config.h"
#include "models.h"

extern void NMG_game_init();
// 游戏区域所有的格子
NMG_MODEL_CELL cell_list[NMG_CFG_CELL_COUNT];

int main(int argc, char *argv[])
{
	printf("This is my first gui project.\n");

	NMG_game_init();

	for (int i = 0; i < NMG_CFG_CELL_COUNT; i++){
		printf("left:%5d top:%5d right:%5d bottom%5d\n", 
			cell_list[i].left, cell_list[i].top, 
			cell_list[i].right, cell_list[i].bottom);
	}

	return 0;
}

在主函数里调用,NMG_game_init()游戏初始化函数,然后在NMG_game_init()里调用NMG_view_init()视图初始化函数。

/*******************************************************
* 游戏模块函数
* 函数前缀:NMG_game_
*
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/

extern void NMG_view_init();

// 游戏初始化
void NMG_game_init() {

    NMG_view_init();

    // todo: 第一次定义一个数字随机显示在格子上
}

至于为何要嵌套一层调用,这个就是我们上面提到的要使用MVC模式编程。这里的函数还有要完善的地方,到时候代码会变多,就能显现出MVC的优点了。

/*******************************************************
* 窗口UI绘制
* 函数前缀:NMG_view_
*
* Author:   Lunixy
* Date:     2022-10-22
*******************************************************/

#include <easyx.h>
#include <conio.h>

#include "config.h"
#include "models.h"

extern NMG_MODEL_CELL cell_list[NMG_CFG_CELL_COUNT];
static void _NMG_view_left_init(int width, int height);

// 窗口初始化
void NMG_view_init() {
	initgraph(NMG_CFG_WINDOW_WIDTH, NMG_CFG_WINDOW_HEIGHT, EX_SHOWCONSOLE);
	cleardevice();

	int game_area_width = NMG_CFG_WINDOW_HEIGHT;

	// 左侧游戏区域
	_NMG_view_left_init(game_area_width, NMG_CFG_WINDOW_HEIGHT);

	// 右侧功能区域
	setfillcolor(BROWN);
	fillrectangle(game_area_width, 0, NMG_CFG_WINDOW_WIDTH, NMG_CFG_WINDOW_HEIGHT);

	_getch();				// 按任意键继续
	closegraph();			// 关闭绘图窗口
}

// 左侧游戏区域初始化:填满小格子
static void _NMG_view_left_init(int width, int height) {
	setfillcolor(YELLOW);
	fillrectangle(0, 0, width, height);

	int size_w = width / NMG_CFG_CELL_COLUMN_COUNT;
	int size_h = height / NMG_CFG_CELL_ROW_COUNT;
	for (int i = 0; i < NMG_CFG_CELL_ROW_COUNT; i++){
		for (int j = 0; j < NMG_CFG_CELL_COLUMN_COUNT; j++){
			setfillcolor((i + j) % 2 == 0 ? WHITE : BLACK);
			fillrectangle(j * size_w, i * size_h, (j + 1) * size_w, (i + 1) * size_h);
			// 初始化所有格子的数据
			cell_list[i * NMG_CFG_CELL_ROW_COUNT + j] = { j * size_w, i * size_h, (j + 1) * size_w, (i + 1) * size_h, size_h, size_w };
		}
	}
}

在视图里简单实现一下窗口布局,EasyX主要就是在这里发挥它的作用的。

总结

OK,到这一步我们游戏开发的基本框架算是搭建好了,接下来需要开发游戏的核心玩法了。

如果你对C语言的GUI编程感兴趣的话,请持续关注本系列文章,有建议意见欢迎留言或私信。

PS:需要源码的话请留言,人数多的话我就把项目推到GitHub上去供大家参考。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值