C语言结构数组实现贪吃蛇小游戏

一、设计思路

蛇身本质上就是个结构数组,数组里存储了坐标x、y的值,再通过一个循环把它打印出来,蛇的移动则是不断地刷新重新打印。所以撞墙、咬到自己只是数组x、y值的简单比较。

二、用上的知识点

结构数组Windows API函数

三、具体实现

先来实现静态页面,把地图、初始蛇身、食物搞定。

这里需要用到Windows API的知识,也就是对控制台上坐标的修改

//这段代码来自参考1
void Pos(int x, int y)
{
 COORD pos;
 HANDLE hOutput;
 pos.X = x;
 pos.Y = y;
 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 SetConsoleCursorPosition(hOutput, pos);
}

COORD是Windows API中定义的一种结构,表示在控制台上的坐标

typedef struct _COORD {
SHORT X; // horizontal coordinate
SHORT Y; // vertical coordinate
} COORD;

而代码中第七行则是获得屏幕缓冲区的句柄,第八行是直接修改光标位置的函数。

1.地图。

有了Pos()函数,打印一个框就不是问题了。假如我们用"-"作为上下边框,把"|"作为左右边框,这看起来没什么不妥,但其实我们已经掉进了坑里,直接上代码及实际效果图吧。

//LONG==60
//WIDTH==30
void CreateMap()
{
 int i;
 for(i=0;i<LONG;i++)//上下两行
 {
 Pos(i,1);
 printf("-");
 Pos(i,WIDTH-1);
 printf("-");
 }
 for(i=2;i<WIDTH-1;i++)//左右两列
 {
 Pos(0,i);
 printf("|");
 Pos(LONG-1,i);
 printf("|");
 }
}

发现了问题吗?这是一条正常的蛇。。。那为什么看起来不正常呢?我们把边框都换成"#"来看看…

这就清楚多了啊,要知道我们上下边框可是各有60个"#"的,长60宽30的长方形输出之后竟然成了个正方形。

原因在这

控制台上每个字符的长宽比例(像素点)是不同的,所以才会出现上图这种蛋疼的情况。

解决方法其实也很简单,我们需要引入一些特殊符号,比如"●""■""⊙"等,这些字符的特点是它占据两个普通字符的位置

所以上下边框就有60/2=30个符号,要让它仍然是个正方形的话,左右也可以设为30(28+2)个符号.

代码及效果图如下

void CreateMap()
{
 int i;
 for(i=0;i<LONG;i+=2)
 {
 Pos(i,0);
 printf("■");
 Pos(i,WIDTH-1);
 printf("■");
 }
 for(i=1;i<WIDTH-1;i++)
 {
 Pos(0,i);
 printf("■");
 Pos(LONG-2,i);
 printf("■");
 }
}

这样看就舒服多了,不过也让复杂度提升了一些,上边框每个符号的坐标分别是(0,0)(2,0)(4,0)…(2*n-2,0)这个在蛇的移动及食物的模块再提。

2.初始化一条蛇

因为蛇以及食物 本质上都是一个坐标,所以我们可以定义一个新的数据类型Node,每一个Node都是一个存储了两个变量(x、y)的结构体,再通过Node来定义蛇和食物。

typedef struct node{
 int x;
 int y;
}Node; 

Node snake[60];

好了,我们现在定义了一条叫snake的蛇。为了这条蛇肥胖适中长宽比例一致,我们用"⊙"代表蛇的每一节。刚开始我们令蛇出现在地图中间位置,蛇头在右,共3个节点。所以我们需要求得每个节点的坐标。

 void InitializeSnake()
{
 int i;
 for(i=0;i<3;i++)
 {
 snake[i].x = (LONG/2-i*2);//(30,15)(28,15)(26,15)
 snake[i].y = WIDTH/2;
 Pos(snake[i].x,snake[i].y);
 printf("⊙");
 }
}

