贪吃蛇项目设计

                                                      绪 言

贪吃蛇游戏是一个深受人们喜爱的游戏,一条在密闭的围墙内,在围墙内随机出现一个食物,通过按键盘上四个光标键控制蛇向上下左右四个方向移动,蛇头撞到食物,则表示食物被蛇吃掉,这时蛇的身体长一节,同时计10分,接着又出现食物,等待被蛇吃掉,如果蛇在移动过程中,撞到墙壁或身体交叉蛇头撞到自己的身体游戏结束。

这个项目主要分为三部分分别为游戏中蛇的移动实现、游戏的界面设计实现、游戏的蛇的模型构成实现。在项目中用一个链表示蛇身体的一个节点,身体每长一节,增加一个Node。移动时必须从蛇头开始,所以蛇不能向相反的方向移动,如果不按任意键,蛇自行在当前方向上前移,但按下有效方向键后,蛇头朝着该方向移动,一步移动一节身体,所以按下有效方向键后,先确定蛇头的位置,而后蛇的身体随蛇头移动,图形的实现是从蛇头新位置开始画出蛇。

一、项目背景

贪吃蛇这个项目的实现的基础是数据结构+c语言,既是对前一段时间的学习的知识的巩固,又是对未来的一个拓展。在这个项目的实现过程用到的数据类型 (整型 .实型 .字符型 .指针 .数组.结构等 );运算类型 (算术运算 .逻辑运算 .自增自减运算.赋值运算等 );程序结构 (顺序结构 .判断选择结构 .循环结构 );大程序的功能分解方法 (即函数的使用 )等.进一步掌握各种函数的应用以及文件的读写操作等。

二、需求分析

根据主界面显示内容进行选择并进一步执行相应操作。

  1. 在这个游戏设计过程中,只设计了一条蛇。
  2. 在这个游戏设计过程中,只设计了一个“食物”。
  3. 游戏部分只有边界有墙。

通过函数 , 主界面是一个封闭的围墙,用两个循环语句分别在水平和垂直方

  1. 向输出连续的宽度和高度的矩形方块。
  2. 游戏结束条件。

1、蛇撞到墙

2、蛇撞到自己

     5.得分的辅助

     6.字符界面

三、程序设计

将此系统化分为如下模块:
1、初始图形模块:可用函数 main()函数来实现此操作。
2、开始画面模块:用 View.h来实现。出现运行界面。
3、玩游戏的具体过程模块:可用 Control.h函数来实现。

          通过按键盘上四个光标键控制蛇向上下左右四个方向移动,小蛇在移动的过程中吃食。

4、模型构造

           通过结构体构造贪吃蛇的身体部分以及方向的实现。


四、具体实现

  1. view的实现

此时需要定位屏幕中的光标,用到函数 GetStdHandle。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。可以嵌套使用。

具体参考:https://baike.baidu.com/item/GetStdHandle/6909878

#pragma once

#include <windows.h>
#include <stdio.h>
#include "Model.h"


void SetPos(int X, int Y)
{
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);//可以实现光标的位置控制
	COORD coord;
	coord.X = X;//坐标为GetStdHandle()返回标准的输出的句柄,也就是获得输出屏幕缓冲区的句柄,并赋值给对象c*/
	coord.Y = Y;

	SetConsoleCursorPosition(hStdout, coord);
}

// 显示外墙
void DisplayWall(int width, int height)
{
	int i;

	// 上边的墙
	SetPos(0, 0);
	for (i = 0; i < width + 2; i++) {
		printf("█");
	}

	// 下边的墙
	SetPos(0, height + 1);
	for (i = 0; i < width + 2; i++) {
		printf("█");
	}

	// 左边的墙
	for (i = 0; i < height + 2; i++) {
		SetPos(0, i);
		printf("█");
	}

	// 右边的墙
	for (i = 0; i < height + 2; i++) {
		SetPos(2 * (width + 1), i);
		printf("█");
	}
}

