C++ 实现贪吃蛇最基本模板

    科技2022-07-10  128

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

    每天都要过得有意义~

    Processed: 0.023, SQL: 8