关于假期实训的复盘与思考——基于C语言的贪吃蛇项目(2022.1.14)

目录

引文:

复盘:

项目开发:

        需求分析:

        设计:

        编码开发:

测试:

第一次实训总结:


引文:

        今日结束了七日的假期实训,虽然七日的假期实训课程时间短暂,但是在知识点方面仍可以用极为丰富概括,短短七日让我对软件工程、程序设计及制作、程序测试等等方面有了更进一步的认识,让我一个刚刚踏入软件工程的大一学生大开眼界。这次实训不仅仅有全套的软件设计步骤,还有极为详细的代码编写和纠错教程。对于我而言,短短七日,我甚至可以用脱胎换骨来形容。

        回想起刚刚入学的那几天我还在和同学高谈阔论以后Java,C++等语言有多么丰富,却看不上正在学习的C语言时的潇洒姿态时,我不禁感到十分羞愧,现在想起来C语言只不过是一门面向过程的语言而已,别人做到的它依旧可以。当我自己沉浸在cin、cout时,我却忘记了printf和scanf,这个假期让我重新审视自己重新认识C语言。

        C语言很多人都知道是一门十分基础的语言,不了解的同志百度百科奉上

c语言_百度百科 (baidu.com)),基础的语言必然很是繁琐,当别人(C++)在用cout输出一切时C语言还在printf(“%……”,……);但是基础好就好在很容易理解。所以好多人在说C语言学好很容易但是想要精通你得超越全世界。

        说了这么多,现在让我们看看这个假期我的收获。

复盘:

        这个假期我们用C语言设计了一个贪吃蛇程序,虽然有老师一步步带着做,我还是学到了不少。

项目开发:

        实训第一天我们进行了项目开发的相关知识讲解,项目开发分为需求分析、原型设计、概要设计、详细设计、编码开发、测试、上线部署、运行维护。在这之中最轻松的是编码开发,所以代码开发我最后说。

         因为项目较小我们把三个设计步骤和为一步,部署和维护因为没有资源只能舍去。

        需求分析:

         需求分析这个词第一次听说还是在软工职业实践这门课上,老师在讲这些内容时我还很不解,为什么编码是最简单的,在我看来似乎编那几句代码很费劲。这次让我们亲自做一下需求分析时我们才发现,这些真的很费劲,要不是老师,我估计几天也出不来这个项目的设计,经过大家的头脑风暴后我们写出一些可实现的功能预想,并做了基本功能和拓展功能的分类。

        看着这些功能我们一脸兴奋,接下来一句话让我们瞬间沉默,老师问:“现在我们应该怎么实现这些功能?”,我们都沉默了,谁知道啊,现在还在底层摸爬滚打的我们面对这些天方夜谭的东西谁都说不出个一二三,这就是设计阶段。

        设计:

        设计阶段绝对是这次实训的噩梦,那天我们一脸痴呆的看着老师在屏幕上敲着那些见都没见过的函数名,还有理有据地解释着,总的来说呢老师为我们打开了除了stdio.h这个头文件之外的另一个世界的大门windows.h头文件,这个函数足以满足我对设计的所有想象。当然这些也只仅仅是显示模块,我们为了程序的稳定运行还设计了控制模块和数据模块,各司其职。       

这里只展示部分

         在整个程序的设计过程我的思想渐渐明晰,知道接下来该干什么,知道每一步是在实现哪个功能,知道一个程序剥开外皮的内在真正是什么,面对这些函数,我也从刚开始的不知所措变得沉着冷静,善于思考,也明白了没有一门语言是无处所用的,只是在于你挖掘的深度和思维的广度,不知从哪来了一股自信,相信自己可以搞定这些东西。

        编码开发:

        终于来到了编码环节,这里我先总结一下windows.h这个头文件吧,方便以后复习使用,这个头文件是一个很重要的头文件,在windows桌面程序开发中起到很大的作用,提供各种各样的不同功能的接口,在本次程序设计中我们用其画界面,移动光标,进行构图,定位坐标……甚至发挥想象自己DIY,创造属于自己的蛇。

        以下是windows.h中常用的函数:

1.GetStdHandle

        这个函数用于取得标准设备的句柄(标准输入、标准输出、标准错误)。分别对应STD_INPUT_HANDLE  STD_OUTPUT_HANDLE   STD_ERROR_HANDLE三个句柄。

2.SetConsoleTitle:

        这个函数就有意思了,它是用来改变控制台窗口的名称的,具体用法如下就是在其后加括号双引号里面写自己想改的名字而已,very easy。

3.SetConsoleCursorPosition:

        这个函数用于定位光标。(这次好像没有接触)

4.SetConsoleCursorInfo:

        这个函数也和光标有关,它是用来设置光标的信息的,比如光标的厚度或者透明度。

比如这样:

    //光标信息,用于隐藏光标
    CONSOLE_CURSOR_INFO cursor = {1, FALSE};
    SetConsoleCursorInfo(handle, &cursor);

