这份代码只是一个最最基础的模型,并没有图形界面,实现了蛇的移动,基本地图的建设,食物生成等。剩下的功能都比较简单,也很繁琐,暂时不想再写下去了。贪吃蛇在大一的时候写过一次,但是当时刚学完C语言,写的代码都是放在一个main函数里面,现在有点时间了,准备再来用面向对象的思想进行一次编程。(这篇文章代码只适合小白,大牛请自动忽略)
  贪吃蛇最主要的数据结构就是头插法实现链表。这样蛇身往前走的动作就变得格外简单。当然我使用了双向链表,纯粹是为了编写代码简单。这样蛇在向前移动时,通过方向计算下一个位置,判断是否可行,如果不可行生命置0,可行判断是否是食物,然后增加蛇头,如果是食物,那么直接返回,如果不是,说明蛇是移动而不是吃东西,所以尾巴需要向前,那么头部增加完之后需要尾巴减去一格,这里就显示出双向链表的好处,可以很轻松的找到你的上一个结点。
  需要注意的是,命令行的直角坐标和数组正好是相反的,所以坐标再GotoXY函数的时候需要转换一下。
  再就是需要搞清楚自己的设计思路,因为个人是按照面向对象的思想进行编码的,所以用了两个类,一格是界面,一个是蛇身,两者关系每个人设计思路都是不一样的,代码实现也有所不同,笔者这里只是一种实现方式而已,只要能达到目标都是好的。还有需要注意的就是,本人用的编程环境是VS2019,这其中有一个老问题了,模板类型的成员函数的生命和定义必须在一个文件,所以,这里需要注意一下。那么,下面话不多说,上代码:

// Snake.h
#pragma once

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

#define SPACE	1
#define FOOD	2
#define SNAKE	3
#define WALL	4

#define UP		72
#define DOWN	80
#define LEFT	75
#define RIGHT	77

void GotoXY(int x, int y);

struct Node {	// 结点结构体
	int x, y;
	Node* nex, * pre;
	Node(int X, int Y) { x = X; y = Y; nex = pre = NULL; }
};

template <int row, int col>
class Map {	// 地图类
	char maze[row][col];
	void PrintWall(int x, int y);
public:
	Map();
	void CreateFood();
	void PrintSnakeBody(int x, int y);
	// 将坐标x,y处置为空地
	void FreeCell(int x, int y);
	// 查看坐标位置是什么,蛇、空地、食物、墙
	char GetTag(int x, int y) {
		return maze[x][y];
	}
	~Map() { GotoXY(0, col + 2); };
};

class Snake {
	// 蛇头、蛇尾
	Node* SnakeHead, * SnakeTail;
	// 蛇所在的地图
	Map<30, 30> mp;
	// 蛇头现在的运动方向
	char direction;
	// 蛇现在生命值,生或死
	bool life;
	// 计时器,timer达到timer_mod的时候蛇移动一格
	int timer, timer_mod;
public:
	Snake();
	~Snake();
	void Move();
	bool IsAlive() {
		return life;
	}
	// 蛇的转向,目标方向不能是自己当前相反的方向
	void TurnAround(char Tag) {
		if (Tag == UP && direction != DOWN)	direction = UP;
		else if (Tag == DOWN && direction != UP)	direction = DOWN;
		else if (Tag == LEFT && direction != RIGHT)	direction = LEFT;
		else if (Tag == RIGHT && direction != LEFT)	direction = RIGHT;
	}
	// 计时器跳动,当到时间时,使蛇移动
	void Tick();
	// 重置计时器
	void ResetTimer() {
		timer = 0;
	}
};

template <int row, int col>
Map<row, col>::Map() {
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			if (i == 0 || i == row - 1 || j == 0 || j == col - 1) {
				this->maze[i][j] = WALL;
				PrintWall(i, j);
			}
			else
				this->maze[i][j] = SPACE;
		}
	}
}

template <int row, int col>
void Map<row, col>::FreeCell(int x, int y) {
	GotoXY(y << 1, x);
	maze[x][y] = SPACE;
	printf("  ");
}

