[language C] snake eating

Keywords: Windows

I. knowledge points:

Structure linked list, dynamic memory allocation, keyboard input detection, cursor setting.

II. Implementation logic

1. Multiple structures are involved, including snake, direction, node, position coordinate and game 
2. The nodes are connected in series to form a chain list, traversing to get the member coordinates, printing symbols to get the snake body. 
3. Continuously add the head, remove the tail, traverse the coordinates again, and then print to form the movement of the snake.
4. The position of food production cannot be determined beyond the boundary or coincide with the snake's body. 
5. It is a rule to judge the direction of the snake. It is not allowed to reverse.
6. The realization of steering determines the new joint coordinates (the top, bottom, left and right of the current head) according to the direction of travel 
7. Death detection: whether the coordinates of the head node coincide with the wall or other joints of the body. 
8. Accelerate and decelerate, and set the refresh sleep time. 
9. The cursor can be set to print the setting symbol at the setting position.

Source code

//Model.h structure header file
#pragma once

typedef enum Direction
{
	UP, DOWN, LEFT, RIGHT
}Direction;
typedef struct Position
{
	int x;
	int y;
}Position;
typedef struct Node
{
	Position pos;
	struct Node *next;
}Node;
typedef struct Snake
{
	Node *head;
	Node *tail;
	Direction direction;
}Snake;
typedef struct Game
{
	Snake snake;
	Position food;

	int width;
	int height;
}Game;
//View.h
#pragma once
#include "Model.h"

void SetPos(int X, int Y);
void DisplayWall(int width, int height);
void DisplaySnake(const Snake *pSnake);
void DisplayFood(const Position *pFood);
void CleanSnakeNode(const Position *pPos);
void DisplaySnakeNode(const Position *pPos);
void GameDescription1();
void GameDescription2();
//View.c
#include "View.h"
#include "Model.h"
#include <Windows.h>
#include <stdio.h>

void ViewInit()
{
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//Console? Cursor? Info this structure contains the information of the console cursor
	CONSOLE_CURSOR_INFO ConsoleCursorInfo;
	GetConsoleCursorInfo(hOutput, &ConsoleCursorInfo);
	ConsoleCursorInfo.bVisible = FALSE;
	SetConsoleCursorInfo(hOutput, &ConsoleCursorInfo);
}
//Set cursor position
void SetPos(int X, int Y)
{
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord = { X, Y };
	SetConsoleCursorPosition(hOutput, coord);
}

static void TranslateSetPos(int x, int y)
{
	int X = 2 * (x + 1);
	int Y = y + 1;

	SetPos(X, Y);
}

void DisplayWall(int width, int height)
{
	ViewInit();
	// upper
	SetPos(0, 0);
	for (int i = 0; i < width + 2; i++) {
		printf("◆");
	}
	// lower
	SetPos(0, height + 1);
	for (int i = 0; i < width + 2; i++) {
		printf("◆");
	}
	// Left
	for (int i = 0; i < height + 2; i++) {
		SetPos(0, i);
		printf("◆");
	}
	// right
	for (int i = 0; i < height + 2; i++) {
		SetPos(2 * (width + 1), i);
		printf("◆");
	}
	
}

void DisplaySnake(const Snake *pSnake)
{
	for (Node *node = pSnake->tail; node != NULL; node = node->next) {
		TranslateSetPos(node->pos.x, node->pos.y);
		printf("■");
	}
}

void DisplayFood(const Position *pFood)
{
	TranslateSetPos(pFood->x, pFood->y);
	printf("■");
}

void CleanSnakeNode(const Position *pPos)
{
	TranslateSetPos(pPos->x, pPos->y);
	printf(" ");
}

void DisplaySnakeNode(const Position *pPos)
{
	TranslateSetPos(pPos->x, pPos->y);
	printf("■");
}
//Game Description Interface
void GameDescription1()
{
	SetPos(40, 14);
	printf("Welcome to the snake game!\n\n");
	system("pause");
	system("color 5e");
	system("cls");
	SetPos(10, 14);
	printf("You will control the movement of the snake through the keyboard: up↑  lower↓  Left ←  right→  accelerate F1  Slow down F2  suspend space  Sign out Esc");
	SetPos(10, 15);
	printf("The body grows when you eat\n");
	SetPos(10, 16);
	printf("Hitting the body or the wall will end the game\n");
	system("pause");
	system("cls");
}
void GameDescription2()
{
	SetPos(64, 15);
	printf("You can't go through the wall, you can't bite yourself\n");
	SetPos(64, 16);
	printf("use↑ ↓ ← → Control Snake Movement.");
	SetPos(64, 17);
	printf("F1 To accelerate, F2 To slow down\n");
	SetPos(64, 18);
	printf("ESC : Quit game    space: Pause game.");
}
//Controller.c
#include "Model.h"
#include "View.h"
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <time.h>

void SnakeInit(Snake *pSnake)
{
	//In the list, the head node is the snake's tail, and the tail node is the snake's head
	pSnake->tail = NULL;
	for (int i = 0; i < 3; i++){
		Node *node = (Node *)malloc(sizeof(Node));
		//Create node from snake head
		node->pos.x = 7 + i;
		node->pos.y = 2;

		if (i == 0){
			pSnake->head = node;
		}
		node->next = pSnake->tail;
		pSnake->tail = node; 
	}
	pSnake->direction = LEFT;
}
bool IsOverLap(Position pos,Snake *pSnake)
{
	Node *node;
	for (node = pSnake->tail; node != pSnake->head; node = node->next){
		if (node->pos.x == pos.x&&node->pos.y == pos.y){
			return true;
		}
	}
	return false;
}
//Produce food
Position GenerateFood(int width, int height, Snake *pSnake)
{
	Position pos;
	SetPos(1, 1);
	do
	{
		pos.x = rand() % width;
		pos.y = rand() % height;
	} while (IsOverLap(pos, pSnake));
	DisplayFood(&pos);
	return pos;
}
//Initialize Game
void GameInit(Game *pGame)
{
	pGame->width = 28;
	pGame->height = 27;
	//Initialize snake
	SnakeInit(&pGame->snake);
	//Initialize food
	//pGame->food = GenerateFood(pGame->width, pGame->height, &pGame->snake);
}
//Get the coordinates where the snake is going
Position GetNextPosition(const Snake *pSnake)
{
	Position next = pSnake->head->pos;
	switch (pSnake->direction){
	case UP:
		next.y -= 1;
		break;
	case DOWN:
		next.y += 1;
		break;
	case LEFT:
		next.x -= 1;
		break;
	case RIGHT:
		next.x += 1;
		break;
	}
	return next;
}
//Add a space to the snake's head
void AddHead(Snake *pSnake, Position nextPos)
{
	Node *node = (Node *)malloc(sizeof(Node));
	node->pos = nextPos;
	node->next = NULL;
	pSnake->head->next = node;
	pSnake->head = node;
	DisplaySnakeNode(&nextPos);//Pay attention to the parameter passing here 
}
//Snake tail minus one grid
void RemoveTail(Snake *pSnake)
{
	Node *tail = pSnake->tail;
	pSnake->tail = pSnake->tail->next; 
	CleanSnakeNode(&tail->pos);
	free(tail);
}
//Judge whether the snake can eat
bool IsEat(Position food, Position next)
{
	return food.x == next.x&&food.y == next.y;
}
//Judge if the snake hit the wall
bool KilledByWall(const Snake *pSnake,int width,int height)
{
	int x = pSnake->head->pos.x;
	int y = pSnake->head->pos.y;
	if (x>=0 && x<width && y>=0 && y<=height){
		return false;
	}
	return true;
}
//Determine if the snake hit itself
bool KilledBySelf(const Snake *pSnake,Position nextPos)
{
	Node *node = pSnake->tail;
	for (node = pSnake->tail; node != pSnake->head; node = node->next){
		if (node->pos.x == nextPos.x&&node->pos.y == nextPos.y){
			return true;
		}
	}
	return false;
}
//Judge whether it's dead or not
bool GameOver(const Snake *pSnake,Position nextPos,int width,int height)
{
	if (KilledByWall(pSnake,width,height)){
		printf("Hit the wall\n");
		return true;
	}
	if (KilledBySelf(pSnake,nextPos)){
		printf("Suicide\n");
		return true;
	}
	else{
		return false;
	}
}
//Pause game
void PauseGame()
{
	while (1)
	{
		Sleep(300);
		if (GetAsyncKeyState(VK_SPACE))
		{
			break;
		}
	}
}
void GameRun()
{
	Game game;
	int score = 0,            //score
		add = 10,             //Bonus points
		sleeptime = 200;      //Normal speed
	GameInit(&game);
	
	GameDescription1();
	DisplayWall(game.width,game.height);
	DisplaySnake(&game.snake);
	game.food = GenerateFood(game.width, game.height, &game.snake);
	GameDescription2();
	while (1)
	{
		SetPos(64, 10);
		printf("score:%d ", score);
		SetPos(64, 11);
		printf("Current food score:%d branch", add);
		if (game.snake.direction != DOWN&&GetAsyncKeyState(VK_UP)){
			game.snake.direction = UP;
		}
		else if (game.snake.direction != UP&&GetAsyncKeyState(VK_DOWN)){
			game.snake.direction = DOWN;
		}
		else if (game.snake.direction != RIGHT&&GetAsyncKeyState(VK_LEFT)){
			game.snake.direction = LEFT;
		}
		else if (game.snake.direction != LEFT&&GetAsyncKeyState(VK_RIGHT)){
			game.snake.direction = RIGHT;
		}
		else if (GetAsyncKeyState(VK_F1))   //accelerate
		{
			if (sleeptime > 50)
			{
				sleeptime -= 30;
				add = add + 2;
				if (sleeptime == 320)
				{
					add = 2;//To prevent the error of adding back after reducing to 1
				}
			}
		}
		else if (GetAsyncKeyState(VK_F2))   //Slow down
		{
			if (sleeptime < 350)
			{
				sleeptime = sleeptime + 30;
				add = add - 2;
				if (sleeptime == 350)
				{
					add = 1; //Guarantee a minimum score of 1
				}
			}
		}
		else if (GetAsyncKeyState(VK_SPACE)){
			//suspend
			PauseGame();
		}
		else if (GetAsyncKeyState(VK_ESCAPE))
		{
			//Sign out
			system("cls");
			SetPos(18,12 );
			printf("Quit game\n");
			break;
		}
		Position nextPos = GetNextPosition(&game.snake);
		if (IsEat(game.food,nextPos)){
            //Add a space to your head
			AddHead(&game.snake,nextPos);
			score += add;
			//Regenerate a food
			game.food = GenerateFood(game.width, game.height, &game.snake);
		}
		else{
           //Add a space to your head
			AddHead(&game.snake,nextPos);
		   //Tail minus one grid
			RemoveTail(&game.snake);
		}
		if (GameOver(&game.snake,nextPos,game.width,game.height)){
			break;
		}
		Sleep(sleeptime);
	}
	
}


//Test the position of the control cursor
void DemoSetPos()
{
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord;

	for (int i = 0; i < 6; i++) {
		coord.X = 2 * i;
		coord.Y = i;
		SetConsoleCursorPosition(hOutput, coord);

		printf("%02d", i);
	}
}

int main()
{
	//DemoSetPos();
	srand((unsigned)time(NULL));
	system("title ★★Greedy snake★★");   //Change command window name
	GameRun();
	
	system("pause");
	system("cls");
	
	return 0;

}

 

Posted by mbariou on Thu, 19 Dec 2019 10:25:11 -0800