这样我们就在(30,15)(28,15)(26,15)三个坐标处确定了一条蛇。X坐标之间减2是因为"⊙"在X轴占两个基本值。

3.随机出现食物

先创建一个变量来存储食物的坐标

Nodefood;

得到它的坐标其实就是用随机值对长、宽取余,使值在区间(地图)范围内。

void CreateFood()
{
 int i;
 srand((unsigned int)time(0));
 while(1)
 {
 do{
  food.x = rand()%(LONG-6)+2;
 }while(food.x%2!=0);
 food.y = rand()%(WIDTH-2)+1;
 for(i=0;i<3+length;i++)
  if(food.x==snake[i].x && food.y==snake[i].y)
  {
  i=-1;
  break;
  }
 if(i>=0)
 {
  Pos(food.x,food.y);
  printf("●");
  break;
 }
 }
 //AfterEatFood();
}

X的坐标值求法为rand()%(LONG-6)+2,因为食物"●"也是两个字符的位置,所以它可能的取值为(2,y)(4,y)…(56,y)上下变宽共30个字符,从0开始,每个+2,所以最后一个为(58,y)

Rand()%(LONG)的取值范围为0~59而x=1,x=2,x=58,x=59是地图范围,所以得对LONG-6(60-6=54)取余,这样取值范围就是0~54,再加2,就成了2~56.又因为蛇的各节坐标及移动x坐标都是+2,所以食物的x坐标必须是偶数,这可以用一个do(…)while()搞定,先取值,再判断,不行就再取值

Y的坐标稍微简单些,只要保证坐标值在1~28就行。

另外求出了坐标之后要判断食物是否与蛇身重合,重合的话重新赋值。

搞完上面的,我们就有了一个基本的(静态)效果了,现在我们要让它动起来

注:第86行是设置控制台窗口长、宽的系统函数。

4.让蛇动起来

蛇每次移动背后发生的事就是数组里的值改变,再在每个坐标位置打印蛇身。

为了让蛇一直动,我们就需要一个循环

while(1)
{
 //获得输入,改变坐标
 //在每个坐标处输出
}

首先,我们需要确定方向,而这需要两个变量,一个是输入值(可能是任意值),另一个则是确定方向的变量。

这里介绍一个函数

int kbhit(void);
// 检查当前是否有键盘输入,若有则返回一个非0值,否则返回0

这是一个非阻塞函数,有键按下时返回非0,但此时按键码仍然在键盘缓冲队列中。所以在确定键盘有响应之后,再用一个char变量将输入从缓冲区中调出来。

if(kbhit())
 ch = getch(); 

再对ch做判断,如果是符合情况(不能往后走等)的输入,则开始执行switch改变坐标

if(ch=='w'&&direction!='s')
 direction = ch;
else if(ch=='s'&&direction!='w')
 direction = ch;
else if(ch=='a'&&direction!='d')
 direction = ch;
else if(ch=='d'&&direction!='a')
 direction = ch;
else if(ch==' ')
 continue;

这里设置空格是暂停,而为了让蛇一开始就移动,我们把direction设置为d(往右)。

在方向确定了之后,再用一个switch语句进行坐标判断

switch(direction)
{
 case 'w':
 if(snake[0].x==food.x && snake[0].y-1==food.y)
 {
  length++;
  score+=10;
  snake[2+length].x = snake[2+length-1].x;
  snake[2+length].y = snake[2+length-1].y;
  for(i=length+3-2;i>0;i--)
  {
  snake[i].x = snake[i-1].x;
  snake[i].y = snake[i-1].y;
  }
  CreateFood();
 }
 else
 {
  Pos(snake[2+length].x,snake[2+length].y);
  printf(" ");
  for(i=length+3-1;i>0;i--)
  {
  snake[i].x = snake[i-1].x;
  snake[i].y = snake[i-1].y;
  }
 }
 snake[0].y -=1;
 break;
 case 's':
 //。。。
 case 'a':
 //。。。
 case 'd':
 //。。。
}

