今日のひらしょー本(20150321):Cp1 ひらしょーさんのコードと違う部分

今日のひらしょー本の目次

http://snoopopo.hatenablog.com/entry/2015/04/27/114854

今回は ひらしょー本の説明を読む→自分でコード組んでみる→ひらしょーさんのコード見る

の流れでやったので、けっこう違う部分があるけど,javaっぽい書き方をしたりひとりよがりになものになりそうなので、 ひらしょーさんのコードはよく読んできっちり理解しておこう.

#ひらしょーさんのソースは、 ( 01_FirstGame\NimotsuKun2\main.cpp ) より抜粋

参照で別名にして書くのを簡単にしてる?

・ひらしょーさん

Array2D< Object >& o = mObjects;

↑のように mObjects を o という参照に入れてる。=別名にしてる。

それ以降は o( x, y ) といった感じで呼べてて短くていい感じだからやったのかな.

僕のソースだと mObjects( x, y )とそのまま書いてる.

更新対象を絞ってる

・ひらしょーさん

//A.その方向が空白またはゴール。人が移動。
if ( o( tx, ty ) == OBJ_SPACE ){
    o( tx, ty ) = OBJ_MAN;
    o( x, y ) = OBJ_SPACE;
//B.その方向が箱。その方向の次のマスが空白またはゴールであれば移動。
}else if ( o( tx, ty ) == OBJ_BLOCK ){
    //2マス先が範囲内かチェック
    int tx2 = tx + dx;
    int ty2 = ty + dy; 
    if ( tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h ){ //押せない
        return;
    }
    if ( o( tx2, ty2 ) == OBJ_SPACE ){
        //順次入れ替え
        o( tx2, ty2 ) = OBJ_BLOCK;
        o( tx, ty ) = OBJ_MAN;
        o( x, y ) = OBJ_SPACE;
    }
}

・僕のコード

//移動不可能チェック
if( Object::OBJ_WALL == mObject( tpx, tpy ) ){
    //移動出来ない
    return;

}else if( Object::OBJ_BLOCK == mObject( tpx, tpy ) ){

    //ターゲットプレイヤー位置がブロックで、ターゲットブロック位置が壁かブロックの場合
    if( Object::OBJ_BLOCK == mObject( tbx, tby ) || Object::OBJ_WALL ==  mObject( tbx, tby ) ){
        //移動出来ない
        return;
    }
}

//プレイヤー座標の更新
mObject( px, py ) = Object::OBJ_SPACE;

//ターゲットプレイヤー座標の更新
if( Object::OBJ_SPACE == mObject( tpx, tpy ) ){
    mObject( tpx, tpy ) = Object::OBJ_MAN;      
}else if( Object::OBJ_BLOCK == mObject( tpx, tpy ) ){
    mObject( tpx, tpy ) = Object::OBJ_MAN;  

    //ターゲットブロックの位置も更新
    if( Object::OBJ_SPACE == mObject( tbx, tby ) ) {
        mObject( tbx, tby ) = Object::OBJ_BLOCK;
    }
}

このデータの状態を更新する場所だけのはなしじゃないのだけど、

ひらしょーさんのコードの方が、更新対象を確実に絞って更新してるのに対して僕のコードはなんというか 更新しないものを最初にチェックして弾いて、それ以外を更新してる感じ.というかそういう脳になってる.

実は最初のチェックが漏れてて変な動きをしたので、更新対象だけを確実に絞ってあとはスルーすればいいの考え方に変えていこう.

列挙型は数値だから添字になる

ひらしょーさんのコードではswitchを使ってるけど、NimotsuKun2の前verのNimotsuKunでは enum Objectを数値とみたててchar配列の順番と合わせてとってたから、 今回でゴール状態を別の配列に持つように変わってもこういう風にしてみた.

個人的にはenumを配列の添字として使うのはわかりやすいからけっこう好きだ.

・僕のコード

const char font[]     = {' ', 'p', 'o', '#'};
const char goalfont[] = {'.', 'P', 'O'};

for( int y = 0; y < mStageY; ++y ){
    for( int x = 0; x < mStageX; ++x ){
        Object obj = mObject( x, y );
        bool goal = mGoal( x, y );
        if ( true == goal) {
            cout << goalfont[obj];    //列挙型は数値型なので、順番を合わせればとれる。
        }else{
            cout << font[obj];    //列挙型は数値型なので、順番を合わせればとれる。
        }
    }
    cout << endl;
}

縦横サイズのとり方

・ひらしょーさん

mWidth = mHeight = 0; //初期化
//現在位置
int x = 0;
int y = 0;
for ( int i = 0; i < size; ++i ){
    switch ( stageData[ i ] ){
        case '#': case ' ': case 'o': case 'O':
        case '.': case 'p': case 'P':
            ++x;
            break;
        case '\n': 
            ++y;
            //最大値更新
            mWidth = max( mWidth, x );
            mHeight = max( mHeight, y );
            x = 0; 
            break;
    }
}

とくに解説などで触れてないけれど、 この //最大値更新 の部分で std::max という標準ライブラリを使ってるみたいだ.

この辺りはライブラリに何があるのかよくわからないので追々…

文字列のループは最後のNULL文字で…

c++では文字列 char[] の最後に必ずNULL文字がつく、というはなしがあったから物珍しさで 文字列のstageデータでループする処理は全て以下のように書いてしまったけど.

・僕のコード

const char* i = stage;
int x = 0;
int y = 0;

while( *i != '\0'){   //char[]は最後に \0 が入っている
~(略)~
}

やっぱりforの方がよかったかな.と思う.

ファイル読込のときにファイルのサイズ取ってるから、 ひらしょーさんのコードでは、for文でファイルサイズの文だけループするように書いてる.

自分がwhileよりforの方がいいと思うのは、EffectiveJavaから知った以下の理由。

・ループ変数が使える

・コピペでの間違いが起こりにくい

http://snoopopo.hatenablog.com/entry/2015/02/07/170842

ここ↑でも少し書いてた.