コンソールで動くシューティングゲームを作ってみた
眠かったから,ncurses ライブラリを使ってシューティングゲームを作ってみた.10年以上昔,N88-BASICで同じようなプログラムを作ってたのを思いだした.
作ったのは,
こんなん.十字キーで動かしてスペースでミサイルを発射(3発まで).敵を全部撃ち落とすか,自分が敵にやられると終了.嫌になったらqキーで終了.
コード(雑)
//------------------------------------------------------------------------------------------- /*! \file sample.cpp \brief shooting game on console \author aki-yam \version 0.1 \date Apr.02, 2009 \version 0.2 \date Apr.28, 2009 スレッド関連のバグ修正 */ //------------------------------------------------------------------------------------------- #include <iostream> #include <string> #include <vector> #include <algorithm> #include <curses.h> //------------------------------------------------------------------------------------------- namespace loco_rabbits { class TWorld { private: int sizex, sizey; int oldx, oldy; WINDOW *win; bool locked; struct TCell { char s; int col; TCell(void) : s(' '), col(0) {}; TCell(char _s) : s(_s), col(0) {}; TCell(char _s,int _c) : s(_s), col(_c) {}; }; std::vector<TCell> display; TCell& getCell (int x, int y) {return display[(sizex+2)*y+x];}; const TCell& getCell (int x, int y) const {return display[(sizex+2)*y+x];}; public: TWorld(void) : sizex(0), sizey(0), oldx(-1), oldy(-1), win(NULL), locked(false) {}; ~TWorld(void) { endwin(); }; void init(int _sizex, int _sizey, bool hidecursor=true) { sizex=_sizex; sizey=_sizey; if(win!=NULL) {endwin(); win=NULL;} display.resize ((sizex+2)*(sizey+2)); win=initscr(); timeout(1); noecho(); cbreak(); leaveok(stdscr, TRUE); scrollok(stdscr, FALSE); if (hidecursor) curs_set(0); if (has_colors()) { start_color(); use_default_colors(); init_pair(0, COLOR_BLACK, -1); init_pair(1, COLOR_RED, -1); init_pair(2, COLOR_GREEN, -1); init_pair(3, COLOR_YELLOW, -1); init_pair(4, COLOR_BLUE, -1); init_pair(5, COLOR_CYAN, -1); init_pair(6, COLOR_MAGENTA, -1); init_pair(7, COLOR_WHITE, -1); } clear(); flush(); }; void clear(void) { std::fill (display.begin(),display.end(),TCell()); // draw walls attrset (COLOR_PAIR(2)); int x,y; for(x=1,y=0;x<=sizex;++x) getCell(x,y)=TCell('-',2); for(x=0,y=1;y<=sizey;++y) getCell(x,y)=TCell('|',2); for(x=1,y=sizey+1;x<=sizex;++x) getCell(x,y)=TCell('-',2); for(x=sizex+1,y=1;y<=sizey;++y) getCell(x,y)=TCell('|',2); getCell(0,0)=TCell('+',2); getCell(0,sizey+1)=TCell('+',2); getCell(sizex+1,0)=TCell('+',2); getCell(sizex+1,sizey+1)=TCell('+',2); }; void forceRange(int &x, int &y) { if(x<0) x=0; else if(x>=sizex) x=sizex-1; if(y<0) y=0; else if(y>=sizey) y=sizey-1; }; void flush (void) { locked= true; std::vector<TCell>::const_iterator itr(display.begin()); for(int y=0;y<sizey+2;++y) for(int x=0;x<sizex+2;++x,++itr) { attrset (COLOR_PAIR(itr->col)); mvaddch (y,x,itr->s); } attrset (COLOR_PAIR(0)); refresh(); locked= false; }; void putChar (int x, int y, char s, int col=0) { forceRange(x,y); ++x; ++y; getCell(x,y)=TCell(s,col); }; int getChar (void) { while (locked) usleep(10); return getch(); }; void putString (int x, int y, const char *str, int col=0) { locked= true; attrset (COLOR_PAIR(col)); for (const char *s=str; *s!='\0'; ++s,++x) mvaddch(sizey+2+y,x,*s); flush(); locked= false; }; }; //------------------------------------------------------------------------------------------- /* these names conflict with STL */ #undef clear //------------------------------------------------------------------------------------------- } //------------------------------------------------------------------------------------------- #include <sstream> #include <iomanip> #include <pthread.h> #include <cstdlib> #include <cmath> //------------------------------------------------------------------------------------------- inline double u_rand (const double &max) { return (max)*static_cast<double>(rand()) / static_cast<double>(RAND_MAX); } inline double u_rand (const double &min, const double &max) { return u_rand(max - min) + min; } inline int u_rand (int min, int max) // return [min,max] { return floor(u_rand(static_cast<double>(min),static_cast<double>(max+1))); } //------------------------------------------------------------------------------------------- using namespace std; using namespace loco_rabbits; //------------------------------------------------------------------------------------------- struct TMissile { bool active; float ry; int x,y; TMissile(void) : active(false) {} void fire (int _x, int _y) { active= true; x= _x; y= _y; ry= y; } void step(void) { ry-=0.5; y=static_cast<int>(ry); } }; struct TEnemy { bool active, fired; int x,y; TEnemy(void) : active(true), fired(false) {} void step(void) { x+= u_rand(-1,1); y+= 1; } }; enum TKeyFlag {kfNone=0,kfEnd,kfFire}; class TSimulator { private: TWorld world; int sizex, sizey; int x, y; int velx, vely; bool fired; vector<TMissile> missile; vector<TEnemy> enemy; TKeyFlag keyflag; pthread_t thread_kbhit; void step_simulate (void); friend void* kbhit (void *sim); // この関数は private メンバにアクセスできる public: TSimulator (int num_of_enemy=3, int num_of_missile=3) : missile(num_of_missile), enemy(num_of_enemy) {} void start (int _sizex, int _sizey); }; //------------------------------------------------------------------------------------------- void* kbhit (void *arg) { TSimulator *sim(reinterpret_cast<TSimulator*>(arg)); while(!sim->fired && sim->keyflag!=kfEnd) { int key= sim->world.getChar(); if (key=='q') { sim->keyflag= kfEnd; break; } if (key==0x41) sim->vely-=1; else if (key==0x42) sim->vely+=1; else if (key==0x43) sim->velx+=1; else if (key==0x44) sim->velx-=1; else if (key==' ') sim->keyflag= kfFire; } return NULL; } //------------------------------------------------------------------------------------------- void TSimulator::start (int _sizex, int _sizey) { sizex= _sizex; sizey= _sizey; world.init (sizex,sizey); world.putString(0,0,"usage: arrow key: move, space: missile, `q': exit",2); // setup myself x= sizex/2; y= sizey-1; velx=vely= 0; fired=false; // setup enemy for (size_t i(0); i<enemy.size(); ++i) { enemy[i].x= u_rand(0,sizex-1); enemy[i].y= u_rand(0,4); } // setup missile keyflag= kfNone; if (pthread_create(&thread_kbhit, NULL, &kbhit, this)!=0) { cerr<<"error in pthread_create"<<endl; exit(1); } while (keyflag!=kfEnd && !fired) { world.clear(); // simulate step_simulate(); // plot for (size_t i(0); i<enemy.size(); ++i) { if (enemy[i].active) { if (enemy[i].fired) world.putChar (enemy[i].x,enemy[i].y,'*',1); else world.putChar (enemy[i].x,enemy[i].y,'V',6); } } for (size_t i(0); i<missile.size(); ++i) if (missile[i].active) world.putChar (missile[i].x,missile[i].y,'^',3); if (fired) { world.putChar (x,y,'*',1); world.putString(0,1,"crash! (hit any key)",1); } else world.putChar (x,y,'A',4); world.flush(); usleep(70000); } usleep(1000000); void *ret(NULL); pthread_join(thread_kbhit,&ret); } //------------------------------------------------------------------------------------------- void TSimulator::step_simulate (void) { if (keyflag==kfFire) { keyflag= kfNone; for (size_t i(0); i<missile.size(); ++i) if (!missile[i].active) // 使用していないミサイル {missile[i].fire(x,y); break;} } x+= velx; y+= vely; velx= 0; vely= 0; world.forceRange(x,y); bool no_enemy(true); for (size_t i(0); i<enemy.size(); ++i) { if (enemy[i].fired) enemy[i].active= false; if (enemy[i].active) { no_enemy= false; enemy[i].step(); if (enemy[i].y>=sizey) enemy[i].y=0; world.forceRange(enemy[i].x,enemy[i].y); if (enemy[i].x==x && enemy[i].y==y) fired= true; } } if (no_enemy) { keyflag= kfEnd; world.putString(0,1,"you win! (hit any key)",4); } for (size_t i(0); i<missile.size(); ++i) { if (missile[i].active) { missile[i].step(); if (missile[i].y<0) missile[i].active= false; for (size_t i(0); i<enemy.size(); ++i) { if (enemy[i].x==missile[i].x && enemy[i].y==missile[i].y) { enemy[i].fired= true; missile[i].active= false; } } } } } //------------------------------------------------------------------------------------------- int main(int argc, char**argv) { srand((unsigned)time(NULL)); TSimulator sim; sim.start(40,20); return 0; } //-------------------------------------------------------------------------------------------
コンパイル
g++ -g -Wall -O2 ファイル名.cpp -lm -lncurses -lpthread