【C语言从不挂科到高绩点】27-百来行代码实现贪吃蛇游戏

一、贪吃蛇棋盘图

【实现步骤】

  1. 定义头文件,在头文件中分别定义如下内容
    1. 定义游戏棋盘的宽度和高度
    2. 定义组成蛇身体的节点结构体(结构体中有横纵坐标)
    3. 定义蛇的结构体(蛇的身体是由若干个节点组成,思考,蛇的身体最多有多少个节点
    4. 定义分数变量
    5. 定义记录蛇尾的位置的横纵坐标
    6. 定义蛇头移动的偏移量
// windows 下防止头文件多次引用的方式
// 功能类似#ifndef
#pragma once


// 游戏棋盘的列数(宽度)
#define WIDTH 60  // 横向60个格子

// 游戏棋盘的行数(高度)
#define HEIGHT 20 // 纵向20个格子


// 定义组成蛇身体的格子(节点)结构体
struct BODY {
	// 每个节点都会有位置
	int X;// 节点的横坐标
	int Y;// 节点的纵坐标
};

// 定义蛇的结构体
struct SNAKE {

	// 定义蛇当前的尺寸(当前,蛇的身体,是由几个格子组成)
	int size;


	// 蛇是由一个个节点构成的 
	// 定义节点结构体数组,用来存放所有的系欸但
	// 对于蛇而言,最多铺满整个游戏棋盘
	struct BODY body[WIDTH * HEIGHT];
} ;


// 偏移的坐标:
int dx = 0;// 横坐标的偏移量
int dy = 0;// 纵坐标的偏移量

// 记录蛇尾的坐标
int lx = 0;
int ly = 0;


// 食物
int food[2] = { 0 }; // food[0] 为横坐标    food[1]为纵坐标


// 分数
int score = 0;




// 声明初始化墙壁的函数
void InitWall();


// 声明初始化蛇的函数
void InitSnake();


// 声明显示界面的函数
void ShowUI();


void InitFood();//初始化食物的函数

// 声明开始游戏的函数
void PlayGame();



2. 新建 snack.c 源文件,完成游戏的业务功能

  1. 在源文件中定义初始化墙壁的 InitWall 函数,注意打印游戏边界,然后在头文件中声明 InitWall 函数,最后在 main 函数中调用此函数
    1. 在源文件中定义开始游戏的函数 PlayGame
    2. 设置蛇默认的移动方向
    3. 在 ShowUI 中更新蛇尾,将原来蛇尾的位置替换成空格
    4. 注意在头文件中声明 PlayGame ,并在 main 函数中调用该函数
    5. 如果蛇吃到了自己(蛇头的位置和蛇身体某个节点的位置重叠了),就 return,结束 PlayGame 函数,结束游戏
    6. 根据蛇头偏移量设置蛇头的位置
    7. 让蛇后一个节点替换前一个节点位置,让蛇移动起来
    8. 记录蛇尾的位置
    9. 获取键盘的按键,根据按键计算蛇头的偏移量
    10. 如果蛇没有碰到边界,就一直循环游戏,每循环一次刷新一下页面,重新调用 showUI 函数

 2. 完成显示食物功能

        在头文件中定义食物 Food,int 类型的数组,food[0] 表示横坐标,food[1] 表示纵坐标

        在 PlayGame 中判断是否吃到了食物,如果吃到食物,蛇的长度增加,并重新调用 InitFood 生成食物

        在 ShowUI 中将光标定位到食物的位置上,并打印食物

        在源文件中定义初始化食物的函数 InitFood,随机生成食物的横纵坐标,注意函数的声明和调用

3. 显示分数

  1. 在 showUI 中将光标定位到需要显示分数的位置,并打印分数每次吃到食物后分数增加

【参考代码】

#include<stdio.h>
#include "snake.h"

#include <Windows.h>
#include <conio.h>
#include <stdlib.h>

#include<time.h>

// 定义蛇的结构体变量
struct SNAKE snake;

int main() {

	// 隐藏光标的固定格式
	CONSOLE_CURSOR_INFO cci;
	// 设置光标的尺寸
	cci.dwSize = sizeof(cci);
	// 将光标的可见性设置为FALSE;(不展示)
	cci.bVisible = FALSE;
	// 设置光标的信息
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);

	// 初始化棋盘的墙壁
	InitWall();

	// 初始化蛇
	InitSnake();


	// 初始化食物
	InitFood();

	// 操控游戏的函数
	PlayGame();

	// 显示页面
	//ShowUI();

	return 0;
}

// 食物的位置随机
void InitFood() {
	// 设置随机数种子
	srand(time(0));
	// 在游戏棋盘的返回内随机生成食物横纵坐标
	food[0] = rand() % WIDTH;// 宽度范围内随机生成横坐标
	food[1] = rand() % HEIGHT;// 高度范围内随机生成纵坐标

}

// 初始化蛇
void InitSnake() {
	//游戏开始时蛇的长度2

	// 初始化蛇的尺寸
	snake.size = 2;

	// 根据蛇的尺寸,设置蛇每个节点的位置
	// 数组中第一个节点,看作蛇头
	// 设置第一个节点的横坐标
	snake.body[0].X = WIDTH / 2;
	// 设置第一个节点的纵坐标
	snake.body[0].Y = HEIGHT / 2;

	// 设置第二个节点的横坐标
	snake.body[1].X = WIDTH / 2 - 1;
	// 设置第二个节点的纵坐标
	snake.body[1].Y = HEIGHT / 2;

}

