用vc6.0实现用C语言编写一个简单贪吃蛇游戏
控制键:↑↓←→
#include <stdio.h>
#include <stdlib.h>
// stdlib.h里面定义了五种类型、一些宏和通用工具函数。 类型例如size_t、wchar_t、div_t、ldiv_t和lldiv_t; 宏例如EXIT_FAILURE、EXIT_SUCCESS、RAND_MAX和MB_CUR_MAX等等;
// 常用的函数如malloc()、calloc()、realloc()、free()、system()、atoi()、atol()、rand()、srand()、exit()等等。
// 具体的内容你自己可以打开编译器的include目录里面的stdlib.h头文件看看。
#include <Windows.h>//windows编程头文件
//Windows.h头文件之所重要,是因为头文件封装了许多库函数以及一些类,将一些复杂的工作由库函数处理,
//Windows.h头文件中包含了Windef.h、Winnt.h、Winbase.h、Winuser.h、Wingdi.h等头文件,涉及到了Windows内核API,图形界面接口,图形设备函数等重要的功能
#include <time.h>
#include <conio.h>//控制台输入输出头文件
//控制台输入输出)的简写,其中定义了通过控制台进行数据输入和数据输出的函数,主要是一些用户通过按键盘产生的对应操作,比如getch()函数等等。
#ifndef __cplusplus
//一般用于将C++代码以标准C形式输出(即以C的形式被调用),这是因为C++虽然常被认为是C的超集,但是C++的编译器还是与C的编译器不同的。C中调用C++中的代码这样定义会是安全的。
typedef char bool;
//使用 typedef声明的名称将占用与其他标识符相同的命名空间(不包括语句标签)。 因此,它们不能使用与前一个声明的名称相同的标识符(除了在类类型声明中.
#define false 0
//错误为0
#define true 1
//正确为1
#endif
//#endif
//即可以设置不同的条件,在编译时编译不同的代码,预编译指令中的表达式与C语言本身的表达式基本一至如逻辑运算、算术运算、位运算等均可以在预编译指令中使用。
//之所以能够实现条件编译是因为预编译指令是在编译之前进行处理的,通过预编译进行宏替换、条件选择代码段,然后生成最后的待编译代码,最后进行编译。
//将光标移动到控制台的(x,y)坐标点处
void gotoxy(int x, int y)
{
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
#define SNAKESIZE 100//蛇的身体最大节数
#define MAPWIDTH 78//宽度
#define MAPHEIGHT 24//高度
//食物的坐标
struct {
int x;
int y;
}food;
//蛇的相关属性
struct {
int speed;//蛇移动的速度
int len;//蛇的长度
int x[SNAKESIZE];//组成蛇身的每一个小方块中x的坐标
int y[SNAKESIZE];//组成蛇身的每一个小方块中y的坐标
}snake;
//绘制游戏边框
void drawMap();
//随机生成食物
void createFood();
//按键操作
void keyDown();
//蛇的状态
bool snakeStatus();
//从控制台移动光标
void gotoxy(int x, int y);
int key = 72;//表示蛇移动的方向,72为按下“↑”所代表的数字
//用来判断蛇是否吃掉了食物,这一步很重要,涉及到是否会有蛇身移动的效果以及蛇身增长的效果
int changeFlag = 0;
int sorce = 0;//记录玩家的得分
int i;
void drawMap()
{
//打印上下边框
for (i = 0; i <= MAPWIDTH; i += 2)//i+=2是因为横向占用的是两个位置
{
//将光标移动依次到(i,0)处打印上边框
gotoxy(i, 0);
printf(\”■\”);
//将光标移动依次到(i,MAPHEIGHT)处打印下边框
gotoxy(i, MAPHEIGHT);
printf(\”■\”);
}
//打印左右边框
for (i = 1; i < MAPHEIGHT; i++)
{
//将光标移动依次到(0,i)处打印左边框
gotoxy(0, i);
printf(\”■\”);
//将光标移动依次到(MAPWIDTH, i)处打印左边框
gotoxy(MAPWIDTH, i);
printf(\”■\”);
}
//随机生成初试食物
while (1)
{
srand((unsigned int)time(NULL));
food.x = rand() % (MAPWIDTH – 4) + 2;
food.y = rand() % (MAPHEIGHT – 2) + 1;
//生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,若不一致
//会导致吃食物的时候只吃到一半
if (food.x % 2 == 0)
break;
}
//将光标移到食物的坐标处打印食物
gotoxy(food.x, food.y);
printf(\”*\”);
//初始化蛇的属性
snake.len = 3;
snake.speed = 200;
//在屏幕中间生成蛇头
snake.x[0] = MAPWIDTH / 2 + 1;//x坐标为偶数
snake.y[0] = MAPHEIGHT / 2;
//打印蛇头
gotoxy(snake.x[0], snake.y[0]);
printf(\”■\”);
//生成初试的蛇身
for (i = 1; i < snake.len; i++)
{
//蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+2
snake.x[i] = snake.x[i – 1] + 2;
snake.y[i] = snake.y[i – 1];
gotoxy(snake.x[i], snake.y[i]);
printf(\”■\”);
}
//打印完蛇身后将光标移到屏幕最上方,避免光标在蛇身处一直闪烁
gotoxy(MAPWIDTH – 2, 0);
return;
}
void keyDown()
{
int pre_key = key;//记录前一个按键的方向
if (_kbhit())//如果用户按下了键盘中的某个键
{
fflush(stdin);//清空缓冲区的字符
//getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
key = _getch();//第一次调用返回的不是实际值
key = _getch();//第二次调用返回实际值
}
/*
*蛇移动时候先擦去蛇尾的一节
*changeFlag为0表明此时没有吃到食物,因此每走一步就要擦除掉蛇尾,以此营造一个移动的效果
*为1表明吃到了食物,就不需要擦除蛇尾,以此营造一个蛇身增长的效果
*/
if (changeFlag == 0)
{
gotoxy(snake.x[snake.len – 1], snake.y[snake.len – 1]);
printf(\” \”);//在蛇尾处输出空格即擦去蛇尾
}
//将蛇的每一节依次向前移动一节(蛇头除外)
for (i = snake.len – 1; i > 0; i–)
{
snake.x[i] = snake.x[i – 1];
snake.y[i] = snake.y[i – 1];
}
//蛇当前移动的方向不能和前一次的方向相反,比如蛇往左走的时候不能直接按右键往右走
//如果当前移动方向和前一次方向相反的话,把当前移动的方向改为前一次的方向
if (pre_key == 72 && key == 80)
key = 72;
if (pre_key == 80 && key == 72)
key = 80;
if (pre_key == 75 && key == 77)
key = 75;
if (pre_key == 77 && key == 75)
key = 77;
/**
*控制台按键所代表的数字
*“↑”:72
*“↓”:80
*“←”:75
*“→”:77
*/
//判断蛇头应该往哪个方向移动
switch (key)
{
case 75:
snake.x[0] -= 2;//往左
break;
case 77:
snake.x[0] += 2;//往右
break;
case 72:
snake.y[0]–;//往上
break;
case 80:
snake.y[0]++;//往下
break;
}
//打印出蛇头
gotoxy(snake.x[0], snake.y[0]);
printf(\”■\”);
gotoxy(MAPWIDTH – 2, 0);
//由于目前没有吃到食物,changFlag值为0
changeFlag = 0;
return;
}
void createFood()
{
if (snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
{
//蛇头碰到食物即为要吃掉这个食物了,因此需要再次生成一个食物
while (1)
{
int flag = 1;
srand((unsigned int)time(NULL));
food.x = rand() % (MAPWIDTH – 4) + 2;
food.y = rand() % (MAPHEIGHT – 2) + 1;
//随机生成的食物不能在蛇的身体上
for (i = 0; i < snake.len; i++)
{
if (snake.x[i] == food.x && snake.y[i] == food.y)
{
flag = 0;
break;
}
}
//随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
if (flag && food.x % 2 == 0)
break;
}
//绘制食物
gotoxy(food.x, food.y);
printf(\”*\”);
snake.len++;//吃到食物,蛇身长度加1
sorce += 10;//每个食物得10分
snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
}
return;
}
bool snakeStatus()
{
//蛇头碰到上下边界,游戏结束
if (snake.y[0] == 0 || snake.y[0] == MAPHEIGHT)
return false;
//蛇头碰到左右边界,游戏结束
if (snake.x[0] == 0 || snake.x[0] == MAPWIDTH)
return false;
//蛇头碰到蛇身,游戏结束
for (i = 1; i < snake.len; i++)
{
if (snake.x[i] == snake.x[0] && snake.y[i] == snake.y[0])
return false;
}
return true;
}
int main()
{
drawMap();
while (1)
{
keyDown();
if (!snakeStatus())
break;
createFood();
Sleep(snake.speed);
}
gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2);
printf(\”Game Over!\\n\”);
gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2 + 1);
printf(\”本次游戏得分为:%d\\n\”, sorce);
Sleep(500);
return 0;
}
嵌入式C语言实现进程调度实验学习
通过一个简单的进程调度模拟程序的实现,加深对各种进程调度算法,进程切换的理解。
1、进程调度算法:采用动态最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)。
2、每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:
进程名—-进程标示数ID;
优先数—-Priority,优先数越大优先权越高;
到达时间—-进程的到达时间为进程输入的时间;
进程还需要运行时间—-AllTime,进程运行完毕AllTime =0;
已用CPU时间—-CPUTime;
进程的阻塞时间StartBlock—-表示当进程在运行StartBlock个时间片后,进程将进入阻塞状态;
进程的阻塞时间StartTime—-表示当进程阻塞StartTime个时间片后,进程将进入就绪状态;
进程状态—-State;
队列指针—-Next,用来将PCB排成队列。
3、调度原则
进程的优先数及需要的运行时间可以事先人为地指定(也可以由随机数产生)。进程的到达时间为进程输入的时间;
进程的运行时间以时间片为单位进行计算;
进程在就绪队列中带一个时间片,优先数加1;
每个进程的状态可以是就绪R(Ready)、运行R(Run)、阻塞B(Block)、或完成F(Finish)四种状态之一;
就绪进程获得CPU后都只能运行一个时间片,用已占用CPU时间加1来表示;
如果运行一个时间片后,进程的已占用CPU时间已达到所需要的运行时间,则撤消该进程,如果运行一个时间片后进程的已占用CPU时间还未达所需要的运行时间,也就是进程还需要继续运行,此时应将进程的优先数减3,然后把它插入就绪队列等待CPU;
每进行一次调度程序都打印一次运行进程、就绪队列、以及各个进程的PCB,以便进行检查;
重复以上过程,直到所要进程都完成为止。
Ⅰ:函数实现:
创建进程PCB:PCB* CreateProcess(PCB *H,int num)
将进程队列排序:PCB* Ready(PCB *H)
将q结点插入进程队列H:void Insert(PCB *H,PCB *q)
输出正在运行的进程:void Disp1(PCB *H)
输出就绪队列中的进程 :void Disp2(PCB *H)
进程调度实现PCB* Dispatch(PCB *H)
Ⅱ:主要功能的实现过程:
1.构建进程控制块PCB的数据结构:
根据实验要求构建进程的PCB信息,构建数据结构,分析可知进程名ID、优先数Priority、到达时间Get Time、进程还需要运行时间AllTIme、已用CPU时间CUPTime、进程阻塞时间StartBlock、StartTime、都可用int类型来表示,进程状态选择使用字符指针char *State实现字符串来表示,队列指针使用数据结构本身的指针Struct pcb *Next来表示,具体实现的代码截图如下:
图3-1进程控制块PCB的数据结构
2.创建进程队列:
使用带头结点的链表构建进程队列,根据用户输入的进程数创建多个进程,用户输入进程的ID、优先数以及运行时间,并且询问是否要有阻塞,有输入,没有则默认等于-1(使默认值小于零便于控制),将到达时间和CPU使用时间赋值为0,进程状态赋值为就绪,具体代码截图如下:
图3-2构建进程队列
3.将进程队列按优先数从大到小排序(使用带头结点的链表交换节点实现冒泡排序):
创建两个PCB指针变量head和rear分别指向进程队列的第一个元素结点和最后有个元素结点,使用head指针来比较head当前结点的优先数和head后一个结点的优先数大小,每一轮比较都将优先数最小的结点放到链表的最后,多轮循环实现将进程队列按优先数从大到小排序,并返回进程队列的头结点,具体代码截图如下:
图3-3进程队列排序实现就绪队列
4.进程调度的实现过程:
其中包含两个带头结点的链表表示的队列分别为就绪队列和阻塞队列,首先创建就绪队列ready和阻塞队列block,创建一个后期循环调度需要用到的中间进程队列end,首先循环遍历进程队列中各个进程结点,将进程状态为就绪的进程结点插入就绪队列中,将进程状态为阻塞的进程结点插入阻塞队列中。这两个队列总共分为三种情况
1)两个队列均为空,所以不需要任何操作,直接返回。
2)就绪队列不为空,而阻塞队列空或者不空都可过程如下:
将插入完毕的就绪队列按优先数大小进行排序,根据实验要求输出正在运行的进程后该进程的优先数减3,进程CPU使用时间加1,进程还需要运行时间减1,startblock减1。首先判断startblock是否为0,为零将进程状态设为阻塞,其次如果startblock不等于0而运行时间等于0,那么将该进程的进程状态改为完成,如果都不是上述两种状态,那么将进程的进程状态改为运行。之后输出ready的第一个进程,也就是正在运行的进程,然后遍历就绪进程队列,如果状态使就绪那么优先数加1,如果不是就绪状态而是运行状态那么就将状态改成就绪(不加1),当然如果使完成不需要任何操作,然后将ready进程队列插入end队列。遍历block进程队列,starttime减1,如果starttime等于0,那么将进程状态从阻塞改为就绪,并且将每一进程插入到end队列里。
3)就绪队列为空,阻塞队列为空,只进行如下:
遍历block进程队列,starttime减1,如果starttime等于0,那么将进程状态从阻塞改为就绪,并且将每一进程插入到end队列里。
最后返回end 在下一次的将队列分为就绪和阻塞队列里,状态为完成的进程使不会插入的,如此反复直至进程队列为空程序终止。
进程调度的代码截图如下:
图3-4进程调度
Ⅲ运行结果:
输入:
3-5输入数据
输入进程数为3,第一个进程的进程ID为1,优先数为3,需要运行的时间为4,时间2之后进入阻塞,阻塞时间为2,第二个进程进程ID为2,优先数为1,需要运行的时间为3,
第三个进程ID为3,优先数为2,需要运行时间为2。
输出:
图3-6-1第一次运行结果
第一次运行所有进程的状态都是就绪,其中优先级最高的进程1,先运行,优先级减3变成0,运行时间减1变成3,已用时间加1变成1,开始阻塞时间减1变成1,阻塞时间不变,状态为运行,其他两个进程优先数均加1。
图3-6-2第二次运行结果
第二次运行,由于进程1在第一次运行减3,已经变得很小,测个进程3的优先级最大,先运行,变化如一所示,唯一不同就是进程3没有阻塞过程,只少了这一个过程,其他过程均相同。
图3-6-3第三次运行结果
图3-6-4第四次运行结果
第四次运行进程1的startblock时间已为0,进程状态变为阻塞,结果还需要在下一次运行才能看出来。
图3-6-5第五次运行结果
第五次运行就进程1而言,在上一次运行进入阻塞,此次运行进程1没有运行也没有在就绪队列里,它进入了阻塞队列,如果在他阻塞时间2耗尽后,也就是第七次运行,如果进程1出现在运行或者就绪队列里,证明进程阻塞是没有问题的。当然就进程3而言,运行时间耗尽,状态变成完成,结果要在下一次运行中才能看出。
图3-6-6第六次运行结果
第六次运行进程3,已经不在运行或者就绪队列里了,也没有进行阻塞,所以进程3被释放,保证了进程运行时间耗尽进程释放的功能。
图3-6-7第七次运行结果
第七次运行,此刻进程2的情况和第五次运行的进程3的情况相同,就不做过多描述,重要的是进程1在耗尽阻塞时间2之后,回到了就绪队列中,证明阻塞的成功实现。
图3-6-8第八九次运行结果
第八次第九次运行结果都是进程1 运行了2个时间,不做描述。
图3-6-8第十次运行结果
第十次程序中进程都完成已无进程。
1.在实现将进程队列按优先数由大到小排序时节点交换的指针不清晰导致无法实现对进程队列的排序;通过画图对指针移动过程的调试解决了此问题。
2.进程调度函数中将进程队列插入就绪队列和阻塞队列时因为没有构建相应的头结点导致无法插入;创建相应的就绪队列和阻塞队列的头节点解决了此问题。
3.在无阻塞情况下完成后,将阻塞机制加入时,与原有程序有许多冲突:通过将就绪和阻塞队列分情况,解决了此次问题,还有就是阻塞的加入我刚觉有点像是信号量,一个加锁,一个解锁。
实验完整源代码:
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。