贪吃蛇(C语言)

今天也要努力学习,争取考上杭电!

今天帮朋友写了贪吃蛇,我自己都没写过,还要骂骂咧咧帮别人写。
本来是写了一个版本,结果所有功能实现以后,发现了一个bug!!!这个bug是啥呢,就是我的蛇紧急转弯时候,如果身子挨在一起,就会在头尾在同一直线时 断掉。哈哈哈哈,让我想到了有的蛇砍掉头还能蹦跶两下…不过后面改掉了。
如果有要写贪吃蛇的大一朋友们,可以参考一下这个思路:
1、实现动态的效果,肯定是要不停清屏、输出的
2、蛇头要有一个移动方向,如果不人为干预的话,那就应该遵循旧的移动方向
3、如果从键盘键入了一个方向,键入的方向和当前方向相反,就是无效的键入
4、不需要一直键入,而是想键入就键入,那么应该使用非阻塞的getch(kbhit()和getch()配合使用)
5、蛇的下一个位置,判断当前蛇头按方向的下一个坐标是不是墙、某一节身子、食物、空白
6、是墙、某一节身子的话,就结束了
7、是空白的话,就可以走,那么空白的坐标变成头的,原来的头变成身子,尾巴向前移
8、是食物的话,就可以走,那么食物的坐标变成头的,原来的头变成身子,而尾巴不需要动
9、尾巴的判断,把尾巴的最后一节变为空(通常情况下,一个尾巴肯定是线性的,与倒数第二节尾巴相连,所以我的bug就是出现在这里,如果只是判断到一个相连的就不判断了,那么两个相邻的时候就出问题了,后面我的思路是改用了单链表,存储每一节点的坐标,这样甚至就不用判断尾巴了,但是消耗的空间和原来只用两个变量存储尾坐标相比多了一个长度为蛇身的链表
10、食物生成,就是生成一对随机数做坐标,随机数范围在地图合理范围内,如果生成的坐标是身子什么的从新生成一对再
11、至于数据存储,为了不来回传参,我选择了用的全局变量,因为真的用了好多控制变量

我的代码中,地图是用了一个二维字符数组,蛇身体坐标用的单链表实现

下面给列一下会用的一些不常用的函数及功能,可以参考,具体原型自行查吧:

1、gotoxy(int x, int y):将光标移动到控制台的某个坐标,这个要看你的环境,没有的话自己写或者下库
2、kbhit():检查当前是否有键入,没有返回0 , 头文件:conio.h
3、getch():无回显,自动读取,不需要按回车!可以搭配kbhit()使用,头文件:conio.h

然后,就是方向键,键入是返回两个值,第一个值是-32,说明是方向键,这时候再读取第二个判断是啥方向

大概就写完了,明天也要努力学习考杭电!

代码实现:

#include <conio.h>
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
#include <windows.h>
#include <time.h>

#define MAP_HEIGTH 30
#define MAP_WIDTH 60

typedef struct snake{
	int x;
	int y;
	struct snake *next; 
}snake,*psnake;

char map[MAP_HEIGTH][MAP_WIDTH+1];
psnake sh,st;			//蛇头 蛇尾指针,但蛇尾是链表头指针 
int h_x,h_y,t_x,t_y;	//头坐标 尾坐标 
int point = 75;			//方向值,初始向左     ←75  →77  ↑72  ↓ 80
char get_p,get_key;		//key用来接收键入,方向键返回两个值,第二个用p接收 
int food = 0;			//食物数目 
int sleep = 30;			//休眠时间 
int length = 5;			//蛇长 

void gotoxy(int x, int y) {
	//DEVC环境需要下库graphics.h
	//所以自己写了 
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos;
    pos.X = x;
    pos.Y = y;
    SetConsoleCursorPosition(handle, pos);
}

void insert_head(int x, int y){
	//更新蛇头
	psnake h = (psnake)malloc(sizeof(snake));
	h -> x = x;
	h -> y = y;
	h -> next = st;
	sh -> next = h;
	sh = h;
}

void del_tail(){
	//删除蛇尾坐标 
	psnake del = st;
	st = st -> next;
	free(del);
	sh -> next = st;	
}

void ini_snake(){
	int i;
	h_x = (MAP_WIDTH - 5) / 2;
	h_y = MAP_HEIGTH / 2;
	t_x = h_x + 5;
	t_y = h_y;
	map[h_y][h_x] = 'O';
	//初始蛇头坐标存储 
	sh = (psnake)malloc(sizeof(snake));
	st = sh;
	sh -> x = h_x;
	sh -> y = h_y;
	sh -> next = st;
	psnake q = sh;
	for(i = h_x + 1;i <= t_x;i++){
		map[h_y][i] = '+';
		//初始蛇身子坐标存储 
		psnake p =  (psnake)malloc(sizeof(snake));
		p -> x = i;
		p -> y = h_y;
		p -> next = q;
		q = p;
		sh -> next = p;
		st = p;
	}
}

void produce_food(){
	//始终保证地图中有5个食物
	// 1 <= x <= MAP_WIDTH-2
	// 1 <= y <= MAP_HEIGTH-2
	 srand(time(NULL));
	 while(food != 5){	
	 	int x = (int)rand() % (MAP_WIDTH-2) +1;		//[1,58]
	 	int y = (int)rand() % (MAP_HEIGTH-2) +1;	//[1,28]
	 	if(map[y][x] == 'O' || map[y][x] == '+' || map[y][x] == '#'){
		 	//等于身体重新生成,边界不在生成范围 
		 	continue;
		}else{
			map[y][x] = '#'; 
			food++;
		}
	 }
}

void ini_map(){
	int i,j;
	map[0][0] = '*';
	map[0][MAP_WIDTH-1] = '*';
	map[MAP_HEIGTH-1][0] = '*';
	map[MAP_HEIGTH-1][MAP_WIDTH-1] = '*';
	for(i = 1;i < MAP_WIDTH-1;i++){
		map[0][i] = '*';
		map[MAP_HEIGTH-1][i] = '*';
	}
	map[0][MAP_WIDTH] = '\0';
	map[MAP_HEIGTH-1][MAP_WIDTH] = '\0';
	for(i =1;i < MAP_HEIGTH-1;i++){
		map[i][0] = '*';
		for(j = 1;j < MAP_WIDTH-1;j++){
			map[i][j] = ' ';
		}
		map[i][MAP_WIDTH-1] = '*';
		map[i][MAP_WIDTH] = '\0';
	}
	ini_snake();
	produce_food();
} 

void load_map(){
	int i;
	printf("\n");
	for(i = 0;i < MAP_HEIGTH; i++){
		printf("  %s\n",map[i]);
	}
	return;
}

int check_next(){
	int flag = 0;		//判断是否是食物 
	int next_x,next_y;	//下一个的坐标 
	switch(point){		//头的下一个按方向判断 
		case 75:		//左 
			next_x = h_x - 1, next_y = h_y; 
			break;
		case 77:		//右 
			next_x = h_x + 1, next_y = h_y; 
			break;
		case 72:		//上 
			next_x = h_x, next_y = h_y - 1; 
			break;
		case 80:		//下 
			next_x = h_x, next_y = h_y + 1; 
			break;
	}
	if(map[next_y][next_x] == '*' || map[next_y][next_x] == '+')	return 0;		//撞到墙或自己
	else{
		if(map[next_y][next_x] == '#')	flag = 1;	//下一个是食物,则尾不变 
		map[next_y][next_x] = 'O';			//下一个变成新的头
		map[h_y][h_x] = '+';				//当前头变成身体
		h_y = next_y;						//更新头坐标
		h_x = next_x;						//更新头坐标
		insert_head(next_x,next_y);				//更新头坐标
	}
	if(flag){
		food--;					//食物减少 
		length++; 
		if(sleep > 10 && length % 10 == 0)	sleep = sleep - 2 ;
		return 1;				//如果是食物,则尾不变 	
	}
	t_y = st -> y;
	t_x = st -> x;
	map[t_y][t_x] = ' ';
	del_tail();
	return 1;
}

void load_end(){
	system("color 04"); 
	gotoxy((MAP_WIDTH-9)/2,MAP_HEIGTH/2);
	printf("GAME OVER !");
}

void monitor_key(){
	//kbhit判断缓冲区有没有键入
	//getch无回显读取一个字符 
	if(kbhit()){	//有键入 
		get_key = getch();
		if(get_key == -32){
			//方向键返回两个值,需要读取两次 
			get_p = getch();
			if(get_p != (152-point)){
				//相反方向相加=152
				//相反方向无效 
				point = get_p;
			}
		}
	}
} 

void start(){
	int YoN = 1;
	ini_map();
	system("color 70"); 
	while(YoN){
		system("CLS");
		if(food != 5)	produce_food();
		load_map();		//加载界面
		gotoxy(27,32); 
		printf("Length : %d",length);;
		Sleep(sleep);
		monitor_key();
		YoN = check_next(); 	
	}
	load_end();
} 

int main()
{
	system("mode con cols=65 lines=35");
	gotoxy((MAP_WIDTH-10)/2,MAP_HEIGTH/2);
	printf("Level 1-9\n");
	gotoxy((MAP_WIDTH-2)/2,MAP_HEIGTH/2+2);
	scanf("%d",&sleep);
	sleep=(10-sleep)*10;
	start();
	while(1){};
} 
#define N 200<br>#include <graphics.h><br>#include <stdlib.h><br>#include <dos.h><br>#define LEFT 0x4b00<br>#define RIGHT 0x4d00<br>#define DOWN 0x5000<br>#define UP 0x4800<br>#define ESC 0x011b<br>int i,key;<br>int score=0;/*得分*/<br>int gamespeed=50000;/*游戏速度自己调整*/<br>struct Food<br>{<br> int x;/*食物的横坐标*/<br> int y;/*食物的纵坐标*/<br> int yes;/*判断是否要出现食物的变量*/<br>}food;/*食物的结构体*/<br>struct Snake<br>{<br> int x[N];<br> int y[N];<br> int node;/*蛇的节数*/<br> int direction;/*蛇移动方向*/<br> int life;/* 蛇的生命,0活着,1死亡*/<br>}snake;<br>void Init(void);/*图形驱动*/<br>void Close(void);/*图形结束*/<br>void DrawK(void);/*开始画面*/<br>void GameOver(void);/*结束游戏*/<br>void GamePlay(void);/*玩游戏具体过程*/<br>void PrScore(void);/*输出成绩*/<br>/*主函数*/<br>void main(void)<br>{<br> Init();/*图形驱动*/<br> DrawK();/*开始画面*/<br> GamePlay();/*玩游戏具体过程*/<br> Close();/*图形结束*/<br>}<br>/*图形驱动*/<br>void Init(void)<br>{<br> int gd=DETECT,gm;<br> initgraph(&gd,&gm,"c:\\tc");<br> cleardevice();<br>}<br>/*开始画面,左上角坐标为(50,40),右下角坐标为(610,460)的围墙*/<br>void DrawK(void)<br>{<br>/*setbkcolor(LIGHTGREEN);*/<br> setcolor(11);<br> setlinestyle(SOLID_LINE,0,THICK_WIDTH);/*设置线型*/<br> for(i=50;i<=600;i+=10)/*画围墙*/<br> {<br> rectangle(i,40,i+10,49); /*上边*/<br> rectangle(i,451,i+10,460);/*下边*/<br> }<br> for(i=40;i<=450;i+=10)<br> {<br> rectangle(50,i,59,i+10); /*左边*/<br> rectangle(601,i,610,i+10);/*右边*/<br> }<br>}<br>/*玩游戏具体过程*/<br>void GamePlay(void)<br>{<br> randomize();/*随机数发生器*/<br> food.yes=1;/*1表示需要出现新食物,0表示已经存在食物*/<br> snake.life=0;/*活着*/<br> snake.direction=1;/*方向往右*/<br> snake.x[0]=100;snake.y[0]=100;/*蛇头*/<br> snake.x[1]=110;snake.y[1]=100;<br> snake.node=2;/*节数*/<br> PrScore();/*输出得分*/<br> while(1)/*可以重复玩游戏,压ESC键结束*/<br> {<br> while(!kbhit())/*在没有按键的情况下,蛇自己移动身体*/<br> {<br> if(food.yes==1)/*需要出现新食物*/<br> {<br> food.x=rand()%400+60;<br> food.y=rand()%350+60;<br> while(food.x%10!=0)/*食物随机出现后必须让食物能够在整格内,这样才可以让蛇吃到*/<br> food.x++;<br> while(food.y%10!=0)<br> food.y++;<br> food.yes=0;/*画面上有食物了*/<br> }<br> if(food.yes==0)/*画面上有食物了就要显示*/<br> {<br> setcolor(GREEN);<br> rectangle(food.x,food.y,food.x+10,food.y-10);<br> }<br> for(i=snake.node-1;i>0;i--)/*蛇的每个环节往前移动,也就是贪吃蛇的关键算法*/<br> {<br> snake.x[i]=snake.x[i-1];<br> snake.y[i]=snake.y[i-1];<br> }<br> /*1,2,3,4表示右,左,上,下四个方向,通过这个判断来移动蛇头*/<br> switch(snake.direction)<br> {<br> case 1:snake.x[0]+=10;break;<br> case 2: snake.x[0]-=10;break;<br> case 3: snake.y[0]-=10;break;<br> case 4: snake.y[0]+=10;break;<br> }<br> for(i=3;i<snake.node;i++)/*从蛇的第四节开始判断是否撞到自己了,因为蛇头为两节,第三节不可能拐过来*/<br> {<br> if(snake.x[i]==snake.x[0]&&snake.y[i]==snake.y[0])<br> {<br> GameOver();/*显示失败*/<br> snake.life=1;<br> break;<br> }<br> }<br> if(snake.x[0]<55||snake.x[0]>595||snake.y[0]<55||<br> snake.y[0]>455)/*蛇是否撞到墙壁*/<br> {<br> GameOver();/*本次游戏结束*/<br> snake.life=1; /*蛇死*/<br> }<br> if(snake.life==1)/*以上两种判断以后,如果蛇死就跳出内循环,重新开始*/<br> break;<br> if(snake.x[0]==food.x&&snake.y[0]==food.y)/*吃到食物以后*/<br> {<br> setcolor(0);/*把画面上的食物东西去掉*/<br> rectangle(food.x,food.y,food.x+10,food.y-10);<br> snake.x[snake.node]=-20;snake.y[snake.node]=-20;<br> /*新的一节先放在看不见的位置,下次循环就取前一节的位置*/<br> snake.node++;/*蛇的身体长一节*/<br> food.yes=1;/*画面上需要出现新的食物*/<br> score+=10;<br> PrScore();/*输出新得分*/<br> }<br> setcolor(4);/*画出蛇*/<br> for(i=0;i<snake.node;i++)<br> rectangle(snake.x[i],snake.y[i],snake.x[i]+10,<br> snake.y[i]-10);<br> delay(gamespeed);<br> setcolor(0);/*用黑色去除蛇的的最后一节*/<br> rectangle(snake.x[snake.node-1],snake.y[snake.node-1],<br> snake.x[snake.node-1]+10,snake.y[snake.node-1]-10);<br> } /*endwhile(!kbhit)*/<br> if(snake.life==1)/*如果蛇死就跳出循环*/<br> break;<br> key=bioskey(0);/*接收按键*/<br> if(key==ESC)/*按ESC键退出*/<br> break;<br> else<br> if(key==UP&&snake.direction!=4)<br>/*判断是否往相反的方向移动*/<br> snake.direction=3;<br> else<br> if(key==RIGHT&&snake.direction!=2)<br> snake.direction=1;<br> else<br> if(key==LEFT&&snake.direction!=1)<br> snake.direction=2;<br> else<br> if(key==DOWN&&snake.direction!=3)<br> snake.direction=4;<br> }/*endwhile(1)*/<br>}<br>/*游戏结束*/<br>void GameOver(void)<br>{<br> cleardevice(); <br> PrScore();<br> setcolor(RED);<br> settextstyle(0,0,4);<br> outtextxy(200,200,"GAME OVER");<br> getch();<br>}<br>/*输出成绩*/<br>void PrScore(void)<br>{ <br> char str[10];<br> setfillstyle(SOLID_FILL,YELLOW);<br> bar(50,15,220,35);<br> setcolor(6);<br> settextstyle(0,0,2);<br> sprintf(str,"score:%d",score);<br> outtextxy(55,20,str);<br>}<br>/*图形结束*/<br>void Close(void)<br>{ <br> getch();<br> closegraph();<br>}<br>
下面是一个简单的贪吃蛇C语言代码,供参考: ```c #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <windows.h> #define WIDTH 20 #define HEIGHT 20 #define MAX_LENGTH (WIDTH*HEIGHT) int snake[MAX_LENGTH][2]; int food[2]; int score; int length; int direction; int running; void init() { // 初始化贪吃蛇的位置 snake[0][0] = WIDTH / 2; snake[0][1] = HEIGHT / 2; snake[1][0] = WIDTH / 2 - 1; snake[1][1] = HEIGHT / 2; snake[2][0] = WIDTH / 2 - 2; snake[2][1] = HEIGHT / 2; length = 3; // 初始化食物的位置 food[0] = rand() % WIDTH; food[1] = rand() % HEIGHT; // 初始化分数 score = 0; // 初始化方向 direction = 'd'; // 初始化运行标志 running = 1; } void draw() { system("cls"); // 画出地图 for (int i = 0; i <= HEIGHT; i++) { for (int j = 0; j <= WIDTH; j++) { if (i == 0 || i == HEIGHT || j == 0 || j == WIDTH) { printf("#"); } else if (i == snake[0][1] && j == snake[0][0]) { printf("@"); } else if (i == food[1] && j == food[0]) { printf("*"); } else { int flag = 0; for (int k = 1; k < length; k++) { if (i == snake[k][1] && j == snake[k][0]) { printf("#"); flag = 1; break; } } if (!flag) { printf(" "); } } } printf("\n"); } // 显示分数 printf("Score: %d\n", score); } void update() { // 记录蛇尾位置 int tail[2]; tail[0] = snake[length - 1][0]; tail[1] = snake[length - 1][1]; // 移动蛇身 for (int i = length - 1; i > 0; i--) { snake[i][0] = snake[i - 1][0]; snake[i][1] = snake[i - 1][1]; } // 根据方向移动蛇头 switch (direction) { case 'w': snake[0][1]--; break; case 'a': snake[0][0]--; break; case 's': snake[0][1]++; break; case 'd': snake[0][0]++; break; } // 判断是否吃到食物 if (snake[0][0] == food[0] && snake[0][1] == food[1]) { // 食物位置随机生成 food[0] = rand() % WIDTH; food[1] = rand() % HEIGHT; // 分数加一 score++; // 蛇身长度加一 length++; } // 判断是否碰到墙壁或自己的身体 if (snake[0][0] == 0 || snake[0][0] == WIDTH || snake[0][1] == 0 || snake[0][1] == HEIGHT) { running = 0; } for (int i = 1; i < length; i++) { if (snake[0][0] == snake[i][0] && snake[0][1] == snake[i][1]) { running = 0; break; } } } void input() { // 获取键盘输入 if (_kbhit()) { char c = _getch(); if (c == 'w' || c == 'a' || c == 's' || c == 'd') { // 只有输入合法的方向键才会改变方向 if ((c == 'w' && direction != 's') || (c == 'a' && direction != 'd') || (c == 's' && direction != 'w') || (c == 'd' && direction != 'a')) { direction = c; } } } } int main() { init(); while (running) { draw(); update(); input(); Sleep(100); } printf("Game over\n"); printf("Your score: %d\n", score); return 0; } ``` 这个代码使用了Windows.h库中的一个函数`Sleep()`,该函数可以让程序暂停一段时间,单位是毫秒。在这个代码中,每100毫秒刷新一次屏幕,相当于一秒钟刷新10次。如果需要在其他平台上运行此代码,需要修改画图函数和暂停函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值