vol.2_4 ぷよの自由落下

はじめに

今回はぷよを自由落下させます。もちろん、自由落下中も操作できるように仕組みます。
細かい設置までの猶予時間等は今回は実装しませんが、実際にぷよを組めるようにします。
まだ回転はできませんし、ぷよの消去等もしません。w

時間経過

現在の状況では、キー入力がなければぷよはその場にとどまることになります。これを単位時間ごとに更新して自由落下するようにします。
これには時間の取得が必要となってきます。
sys/time.htime.hをインクルードします。
sys/time.hはtime.hの上位互換的なやつで、time.hでは時間を秒単位で取得しますが、sys/time.hには 構造体timevalが定義されていて、次のようになっています:

struct timeval {
        time_t      tv_sec;     /* 秒 */
        suseconds_t tv_usec;    /* マイクロ秒 */
};

これを利用してぷよの自由落下を実装しましょう!

ぷよは2.4マス/sの速度で自由落下をしていくので、1マス移動するのに1/2.4秒(=0.41666...秒)
だいたい0.416秒を計測するようなものを作成します。

作成の前に今回使う関数とデータ型についての説明から入れます。

gettimeofday関数

・プロトタイプ宣言

int gettimeofday(struct timeval *tv, struct timezone *tz);

・詳細
sys/time.hのインクルードが必要。
現在時刻を秒数と、それに伴うマイクロ秒で取得し第一引数に格納する。
(1970年1月1日0時0分0秒を紀元としてカウント)
※第二引数は現在廃止予定なので、NULLを必ず指定すること!
戻り値:成功時0 失敗時−1

time_t型、suseconds_t型

データ型の一種で、正体はunsigned long int 型です。
printf関数などで出力するときは変換指定子を%ldにしてください。

時間経過を実装

では、指定時間が経過しているかどうかを判断するisPassedTime関数を作成します!

____game.h____

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>

#define TRUE_BOOL 1
#define FALSE_BOOL 0
#define FIELD_HEIGHT 14
#define FIELD_WIDTH 15
#define FIELD_START_X 1

#define INIT_ROLL_PUYO_X 7
#define INIT_ROLL_PUYO_Y 2
#define INIT_AXIS_PUYO_X 7
#define INIT_AXIS_PUYO_Y 3

#define CELL_TYPE_EMPTY 0
#define CELL_TYPE_WALL 1
#define CELL_TYPE_RED 2
#define CELL_TYPE_BLUE 3
#define CELL_TYPE_GREEN 4
#define CELL_TYPE_YELLOW 5
#define CELL_TYPE_PURPLE 6
#define CELL_TYPE_OJAMA 7

#define FREE_FALL_TIME_PER_SQUARE 416000 //自由落下:1マスあたり0.416秒

typedef struct {
    int rx, ry; //軸でないぷよ(生成時は上側のぷよ)の座標
    int ax, ay; //軸となるぷよ(生成時は下側のぷよ)の座標
    int rollPuyoColor; //軸でないぷよの色情報
    int axisPuyoColor; //軸となるぷよの色情報
} twinPuyoes_t;

int kbhit(void);
void putColor(int, int, int);
int isPassedTime(unsigend long int, struct timeval *);

____isPassedTime.c____

#include "game.h"

int isPassedTime(unsigned long int usec, struct timeval *basis)
{
    suseconds_t passedTime;
    struct timeval present;
    
    gettimeofday(&present, NULL);
    passedTime = 1000000 * (present.tv_sec - basis->tv_sec) + (present.tv_usec - basis->tv_usec);
    
    if(passedTime < usec) return FALSE_BOOL;
    
    return TRUE_BOOL;    
}

____puyo.c____(isPassedTime関数の実行の確認のため)

#include "game.h"

int main(void)
{
    system("clear"); //画面のクリア
    
    //isPassedTime関数のサンプル実行
    //ここから
    struct timeval basis;
    gettimeofday(&basis, NULL);
    getchar(); //待ち
    
    printf("%d\n", isPassedTime(FREE_FALL_TIME_PER_SQUARE, &basis));
    getchar(); //待ち
    //ここまで
    
    
    //フィールドの描画
    for(int y = 1; y <= FIELD_HEIGHT; y++){
        for(int x = 1; x <= FIELD_WIDTH; x++){
            if((y != 1) && (x == 1 || x == FIELD_WIDTH || y == FIELD_HEIGHT))
                putColor(y, x, CELL_TYPE_WALL); //壁・床の描画
        }
        printf("\n");
    }
    
    //組みぷよの生成
    twinPuyoes_t tsumo = {
        INIT_ROLL_PUYO_X, INIT_ROLL_PUYO_Y,
        INIT_AXIS_PUYO_X, INIT_AXIS_PUYO_Y,
        CELL_TYPE_GREEN,
        CELL_TYPE_RED
    };
    
    //初期位置に設置
    putColor(tsumo.ry, tsumo.rx, tsumo.rollPuyoColor);
    putColor(tsumo.ay, tsumo.ax, tsumo.axisPuyoColor);
    
    //ゲーム部分
    while(1){
        if(kbhit()){
            //キーが入力されたかどうかのフラグ
            int keyFlag = FALSE_BOOL;
            //位置を変更するとき、もとの位置のぷよはクリアしたい
            //もとのぷよの情報をここに保持しておく
            twinPuyoes_t old;
            
            switch(getchar()){
                case 'a':
                    //移動先が適切でなければ終了
                    if(tsumo.rx - 2 == FIELD_START_X || tsumo.ax - 2 == FIELD_START_X)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.rx -= 2;
                    tsumo.ax -= 2;
                    keyFlag = TRUE_BOOL;
                    break;
                
                case 'd':
                    //移動先が適切でなければ終了
                    if(tsumo.rx + 2 == FIELD_WIDTH || tsumo.ax + 2 == FIELD_WIDTH)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.rx += 2;
                    tsumo.ax += 2;
                    keyFlag = TRUE_BOOL;
                    break;
                
                case 's':
                    //移動先が適切でなければ終了
                    if(tsumo.ry + 1 == FIELD_HEIGHT || tsumo.ay + 1 == FIELD_HEIGHT)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.ry++;
                    tsumo.ay++;
                    keyFlag = TRUE_BOOL;
                    break;
                    
                default: break;
            }
            
            if(keyFlag == TRUE_BOOL){
                //oldの消去
                putColor(old.ry, old.rx, old.rollPuyoColor);
                putColor(old.ay, old.ax, old.axisPuyoColor);
                //ぷよの描画
                putColor(tsumo.ry, tsumo.rx, tsumo.rollPuyoColor);
                putColor(tsumo.ay, tsumo.ax, tsumo.axisPuyoColor);
            }
        }
    }
    
    return 0;
}

実行結果:

~$ gcc -c kbhit.c
~$ gcc -c putColor.c
~$ gcc -c isPassedTime.c
~$ gcc -c puyo.c
~$ gcc -o run kbhit.o putColor.o isPassedTime.o puyo.o
~$ ./run
(エンターを0.416秒以内に再び押せば)
0

(エンターを押したのが0.416秒以降であれば)
1

ちゃんと時間経過を取得できていますね。
これを使って次にぷよの自動落下を実装しましょう!

ぷよの自動落下

時間経過が取得できれば自動落下自体は簡単に実装できます。
0.416秒経過したらぷよのy座標を1つ増やせばいいわけですから、構造自体は単純です。
前回同様壁・床の判定はしておきたいのでそこは注意点となります。
再描画等の処理はキー入力の時に作成したものをほぼコピペします。
ゲーム部分を編集していきます。

____puyo.c____

#include "game.h"

