2019-05-13

Raspberry Pi3を使ったリアル波形描画(5) スタートレックゲームの一部を作ってみる

今回はテキストで遊ぶスタートレックゲームのほんの一部分を c++ で書いてみる。
オリジナルのスタートレックゲームはテキストのゲームでありながら,自分がスタートレック号にのって宇宙を移動しながらクリンゴンを倒していく雰囲気を十分に味わうことができた。

iPhone を持っている人は App Store に Old Trek という名前で登録がある。(120円)
詳しくはこちらを参照のこと。

スタートレックゲームについてより詳しく知りたい方はこちらを参照のこと。

スタートレックシミュレーションゲーム(Wikipedia)

<スタートレックゲームのオリジナルの動画>
  1. スタートレックゲームのソースコードと解説
  2. MZ-80 でスタートレックゲームを遊んでいる動画
  3. Apple II でスタートレックゲームを遊んでいる動画
スタートレックゲームのイメージは概ねこんな感じだ。左下の8×8のマスがショートレンジセンサーの結果で,上の8×8のマスがロングレンジセンサーのマップになる。

ショートレンジセンサーの8×8のエリアが64個ぶん広がっている宇宙があるという設定だ。ロングレンジセンサー[517]の数字の意味は,5つの敵=K,1つの基地=B,7つの星=※があるという意味。ショートレンジセンサーのマップのEがエンタープライズ号を示している。Bの基地まで移動するとエネルギーを補給できる。

クリンゴンは光子魚雷(Photon Torpedos)か,フェーザー砲(Fire Phaser)で攻撃する。フェーザー砲はエネルギー値を設定でき範囲攻撃ができる。光子魚雷は当たればクリンゴンを破壊できる。クリンゴンから攻撃を受けることもある。移動は短距離はインパルスエンジン,長距離はワープエンジンを使用する。

今回は,8×8のショートレンジセンサーのレンジを用意し,ランダムにクリンゴンと星とエンタープライズ号を配置し,エンタープライズ号をインパルスエンジンで移動するプログラムを作ってみる。

移動だけで,攻撃とかワープとか補給などはできない。前回のタートルクラスを参考にして(派生させて),エンタープライズ号とクリンゴンと星のオブジェクトを生成して,それぞれのオブジェクトを動かせるようにしてみる。

方向は0~360° で方向を指定する。これは亀(タートルクラス)のときと同じ。

            0°
           ↑
     270° ←  亀   →  90°
                   ↓
           180°

オブジェクト思考設計をする意味は,前回も述べたようにソフトウェアを再利用可能な資産にして,毎回ゼロから作り直すようなことをしない点にある。

今回のような仕様のソフトウェアはいろいろな方法で作成することができるが,エンタープライズ号とクリンゴンと星はどれも同じような特長(移動方法など)があるので,これらをオブジェクト指向設計でうまく設計すれば効率的にソフトウェアを作ることができる。

事前準備

  • タートルクラス(x,yの位置情報を持ち,向きの変更と前進ができる)を応用して,宇宙船クラスを作成。
  • 小宇宙クラスを作成し 8x8 のマップを作成。
  • クリンゴン(2個以上5個以下)と,惑星(4個以上7個以下)をランダムにマップに配置。
  • エンタープライズ号を配置。
  • エンタープライズ号を移動できるようにして,マップを表示させる。
  • これらをテキスト表示で行う。


Spaceship クラスは,方向とX座標とY座標を内部変数として持っている。そして,前進,右向き,左向き,任意の位置に移動などのサービスを提供する。

このクラスから実態となる宇宙船,クリンゴン,惑星を生成しマップに配置する。

【ap_smallspace.h のソース】

#ifndef AP_SMALLSPACE_H
#define AP_SMALLSPACE_H

#include "ap_spaceship.h"


class smallspace
{
public:
    smallspace();
    void deployPlanets(int num);
    void deployKlingons(int num);
    void deployEnterprise(void);
    unsigned short mSpaceMap[8][8];

    Spaceship Enterprise;
    Spaceship Klingons[8];
    Spaceship Planets[8];
    int KlingonsNum;
    int PlanetsNum;

    void displayMap(void);
private:

};


#endif // AP_SMALLSPACE_H

【ap_spaceship.h のソース】

#ifndef AP_SPACESHIP_H
#define AP_SPACESHIP_H

class Spaceship
{
public:
    Spaceship();
    void turnleft();                // 左を向く
    void turnright();               // 右を向く
    void forward(int distanse);     // 前進する
    int getXPosition(void);        // 現在のX軸の位置を得る
    int getYPosition(void);        // 現在のY軸の位置を得る
    int getDiraction(void);        // 現在の向きを得る
    void setDiraction(int);        // 方向を設定する
    void resetPosition();
    void warp(int x, int y);

private:
    int mX;          // X座標
    int mY;          // y座標
    int mD;          // 向き
};


#endif // AP_SPACESHIP_H


【ap_stertrekgame.h のソース】

#ifndef AP_STARTREKGAME_H
#define AP_STARTREKGAME_H

#include "ap_smallspace.h"

class startrekgame
{
public:
    startrekgame();
    ~startrekgame();

    // 惑星・エンタープライズ号・クリンゴンを配置
    void initStarTrekGame();
    // 小宇宙のマップを表示
    void displaySpaceMap();
    // コマンド受け付け
    int inputCommand();
    // コマンド実行
    void execCommand();

private:
    smallspace* mSmallspace;        // 小宇宙ポインタ

    int mCommand = 0;               // コマンド
    int mFunction = 0;              // ファンクション

    void inputMoveFunction();
};


#endif // AP_STARTREKGAME_H

【ap_smallspace.cpp のソース】

#include "ap_smallspace.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


smallspace::smallspace()
{
    for (int i=0; i<8 ;i++){
        for (int j=0; j<8; j++){
            mSpaceMap[i][j]=0;
        }
    }
}

void smallspace::deployPlanets(int num)
{
    int i = 0;
    int posX, posY;
    // 現在時刻を元に乱数の種を srand 関数で生成する。unsgined int に明示的にキャストしておく
    srand(static_cast<unsigned int>(time(nullptr)));
    // 3から7の間の乱数を作成する
    if (num==0) num = rand() % 4 + 3;
    PlanetsNum=num;

    while (i<(num)) {
        posX =rand() % 8;
        posY =rand() % 8;

        if (mSpaceMap[posX][posY]==0){
            mSpaceMap[posX][posY]=100;
            Planets[i].warp(posX,posY);
            i++;
        }
    }
}

void smallspace::deployKlingons(int num)
{
    int i = 0;
    int posX, posY;
    srand(static_cast<unsigned int>(time(nullptr)));

    if (num==0) num = rand() % 3 + 2;
    KlingonsNum=num;

    while (i<(num)) {
        posX =rand() % 8;
        posY =rand() % 8;

        if (mSpaceMap[posX][posY]==0){
            mSpaceMap[posX][posY]=10;
            Klingons[i].warp(posX,posY);
            i++;
        }
    }
}

void smallspace::deployEnterprise()
{
    int i=0;
    int posX, posY;
    srand(static_cast<unsigned int>(time(nullptr)));

    while (i<1) {
        posX =rand() % 8;
        posY =rand() % 8;

        if (mSpaceMap[posX][posY]==0){
            mSpaceMap[posX][posY]=1;
            Enterprise.warp(posX, posY);
            i++;
        }
    }
}

void smallspace::displayMap()
{
    int x, y;
    printf ("\n");
    printf ("    1  2  3  4  5  6  7  8 \n");
    for (y=0; y<8; y++){
        printf( "%2d ", y+1);
        for (x=0; x<8; x++){
            if (mSpaceMap[x][y]==0) printf(" . ");
            if (mSpaceMap[x][y]==100) printf(" * ");
            if (mSpaceMap[x][y]==10 ) printf(" K ");
            if (mSpaceMap[x][y]==1 )  printf(" E ");
        };
        printf ("\n");
    };
    printf ("\n");
}


【ap_spaceship.cpp のソース】
#include "ap_spaceship.h"

Spaceship::Spaceship()
{
    resetPosition();
}

void Spaceship::turnleft()
{
    mD=mD-90;
    if (mD < 0) mD = mD+360;
}

void Spaceship::turnright()
{
    mD=mD+90;
    if (mD>360) mD=mD-360;
}

void Spaceship::forward(int distanse)
{
    if (mD==0) mY=mY-distanse;
    if (mD==180) mY=mY+distanse;
    if (mD==90) mX=mX+distanse;
    if (mD==270) mX=mX-distanse;
}

int Spaceship::getXPosition()
{
    return mX;
}

int Spaceship::getYPosition()
{
    return mY;
}

int Spaceship::getDiraction()
{
    return mD;
}

void Spaceship::setDiraction(int d)
{
    switch (d) {
    case 0:
        mD=0;
        break;
    case 90:
        mD=90;
        break;
    case 180:
        mD=180;
        break;
    case 270:
        mD=270;
        break;
    default:
        mD=0;
        break;
    }
}

void Spaceship::resetPosition()
{
    mX=0;
    mY=0;
    mD=0;
}

void Spaceship::warp(int x, int y)
{
    resetPosition();
    turnright();
    forward(x);
    turnright();
    forward(y);
}

【ap_stertrekgame.cpp のソース】

#include "ap_startrekgame.h"
#include "ap_smallspace.h"

#include <stdio.h>

startrekgame::startrekgame()
{
    mSmallspace = new smallspace;
    initStarTrekGame();
}


startrekgame::~startrekgame()
{
    delete mSmallspace;
}

void startrekgame::initStarTrekGame()
{

    mSmallspace->deployKlingons(0);
    mSmallspace->deployPlanets(0);
    mSmallspace->deployEnterprise();

//    printf ("x1:%3d , y1:%3d\n", mSmallspace->Enterprise.getXPosition()+1, mSmallspace->Enterprise.getYPosition()+1);

}

void startrekgame::displaySpaceMap()
{
    mSmallspace->displayMap();
}

int startrekgame::inputCommand()
{
    mCommand = 0;
    // コマンドを入力
    printf ("COMMAND (-1:quit 0:map 1:move) ? ");
    scanf ("%d", &mCommand);

    return mCommand;
}

void startrekgame::execCommand()
{
    switch (mCommand) {
    case 0:
        mSmallspace->displayMap();
        break;
    case 1:
        inputMoveFunction();
        break;

    default:
        break;
    }
}

void startrekgame::inputMoveFunction()
{
    int distance=0;
    int direction=0;
    int x1, y1, x2, y2;
    Spaceship tempSpaceship = mSmallspace->Enterprise;

    x1=tempSpaceship.getXPosition();
    y1=tempSpaceship.getYPosition();

    printf ("direction ? ");
    scanf ("%d", &direction);

    if (direction>=0 && direction<360){
        tempSpaceship.setDiraction(direction);

        printf ("distance ? ");
        scanf ("%d", &distance);

            x1=tempSpaceship.getXPosition();
            y1=tempSpaceship.getYPosition();

//            printf ("x1:%3d , y1:%3d \n", x1+1, y1+1);

            tempSpaceship.forward(distance);
            x2=tempSpaceship.getXPosition();
            y2=tempSpaceship.getYPosition();

//            printf ("x2:%3d , y2:%3d\n", x2+1, y2+1);

        if ( (x2<0) || (x2>7 ) || (y2<0) || (y2>7) ){
            printf ("Out of range!\n\n");
            return;
        }

        if (mSmallspace->mSpaceMap[x2][y2]==0){
            mSmallspace->mSpaceMap[x1][y1]-=1;
            mSmallspace->mSpaceMap[x2][y2]+=1;
            mSmallspace->Enterprise.setDiraction(direction);
            mSmallspace->Enterprise.forward(distance);
            mSmallspace->displayMap();
        } else {
            printf ("Can not move!\n\n");
        }

    } else {
        printf ("Input 0-360!\n\n");
    }
}


【main.cpp のソース】

#include <QCoreApplication>
#include "ap_spaceship.h"
#include "ap_smallspace.h"
#include "ap_startrekgame.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // ゲームを作成
    startrekgame* startrekgameA = new startrekgame;

    // マップを表示
    startrekgameA->displaySpaceMap();

    int command = 0;

    // 終了コマンドが入力されるまで繰り返し
    while (command >=0 ){

        // コマンドを入力
        command = startrekgameA->inputCommand();

        // コマンドを実行
        if (command>=0){
            startrekgameA->execCommand();
        }
    };

    // ゲームを終了する
    printf ("\nGame End.\n");

    // ゲームを消去
    delete startrekgameA;

    return app.exec();
}


前回 Turtleクラス を作成し,方向転換や前進できるようなクラスを作り,それを応用して スタートレックゲームの一部の部分を作ってみた。

オブジェクト思考設計で大事なのは,なぜ,オブジェクト思考設計をするのかという目的を常に忘れないことだ。

自分が楽になる,開発期間を短縮する,毎回一から作り直さない ようにするには,ソフトウェアの再利用が必ず必要となる。ただ,コピペするのではなく,作り直しをできるだけ少なくして,目的を達成するためにはどうすればよいかを考える。

Qt は非常に便利だが,C++ やオブジェクト指向設計を理解していないと,なぜ,それができるのかが分からず,商品化まで到達するのは難しいと思う。

次回は,リアル波形を表示するために必要なマルチスレッド処理をQtでやってみる。

P.S.

今回紹介した超簡易版スタートレックゲームの Qtプロジェクトを含むソースファイルを入手できるようにしました。

【スタートレックゲームのソースファイルの入手方法】
次のメールアドレスに件名:スタートレックソースファイル送信 と書いてメールをお送りください。(件名は”スタートレックソースファイル送信”でなければ返信されません。メール本文は何でも構いません。
メールアドレス:CriticalSoftwareConsulting[あっとまーく]gmail.com

0 件のコメント: