C++ simple snake game

  • 2020-06-03 07:46:11
  • OfStack

About a month ago, I added push to csdn, the snake game I wrote in the last semester, and said that c-style snake was a little bit troublesome to write (c language for snake game), and I was going to write c++ again. Now our major just finished teaching c++, the school also assigned a simple snake programming problem, down to the realization of c++ thought is a lot clearer, so again c++ code push up, for your reference :)

Directly on the code, c++ the whole game is divided into several files, separate, have 1 fixed c++ students should be able to easily understand.

1. Global header file (ES17en.hpp)


#ifndef _GLOBAL_H_
#define _GLOBAL_H_

#ifndef SYMBOLS
#define HEAD '@'
#define BODY 'X'
#define EMPTY '+'
#define FOOD '$'
#endif // !SYMBOLS

enum direction { up = 0, down = 1, left = 2, right = 4, freeze = 5 };

struct point {
 int x;
 int y;
 point(int x = 0, int y = 0) : x(x), y(y) {}
 point(const point& another) : x(another.x), y(another.y) {}
 point& operator=(const point& other) {
 x = other.x;
 y = other.y;
 return *this;
 }
 friend bool operator==(const point& point1, const point& point2) {
 return point1.x == point2.x && point1.y == point2.y;
 }
 point& move(direction d) {
 switch (d) {
 case up:
 x--;
 break;
 case down:
 x++;
 break;
 case left:
 y--;
 break;
 case right:
 y++;
 break;
 case freeze:
 default:
 break;
 }
 return *this;
 }
};

#endif // !_GLOBAL_H_

2. Declaration and implementation of snake class (ES23en.hpp)

(In order to simplify the structure, the declaration and implementation are put together in the hpp file, reducing the encapsulation by 1 point. In fact, the header file should be separated from the implementation file by 1 point.)

The container list is used here as the expression form of the snake body (body), which is very convenient for expression. If readers are interested, they can use the array to realize 1. If 1 is not careful, there will be interesting memory errors.


#ifndef _SNAKE_H_
#define _SNAKE_H_
#include <iostream>
#include <list>
#include "global.hpp"

class snake {
 point head;
 std::list<point> body;
 public:
 snake(point initial_head);
 snake();
 ~snake() {}
 point& getHead();
 std::list<point>& getbody();
 void grow(point);
 void setHead(point);
};

snake::snake() {
 head.x = 0;
 head.y = 0;
}

snake::snake(point initial_head) {
 setHead(initial_head);
}

void snake::setHead(point _head) {
 head = _head;
}

void snake::grow(point second_node) {
 this -> body.push_front(second_node);
}

point& snake::getHead() {
 return head;
}

std::list<point>& snake::getbody() {
 return body;
}
#endif

3. Declaration and Implementation of map Class (ES36en.ES37en)

Here, an snake class should be included in map as a composition relationship.
It is not feasible to modify the parameters of snake directly in the composition relationship, so the previous declaration of snake class includes functions such as setHead(), getHead(), getbody().


#ifndef _MAP_H_
#define _MAP_H_
#include <iostream>
#include "global.hpp"
#include "snake.hpp"

class map {
 private:
 char** _map;
 snake _snake;
 int height, width;
 std::list<point> foods;
 public:
 map();
 map(point initial_size, point initial_head,
 std::list<point> initial_foods);
 ~map();
 void move(direction d);
 void print();
 bool isGameOver();
 bool isEat();;
 void makemap(void);
};

map::map() {
 _map = NULL;
 height = width = 0;
}

void map::makemap() { //  This is for updating the map 
 for (int i = 0; i < height; i++) {
 for (int j = 0; j < width; j++)
  _map[i][j] = 0;
 }
 for (std::list<point>::iterator i = foods.begin(); i != foods.end(); ++i) {
 _map[i->x][i->y] = FOOD;
 }
 _map[_snake.getHead().x][_snake.getHead().y] = HEAD;
 for (std::list<point>::iterator i = _snake.getbody().begin();
 i != _snake.getbody().end(); ++i) {
 _map[i->x][i->y] = BODY;
 }
 for (int i = 0; i < height; i++) {
 for (int j = 0; j < width; j++) {
  if (_map[i][j] == 0)
  _map[i][j] = EMPTY;
 }
 }
}

map::map(point initial_size, point initial_head, std::list<point> initial_foods)
{
 height = initial_size.x;
 width = initial_size.y;
 _map = new char*[height];
 for (int i = 0; i < height; i++)
 _map[i] = new char[width]();
 _snake.setHead(initial_head);
 foods = initial_foods;
 makemap();
}

map::~map() {
 for (int i = 0; i < height; i++) {
 delete []_map[i];
 }
 delete []_map;
}

void map::print() {
 for (int i = 0; i < height; i++) {
 for (int j = 0; j < width; j++) {
  std::cout << _map[i][j];
 }
 std::cout << std::endl; 
 }
 std::cout << std::endl;
}

bool map::isGameOver() {
 point temp = _snake.getHead();
 if (temp.x == height || temp.y == width || temp.x < 0 || temp.y < 0)
 return true;
 if (_map[temp.x][temp.y] == BODY) return true;
 return false;
}