template <int row, int col>
void Map<row, col>::PrintSnakeBody(int x, int y) {
	/* 坐标转换 */
	GotoXY(y << 1, x);
	maze[x][y] = SNAKE;
	printf("■");
}

template <int row, int col>
void Map<row, col>::PrintWall(int x, int y) {
	GotoXY(y << 1, x);
	printf("%c ", 1);
}

template <int row, int col>
void Map<row, col>::CreateFood() {
	srand((unsigned)time(NULL));
	int x = rand() % row;
	int y = rand() % col;

	while (maze[x][y] != SPACE) {
		x = rand() % row;
		y = rand() % col;
	}

	maze[x][y] = FOOD;
	GotoXY(y << 1, x);
	printf("⊙");
}
// Snake.cpp
#include "Sanke.h"

void GotoXY(int x, int y)
{
	// 更新光标位置 
	COORD pos;
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOutput, pos);
	// 隐藏光标 
	CONSOLE_CURSOR_INFO cursor;
	cursor.bVisible = FALSE;
	cursor.dwSize = sizeof(cursor);
	SetConsoleCursorInfo(hOutput, &cursor);
}

void Snake::Tick() {
	Sleep(100);
	timer++;
	if (timer == 5)
		Move(), timer = 0;
}

void Snake::Move() {
	int nx = SnakeHead->x, ny = SnakeHead->y;
	switch (direction)
	{
	case UP:
		nx--;	break;
	case DOWN:
		nx++;	break;
	case LEFT:
		ny--;	break;
	case RIGHT:
		ny++;	break;
	}
	if (mp.GetTag(nx, ny) == WALL || mp.GetTag(nx, ny) == SNAKE) {
		life = false;
		return;
	}
	Node* TempBody = new Node(nx, ny);
	TempBody->nex = SnakeHead;
	SnakeHead->pre = TempBody;
	SnakeHead = TempBody;
	if (mp.GetTag(nx, ny) == FOOD) {
		mp.PrintSnakeBody(nx, ny);
		mp.CreateFood();
		return;
	}
	mp.PrintSnakeBody(nx, ny);
	mp.FreeCell(SnakeTail->x, SnakeTail->y);
	SnakeTail = SnakeTail->pre;
	delete SnakeTail->nex;
}

Snake::Snake() {
	timer = 1;	timer_mod = 100000000;
	SnakeHead = new Node(15, 15);
	SnakeHead->nex = SnakeTail = new Node(15, 14);
	SnakeTail->pre = SnakeHead;
	mp.PrintSnakeBody(15, 15);
	mp.PrintSnakeBody(15, 14);
	life = true;
	direction = RIGHT;
	mp.CreateFood();
}

Snake::~Snake() {
	Node* temp;
	while (SnakeHead <= SnakeTail) {
		temp = SnakeHead->nex;
		delete SnakeHead;
		SnakeHead = temp;
	}
}
// PetroSnaker.cpp
#include <iostream>
#include "Sanke.h"

int main()
{
    system("mode con cols=120 lines=40");
    Snake ss;
    int timer = 0;
    while (true) {
        if (_kbhit()) {
            char InputChar = _getch();
            if (InputChar == -32) {
                InputChar = _getch();
                switch (InputChar)
                {
                case UP:
                    ss.TurnAround(UP);  ss.Move();  ss.ResetTimer();  break;
                case DOWN:
                    ss.TurnAround(DOWN);  ss.Move(); ss.ResetTimer(); break;
                case LEFT:
                    ss.TurnAround(LEFT);  ss.Move();  ss.ResetTimer(); break;
                case RIGHT:
                    ss.TurnAround(RIGHT);  ss.Move();  ss.ResetTimer(); break;
                }
            }
        }
        if (ss.IsAlive() == false) break;
        ss.Tick();
    }
    return 0;
}

  很简单的几百行代码,很简陋,剩下的还可以添加很多功能,比如一个开始的界面,选择难度,地图大小,在空地中随机产生墙体,暂停和退出游戏等等,这些都是一个完整的游戏应具有的功能,但这些都比较简单,在此基础上给这两个类添加几个函数或者在main函数实现都可,笔者不多啰嗦了。

每天都要过得有意义~