这份代码只是一个最最基础的模型,并没有图形界面,实现了蛇的移动,基本地图的建设,食物生成等。剩下的功能都比较简单,也很繁琐,暂时不想再写下去了。贪吃蛇在大一的时候写过一次,但是当时刚学完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函数实现都可,笔者不多啰嗦了。
每天都要过得有意义~