无意中浏览到网页 blog.csdn.net/jjzhoujun2010/article/details/6709827 ,一看是置顶的文章,是用C语言写的贪吃蛇游戏,觉得很好完,就看了看,粗略看了一下,代码量不是很大,而且关键的算法也给了详细的说明,由于自己更偏重C++,所以就有了用C++来改写此游戏的想法,于是就行动起来。前后大约一天半的时间把整个程序调试通过。
整个代码可以从 https://github.com/lmdyyh/Snake 下载。下面就说说各个文件的作用:
Timer.h和Timer.cpp定义了游戏中的定时器,时间到时会向内核发送SIGALRM信号,测试程序testSnake.cpp中定义了SIGALRM的信号处理程序,整个游戏中只需要一个定时器,所以Timer采用了单例模式编写。
Timer.h
#ifndef TIMER_H
#define TIMER_H
class Timer{
public:
static void wrapUp();
static int set_ticker(int n_msecs);
static Timer* getInstance();
Timer();
private:
static Timer* _instance;
};
#endif
Food.h和Food.cpp定义游戏中蛇的食物,当food被蛇吃了后,会随机生成另一个位置,模拟生成另一个food,而实际游戏中就只有一个food,于是还是采用单例模式。Food是个静态类,里面只能包含静态成员,而我们又需要设置food的位置,于是就需要两个set函数,set函数作为static类成员函数时,会出现问题,因为静态类成员参数中会默认插入const Food作为this的实参,此时就丢弃了const限定符,编译器会报错,于是就需要在set函数后加上const修饰符,可是加上const修饰符后就不能设置数据成员了,此时就需要将位置数据成员用mutable修饰。
Food.h
#ifndef FOOD_H
#define FOOD_H
class Food{
public:
static Food* getInstance();
void dispFood()const;
int getXPos()const;
int getYPos()const;
void setXPos(int)const;
void setYPos(int)const;
protected:
Food(int,int);
private:
static Food* _instance;
mutable int fx_pos;
mutable int fy_pos;
};
#endif
核心程序是Snake.h和Snake.cpp。把一些与所有游戏都相关的函数抽象到Game.h中形成一个抽象类,使Snake类继承至Game类,Snake采用STL中的list双向链表实现,构造函数中首先生成一个空链表,然后push_back一个std::pair<int,int>数据成员作为蛇的初始位置,并且生成一个Food和一个Timer,Snake和Food、Timer采用组合的方式耦合。
Game.h
#ifndef GAME_H
#define GAME_H
class Game{
public:
virtual void gameStart()=0;
virtual void gameOver(int)=0;
virtual void initGame()=0;
virtual void keyControl()=0;
};
#endif
Snake.h
#ifndef SNAKE_H
#define SNAKE_H
#include <boost/noncopyable.hpp>
#include <list>
#include <utility>
#include <boost/shared_ptr.hpp>
#include "Game.h"
#include "Timer.h"
#include "Food.h"
#include <signal.h>
using namespace std;
class Snake :public Game ,boost::noncopyable{
public:
Snake(int ,int );
~Snake();
void insertSnake(int,int);
void shrinkSnake();
void moveSnake();
void gameOver(int);
void gameStart();
void initGame();
void keyControl();
private:
void initSnake();
int x_pos; //position
int y_pos;
int length;//pairList's size
int x_dir;
int y_dir;
int ttm;
int ttg;
bool moved;
bool ate;
typedef list<pair<int,int> > pairList;
pairList* snakeNode;
Timer *timer;
Food *food;
};
#endif
具体的算法参考链接原文。这里timer和food应该使用boost::shared_ptr来管理,防止资源泄漏。
最后说一下这个C++代码中最困难的一点,就是在每当定时器时间到,向系统发射SIGALRM信号,这时就会调用我们用signal设置的信号处理函数,此处信号处理函数是使蛇移动,也就是调用Snake的moveSnake()函数,signal需要的是void (*)(int)的回调函数,而我们传给signal的是void (Snake::*)(int)的回调函数,编译器会报错。这里耽误了很长时间,反复实验了几种方法,最后通过将信号处理函数包装才得以解决,如:
testSnake.cpp
#include "Snake.h"
#include <iostream>
using namespace std;
void Snake_memberFn_Wrapper(int n);
Snake snake(5,10);
int main(){
signal(SIGALRM,Snake_memberFn_Wrapper);
snake.gameStart();
}
void Snake_memberFn_Wrapper(int n){
snake.moveSnake();
}
声明一个全局的snake,将真正的信号处理函数包装进Snake_memberFn_Wrapper中,并将其作为signal的处理函数即可,这样内核收到SIGALRM后就会调用moveSnake了。
又用了一个下午加一个晚上给游戏加了一个背景音乐,用的irrklang库,详见www.ambiera.com/irrklang/docu/index.html#tipsandtricks ,在主线程中开了一个子线程运行音乐。