見出し画像

プロ基礎の演習環境でテトリス作ってみた話

0. 自己紹介

みなさんこんにちは!kCatです!
普段は電子工作やらWeb開発やらゲーム制作やら、いろんなことを浅く広くやっています。
今回アンケートに協力してくださった皆様方ありがとうございました。

受動意識仮説はとても面白い仮説なので、また機会があったら記事を書きたいです!
導通チェッカーを自作した話は大した話ではないのでそのうち書くかもしれません。Arduinoで適当にプルアップした回路を使っただけなので。


1. なんか作りたい

プロ基礎の授業で色々と習ったわけですよ。それで、せっかくならプロ基礎の演習環境で何か作ろうかなと思いました。なんか作るだけならじゃんけんとか作っとけばいいのですが、やっぱりそれではつまらないじゃないですか。じゃんけんなんて10分くらいで作れちゃうし、動きもないし。

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

int main(void) {
    char hand[3][6] = {"gu", "tyoki", "pa"};
    char result[3][5] = {"draw", "win", "lose"};
    int judoge[3][3]= {
        {0, 1, 2},
        {2, 0, 1},
        {1, 2, 0}
    };
    int player, cpu;
    srand((unsigned int)time(NULL));
    for(int i=0; i<3; i++)
        printf("%d:%s, ",i,hand[i]);
    printf("-> ");
    scanf("%d", &player);
    cpu = rand() % 3;
    printf("player:%s, cpu:%s, result:%s\n", hand[player], hand[cpu], result[judoge[player][cpu]]);
    return 0;
}
じゃんけんプログラム実行結果

じゃあ、何を作ろうと考えたときに2年くらい前にC言語でテトリス作ったなぁと思いだしたわけですよ。今までJavaScriptとC言語とProcessingで3回もテトリスを作るくらいにはテトリスのプログラムを書くのが好きなわけです。てことでテトリスを作ろうとなりました。

2. 昔作ったやつは再利用できなかった

2年ほど前にテトリスをC言語で作ったのでそれをコピペしたら動くんじゃね?と思ってコピペしたのですが、まあ案の定上手くいくわけないんですね。その原因がこれです。

エラー内容

どうやらこれはWIndowsでしか使えないライブラリみたいです。演習環境はUnixなのでこのライブラリはincludeできません。ところで、このライブラリはなぜ必要かと言うとkbhit関数と言うキーが押されている状態かどうかを判定するための関数を使用するために必要なわけです。じゃあ、別の方法で判定ができればこのライブラリは必要なくなるので解決できるわけです。そこで色々と調べてみた結果このサイトを見つけました。このプログラムで使われているライブラリはすべてUnix用のライブラリで様々なUnix用の関数が用意されているそうです。正直僕にはこのプログラムの意味がイマイチ理解できませんでしたが、Unixのファイルの入出力の関数を使っていることから推測するにシステムファイルなどにアクセスしてるのではないかなと思います。詳しい人いたら教えてください。

3. プログラムの制作

これで、エラーも出なくなったし作れるようになったわけです。じゃあ、あとはコピペで… とも思いましたがそれでは面白くありません。当然1から作ります。また、昔作ったプログラムではテトリミノを配列と座標は変数で管理していましたが、今では構造体が使えるので少しでもいい書き方ができるようにリメイクしました。

typedef struct {
    int x, y, id;
    int mino[4][4];
} TetroMino;

4. プログラムの説明

キーボード入力を受け取れるようになったのでテトリミノを表示して動かしたいわけなのですが、どうやって表示しましょう。Processingみたいにrect関数なんてものはありません。printfだけでどうにかしたいわけです。てことで、"#"や"O"や"X"などのような文字を使ってそれっぽくすることにしました。また、プロ基礎の授業で習ったように作るとゲーム画面の描画が上手くいかないですよね。なぜなら画面をクリアできないからです。じゃあ、どうやってクリアするかなんですが、これは簡単です。"stdlib.h"に用意されているsystem関数を使い"clear"コマンドを呼び出せばいいのです。これで描画ができるようになりました。じゃあ後は回転したり動いたり消えたりゲームオーバー処理があれば完成なわけですが、この部分って説明することなくないですか?ないわけではないのですが、この部分ってどの言語で書いてもほぼ同じアルゴリズムになると思うので、ネットにある先駆者の説明を見た方が説明しなくて楽 分かりやすいと思うので、簡単に説明します。

5. テトリミノの移動とか回転

移動は簡単ですよね。横に移動するなら"x++"もしくは"x--"をすれば左右に移動できます。縦移動も同じです。じゃあ問題は回転をどうするかなのですが、これはExcelなどにテトリミノを書いてみると思いつくと思います。

Excelで考える

そうです右回転をする場合(i, j) = (j, 3-i)とすればいいわけです。C言語で書くならこんな感じですかね。

for(int i=0; i<4; i++) {
    for(int j=0; j<4; j++) {
        mino[i][j] = mino[j][3-i];
    }
}