void DisplaySnakeNode(Position pos)
{
	SetPos(8 * (pos.x + 1), pos.y + 1);
	printf("◆");
}

void CleanSnakeNode(Position pos)
{
	SetPos(8 * (pos.x + 1), pos.y + 1);
	printf("  ");
}


void DisplayFood(Position pos)
{
	SetPos(2 * (pos.x + 1), pos.y + 1);
	printf("⊙");
}

void DisplaySnake(const Snake *pSnake)
{
	Node *pNode = pSnake->pHead;
	while (pNode) {
		DisplaySnakeNode(pNode->data);
		pNode = pNode->pNext;
	}
}

屏幕中的坐标系的起始点为(0.0),向右为系,向下为y。

#pragma once

// 坐标原点位于左上角,
// 向右为 x 轴的正方向,
// 向下为 y 轴的正方向,
typedef struct Position {
	int	x;
	int y;
}	Position;

// 链表结点(单向)
typedef struct Node {
	Position	data;
	struct Node *pNext;
}	Node;


// 表示蛇当前的前进方向
typedef enum Direction {
	UP, DOWN, LEFT, RIGHT
}	Direction;

// 蛇的结构
typedef struct Snake {
	Direction direction;
	Node *pHead;
}	Snake;

// 一盘游戏中用到的其他非独立字段
typedef struct Game {
	Snake		snake;
	Position	foodPosition;

	int	score;	// 当前得分
	int scorePerFood;	// 每吃一个食物得分
	int speed;	//	蛇的前进速度
	int width;	// 宽
	int height;	// 高
}	Game;

这是游戏的主要组成部分,它采用将前一节的坐标赋给后一节的坐标,用背景颜色将最后节去除,当蛇头的坐标与食物相等时,表示食物被吃掉了。 

在此期间食物的生成规则:

  1. 随机
  2. 要在范围内
  3. 不要和蛇重复

在项目中“吃食物”的过程,其实就是在链表中不断插入的过程,以此来实现蛇的身体增长。同理蛇的移动最后的显示其实是尾删的过程

 

#pragma once

#include "Model.h"
#include "View.h"
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>



//typedef struct Game {
//	Snake		snake;
//	Position	foodPosition;
//
//	int	score;	// 当前得分
//	int scorePerFood;	// 每吃一个食物得分
//i	nt speed;	//	蛇的前进速度
//}	Game;

void SnakeInit(Snake *pSnake)
{
	int i;

	pSnake->pHead = NULL;
	for (i = 0; i < 3; i++) {
		Position pos;
		pos.x = i + 5;
		pos.y = 5;

		Node *pNewNode = (Node *)malloc(sizeof(Node));
		assert(pNewNode);
		pNewNode->data = pos;
		pNewNode->pNext = pSnake->pHead;
		pSnake->pHead = pNewNode;
	}

	pSnake->direction = RIGHT;
}

// 1 重叠
// 0 不重叠
int IsOverlapSnake(int x, int y, const Snake *pSnake)
{
	Node *pNode;
	for (pNode = pSnake->pHead; pNode; pNode = pNode->pNext) {
		if (pNode->data.x == x && pNode->data.y == y) {
			return 1;
		}
	}

	return 0;
}

// 生成食物
// 1. 随机
// 2. 范围内
// 3. 不和蛇重叠
void FoodInit(Position *pFood, int width, int height, const Snake *pSnake)
{
	int x, y;

	do {
		x = rand() % width;
		y = rand() % height;
	} while (IsOverlapSnake(x, y, pSnake));

	pFood->x = x;
	pFood->y = y;
}

void GameInit(Game *pGame)
{
	
	pGame->width = 28;
	pGame->height = 32;
	system("mode con cols=60 lines=38");

	SnakeInit(&(pGame->snake));
	FoodInit(
		&(pGame->foodPosition), 
		pGame->width, 
		pGame->height,
		&(pGame->snake));

	pGame->score = 0;
	pGame->scorePerFood = 10;
	pGame->speed = 200; // 毫秒
}

