简单贪吃蛇游戏的C++实现

来源:互联网 发布:mac安装win8系统 编辑:程序博客网 时间:2024/04/28 20:22

    • 1全局头文件globalhpp
    • 2snake类的声明和实现snakehpp
    • 3map类的声明和实现maphpp
    • 4游戏运行主文件gamecpp
    • 5小结

我大概在一个多月前把自己上学期写的c代码的贪吃蛇游戏push到csdn上,并且说c风格的贪吃蛇写起来有些麻烦(贪吃蛇游戏的c语言实现),准备用面向对象的c++再写一遍。现在我们专业恰好刚教完了c++,学校也布置了一道简单的贪吃蛇的编程题目,实现下来,的确觉得c++的思路清晰很多,所以再次把c++的代码push上来,供大家对比参考:)

直接上代码,c++把整个游戏拆分成几个文件,分开上,有一定的c++基础的同学应该可以很容易看懂。

1、全局头文件(global.hpp)

#ifndef _GLOBAL_H_#define _GLOBAL_H_#ifndef SYMBOLS#define HEAD '@'#define BODY 'X'#define EMPTY '+'#define FOOD '$'#endif  // !SYMBOLSenum 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、snake类的声明和实现(snake.hpp)

(为了简化结构,把声明和实现共同放在了hpp文件里,减少了一点封装性,实际上应该分开头文件和实现文件好一点)

此处使用了容器list作为蛇身(body)的表达形式,这样可以非常方便地进行表达,读者有兴趣可以用数组实现一下,一不小心就会出现有趣的内存错误。。。

#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、map类的声明和实现(map.hpp)

在这里,map中应该包含一个snake类作为组合关系。
在组合关系里面,想要直接修改snake的各种参数是不可行的,所以在前面snake类的声明里加上了诸如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() { // 这个是用来更新地图的    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())) {  // 为了避免追尾问题        _map[_snake.getbody().back().x][_snake.getbody().back().y] = EMPTY;    }    _snake.getHead().move(d);    if (_snake.getHead() == _snake.getbody().front()) {  // 判断蛇是否往回走        _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

蛇移动的算法是,头先动,如果判断可以走,则把头原来的位置push_front到body的头部,然后用pop_back把body的尾部抹去
(读者不熟悉list容器的操作的话可以先去了解一下,很容易的:))

4、游戏运行主文件(game.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() {  // 看,main函数只有这么短!!!!  GameUI greedySnake;  greedySnake.GameLoop();  return 0;}

(事实上为了达到封装性,gameUI的类也应该分开来实现。)

5、小结

这个贪吃蛇还比较的低端,只能实现一键一步的行走方式,还没有像我的c语言贪吃蛇那样可以自己走,并且有AI模式。实现自己走要使用到windows的下的一个库,实现起来还比较麻烦,可以参考我的c贪吃蛇。用c++重写一遍贪吃蛇,主要作用是可以更加清楚地体会到类的封装与使用。

以后如果有时间,笔者可能会为这个贪吃蛇写下补丁什么的,体会一下c++的代码重用和方便修改的特性,这是c语言所没有的优点。

Enjoy coding! :)

1 0
原创粉丝点击