对蛇头的下一步做判断,如果吃到了食物的话,则先对分数等全局变量进行处理,再把snake[2+length-1](吃到食物后的倒数第二个变量)的值赋值给snake[2+length](此时新加的尾节)。

再从倒数第二节开始,把前一节的坐标值赋给后一节,直到第二节得到了之前蛇头坐标。在食物被吃了之后,再调用随机出现食物函数。

如果没有吃到食物的话,先到之前最后一节的坐标处,输入空格,算是销毁它再对各节重新赋值。在蛇头后每节都赋值完成之后,根据输入值单独对蛇头赋值,如输入是'w',则往上,所以蛇头纵坐标减一。

对其余输入也是同样的道理,在snake数组各值都更新之后,再用一个函数把它打印出来。

这样移动部分就实现了,现在只需处理一些小模块就行。

5.移动后的处理。

这一部分相对简单,即对判断蛇是否撞墙、是否咬到自身,再对这种情况做处理,我们用两个函数搞定它

int ThroughWall()
{
 if(snake[0].x==0 || snake[0].x==58 ||
 snake[0].y==0 || snake[0].y==29)
 {
  Pos(25,15);
  printf("撞墙 游戏结束");
  return 1;
 }
 Pos(0,WIDTH);
 printf(" ");
}
int BiteItself()
{
 int i;
 for(i=3;i<=2+length;i++)
 if((snake[0].x==snake[i].x) && (snake[0].y==snake[i].y))
 {
  Pos(25,15);
  printf("咬到自己 游戏结束");
  return 1;
 }
}

当返回值为1时,游戏也就GG了。

if(ThroughWall()==1)
{
 Pos(25,WIDTH);
 system("pause");
 exit(0);
}
if(BiteItself()==1)
{
 Pos(25,WIDTH);
 system("pause");
 exit(0);
}

最后再加一行Sleep()函数,对刷新时间(每次重新打印的时间间隔)做处理。speed是一个变量,在每次吃到食物后递减。

Sleep(speed);

源代码在这:结构数组实现_贪吃蛇源码

四、总结与反思。

首先从蛇的结构上来说,结构数组的实现直接无视了"效率"这个词,数组占用大量空间且有容量限制,并不是一种好办法。

其次是BUG的问题,在ThroughWall()函数中,在对蛇头坐标进行判断时在蛇头移动到(x,1)位置时,游戏直接结束,且没有任何提示。

但诡异的是,在判断后加入 Pos(0,WIDTH);printf(" "); 这两行不相干的语句后,这个问题解决了,而我对这两行语句的原有目的则只是想把闪烁不停光标放到地图外面去。

还有就是while()循环里代码行太多,特别是switch-case 里各项,蛇身的移动(结构数组个元素坐标值的变换)应该抽象成一个move()函数。

五、其他。

这是对我第一份代码(snakeV1.0)的重构,程序结构上有较大变化

重构期间研究了链表实现_贪吃蛇源码,在结构上采用了里面的部分思想。

