众所周知,贪吃蛇有很多的方法可以实现,主流的方法有链表法,和数组法。
数组法的优势是很明显的,但是缺点也很大。
缺点在于:
1,在对蛇这一个数组进行定义的时候,要预留出足够大的空间,这一段空间是没有任何作 用的。
2,每一次显示的时候都要重新对数组进行遍历,使程序的更加复杂。
3,数组只是在显示上,让游戏的画面看起来像是贪吃蛇,但是本质上并没有什么关系。
优势又在于:
1,在判断游戏中的各种关键时刻的时候,数组更加的容易判断。
2,不用特意的设置复杂的坐标进行打印。(数组本身就有坐标的性质)
链表则是于数组的优势完完全全的相反。
缺点在于:
1,对程序员的要求较大,要设计复杂的坐标。
2,在判断游戏的关键时刻的时候较为的困难。
优势在于:
1,思路清晰,逻辑易于理解。
2,打印的本质是更新而不是遍历,程序运行不复杂。
3,没有一丝丝没有用的空间。
上效果:
上代码!
头文件的引入:
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>
#include"cursor.h"
写代码的第一步就是把代码的流程构建出来,然后更具各种功能,制作各种函数。
int main()
{
//开始游戏,把控制台变得可以控制
//隐藏光标
HideCursor();
//开始界面
GameStart();
Welcome();
//画地图
CreatMap();
//画蛇
InitialSnake();
//放食物
setFood();
//游戏相关的运行
RunGame();
}
#include <conio.h>
#include <windows.h>
///设置光标的函数,使控制控制台变得简单
void gotoxy(int x,int y)
{
COORD pos={x,y};
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOut,pos);
}
void HideCursor() { // 隐藏光标位置 ,这个函数复制代码就行
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
控制台的操作部分。
gotoxy();是控制光标到位置。
HideCursor();是在游戏中隐藏光标的函数。这一段不用特别的学习。
贪吃蛇是游戏,所以要制作一个游戏进入的界面,各位可根据自己的需求自由的设置页面。你甚至可以多设置几个页面,把规则什么的都写进去。
void GameStart()
{
system("mode con cols=100 lines=40");
}
void Welcome()
{
printf("\n\n\n");
printf("\t\t\t======================================================\n");
printf("\t\t\t= =\n");
printf("\t\t\t= welcome to snake_t's world =\n");
printf("\t\t\t= Programmer : 乔子轩 =\n");
printf("\t\t\t= =\n");
printf("\t\t\t= =\n");
printf("\t\t\t======================================================\n");
system("pause");
system("cls");
}
对于第一个文件,我的建议是把控制台重新规划一下,这样的话,你自己对自己设置的坐标也有一个认知。
特别要提的是
system("pause"); 这一个语句的意义是把现在控制台的内容定住,强制让他不进入到下一个阶段。
system("cls"); 这一句的作用是把控制台中的内容清空,使适合下一个阶段的显示 。
(头文件:stdlib.h)
贪吃蛇要有一个地图:
这里开始涉及到了坐标的问题,建议大家在开始写之前就把自己的地图坐标完成,不然在后面蛇的移动以及其他的步骤的时候会出现很多的bug。我就是一个过来人。具体怎么设定吗,都是没有问题的,看你自己怎么方便就怎么来。
#define X_MAX 30 /* 水平最大值 */
#define Y_MAX 30 /* 竖直最大值 */
#define X_CHAR_WITH 2 /* 汉字宽度为2,英文为1 */
#define X_OFFSET 8 /* X偏置 */
void CreatMap()
{
for(int i=0;i<(X_MAX+2);i++)
{
gotoxy(X_CHAR_WITH * (i + X_OFFSET-1),0);
printf("墙");
gotoxy(X_CHAR_WITH * (i + X_OFFSET-1), Y_MAX+1);
printf("墙");
}
for(int i=1; i<(Y_MAX+1);i++)
{
gotoxy( (X_OFFSET-1 )* X_CHAR_WITH ,i);
printf("墙");
gotoxy((X_OFFSET + X_MAX ) * X_CHAR_WITH, i);
printf("墙");
}
showScore();
}
这里小炫了一波技,注意,汉字在C语言程序中是属于特殊字符,而特殊字符在C语言中的打印是占据两个空位的,所以我就定义的一个“汉字的宽度”的宏变量,方便之后坐标的使用与打印。
地图什么的也有了。剩下的需要的就是食物分布和蛇了。
· 仔细思考,食物不可以出现在蛇的身体上,所以果断的选择先设定蛇。
蛇的构建:
//结构体定义蛇
typedef struct snake
{
int x;
int y;
struct snake *next;
}snake_t;
snake_t *linkHead = NULL;
snake_t *linkEnd = NULL;
设置的同时,把链表的头尾都用指针定义,以后要用。但是注意,现在还没有意义,头尾是要之后赋予的。
然后就是喜闻乐见的链表构建环节了。
void InitialSnake()
{
snake_t *temp;
linkHead = (snake_t*)malloc(sizeof(snake_t));
linkHead->x = 5;
linkHead->y = Y_MAX / 2;
linkHead->next = NULL;
temp = linkHead ;
for(int i = 1; i < 3; i++) ///这里让循环的次数多一点点有利于我们检查
{
snake_t *p = (snake_t*)malloc(sizeof(snake_t));
p->x = linkHead->x + i;
p->y = linkHead->y;
p->next = NULL;
temp->next = p;
temp = p;
}
linkEnd = temp; //找尾巴
ShowSnake();
}
这一段代码的作用很大,创建了链表的基本结构的同时把链表的头,和链表的尾部都做了赋值,这样的话,之后只要时时刻刻保持着对链表的尾部指针的更新,和头部指针的保护就没有问题了。
蛇的问题我们解决了,之后就是食物的问题了。、
首先对食物放置有一个基本的认识。食物不可以放在蛇的身上,食物不可以放出地图,食物一定要随机生成,啊,烦死了是不是。
typedef struct fd
{
int x;
int y;
}food_t;
food_t *food = NULL; //食物的初始设定
///放置食物
void setFood()
{
int flgFood = 1;
food = (food_t*)malloc(sizeof(food_t));
do
{
snake_t *temp;
srand((unsigned)time(NULL));
food->x = rand() % X_MAX;
food->y = rand() % Y_MAX;
//开始遍历链表,确定食物不在蛇的身体上
temp = linkHead;
while (temp!= NULL)
{
if((temp->x == food->x)&&(temp->y == food->y))
{
flgFood = 0;
}
temp = temp->next;
}
}while(flgFood = 0);
gotoxy(X_CHAR_WITH * (food->x + X_OFFSET), food->y +1 );
printf("食");
}
这段代码中细节满满,有着处理各种情况的样例,宝们慢慢看。(我认为重点是判断食物是不是在蛇身上的那一段哦)
这之前的都是初始化的内容,正真的游戏内容现在才刚刚开始。
#define RIGHT 77 /*键盘上下左右的设定*/
#define LEFT 75
#define DOWN 80
#define UP 72
int flgEnd = 1; //作为状态量去判断游戏是否结束
void RunGame()
{
char ch = RIGHT;
int direction = RIGHT;
int delay = 0;
while(1)
{
Sleep(1);//间隔3毫秒检测按键
if(flgEnd == 0)
{
system("cls");
GameOver();
break;
}
//判断有没有输入
if(kbhit()) //一直获取
{
ch = getch();
switch(ch)
{
case RIGHT:
direction = RIGHT;
break;
case UP:
direction = UP;
break;
case LEFT:
direction = LEFT;
break;
case DOWN:
direction = DOWN;
break;
}
}
if(++delay > 10)
{
MoveSnake(direction); //让蛇正真开始运动的函数,其中还包含了各种判断
delay = 0;
}
}
}
这一段代码和马上要展现的代码中,状态量的运用是程序的关键,我不敢说自己的状态量用的好,但是好歹是可以达成效果。
同时完成延时的方法也是用了两种。第一种使用Sleep函数可以达到精确定时的效果;第二种设置delay变量,然后让delay加到特定的值也是一种方法,但是这种方法没有办法达到精确计时的效果。
接下来的是贪吃蛇的核心程序,运动与判断。
int proDirection = RIGHT; //判断输入的前后方向是否变化的状态量
void MoveSnake(int direction) //移动蛇,给链表加上一个节点, 链表的头是蛇的尾巴!!!链表加上尾巴,删去链表的头
{
//蛇头向前移动一步
snake_t *temp = (snake_t*)malloc(sizeof(snake_t));
temp->next = NULL;
linkEnd->next = temp;
//判断方向的录入是否合理
if(proDirection == UP && direction == DOWN)
{
direction = proDirection;
}
if(proDirection == DOWN && direction == UP)
{
direction = proDirection;
}
if(proDirection == RIGHT && direction == LEFT)
{
direction = proDirection;
}
if(proDirection == LEFT && direction == RIGHT)
{
direction = proDirection;
}
if(direction == RIGHT) //右
{
temp->x = linkEnd->x + 1;
temp->y = linkEnd->y; //这时候temp是链表的尾巴
}
else if(direction == LEFT) //左
{
temp->x = linkEnd->x - 1;
temp->y = linkEnd->y; //这时候temp是链表的尾巴
}
else if(direction == UP) //上
{
temp->x = linkEnd->x;
temp->y = linkEnd->y-1;//这时候temp是链表的尾巴
}
else if(direction == DOWN) //下
{
temp->x = linkEnd->x;
temp->y = linkEnd->y+1;//这时候temp是链表的尾巴
}
proDirection = direction;
linkEnd = temp; //蛇头
//先打印后释放,这样只用打印一个多出来的节点
gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
printf( SNAKE );
//判断撞到了墙,看蛇头的位置,就是链表尾部的坐标
if(linkEnd->x == -1 ||
linkEnd->y == -1 ||
linkEnd->x == X_MAX ||
linkEnd->y == Y_MAX )
{
flgEnd = 0;
}
//吃食物
if( (linkEnd->x == food->x )&& (linkEnd->y == food->y) )
{
free(food);
setFood();
Score++;
showScore();
}
else
{
gotoxy((linkHead->x + X_OFFSET) * X_CHAR_WITH, linkHead->y+1);
printf(" ");//注意这里以前是特殊字符,所以要有两个空格
temp = linkHead->next;
free(linkHead);
linkHead = temp; //把下一个节点又变成了头,temp到了头的位置
}
temp = linkHead;
while (temp!= NULL) //判断是不是咬到了自己
{
if(temp != linkEnd )
{
if((linkEnd->x == temp->x)&&(linkEnd->y == temp->y))
{
flgEnd = 0;
}
}
temp = temp->next;
}
}
这里面完成的功能有
1,更新链表的尾部。
2,判断现在按下的按键于之前的方向是不是又冲突(冲突的话,比如蛇是往前走到,你让他往后,就会直接死掉)
3,蛇的移动。
4,游戏结束的判断。
5,是否吃到食物。
请宝们一个一个对比作用,我就不叙述了。
至于结束函数于分数都是很简单的东西,不在我的讲述范围,要是想要学习的话就自己去看源代码吧。
源代码:
头文件
#ifndef _CURSOR_
#define _CURSOR_
void gotoxy(int x,int y);
void HideCursor();
#endif // _CURSOR_
控制台文件:
#include <conio.h>
#include <windows.h>
///设置光标的函数,使控制控制台变得简单
void gotoxy(int x,int y)
{
COORD pos={x,y};
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOut,pos);
}
void HideCursor() { // 隐藏光标位置 ,这个函数复制代码就行
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
游戏文件:
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>
#include"cursor.h"
//结构体定义蛇
typedef struct snake
{
int x;
int y;
struct snake *next;
}snake_t;
snake_t *linkHead = NULL;
snake_t *linkEnd = NULL;
typedef struct fd
{
int x;
int y;
}food_t;
food_t *food = NULL;
#define X_MAX 30 /* 水平最大值 */
#define Y_MAX 30 /* 竖直最大值 */
#define X_CHAR_WITH 2 /* 汉字宽度为2,英文为1 */
#define X_OFFSET 8 /* X偏置 */
#define RIGHT 77 /*键盘上下左右的设定*/
#define LEFT 75
#define DOWN 80
#define UP 72
#define SNAKE "蛇"
int flgEnd = 1; //作为状态量去判断游戏是否结束
int proDirection = RIGHT; //判断输入的前后方向是否变化的状态量
int Score = 0;
void GameStart()
{
system("mode con cols=100 lines=40");
}
void Welcome()
{
printf("\n\n\n");
printf("\t\t\t======================================================\n");
printf("\t\t\t= =\n");
printf("\t\t\t= welcome to snake_t's world =\n");
printf("\t\t\t= Programmer : 乔子轩 =\n");
printf("\t\t\t= =\n");
printf("\t\t\t= =\n");
printf("\t\t\t======================================================\n");
system("pause");
system("cls");
}
void showScore()
{
gotoxy((X_OFFSET ) * X_CHAR_WITH, Y_MAX+2);
printf("分数:%d", Score);
}
void CreatMap()
{
for(int i=0;i<(X_MAX+2);i++)
{
gotoxy(X_CHAR_WITH * (i + X_OFFSET-1),0);
printf("墙");
gotoxy(X_CHAR_WITH * (i + X_OFFSET-1), Y_MAX+1);
printf("墙");
}
for(int i=1; i<(Y_MAX+1);i++)
{
gotoxy( (X_OFFSET-1 )* X_CHAR_WITH ,i);
printf("墙");
gotoxy((X_OFFSET + X_MAX ) * X_CHAR_WITH, i);
printf("墙");
}
showScore();
}
///显示蛇
void ShowSnake()
{
snake_t *temp;
//显示
temp = linkHead;
while (temp!= NULL)
{
gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
printf(SNAKE);
temp = temp->next;
}
gotoxy(0,31);
}
///放置食物
void setFood()
{
int flgFood = 1;
food = (food_t*)malloc(sizeof(food_t));
do
{
snake_t *temp;
srand((unsigned)time(NULL));
food->x = rand() % X_MAX;
food->y = rand() % Y_MAX;
//开始遍历链表,确定食物不在蛇的身体上
temp = linkHead;
while (temp!= NULL)
{
if((temp->x == food->x)&&(temp->y == food->y))
{
flgFood = 0;
}
temp = temp->next;
}
}while(flgFood = 0);
gotoxy(X_CHAR_WITH * (food->x + X_OFFSET), food->y +1 );
printf("食");
}
///初始化蛇
void InitialSnake()
{
snake_t *temp;
linkHead = (snake_t*)malloc(sizeof(snake_t));
linkHead->x = 5;
linkHead->y = Y_MAX / 2;
linkHead->next = NULL;
temp = linkHead ;
for(int i = 1; i < 3; i++) ///这里让循环的次数多一点点有利于我们检查
{
snake_t *p = (snake_t*)malloc(sizeof(snake_t));
p->x = linkHead->x + i;
p->y = linkHead->y;
p->next = NULL;
temp->next = p;
temp = p;
}
linkEnd = temp; //找尾巴
ShowSnake();
}
void MoveSnake(int direction) //移动蛇,给链表加上一个节点, 链表的头是蛇的尾巴!!!链表加上尾巴,删去链表的头
{
//蛇头向前移动一步
snake_t *temp = (snake_t*)malloc(sizeof(snake_t));
temp->next = NULL;
linkEnd->next = temp;
//判断方向的录入是否合理
if(proDirection == UP && direction == DOWN)
{
direction = proDirection;
}
if(proDirection == DOWN && direction == UP)
{
direction = proDirection;
}
if(proDirection == RIGHT && direction == LEFT)
{
direction = proDirection;
}
if(proDirection == LEFT && direction == RIGHT)
{
direction = proDirection;
}
if(direction == RIGHT) //右
{
temp->x = linkEnd->x + 1;
temp->y = linkEnd->y; //这时候temp是链表的尾巴
}
else if(direction == LEFT) //左
{
temp->x = linkEnd->x - 1;
temp->y = linkEnd->y; //这时候temp是链表的尾巴
}
else if(direction == UP) //上
{
temp->x = linkEnd->x;
temp->y = linkEnd->y-1;//这时候temp是链表的尾巴
}
else if(direction == DOWN) //下
{
temp->x = linkEnd->x;
temp->y = linkEnd->y+1;//这时候temp是链表的尾巴
}
proDirection = direction;
linkEnd = temp; //蛇头
//先打印后释放,这样只用打印一个多出来的节点
gotoxy((temp->x + X_OFFSET) * X_CHAR_WITH, temp->y + 1);
printf( SNAKE );
//判断撞到了墙,看蛇头的位置,就是链表尾部的坐标
if(linkEnd->x == -1 ||
linkEnd->y == -1 ||
linkEnd->x == X_MAX ||
linkEnd->y == Y_MAX )
{
flgEnd = 0;
}
//吃食物
if( (linkEnd->x == food->x )&& (linkEnd->y == food->y) )
{
free(food);
setFood();
Score++;
showScore();
}
else
{
gotoxy((linkHead->x + X_OFFSET) * X_CHAR_WITH, linkHead->y+1);
printf(" ");//注意这里以前是特殊字符,所以要有两个空格
temp = linkHead->next;
free(linkHead);
linkHead = temp; //把下一个节点又变成了头,temp到了头的位置
}
temp = linkHead;
while (temp!= NULL) //判断是不是咬到了自己
{
if(temp != linkEnd )
{
if((linkEnd->x == temp->x)&&(linkEnd->y == temp->y))
{
flgEnd = 0;
}
}
temp = temp->next;
}
}
void GameOver()
{
gotoxy(16,15);
printf("GAME OVER");
gotoxy(16,16);
printf("你的最终得分是%d分",Score);
system("pause");
}
//让蛇动起来
void RunGame()
{
char ch = RIGHT;
int direction = RIGHT;
int delay = 0;
while(1)
{
Sleep(1);//间隔3毫秒检测按键
if(flgEnd == 0)
{
system("cls");
GameOver();
break;
}
//判断有没有输入
if(kbhit()) //一直获取
{
ch = getch();
switch(ch)
{
case RIGHT:
direction = RIGHT;
break;
case UP:
direction = UP;
break;
case LEFT:
direction = LEFT;
break;
case DOWN:
direction = DOWN;
break;
}
}
if(++delay > 10)
{
MoveSnake(direction);
delay = 0;
}
}
}
int main()
{
//开始游戏,把控制台变得可以控制
//隐藏光标
HideCursor();
//开始界面
GameStart();
Welcome();
//画地图
CreatMap();
//画蛇
InitialSnake();
//放食物
setFood();
//游戏相关的运行
RunGame();
}
要是大家不会文件处理,就把三段就粘贴在一个文件中也没有问题,就是少了一点层次感。
拜拜了亲们,期待下次再见!