5.SetConsoleTextAttribute:

        这个函数是我最感兴趣的函数,它可以改变窗口字体的颜色和背景颜色。比如我现在要创造一个游戏区域,那么我需要一个填满空格的有色空间,我就需要以下代码:

//宏变量
#define BACKGROUND_COLOR 0x11           //背景颜色
//绘制游戏背景
void dispBackground()
{
    //设置样式-颜色
    SetConsoleTextAttribute(handle, BACKGROUND_COLOR);
    //输出空格
    for(int i = 0; i < 30; i++)
    {
        printf("%100s\n", "");
    }
}
最后结果(全部)

        

颜色进制对照表

        最后涂完色就是这样一个框,若问宏变量中那个十六进制数是什么,那个是颜色代码,一个十六进制数代表一个颜色,通过颜色进制对照表选自己想要的颜色。

6.system:

        这个也许是我在这些函数方法中见到的最为亲切的一个,因为我经常用它清屏(句柄为CLS)它的句柄也不少,最常用的也许就是冻结屏幕(pause)和清屏了。

7.SetPos:

        这个函数方法一开始我并不知道是干什么的后来我在一次报错中知道了它的作用,它的作用是定位光标,你想让光标在哪,光标就会移动到哪,使用也很简单SetPos(x,y);xy是横纵坐标。

        这些就是关于这次windows.h头文件中部分的函数方法总结,都是自己的理解,如有不对请指明。

        接下来是一个没有接触过的数据类型——枚举类型。

枚举类型enum

        枚举类型指的是当需要定义一个取值范围有限的变量时,可以定义为枚举类型,这种类型更方便一些。代码如下:

//移动方向
typedef enum SnakeDir{
    SnakeLeft =     0b100001,
    SnakeRight =    0b100010,
    SnakeTop =      0b010100,
    SnakeBottom =   0b011000
}SnakeDir;

        这里,蛇的移动方向不会有更多可能,所以定义一个枚举类型方便以后取用。至于后边为什么要用二进制也是为了更好区分上下左右四个方向,用了二进制非0和0两种结果。

        值得一提的是这个函数还用到一种行东西——别名typedef)别名写在函数最后的大括号外部,需要加分号(;)结尾,别名可以与原函数名相同。

链接属性:

        链接属性是对于标识符而言的,有三种属性:external,internal,none。

        none:很好理解没有链接属性,作为单独的个体存在。

        external:属于外部链接属性的标识符不论声明多少次、位于几个源文件都代表一个个体。

        internal:属于内部链接属性的标识符在同一个源文件内指向同一个体,在不同的源文件中指向不同(分属不同)。

        有一种关键字用于修改标识符的链接属性static,当然只是在函数之外还在代码块之外时才是此作用,在函数内时它有另一个含义,使变量的存储类型由自动变量变为静态变量。

结构体struct

        这个知识点在学校的时候老师提过,大致说起来其实就是储存数据的类型不同就用结构体,如果相同就是指针啦,例子代码如下:

//蛇的各种要素
typedef struct Snake{
    int len;			//蛇长度
    int speed;			//蛇速度
    Dir dir;		//蛇的方向
    SnakeNode *head;	//蛇头
}Snake;

不同类型的数据就不一定非要取决于函数定义类型。、

头文件

        头文件的书写很重要,自己写的头文件在引用时如果多次引用会出现令人发指的声明问题,所以这里介绍#ifndef和#endif,他们两个中间的声明或者各种东西再怎么引用也不会出现问题。具体的例子代码如下:

//这里以stdio.h文件为例
//stdio.h
#ifndef STDIO_H_INCLUDED
#define STDIO_H_INCLUDED

/*头文件中放的是
*函数声明
*修饰符 返回值类型 函数名(形参){
*内容
*}
*/

#endif //!STDIO_H_INCLUDED

搞定!

数据结构

        数据结构可以说听大二大三学长学姐抱怨的不能再抱怨了,今天终于遇到了,为什么要用数据结构呢?这个得从蛇说起,贪吃蛇的蛇很重要也很特殊,它要能移动,转向,还要在吃到东西后增长,在碰到墙或自己时死亡,本来一个移动的话我觉得数组就可以满足了,但是后面一系列功能一下子我就懵住了,怎么实现呢,实训老师为我们介绍了数据结构中的链表。

        链表

        链表说起来和数组挺像的,只不过链表由一个个结点组成,和数组最大的区别就是,链表可以随意拆卸组合,可以断开再链接,这个功能神奇到我了,其实后来自己想了一下,链表就是数组和指针的高度结合,每一个结点有自己的x,y还有一个可以指向下一个结点的指针next,每一个链表必须有头(head),接下来我们通过一段代码进行分析:

//初始化一条蛇
void dataInitSnake()
{
int x = 12; int y = 12;

//创建一个新结点
SnakeNode *node = (SnakeNode *)malloc(sizeof(SnakeNode));
//给结点元素赋值
node->x = 12;
node->y = 12;


SnakeNode *node2 = (SnakeNode *)malloc(sizeof(SnakeNode));
//给结点元素赋值
node2->x = 13;
node2->y = 12;


SnakeNode *node3 = (SnakeNode *)malloc(sizeof(SnakeNode));
//给结点元素赋值
node3->x = 14;
node3->y = 12;


node->next = node2; 
node2->next = node3; 
node3->next = NULL;

//将第一个结点赋值给 头head 
snake.head = node;
//设置蛇结点个数
snake.len = 3;
//设置蛇初始化方向
snake.dir = RIGHT;
}

        代码中不难看出应该是分了三段,其中malloc代表新建一个结点,新建语句((链表名 *)malloc(sizef(链表名))),接下来代码很好懂,一个接一个,全接一块,最后没有接的就是NULL,之前设好的结构体snake中的数据在这里一设置便大功告成,一条蛇再经涂色就出现了。

        蛇的移动一定要用到最简单的方式,第一代码好写,第二简单的方式在运行中不会卡顿,第三简单的方式造成程序崩溃的概率小。

        既然已经用了链表了,那么移动也要用到链表的特性,那就是把最后一节放到第一个变成新的蛇头,这样依次转换就实现了蛇在移动的效果,吃食物的时候只增不移动,碰到自己时指针所指变量与结点重合,撞墙是与墙坐标重合,这些问题迎刃而解,也正是在这个时候,我对我以前学的C语言产生了新的体会,或许我通过一些组合创造也能达到完全不同的效果。

游戏控制

        控制模块的书写老师进度很快,总的来说就是大量运用了switch语句,但是有一个新的知识很吸引我,那就是计时器,我一直在想为什么蛇能自己动,计时器的出现解决了我的疑问。

        计时器在我看来就是获取一个时间,在这个时间间隔重新启动一个文件,实现蛇的移动,也可以在难度增加时提高蛇的速度。

下面放一个计时器的例子代码:

//启动定时器
static void startTimer()
{
    //如果定时器已经启动了,停止
    if(timerIsStart)
        stopTimer();

    quere = CreateTimerQueue();
    if(!quere)
        return;
    WINBOOL re = CreateTimerQueueTimer(&timer, quere, timeout, NULL, 0, 1000 - dataGetSnakeSpeed(),
                                       WT_EXECUTEDEFAULT);
    if(!re)
    {
        //构建失败了
        DeleteTimerQueueEx(quere, INVALID_HANDLE_VALUE);
        quere = NULL;
        timer = NULL;
        return;
    }
    timerIsStart = 1;
}
//结束定时器
static void stopTimer()
{
    if(timerIsStart == 0)
        return;
    if(timer != NULL)
    {
        DeleteTimerQueueTimer(quere, timer, INVALID_HANDLE_VALUE);
        timer = NULL;
    }
    if(quere != NULL)
    {
        DeleteTimerQueueEx(quere, INVALID_HANDLE_VALUE);
        quere = NULL;
    }
    timerIsStart = 0;
}
//定时器回调函数
static void CALLBACK timeout(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
    if(gameState != GamePlay)
        return;
    SnakeMoveRe re;
    re = dataSnakeMove();
    if(re.re == 0)
    {
        stopGame();
        return;
    }
    if(re.successState)
    {
        dispSnake(dataGetSnake(), -1, -1);
        dataFoodCreate();
        dispFood(dataGetFoodX(), dataGetFoodY());

        int addGrade = dataGetLevel() + 1;
        dataGradeAdd(addGrade);
        dispUpdateGrade(dataGetGrade());

        return;
    }
    dispSnake(dataGetSnake(), re.snakeFootOldX, re.snakeFootOldY);
}

这个里面包括了三个计时器代码段,分别是启动、关闭、回调,对于游戏而言就是游戏启动,暂停,游戏结束重新开始。

        计时器的编写完成整个程序也就编码完成。

测试:

        测试阶段分为三个阶段:自我测试、内部测试、公开测试,技术测试分为:黑盒测试、白盒测试。最后要提交测试报告,但是因为条件有限,没能亲身体会。

第一次实训总结:

        经过这次的实训我收获了今后写代码时一定要写注释,有注释找起来很方便,其次定义函数和变量时名称一定要与函数表示的内容相结合,意思相关,这样在运用的过程中不会输错也更方便。

        知识点方面,我对如何做一个项目有了一个更深刻的了解,做项目的流程,项目报告的书写,这次贪吃蛇项目中,自己跟着老师一步步书写最后蛇也慢慢浮现出来,整个过程中锻炼自己的思维能力,最终完成整个项目。

        通过这次项目,我知道自己还有很多东西需要了解与学习,路还很长,我仍需更加努力!

        其实看得出来,我最大的收益并不在于知识点的积累,而是写代码时的规范性,想起来以前写代码时在想注释有什么用到第一次找自己想用到的函数找不见,不知道自己定义的函数变量是什么的时候我才慢慢感到注释真的很重要。经过这次实训,我会好好积累错误经验,改正自己的习惯,相信自己会变得更好。

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值