个人空空如也的github:MagicXyxxx的github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C语言菜鸟基础教程之判断

    (一) 先动手编写一个程序: #include <stdio.h> int main() { if(1) { printf("The condition is true!\n"); } return 0; } 运行结果: The condition is true! 再把1依次改为,2,5,100,-10,发现运行结果完全一样. 再改成if(0),此时发现没有运行结果,说明printf()语句没被执行. C语言把判断语句中的任何非0或非空的值当作真.所以if(1), if(

  • 必须知道的C语言八大排序算法(收藏)

    概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序-直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

  • C语言菜鸟基础教程之a++与++a

    (一)a++ 在C语言或其它语言中,++符号表示"自加",就是变量在原来的基础上加1. 例1: a = 0; a++; 则此时a的值为1. 同样的道理,--表示"自减". 例2: a = 100; a--; 则此时a的值为99. 注意,程序语言里没有"自乘"和"自除"的概念. 验证程序: #include <stdio.h> int main() { int a = 0; // 给a赋值 a++; printf(&

  • C 读取ini文件的实例详解

    C 读取ini文件 前言: 在Windows下可以用GetPrivateProfileString或GetPrivateProfileInt方便读取.ini配置文件内容,但是在Linux平台上就一筹莫展了.为了解决该问题,打算用C来读取.ini,即可不受平台的限制了. #define CONF_FILE_PATH "Config.ini" #include <string.h> #ifdef WIN32 #include <Windows.h> #include

  • C语言菜鸟基础教程之求1到100的和

    题目:求1+2+3+--+98+99+100 (1)用数学方法求解 (2)编写C语言程序求解 解: (1)数学方法 S = 1+2+3+--+98+99+100 = (1+100) + (2+99) + (3+98) + -- + (49+52) + (50+51) = 101 * 50 = 5050 (2)C语言编程 #include <stdio.h> int main() { int sum = 0; // 赋初值 for(int i = 1; i <= 100; i++) { s

  • linux下c语言的多线程编程

    我们在写linux的服务的时候,经常会用到linux的多线程技术以提高程序性能 多线程的一些小知识: 一个应用程序可以启动若干个线程. 线程(Lightweight Process,LWP),是程序执行的最小单元. 一般一个最简单的程序最少会有一个线程,就是程序本身,也就是主函数(单线程的进程可以简单的认为只有一个线程的进程) 一个线程阻塞并不会影响到另外一个线程. 多线程的进程可以尽可能的利用系统CPU资源. 1创建线程 先上一段在一个进程中创建一个线程的简单的代码,然后慢慢深入. #incl

  • C语言中二级指针的实例详解

    C语言中二级指针的实例详解 用图说明 示例代码: #include <stdio.h> int main(int argc, const char * argv[]) { // int a = 5; int *p1 = &a; //-打印地址-----地址相同--------------- printf("&a = %p\n", &a);// printf("p1 = %p\n", p1);// int **p2 = &p

  • C语言结构数组实现贪吃蛇小游戏

    一.设计思路 蛇身本质上就是个结构数组,数组里存储了坐标x.y的值,再通过一个循环把它打印出来,蛇的移动则是不断地刷新重新打印.所以撞墙.咬到自己只是数组x.y值的简单比较. 二.用上的知识点 结构数组Windows API函数 三.具体实现 先来实现静态页面,把地图.初始蛇身.食物搞定. 这里需要用到Windows API的知识,也就是对控制台上坐标的修改 //这段代码来自参考1 void Pos(int x, int y) { COORD pos; HANDLE hOutput; pos.X

  • C语言实现简单的贪吃蛇小游戏

    本文实例为大家分享了C语言实现简单贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 #温馨提示:以下设计在VS2019下完美运行,完整代码附在末尾,供大家学习参考 (1)游戏进入界面(动态读取以及温馨提示) void loading()//动态加载图 { int j; gotoxy(43, 11); printf("Loading..."); gotoxy(39, 13); printf("□□□□□□□□"); gotoxy(39, 13); for (j = 8

  • C语言实现贪吃蛇小游戏

    本文实例为大家分享了C语言实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 一.程序实现的原理: 1.构造蛇身:定义一个坐标数组,存放的是蛇的每一节蛇身所在的坐标位置.这样就将移动蛇身的操作转换为移动数组的操作,将吃食物增加蛇身体长度的操作转换为在数组后面追加元素的操作. 2.移动效果:每次移动时,将每一节蛇身(蛇头除外)依次往前移动一节,然后擦去蛇的最后一节,最后确定蛇头的方向,再绘制一个蛇头.这样就会显示一个移动效果. 3.身体增加效果:每次移动时候,判断蛇头是否碰到了食物,如果碰到了食

  • C语言实现简单贪吃蛇小游戏

    本文实例为大家分享了C语言实现简单贪吃蛇的具体代码,供大家参考,具体内容如下 依然是非常传统的贪吃蛇小游戏,网上很多教程,配置好ege图形库就好,不然没有grapics.h另外,还需要添加snakes.h,直接上代码吧,注释很详细了.觉得难度有一部分来源于配置图形库. snakes.h void welcome(); void initSnake(); void createPoint(); void drawSnake(); void drawRandomPoint(); void view(

  • C语言制作贪吃蛇小游戏

    本文实例为大家分享了C语言制作贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 直接上代码 ​#include <stdio.h> #include <stdlib.h> #include <math.h> #include <conio.h> #include <time.h> #include <windows.h>   //MAXWIDTH.MAXHEIGHT.INITLEN 以字符记 #define MAXWIDTH (30)

  • 使用C语言实现贪吃蛇小游戏

    本文实例为大家分享了C语言实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 前言 控制台的欢乐就是这么简单: 提示:以下是本篇文章正文内容,下面案例可供参考 一.贪吃蛇实现的结构和方式 1.用枚举定义蛇的移动方向 enum Dir {     UP,     DOWN,     LEFT,     RIGHT,//枚举不能用分号; }; //创建结构体,对蛇的参数进行设置: struct Snake {     int size;//蛇的节数;     int dir;//蛇的方向;   

  • C语言实现桌面贪吃蛇小游戏

    本篇写的是桌面贪吃蛇小游戏,大家自己看吧,感谢大家的支持,谢谢!O(∩_∩)O~~ #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <commctrl.h> #include <time.h> #include <stdlib.h> #include "shlobj.h" #include <stdio.h> #include <string.h

  • Linux下C语言实现贪吃蛇小游戏

    本文实例为大家分享了C语言实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 此次贪吃蛇小游戏的目的是使得我在Linux底下使用vi进行编写的 心得: 1.自己对linux中如何使用vi更加熟悉 如::wq yy pp dd u 等等 2.对c语言的指针,结构体,链表等更加的牢固 3.借此小项目也运用到多线程作为进入linux的深入学习打下坚实的基础 代码展示 #include<curses.h> #include<stdlib.h> #define UP 1 //1与-1的目的

  • 用C语言实现贪吃蛇小游戏

    本文实例为大家分享了C语言实现贪吃蛇小游戏的具体代码,供大家参考,具体内容如下 实现功能 蛇最开始三节,向右移动.用户可以通过按上下左右来控制蛇的移动,食物随机产生,蛇吃到食物后蛇的身体会变长.蛇撞墙或者撞到自己身体后,游戏结束. 怎么实现 要实现一个贪吃蛇小游戏,首先要想清楚游戏里有什么,怎样实现功能. 很明显游戏中只有两样东西,蛇和食物. 所以要建立蛇和食物信息,然后将蛇和食物进行初始化,在将蛇和食物画出来. 实现的功能有: 1. 蛇的移动  2. 按键控制蛇的移动  3. 食物的产生  4

  • C语言单链表贪吃蛇小游戏

    C语言实现单链表控制台贪吃蛇小游戏 编译环境:vs2019 需求: 统计游戏开始后的时间,控制贪吃蛇:吃到食物蛇身加长,得分加一:碰墙或蛇头碰到身体减一条生命:生命消耗完则结束游戏. 思路: 使用wasd键控制蛇的移动方向,蛇头碰到食物得分加一,并在地图上随机产生一个食物,累加得分,碰墙或碰自己减一条生命,并初始化整条蛇,生命值为0时结束游戏. 做法: 使用单链表控制贪吃蛇移动的核心思想就是:链表存储贪吃蛇所有坐标,每次循环贪吃蛇不断向一个方向插入一个新的结点作为新的蛇头,按下按键控制新蛇头产生

随机推荐