// 显示界面的函数
void ShowUI() {
	// 定义字符光标在控制台位置的结构体变量
	COORD coord; // 结构体中是光标当前的横纵坐标
	// 每次刷新页面,找到原来蛇尾的位置
	coord.X = lx;
	coord.Y = ly;
	// 将光标定位到蛇尾的位置
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
	// 将原来的蛇尾替换成空格
	putchar(' ');

	// 需要在哪个位置上打印,就需要将光标移动到哪个位置上
	// 遍历蛇的身体节点,找到每个节点的位置,并在节点位置上打印蛇
	for (int i = 0; i < snake.size; i++) {
		// 设置光标当前的位置
		coord.X = snake.body[i].X;
		coord.Y = snake.body[i].Y;
		// 将光标移动到对应的位置上
		// 固定格式
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);

		// i= 0,第一节点表示蛇头
		if (i == 0) {
			putchar('O'); // 打印蛇头
		}
		else {
			putchar('*');
		}
	}

	// 画食物
	// 找到食物的位置
	coord.X = food[0];
	coord.Y = food[1];
	// 将光标移动到食物的位置上
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
	// 画个字符代表食物
	putchar('@');


	// 打印分数
	coord.X = 0;
	coord.Y = 21;
	// 跳转光标
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
	printf("分数为:%d",score);

}

// 初始化墙壁的函数
// 打印棋盘边界
void InitWall() {
	// i 表示棋盘的行号
	for (int i = 0; i <= HEIGHT; i++) {  // 此处=多一次循环是用来打印墙壁
		// j表示棋盘的列号
		for (int j = 0; j <= WIDTH; j++) { // 此处= 多一次循环是用来打印墙壁
			// 在最后一行打印下边界
			if (i == HEIGHT) {
				putchar('=');
			}
			else if (j == WIDTH) { // 在最右侧打印右边界
				putchar('|');
			}
			else { // 其他格子打印空字符,表示蛇能够活动的范围
				putchar(' ');
			}

		}
		putchar('\n');
	}


}


// 开始游戏的函数
void PlayGame() {

	// 'A'左、'D'右、'S'下、'W'上
	char key = 'D'; //向右移动

	// 如果蛇在棋盘内,没有撞到墙壁,让游戏持续运行(先不考虑吃到自己)
	while (snake.body[0].X >= 0 && snake.body[0].X < WIDTH
		&& snake.body[0].Y >= 0 && snake.body[0].Y < HEIGHT) {
		
		// 刷新界面
		ShowUI();
		
		// _kbhit() 持续进行监听,直到获取按键结束当前监听
		while (_kbhit()) {
			key = _getch();
		} // 获取到按键之后,就会结束循环

		switch (key) {
			case 'D': case 'd':
				//向右移动
				// 每次移动横坐标向右偏移1
				dx = 1;dy = 0;break;
			case 'A': case 'a':
				// 向左移动
				dx = -1;dy = 0;break;
			case 'W':case 'w':
				// 向上移动
				dy = -1;dx = 0;break;
			case 'S':case 's':
				// 向下移动
				dx = 0;dy = 1;break;
		}

		// 蛇头是否吃到了食物
		//1. 判断蛇头和食物的位置是否重叠了,如果重叠了说明吃到了
		if (snake.body[0].X == food[0] && snake.body[0].Y == food[1]) {
			// 2. 如果吃到了
			//   1) 重新随机食物
			InitFood();
			//	2) 蛇的长度增加
			snake.size++;
			//	3)增长分数
			score++;
		
		}

		// 蛇头是否咬到了自己(除了蛇头以外的其他节点)
		// 排除蛇头,此处i从1开始
		for (int i = 1; i < snake.size; i++) {
			if (snake.body[0].X == snake.body[i].X && snake.body[0].Y == snake.body[i].Y) {
				// 进入到if语句中,说明蛇头咬到了自己
				return;
			}
		}



		// 移动之前先记录蛇尾的坐标
		lx = snake.body[snake.size - 1].X;
		ly = snake.body[snake.size - 1].Y;
		// 蛇移动,实际上就是后面的一个节点,要顶替前面一个节点的位置
		// 除去蛇头(蛇头控制方向)
		// 蛇移动,实际上就是后面一个节点,要顶替前一个节点的位置
		for (int i = snake.size - 1; i > 0; i--) {
			snake.body[i].X = snake.body[i - 1].X;
			snake.body[i].Y = snake.body[i - 1].Y;
		}

		// 更新蛇头的位置
		snake.body[0].X += dx;
		snake.body[0].Y += dy;


		// CPU处理速度非常快,可能一运行代码,蛇就撞墙了
		// 所以此处要减缓循环的速度
		
		// 让程序休眠一段时间,再开启下一次循环
		// 当程序执行到这行代码时会休眠一段时间,然后再运行
		Sleep(200); // 单位:毫秒


	
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听潮阁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值