前回は定義部分のみを説明しましたので、今回はCCLITHGTCYCLEクラスのメンバー関数を解説します。内容的に難しいところはありませんので、またコメント+(解説:)で説明します。

 

///////////////////
//コンストラクター
///////////////////
CLIGHTCYCLE::CLIGHTCYCLE() {

    Init();
}
//(解説:これがコンストラクターで、クラスのインスタンスが作られる際に実行されます。この例ではInit()という関数を実行します。)


///////////////////////////////
//デバイスコンテキストの初期化
//全車共通で一回だけ実行する
///////////////////////////////
void CLIGHTCYCLE::SetDC(HDC hDC) {    

    m_hDC = hDC;
}
//(解説:これはLightCycle走らせる場(表示デバイス)へのアクセス(コンテキスト)を設定します。本プログラムの場合にはCANVASクラスのm_cvsという仮想ウィンドウになります。)

/////////////////
//座標限界の設定
/////////////////
void CLIGHTCYCLE::SetLimit(int minx, int miny, int maxx, int maxy) {

    m_MinX = minx;        //x座標最小値
    m_MinY = miny;        //y座標最小値
    m_MaxX = maxx;        //x座標最大値
    m_MaxY = maxy;        //y座標最大値
}
//(解説:これは表示デバイスの座標上の表示限界を設定する関数です。CLIGHTCYCLEのメンバー変数に値を入れるだけです。)

/////////////////////
//競技車データ初期化
/////////////////////
void CLIGHTCYCLE::Init() {

    //個車属性情報
    m_Color = 0;
    m_StartDir = 0;
    m_StartX = 0;
    m_StartY = 0;
    m_Speed = 0;
    m_Caprice = 0;
    m_Alive = FALSE;
}
//(解説:これはCLIGHTCYCLEクラスに基づいて作られる競技車(インスタンス)に関わる所別の競技車両データです。順位色、開始時進行方向、開始時位置(x, y)、速度(0-2)、方向転換頻度(0-3)、生死フラグ(bool)になります。)


/////////////////////////
//競技車スタート位置設定
/////////////////////////
void CLIGHTCYCLE::GetReady() {

    //内部管理変数の設定
    m_Times = 0;
    m_Alive = TRUE;
    m_Dir = m_StartDir;
    m_x = m_StartX;
    m_y = m_StartY;
}
//(解説:これは競技が始まる際にワーク変数を初期化する関数です。協議が終了し、再度実行する際にも呼ぶ必要があります。)

///////////////////////
//競走車の初期化
//各車の属性を設定する
///////////////////////
void CLIGHTCYCLE::SetData(int col, int dir, int x, int y, int sp = 0, int cap = 0) {

    m_Color = col;
    m_StartDir = dir;
    m_StartX = x;
    m_StartY = y;
    m_Speed = sp;
    m_Caprice = cap;
}
//(解説:これは主にファイルや入力ダイアログからデータを読む場合を想定して作った関数ですが、実際には直接メンバー変数に代入することが多かったです。)

/////////////////////////////////////////
//現在位置(m_x、m_y)と引数の方向(dir)
//から次の位置(m_nx、m_nyに代入される)
//を求め、競技場内か否か、未走破(黒)か
//否か、斜線跨ぎが無いかで進める(TRUE)
//か否(FALSE)かを返す
/////////////////////////////////////////
bool CLIGHTCYCLE::CheckNext(int dir) {

    bool OK = TRUE;        //戻り値用ブール変数
    //現在方向から次の位置をm_nx、m_nyに代入
    switch(dir) {
    case 0:
        m_nx = m_x;
        m_ny = m_y - 1;
        if(m_ny <= m_MinY)    OK = FALSE;
        break;
    case 1:
        m_nx = m_x + 1;
        if(m_nx >= m_MaxX)    OK = FALSE;
        m_ny = m_y - 1;
        if(m_ny <= m_MinY)    OK = FALSE;
        break;
    case 2:
        m_nx = m_x + 1;
        if(m_nx >= m_MaxX)    OK = FALSE;
        m_ny = m_y;
        break;
    case 3:
        m_nx = m_x + 1;
        if(m_nx >= m_MaxX)    OK = FALSE;
        m_ny = m_y + 1;
        if(m_ny >= m_MaxY)    OK = FALSE;
        break;
    case 4:
        m_nx = m_x;
        m_ny = m_y + 1;
        if(m_ny >= m_MaxY)    OK = FALSE;
        break;
    case 5:
        m_nx = m_x - 1;
        if(m_nx <= m_MinX)    OK = FALSE;
        m_ny = m_y + 1;
        if(m_ny >= m_MaxY)    OK = FALSE;
        break;
    case 6:
        m_nx = m_x - 1;
        if(m_nx <= m_MinX)    OK = FALSE;
        m_ny = m_y;
        break;
    case 7:
        m_nx = m_x - 1;
        if(m_nx <= m_MinX)    OK = FALSE;
        m_ny = m_y - 1;
        if(m_ny <= m_MinY)    OK = FALSE;
        break;
    }    
    //次の位置が黒(COLORREF 0)以外であれば走行済
    if(GetPixel(m_hDC, m_nx, m_ny))
        OK = FALSE;
    else {        //黒であっても
        if(dir % 2)    {    //進行方向斜めで
            COLORREF c = GetPixel(m_hDC, m_x, m_ny);
            //m_(x, y)とm_n(x, y)の線が他の線と交差ていなら不可る
            if(c && c == GetPixel(m_hDC, m_nx, m_y))
                OK = FALSE;
        }
    }
    return OK;
}
//(解説:この関数がCCLIGHTCYCLEクラスの重要な関数となります。引数の方向(dir)と位置(m_x, m_y)からswitch文で、次に進む予定の位置(m_nx, m_ny)を求め、次に次の位置が移動限界内か否かを判断します。switch文を抜けると、まず次の位置のピクセルの色を調べ、既に走行されているか否かを判別し、仮に走行軌跡がなくとも走行軌跡を跨いでいるか否かも判別します。結果は進める場合gaTRUE、不可の場合がFALSEになります。)