同じように左回転もExcelを使って考えてみます。

Excelで考える

左回転は(i, j) = (3-j, i)とすれば上手くいきそうですね。

for(int i=0; i<4; i++) {
    for(int j=0; j<4; j++) {
        mino[i][j] = mino[3-j][i];
    }
}

最終的にテトリミノの操作のプログラムはこんな感じになりました。

c = getchar();
switch(c) {
case 65: // up
    if(moveCheck(&tetroMino, 0, -1) == true)
        tetroMino.y--;
    break;
case 66: // down
    if(moveCheck(&tetroMino, 0, 1) == true)
        tetroMino.y++;
    break;
case 67: // right
    if(moveCheck(&tetroMino, 1, 0) == true)
           tetroMino.x++;
     break;
case 68: //left
    if(moveCheck(&tetroMino, -1, 0) == true)
           tetroMino.x--;
    break;
case 32: // space
    for(t=1; checkGround(&tetroMino, t) == false && checkBottom(&tetroMino, t) == false; t++);
    tetroMino.y += t-1;
    break;
case 120: // x: rotate right
    rotateMino(&tetroMino, 1);
    if(moveCheck(&tetroMino, 0, 0)  == false)
        rotateMino(&tetroMino, -1);
    break;
case 122: // z: rotate left
    rotateMino(&tetroMino, -1);
    if(moveCheck(&tetroMino, 0, 0) == false)
        rotateMino(&tetroMino, 1);
    break;
}            


と言うことでこれでテトリスはほぼ完成ですね。一応下にブロックや底面があるかの処理などもありますが、これはオレンジ色の部分にブロックがあるかどうかを判定するだけでできますね!

ブロックの判定

これらの判定はmoveCheck関数、checkGround関数、checkBottom関数で行っています。この3つの関数を実装したらほぼ完成です!

6. その他の処理

あとはテトリミノを固定する関数、1ライン揃えたら消す関数、ゲームオーバー処理を作れば完成です!
テトリミノの固定は2重for文を使ってfieldの座標上に代入していくだけです。ついでに、ここにゲームオーバー処理も書いています。

void fixMino(TetroMino *tetroMino) {
    int x=tetroMino->x, y=tetroMino->y;
    for(int i=0; i<4; i++) {
        for(int j=0; j<4; j++) {
            if(tetroMino->mino[i][j] == 1) {
                field[y+i][x+j] = tetroMino->id;
            }
        }
    }
    deleteMino();
    if(checkOver(tetroMino) == true) {
        printf("Game Over\n");
        exit(0);
    }
}
bool checkOver(TetroMino *tetroMino) {
    bool flag = false;
    if(tetroMino->y < 0) {
        return true;
    }
    return false;
}

1ライン消す部分は単純に1ライン全部に0より大きい数字(何かしらの色のブロック)があればそのラインを0に変えて、全体を下にズラすということを行っています。

void deleteMino() {
    bool flag;
    for(int i=0; i<20; i++) {
        flag = true;
        for(int j=0; j<10; j++) {
            if(field[i][j] == 0) {
                flag = false;
                break;
            }
        }
        if(flag == false) continue;
        for(int n=i; n>0; n--) {
            for(int j=0; j<10; j++) {
                field[n][j] = field[n-1][j];
            }
        }
        for(int j=0; j<10; j++) {
            field[0][j] = 0;
        }
        i--;
    }        
}

ここまで実装出来たら完成です!

テトリス完成

7. まとめ

大体テトリスはこんな感じで簡単に作れます。ただ、実際のテトリスにはT スピンと呼ばれる回転入れの技や7種1順の法則などがあり、完全に再現するためにはこれらも実装しなければなりません。特にTスピンの実装は難しそうなのでそのうち挑戦してみたいですね!
今回作成したプログラムはこちらのgithubに公開してあるのでよければご覧ください。

8. アドカレ遅刻に対する謝罪と言い訳

僕は25日を担当していたわけですが、あろうことか2日も遅刻してしましました。アンケートまで取ったのに本当にすみませんでした。
ただ、言い訳をさせてください。アンケートが終了した時点でテトリスが完成していなかったんですよ。なぜかと言うとcase文の中にbreakを書き忘れていることに気付かず永遠になんでうまくいかないんだろうと悩んでいたんですね。あと、25日はそう!ソ連解体が公表された日 クリスマスです!クリスマスと言えばクリスマスツリーを作りたくなるじゃないですか!それで寝ずに作ってしまったんですよ。

昼間はと言うと、テトリスのコードを書いていたらテトリスで遊びたくなったのでsteamでぷよぷよテトリスを検索してみたらなんと75%OFFセールをしていたんですよ。これは買うしかないじゃないですか!そしてついつい遊んでしまったんですね。これはしょうがなくないですか?はい、しょうがなくないですね。
アンケートに協力してくれた方、楽しみにしていた方、なんか気にしてくれていた方、皆様本当に遅れてしまいすみませんでした m(__)m


この記事が気に入ったらサポートをしてみませんか?