bool map::isEat() {
 point temp = _snake.getHead();
 if (temp.x == height || temp.y == width || temp.x < 0 || temp.y < 0)
 return false;
 if (_map[temp.x][temp.y] == FOOD) return true;
 else return false;
}

void map::move(direction d) {
 point temp_f = _snake.getHead();
 if (!(_snake.getbody().empty())) { //  To avoid rear-end collisions 
 _map[_snake.getbody().back().x][_snake.getbody().back().y] = EMPTY;
 }
 _snake.getHead().move(d);
 if (_snake.getHead() == _snake.getbody().front()) { //  Decide if the snake is going back 
 _snake.setHead(temp_f);
 _map[_snake.getbody().back().x][_snake.getbody().back().y] = BODY;
 return;
 }
 if (!isGameOver()) {
 if (isEat()) {
  point eaten = _snake.getHead();
  foods.remove(eaten);
  _snake.grow(temp_f);
 } else {
  _snake.getbody().push_front(temp_f);
  _snake.getbody().pop_back();
 }
 makemap();
 } else {
 if (!(_snake.getbody().empty())) {
  _map[_snake.getbody().back().x][_snake.getbody().back().y] = BODY;
 }
 }
}
#endif

The algorithm of snake movement is that the head moves first. If the decision is ok, the original position of the head, push_front, is transferred to the head of body, and then the tail of body is erased with pop_back
(if readers are not familiar with the operation of list container, they can learn about it first. It is very easy :)

4. Game run main file (ES61en. cpp)


#include "map.hpp"
#include "global.hpp"
#include <iostream>
#include <list>
#include <algorithm>

using std::cin;
using std::cout;
using std::cerr;
using std::endl;

class InvalidInputException {
 public:
 InvalidInputException() { cerr << "Invalid input!" << endl; }
};
class DuplicateInputException : public InvalidInputException {
 public:
 DuplicateInputException() { cerr << "Duplicate input!" << endl; }
};

class GameUI {
 private:
 map* world;
 point initial_size;
 point initial_head;
 std::list<point> initial_foods;

 public:
 GameUI() {
 cout << "please input two positive integers indicates the map size!"
  << endl;
 cin >> initial_size.x >> initial_size.y;
 if (initial_size.x <= 5 || initial_size.y <= 5 || initial_size.x > 15 ||
 initial_size.y > 15) {
 cout << "invalid input" << endl;
 throw InvalidInputException();
 }
 cout << "please input two positive integers(range(0, size_x-1), "
  "range(0,size_y-1)) the initialize snake head position!"
  << endl;
 cin >> initial_head.x >> initial_head.y;
 if (initial_head.x >= initial_size.x || initial_head.x < 0 ||
 initial_head.y >= initial_size.y || initial_head.y < 0) {
 cout << "invalid input" << endl;
 throw InvalidInputException();
 }
 int food_num;
 cout << "please input how many food you will put and then input food "
  "position which is different form each other"
  << endl;
 cin >> food_num;

 if (food_num <= 0) {
 throw InvalidInputException();
 }

 while (food_num > 0) {
 food_num--;
 point temp;
 cin >> temp.x >> temp.y;
 if (temp.x >= 0 && temp.x < initial_size.x && temp.y >= 0 &&
  temp.y < initial_size.y &&
  std::find(initial_foods.begin(), initial_foods.end(), temp) ==
  initial_foods.end() &&
  !(temp.x == initial_head.x && temp.y == initial_head.y)) {
 initial_foods.push_back(temp);
 } else {
 throw DuplicateInputException();
 }
 }

 world = new map(initial_size, initial_head, initial_foods);
 }

 ~GameUI() { delete world; }

 void GameLoop() {
 world->print();
 bool exit = false;
 while (true) {
 char operation = getInput();
 switch (operation) {
 case 'w':
 case 'W':
  this->world->move(up);
  break;
 case 's':
 case 'S':
  this->world->move(down);
  break;
 case 'a':
 case 'A':
  this->world->move(left);
  break;
 case 'd':
 case 'D':
  this->world->move(right);
  break;
 case 'q':
 case 'Q':
  exit = true;
  break;
 default:
  this->world->move(freeze);
 }
 world->print();
 if (world->isGameOver()) {
 cout << "Game Over!" << endl;
 break;
 }
 if (exit) {
 cout << "Bye!" << endl;
 break;
 }
 }
 }

 char getInput() {
 char temp;
 cin >> temp;
 return temp;
 }
};

int main() { //  Look, main The function is only this short!! 
 GameUI greedySnake;
 greedySnake.GameLoop();
 return 0;
}

(In fact, for encapsulation, gameUI's classes should be implemented separately.)

5, summary

This gluttonous snake is still at the low end, only able to walk with one key and one step. It has no AI mode like my c language, which can walk by itself. To realize my own way, I need to use one of the libraries under windows, but it is still troublesome to realize, please refer to my c snake. c++ rewrite 1 snake, the main role is to more clearly understand the encapsulation and use of classes.

In the future, if there is time, I may write a patch for this snake, to experience 1 c++ code reuse and easy to modify the features of c language does not have the advantage.

Enjoy coding! :)

For more exciting content about C++ mini games, please click on the topic: C++ classic mini games to learn more


Related articles: