2012年2月4日土曜日

C++プログラミング入門(1) // 倉庫番プログラムの実装

うまく修論が通れば大学院を卒業して,晴れて社会人になる.
入社試験でC++のプログラミングを課されるくらいなので,きっと仕事でC++を使うんだろう.
そこで,軽く文法等を覚えるために入社前に勉強することにした.

【現状】
研究ではもっぱら数値計算用ソフト(MATLAB)のみを使っており,メモリ等を意識してプログラムを組むことは無い.
変数の宣言やメモリの確保のような行儀の良いプログラムを書くのは3年前くらいにCでかじった程度.

純粋に文法を勉強するのもあほらしいので,


ゲームプログラマになる前に覚えておきたい技術



っていう分厚い本を買って読むことにした.

第一章ではいわゆる倉庫番のプログラムのお勉強.
読みすすめていくと,プログラムから醸しだされる著者の優秀さを感じ取った.
優秀なプログラマの書くプログラムは読んでて感動する.


本を読んでみて重要だと思った点をコメントアウトで追記しつつプログラムを組んだ.
以下はコンソール上で倉庫番を楽しむプログラム.
コピペしてコンパイルすれば動くはず.


#include<iostream>
using namespace std;


// ステージのオブジェクトの定義
// 列挙型に出来るときはなるべく使う.
// 利点は2つ.変な値が入らない+デバッグの際に列挙型の名前で確認できる.
// 区別のために要素は大文字.
enum Obj{
// 列挙型は単なるint型
SPACE, // 0
WALL, // 1
GOAL,
MAN,
MAN_ON_GOAL,
BLOCK,
BLOCK_ON_GOAL,

UNKNOWN,
};


// ステージ
// グローバル変数はどこからも見えるので接頭辞をつけて区別する.
// いじらない変数にはconstをつける.
// キャメル記法推奨.
// 文字列定数の途中で改行する場合は文末に\が必要.
const char gStage[] = "\
##########\n\
#    p   #\n\
#  .o  0 #\n\
#        #\n\
##########";

// 幅と高さ
const int gHeight = 5,gWidth = 10;

void initialize(Obj *state,const int height,const int width,const char *stage){
const char *p = stage;
int x=0,y=0;

while(*p){
 Obj t;
 switch(*p){
  case ' ': t = SPACE; break;
  case '#': t = WALL; break;
  case '.': t = GOAL; break;
  case 'p': t = MAN; break;
  case 'P': t = MAN_ON_GOAL; break;
  case 'o': t = BLOCK; break;
  case '0': t = BLOCK_ON_GOAL; break;
  case '\n': t = UNKNOWN; y++; x=0; break;
  default : t = UNKNOWN; break;
 }

 if(t!=UNKNOWN){
  state[y*width+x] = t; // 縦height,横widthをもつ二次元配列における(x,y)にアクセス.
  x++;
 }

 p++;
}
}

void draw(const Obj *state,const int height,const int width){
const char c[] = {' ','#','.','p','P','o','0'}; // 列挙型は単なるint型
for(int h=0;h<height;h++){
 for(int w=0;w<width;w++){
  cout << c[state[h*width+w]];
 }
 cout << "\n";
}
}

void update(Obj *state,const char input,const int height,const int width){

int x,y,tx,ty,dx=0,dy=0;
int p,tp,tp2;

// 移動方向の定義
// 位置 + 差分
switch(input){
 case 'a': dx = -1; break;
 case 's': dx = 1; break;
 case 'w': dy = -1; break;
 case 'z': dy = 1; break;
 case 'q': cout << "Bye"; break;
 default : cout << "undefined input\n please input \"a,s,w,z\""; break;
}

// プレイヤー位置の探索
for(int i=0;i<height*width;i++){
 if(state[i]==MAN || state[i]==MAN_ON_GOAL){
  x = i%width; // 現在地のx座標
  y = i/width; // 現在地のy座標
  // 計算で求まる情報はなるべく保存しないほうがいい...
  p = i; // 現在地
 }
}

// 移動できるか判定
tx = x + dx; ty = y + dy;
if(tx < 0 || width <= tx || ty < 0 || height <= ty){
 cout << "can't move";
 return;
}

// 移動先の計算
tp = ty*width + tx;
tp2 = (ty+dy)*width + (tx+dx);

// 移動
switch(state[tp]){
 case SPACE:
  state[p] = (state[p]==MAN) ? SPACE : GOAL;
  state[tp] = MAN;
  break;

 case GOAL:
  state[p] = (state[p]==MAN) ? SPACE : GOAL;
  state[tp] = MAN_ON_GOAL;
  break;

 case BLOCK:

  switch(state[tp2]){
   case SPACE:
    state[tp2] = BLOCK;
    state[tp] = MAN;
    state[p] = (state[p]==MAN) ? SPACE : GOAL;
    break;

   case GOAL:
    state[tp2] = BLOCK_ON_GOAL;
    state[tp] = MAN;
    state[p] = (state[p]==MAN) ? SPACE : GOAL;
    break;

   default:
    break;
  }
  break;

 case BLOCK_ON_GOAL:

  switch(state[tp2]){
   case SPACE:
    state[tp2] = BLOCK;
    state[tp] = MAN_ON_GOAL;
    state[p] = (state[p]==MAN) ? SPACE : GOAL;
    break;

   case GOAL:
    state[tp2] = BLOCK_ON_GOAL;
    state[tp] = MAN_ON_GOAL;
    state[p] = (state[p]==MAN) ? SPACE : GOAL;
    break;

   default:
    break;
  }
  break;

 default:
  break;
}


}

bool isClear(const Obj *state,const int height,int width){

for(int i=0;i<height*width;i++){
 if(state[i]==BLOCK){
  return false;
 }
}

cout << "Congratulations!!";
return true;
}

int main(){
char input;
Obj *state = new Obj[gHeight*gWidth];

initialize(state,gHeight,gWidth,gStage);
draw(state,gHeight,gWidth);

do{
 cout << "Input:";
 cin >> input;

 update(state,input,gHeight,gWidth);
 draw(state,gHeight,gWidth);
}while(input!='q' && !isClear(state,gHeight,gWidth));


delete[] state; // メモリの解放
state = 0; // 使い終わったポインタには0を代入


return 0;
}







【実行画面】