C言語ケーススタディ 文字表示で作ったゲームの基礎(複数の敵キャラクターを動かす)




2014年10月より個人の方を対象に、Study C無料提供を開始しました。
C言語を勉強中の方は、学習・教育に最適なC言語インタープリタのStudy Cを使ってみてください(個人の方は無料です)。
大学・高専・高校などの教育機関での採用実績も多数あるロングセラー商品Study Cが、個人向けに無料提供を始めました。
インタープリタの手軽さに加え、ゲームや3Dタートルグラフィックで楽しく勉強したりと、C言語の学習を強力にサポートします。
ブロック崩しゲーム 3Dツリー クリスマスツリー
また、このようなボタンの用意されているページでは、掲載しているプログラムをStudy Cに直接ロードし実行したりすることができます。
Study Cにロードする Study Cにロードし編集する Study Cにロードし実行する
Study C無料利用についての詳細は、このページを参照してください。



今回は、前回作成したプログラムを改造して複数の敵(「o」)を表示するように改造します。 しかし、今のままではプログラムが汚くなってしまっているのでプログラムをきれい改造します。 改造のポイントは次の点です

(1)「+」や「o」の位置などを管理する変数のまとまりが悪いので構造体で管理します。
 現座標(x,y)、前の座標(ox,oy)を構造体のメンバ変数として定義します。
 「+」と「o」で兼用に使うので文字を識別するためにmarkというメンバ変数を用意します。
 「+」はmy、「o」はenemyという構造体変数で管理します。
(2)「+」と「o」でほとんど同じ初期化処理が繰り返されるので関数にします(InitItem関数)。
(3)「+」と「o」でほとんど同じ描画処理が繰り返されるので関数にします(DrawItem関数)。
(4)「+」と「o」でほとんど同じ移動処理が繰り返されるので関数にします(MoveItem関数)。

#include <stdio.h>
#include <time.h>

struct StrItem {
        char    mark;
        int     x, y;
        int     ox, oy;
};


void    InitItem(struct StrItem *item, int x, int y, char mark)
{
        item->ox = item->oy = -1;
        item->x = x;
        item->y = y;
        item->mark = mark;
}


int     DrawItem(struct StrItem *item)
{
        /*位置が変更されていなければ表示しない*/
        if(item->ox != item->x || item->oy != item->y){
                /*初回は消去しない*/
                if(item->ox != -1 && item->oy != -1)
                        printf("\033[%d;%dH ", item->oy+1, item->ox+1);
                item->ox = item->x;
                item->oy = item->y;
                printf("\033[%d;%dH%c", item->y+1, item->x+1, item->mark);
                return(1);
        }
        return(0);
}


int     MoveItem(int direct, struct StrItem *item)
{
        switch(direct){
        case 0: /*上*/
                if(item->y <= 0)
                        break;
                item->y--;
                return(1);
        case 1: /*下*/
                if(item->y >= 24)
                        break;
                item->y++;
                return(1);
        case 2: /*左*/
                if(item->x <= 0)
                        break;
                item->x--;
                return(1);
        case 3: /*右*/
                if(item->x >= 79)
                        break;
                item->x++;
                return(1);
        default:
                return(0);
        }
}


main()
{
        int     ch;
        struct StrItem
                my, enemy;
        time_t  last_time;

        InitItem(&enemy, 5, 5, 'o');
        InitItem(&my, 40, 10, '+');

        printf("\033[2J");      /*画面消去*/
        last_time = time(NULL);
        for(;;){
                DrawItem(&enemy);
                DrawItem(&my);

                if(kbhit()){
                        ch = getch();
                        if(ch == 0x1B)
                                return;
                        MoveItem(ch-5, &my);
                }

                if(last_time == time(NULL))
                        continue;
                last_time = time(NULL);
                MoveItem(rand()%4, &enemy);
        }
}

動作自体は全く同じプログラムですが、かなりきれいに(見やすくなった)と思います。 一つの関数が大きくなったり類似の処理が何カ所もでてくるとプログラムが見づらくなり改造も容易ではなくなってきます。

次に敵キャラクター('o')が複数表示できるように改造します。
(1)敵の数はENEMY_NUMで定義します。
(2)変数enemyを配列にします。
(3)変数enemyを使用している場所をループで0からENEMY_NUM-1まで繰り返すようにします。
(4)敵の出現位置(InitItemのパラメータ)に乱数を使用するようにします。
(5)一秒単位の動作では遅いので、time関数をGetTickCount関数(1000分の1秒単位でカウントアップした値を返します)に変更します。
 time関数を使っていた所をGetTickCount関数に変更します。
 GetTickCount関数の値が400以上経過していれば(約0.4秒)処理するようにします。
 GetTickCount関数の値は一周すると0に戻るので、前回の値より小さい値になった場合も処理するようにします。

#include <stdio.h>
#include <time.h>

#define ENEMY_NUM       10

struct StrItem {
        char    mark;
        int     x, y;
        int     ox, oy;
};


void    InitItem(struct StrItem *item, int x, int y, char mark)
{
        item->ox = item->oy = -1;
        item->x = x;
        item->y = y;
        item->mark = mark;
}


int     DrawItem(struct StrItem *item)
{
        /*位置が変更されていなければ表示しない*/
        if(item->ox != item->x || item->oy != item->y){
                /*初回は消去しない*/
                if(item->ox != -1 && item->oy != -1)
                        printf("\033[%d;%dH ", item->oy+1, item->ox+1);
                item->ox = item->x;
                item->oy = item->y;
                printf("\033[%d;%dH%c", item->y+1, item->x+1, item->mark);
                return(1);
        }
        return(0);
}


int     MoveItem(int direct, struct StrItem *item)
{
        switch(direct){
        case 0: /*上*/
                if(item->y <= 0)
                        break;
                item->y--;
                return(1);
        case 1: /*下*/
                if(item->y >= 24)
                        break;
                item->y++;
                return(1);
        case 2: /*左*/
                if(item->x <= 0)
                        break;
                item->x--;
                return(1);
        case 3: /*右*/
                if(item->x >= 79)
                        break;
                item->x++;
                return(1);
        default:
                return(0);
        }
}


main()
{
        int     ch;
        struct StrItem
                my, enemy[ENEMY_NUM];
        int     i1;
        unsigned long
                tick;

        for(i1 = 0; i1 < ENEMY_NUM; i1++){
                InitItem(&enemy[i1], rand() % 80, rand() % 24, 'o');
        }
        InitItem(&my, 40, 10, '+');

        printf("\033[2J");      /*画面消去*/
        tick = GetTickCount();
        for(;;){
                for(i1 = 0; i1 < ENEMY_NUM; i1++){
                        DrawItem(&enemy[i1]);
                }
                DrawItem(&my);

                if(kbhit()){
                        ch = getch();
                        if(ch == 0x1B)
                                return;
                        MoveItem(ch-5, &my);
                }

                if(GetTickCount() >= tick && GetTickCount() < tick + 400)
                        continue;
                tick = GetTickCount();
                for(i1 = 0; i1 < ENEMY_NUM; i1++){
                        MoveItem(rand()%4, &enemy[i1]);
                }
        }
}