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)CheckHitClearという関数を作成し「+」と重なった「o」を消滅させます(markを「\0」にします)。
(2)markが「\0」のデータは移動と再描画を行わないようにします。
(3)残りの「o」の数を表示するようにします。

#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->mark == '\0')
                return(0);
        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)
{
        if(item->mark == '\0')
                return(0);
        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);
        }
}


int     CheckHitClear(struct StrItem *my, struct StrItem *enemy)
{
        int     i1, rest, hit_flag;

        hit_flag = 0;
        for(i1 = 0; i1 < ENEMY_NUM; i1++){
                if(enemy[i1].mark != '\0' && my->x == enemy[i1].x && my->y == enemy[i1].y){
                        hit_flag =1;
                        enemy[i1].mark = '\0';
                }
        }
        rest = 0;
        for(i1 = 0; i1 < ENEMY_NUM; i1++){
                if(enemy[i1].mark != '\0')
                        rest++;
        }
        if(hit_flag != 0){
                printf("\033[26;1H残り : %3d", rest);
        }
        return(rest);
}


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");      /*画面消去*/
        printf("\033[26;1H残り : %3d", ENEMY_NUM);
        tick = GetTickCount();
        for(;;){
                for(i1 = 0; i1 < ENEMY_NUM; i1++){
                        DrawItem(&enemy[i1]);
                }
                DrawItem(&my);

                if(kbhit()){
                        ch = getch();
                        if(ch == 0x1B){
                                printf("\033[26;1H");
                                return;
                        }
                        MoveItem(ch-5, &my);
                }

                if(CheckHitClear(&my, enemy) == 0){
                        return;
                }

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

これでゲームとして遊べるようになりましたが、何点か問題があるので改造します。
(1)「+」と「o」が重なると、「+」が移動するまで表示されなくなる問題を改善します。
 これは「+」と「o」が重なった時に、「o」の消去処理で「+」が表示されてしまうためです。
 「+」と「o」が重なった場合は、「+」を再描画する処理を追加します。
(2)ゲームをクリアするまでにかかった時間を最後に表示するようにします。
 起動時にtime関数で開始時刻を記録し終了時にその差を求め表示します。
(3)簡単な音を鳴らすようにします。
 MessageBeep関数を使用して音がなるように変更します。
 MessageBeep関数はWindowsの関数です。パラメータの値を変えると音が変わります。

#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->mark == '\0')
                return(0);
        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)
{
        if(item->mark == '\0')
                return(0);
        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);
        }
}


void    HitSound()
{
        int        i1;

        for(i1 = 0; i1 < 100; i1++)
                MessageBeep(-1);
}


int     CheckHitClear(struct StrItem *my, struct StrItem *enemy)
{
        int     i1, rest, hit_flag;

        hit_flag = 0;
        for(i1 = 0; i1 < ENEMY_NUM; i1++){
                if(enemy[i1].mark != '\0' && my->x == enemy[i1].x && my->y == enemy[i1].y){
                        hit_flag =1;
                        enemy[i1].mark = '\0';
                }
        }
        rest = 0;
        for(i1 = 0; i1 < ENEMY_NUM; i1++){
                if(enemy[i1].mark != '\0')
                        rest++;
        }
        if(hit_flag != 0){
                printf("\033[26;1H残り : %3d", rest);
                HitSound();
        }
        return(rest);
}


void    Finish(time_t sec)
{
        int     i1;

        for(i1 = 0; i1 < 1000; i1++){
                printf("クリア ");
        }
        MessageBeep(0x10);
        printf("\n*** %d秒 です ***", sec);
}


main()
{
        int     ch;
        struct StrItem
                my, enemy[ENEMY_NUM];
        time_t  start_time;
        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");      /*画面消去*/
        printf("\033[26;1H残り : %3d", ENEMY_NUM);
        start_time = time(NULL);
        tick = GetTickCount();
        for(;;){
                for(i1 = 0; i1 < ENEMY_NUM; i1++){
                        DrawItem(&enemy[i1]);
                }
                for(i1 = 0; i1 < ENEMY_NUM; i1++){
                        if(enemy[i1].x == my.x && enemy[i1].y == my.y){
                                DrawItem(&my);
                                my.ox = my.oy = -1;
                                break;
                        }
                }
                DrawItem(&my);

                if(kbhit()){
                        ch = getch();
                        if(ch == 0x1B){
                                printf("\033[26;1H");
                                return;
                        }
                        MoveItem(ch-5, &my);
                }

                if(CheckHitClear(&my, enemy) == 0){
                        Finish(time(NULL)-start_time);
                        return;
                }

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