C言語ケーススタディ 文字表示で作ったゲームの基礎(カーソルキーを押していない間に処理を行う)




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



前回作成したプログラムは、getch()関数のところで処理が待たされ何かキーが押されるまでgetch()関数から抜けでることができませんでした。 これでは、敵キャラクターを一定間隔で動かすようなプログラムを作ることができません(カーソルキーを押すと、そのタイミングで敵も動くプログラムしか作れません)。

まず、前回のプログラムに敵となる「o」文字が動き回る機構を組み込みます。基本的には「+」を動かすのと同じ方法で行います。 座標を保持する変数x、y、ox、oyを、それぞれex、ey、oex、oeyにして同じ機構を作成します。「o」文字は勝手に動き回るようにrand()関数(乱数を発生させる関数)で移動させます。

main()
{
        int     ch;
        int     x, y, ox, oy;
        int     ex, ey, oex, oey;

        oex = oey = -1;
        ex = 5;
        ey = 5;

        ox = oy = -1;
        x = 40;
        y = 10;
        printf("\033[2J");      /*画面消去*/
        for(;;){
                /*敵キャラクターを表示する*/
                if(oex != ex || oey != ey){
                        if(oex != -1 && oey != -1)
                                printf("\033[%d;%dH ", oey+1, oex+1);
                        oex = ex;
                        oey = ey;
                        printf("\033[%d;%dHo", ey+1, ex+1);
                }

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

                ch = getch();
                switch(ch){
                case 5: /*上*/
                        if(y <= 0)
                                break;
                        y--;
                        break;
                case 6: /*下*/
                        if(y >= 24)
                                break;
                        y++;
                        break;
                case 7: /*左*/
                        if(x <= 0)
                                break;
                        x--;
                        break;
                case 8: /*右*/
                        if(x >= 79)
                                break;
                        x++;
                        break;
                case 0x1B:
                        return;
                }

                /*敵キャラクターを移動させる*/
                switch(rand()%4){
                case 0: /*上*/
                        if(ey <= 0)
                                break;
                        ey--;
                        break;
                case 1: /*下*/
                        if(ey >= 24)
                                break;
                        ey++;
                        break;
                case 2: /*左*/
                        if(ex <= 0)
                                break;
                        ex--;
                        break;
                case 3: /*右*/
                        if(ex >= 79)
                                break;
                        ex++;
                        break;
                }
        }
}

このプログラムを実行すると「+」と「o」が画面上に表示されます。 ここでカーソルキーをおして「+」を動かすとそれと同じタイミングで「o」も動きます。

カーソルキーを押していない間も敵「o」が勝手に動き回ってくれないとゲームらしくありません。 このような用途のためにkbhit()関数が用意されています。 この関数はキーボードが押されているかいないかを調べることができます。

※kbhit()関数は、WindowsやDOS向けのコンパイラには概ね用意されていると思います。 ただし移植性が悪くUNIX/Linuxでは用意されていません(類似機能の関数を作成することは可能です)。

kbhit()関数を使って最初のプログラムを改造してみます。getch()関数を呼んでいる所をif(kbhit())で囲んでしまいキーボードが押されない場合は、getch()関数を呼び出さないようにします。 また、毎回「o」文字を動かしていると動きが早くなってしまうので、time()関数を使って一秒以上経過していない場合は'o'を動かさないように変更します。

main()
{
        for( ; ; ){
                if(kbhit())
                        break;
                printf("*");
        }
}

このプログラムを実行すると画面上に「*」がたくさん表示され続けます。 その状態でなにかキーを押すとkbhit()関数がそれを検知しTRUE(真の値=0以外の値)を返してきます。 if文でkbhit()関数がTRUEを返すとループが中断し処理が終了します。

kbhit()関数を使用しgetch()関数はキーボードが押されているときだけ呼び出すようにするとgetch()関数はすぐに処理を返してきます。 また、kbhit()関数がFALSE(偽の値=0)を返した場合は、敵の「o」を動かすなど別の処理を行うことができます。

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

main()
{
        int        ch;
        int        x, y, ox, oy;
        int        ex, ey, oex, oey;
        time_t     last_time;

        oex = oey = -1;
        ex = 5;
        ey = 5;

        ox = oy = -1;
        x = 40;
        y = 10;
        printf("\033[2J");      /*画面消去*/
        last_time = time(NULL);
        for(;;){
                /*敵キャラクターを表示する*/
                if(oex != ex || oey != ey){
                        if(oex != -1 && oey != -1)
                                printf("\033[%d;%dH ", oey+1, oex+1);
                        oex = ex;
                        oey = ey;
                        printf("\033[%d;%dHo", ey+1, ex+1);
                }

                /*位置が変更されていなければ表示しない*/
                if(ox != x || oy != y){
                        /*初回は消去しない*/
                        if(ox != -1 && oy != -1)
                                printf("\033[%d;%dH ", oy+1, ox+1);
                        ox = x;
                        oy = y;
                        printf("\033[%d;%dH+", y+1, x+1);
                }
                if(kbhit()){
                        ch = getch();
                        switch(ch){
                        case 5: /*上*/
                                if(y <= 0)
                                        break;
                                y--;
                                break;
                        case 6: /*下*/
                                if(y >= 24)
                                        break;
                                y++;
                                break;
                        case 7: /*左*/
                                if(x <= 0)
                                        break;
                                x--;
                                break;
                        case 8: /*右*/
                                if(x >= 79)
                                        break;
                                x++;
                                break;
                        case 0x1B:
                                return;
                        }
                }

                /*一秒以上経過していなければ何もしない*/
                if(last_time == time(NULL))
                        continue;
                last_time = time(NULL);

                /*敵キャラクターを移動させる*/
                switch(rand()%4){
                case 0: /*上*/
                        if(ey <= 0)
                                break;
                        ey--;
                        break;
                case 1: /*下*/
                        if(ey >= 24)
                                break;
                        ey++;
                        break;
                case 2: /*左*/
                        if(ex <= 0)
                                break;
                        ex--;
                        break;
                case 3: /*右*/
                        if(ex >= 79)
                                break;
                        ex++;
                        break;
                }
        }
}