// 得到下一步的坐标
Position GetNextPosition(const Snake *pSnake)
{
	Position nextPos;
	nextPos.x = pSnake->pHead->data.x;
	nextPos.y = pSnake->pHead->data.y;

	switch (pSnake->direction) {
	case UP:
		nextPos.y -= 1;
		break;
	case DOWN:
		nextPos.y += 1;
		break;
	case LEFT:
		nextPos.x -= 1;
		break;
	case RIGHT:
		nextPos.x += 1;
		break;
	}

	return nextPos;
}

// 头插
void PushFront(Snake *pSnake, Position nextPos)
{
	Node *pNewNode = (Node *)malloc(sizeof(Node));
	assert(pNewNode);
	pNewNode->data = nextPos;

	pNewNode->pNext = pSnake->pHead;
	pSnake->pHead = pNewNode;

	DisplaySnakeNode(nextPos);
}

// 尾删
void PopBack(Snake *pSnake)
{
	Node *pNode = pSnake->pHead;
	while (pNode->pNext->pNext != NULL) {
		pNode = pNode->pNext;
	}

	CleanSnakeNode(pNode->pNext->data);
	free(pNode->pNext);
	pNode->pNext = NULL;
}

// 1 表示撞了
// 0 表示没有
int IsCrashByWall(int width, int height, Position nextPos)
{
	if (nextPos.x < 0 || nextPos.x >= width) {
		return 1;
	}

	if (nextPos.y < 0 || nextPos.y >= height) {
		return 1;
	}

	return 0;
}

// 1 表示撞了
// 0 表示没撞
int IsCrashHimself(const Snake *pSnake)
{
	Node *pHead = pSnake->pHead;	// 第一个结点
	Node *pNode = pSnake->pHead->pNext;	// 第二个结点

	while (pNode != NULL) {
		if (pHead->data.x == pNode->data.x && pHead->data.y == pNode->data.y) {
			return 1;
		}

		pNode = pNode->pNext;
	}

	return 0;
}

void GameStart()
{
	Game	game;

	GameInit(&game);
	DisplayWall(game.width, game.height);
	DisplaySnake(&(game.snake));
	DisplayFood(game.foodPosition);

	while (1) {
		if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN) {
			game.snake.direction = UP;
		}
		else if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP) {
			game.snake.direction = DOWN;
		}
		else if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT) {
			game.snake.direction = LEFT;
		}
		else if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT) {
			game.snake.direction = RIGHT;
		}

		Position nextPos = GetNextPosition(&(game.snake));
		if (nextPos.x == game.foodPosition.x && nextPos.y == game.foodPosition.y) {
			// 吃到食物

			// 蛇增长一格
			PushFront(&(game.snake), nextPos);

			// 加分
			game.score += game.scorePerFood;

			// 生成新食物
			FoodInit(&(game.foodPosition),
				game.width, game.height,
				&(game.snake));

			DisplayFood(game.foodPosition);

		}
		else {
			// 没吃到食物

			// 蛇向前一步
			PushFront(&(game.snake), nextPos);
			PopBack(&(game.snake));
		}

		if (IsCrashByWall(game.width, game.height, nextPos)) {
			break;
		}

		if (IsCrashHimself(&(game.snake))) {
			break;
		}

		Sleep(game.speed);
	}
}

游戏界面的生成 

#include <windows.h>
#include "Controller.h"
#include "View.h"

int main()
{
	
	printf("              欢迎              \n");
	system("pause");
	system("cls");

	GameStart();

	return 0;
}

总结:

  

1、蛇撞到墙

这个问题的解决办法就是通过链表中的节点与“墙”的做表就想比较

2、蛇撞到自己

这个问题的解决办法就是通过链表中的节点与蛇的第一个节点就行判断,如果相当则游戏结束

猜你喜欢

转载自blog.csdn.net/mashui21/article/details/82496913