////////////////////////////////////////
//可能なら進みTRUE、不可なら進まずFALSE
////////////////////////////////////////
bool CLIGHTCYCLE::CanGo() {

    //走行不能の場合は何もしない
    if(!m_Alive)
        return FALSE;
    //方向転換(不能の場合、m_Dirは変更されない)
    if(m_Caprice && !(rand() % ((1 << m_Caprice) * 50)))
        ChangeDir();
    //現在位置に基づいて次の位置を取得
    if(CheckNext(m_Dir)) {            //走行継続可能
        //現在位置に次の位置を代入
        m_x = m_nx;
        m_y = m_ny;
        //競走車の動作回数を増やす
        m_Times++;
        return TRUE;
    }
    else {
        if(ChangeDir()) {            //方向転換で走行継続可能
            //現在位置に次の位置を代入
            m_x = m_nx;
            m_y = m_ny;
            //競走車の動作回数を増やす
            m_Times++;
            return TRUE;
        }
        else {                        //走行継続不能
            m_Alive = FALSE;
            return FALSE;
        }
    }
}
//(解説:この関数はCheckNext関数の親分のようなもので、まず①自分が生きているか否か調べ(死んでいるなら何もしないで帰る)、②次にCheckNextで進めるか否かを判断し、不可の場合↓のChangeDir関数で方向転換可能な方向を確認、可能ならそちらへ進みます。進む場合には「次の位置(m_nx, m_ny)」を「現在位置(m_x, m_y)」に代入し、移動距離(m_Times)を増やします。逆に進行方向へ進めず、方向転換も不可になった(身動きが取れなくなった)場合には生死フラグをFALSE(死)にして「もう無理(FALSE)」を返します。)

///////////////////////////////////////////
//現在の方向以外で進める方向があれ方向転換
// 7 0 1    現在の方向から時計回りに2つ、
// 6 * 2    反時計回りに2つ方向を設定し、
// 5 4 3    CanGo()を実行する
//進めればTRUE、不可であればFALSEを返す
//FALSEの場合、進行ができないので終了する
///////////////////////////////////////////
bool CLIGHTCYCLE::ChangeDir() {

    //方向転換候補の特定
    int newdir[4];                        //転換方向候補
    newdir[0] = (m_Dir + 6) % 8;        //反時計回り2つ目
    newdir[1] = (m_Dir + 7) % 8;        //反時計回り1つ目
    newdir[2] = (m_Dir + 1) % 8;        //時計回り1つ目
    newdir[3] = (m_Dir + 2) % 8;        //時計回り2つ目
    //4候補について進行可能か否か確認(不可は-1を代入)
    int result[4];                        //進行可能方向とその数
    int count = 0;
    for(int i = 0; i < 4; i++) {
        if(CheckNext(newdir[i])) {
            result[count] = newdir[i];
            count++;
        }
    }
    //結果処理-進行可能方向(複数)があれば(乱数で選択し)m_Dirを変更する
    if(count) {
        m_Dir = result[rand() % count];    //進行可能方向候補からランダムに選ぶ
        CheckNext(m_Dir);                //再度m_nxとm_nyを設定する為
        return TRUE;
    }
    else
        return FALSE;
}
(解説:ここでやっているのは↑のコメントのように4つの転舵方向を剰余演算子%を活用して求め、それぞれの方向を使ってCheckNext関数で調べます。方向転換可能な方向があれば結果配列にその数と共に記録し、進行可能方向が複数あれば乱数で選択するようにしています。)

/////////////////
//死亡処理と通知
/////////////////
char* CLIGHTCYCLE::Dead() {

    m_Alive = FALSE;
    wsprintf(m_msg, "は走行距離%dで死亡。", m_Times);
    return m_msg;
}

(解説:一応生死フラグはCanGo関数で立てますが、ここでも立ててますね。一番大事なのは、移動距離がいくつで死亡したかのメッセージへオンポインターを返すことです。(メッセージの完成はCMyWndでやっていますが、ClightCycleインスタンスも色情報を持っているので全文書けますね。またこの関数をCanGoのm_Alive = FALSE;文の代わりに呼んだ方がスマートですが、ポインターをm_msgにアクセスさせることで渡すよりもDead()関数の戻り値で渡す方がキレイに思えたので...)

 

今回はここまでです。次回はLightCycleProc.hをやりましょうか。