int main(void)
{
    system("clear"); //画面のクリア

    //フィールドの描画
    for(int y = 1; y <= FIELD_HEIGHT; y++){
        for(int x = 1; x <= FIELD_WIDTH; x++){
            if((y != 1) && (x == 1 || x == FIELD_WIDTH || y == FIELD_HEIGHT))
                putColor(y, x, CELL_TYPE_WALL); //壁・床の描画
        }
        printf("\n");
    }
    
    //組みぷよの生成
    twinPuyoes_t tsumo = {
        INIT_ROLL_PUYO_X, INIT_ROLL_PUYO_Y,
        INIT_AXIS_PUYO_X, INIT_AXIS_PUYO_Y,
        CELL_TYPE_GREEN,
        CELL_TYPE_RED
    };
    
    //初期位置に設置
    putColor(tsumo.ry, tsumo.rx, tsumo.rollPuyoColor);
    putColor(tsumo.ay, tsumo.ax, tsumo.axisPuyoColor);
    
    //ゲーム部分
    struct timeval startTime; //組みぷよ生成時の時間
    gettimeofday(&startTime, NULL); //時間を取得
    
    while(1){
        //自由落下
        if(isPassedTime(FREE_FALL_TIME_PER_SQUARE, &startTime)){
            if(tsumo.ry + 1 == FIELD_HEIGHT || tsumo.ay + 1 == FIELD_HEIGHT)
                        break;
            
            twinPuyoes_t old; //再描画用
            old = tsumo;
            old.rollPuyoColor = CELL_TYPE_EMPTY;
            old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
            tsumo.ry++;
            tsumo.ay++;
            
            //再描画
            //oldの削除
            putColor(old.ry, old.rx, old.rollPuyoColor);
            putColor(old.ay, old.ax, old.axisPuyoColor);
            //ぷよの描画
            putColor(tsumo.ry, tsumo.rx, tsumo.rollPuyoColor);
            putColor(tsumo.ay, tsumo.ax, tsumo.axisPuyoColor);
            
            //時間をリセット
            gettimeofday(&startTime, NULL);
        }
        
        if(kbhit()){
            //キーが入力されたかどうかのフラグ
            int keyFlag = FALSE_BOOL;
            //位置を変更するとき、もとの位置のぷよはクリアしたい
            //もとのぷよの情報をここに保持しておく
            twinPuyoes_t old;
            
            switch(getchar()){
                case 'a':
                    //移動先が適切でなければ終了
                    if(tsumo.rx - 2 == FIELD_START_X || tsumo.ax - 2 == FIELD_START_X)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.rx -= 2;
                    tsumo.ax -= 2;
                    keyFlag = TRUE_BOOL;
                    break;
                
                case 'd':
                    //移動先が適切でなければ終了
                    if(tsumo.rx + 2 == FIELD_WIDTH || tsumo.ax + 2 == FIELD_WIDTH)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.rx += 2;
                    tsumo.ax += 2;
                    keyFlag = TRUE_BOOL;
                    break;
                
                case 's':
                    //移動先が適切でなければ終了
                    if(tsumo.ry + 1 == FIELD_HEIGHT || tsumo.ay + 1 == FIELD_HEIGHT)
                        break;
                        
                    old = tsumo;
                    old.rollPuyoColor = CELL_TYPE_EMPTY;
                    old.axisPuyoColor = CELL_TYPE_EMPTY;
                    
                    tsumo.ry++;
                    tsumo.ay++;
                    keyFlag = TRUE_BOOL;
                    break;
                    
                default: break;
            }
            
            if(keyFlag == TRUE_BOOL){
                //oldの消去
                putColor(old.ry, old.rx, old.rollPuyoColor);
                putColor(old.ay, old.ax, old.axisPuyoColor);
                //ぷよの描画
                putColor(tsumo.ry, tsumo.rx, tsumo.rollPuyoColor);
                putColor(tsumo.ay, tsumo.ax, tsumo.axisPuyoColor);
            }
        }
    }
    
    return 0;
}

実行結果:
f:id:sion2000114:20191216235357g:plain

おわりに

自動で落下するようになっただけでもなんだか進歩が感じられますね。
現状のプログラムではぷよが床にあたってしまうとwhile文をbreakするのでプログラム自体が終了するようになっています。
次回は連続して組みぷよを生成していくとともに、ぷよが実際に積もるようにします。
また、連続して組みぷよを生成するということで、色もランダムにしましょうか。
では、お疲れ様でした。