C言語初級者がMacのコンソールで実行できるテトリスを作ってみました。参考にした動画はこちらです。テトリスについてはWikipediaも参考にしました。この投稿では作ってみた上で気になった箇所をピックアップして解説していきます。全ソースコードはこちらで確認できます。これについては、Youtubeにあげている方にも許可を頂いています。
テトリス – Wikipedia
日本では、 1988年にセガ・エンタープライゼス(後の セガ・インタラクティブ)から発売された アーケード版( セガ・システム16版)の人気により浸透した。当時はまだ操作法が確立されていなかったが、このシステム16版の登場以降は同作のものが日本国内における 事実上の標準となり、その影響力から特に「 セガテトリス」とよく呼ばれる( 2000年にアーケードと …
拙い動画でお恥ずかしいですが、どうぞご利用下さい。
— 館長 (@gamedokan) 2019年1月9日
まずテトリスの枠を作る
まず最初にテトリスの枠を作ります。テトリスの枠は横が12個、縦が22個のブロックでできています。なので下の図のようにそのブロック箇所へ1を立てて、ブロックを描画していけばよいことになります。
単純に書くと以下のようになりますが、それを少し整理してchar field[FIELD_HEIGHT][FIELD_WIDTH];
のフィールドに値を格納する書き方に変更すると以下のようになります。
# 単純に書いた方 #include <stdio.h> #include <memory.h> #define FIELD_WIDTH 12 #define FIELD_HEIGHT 22 int main() { for (int i = 0; i < FIELD_HEIGHT; i++) { for (int j = 0; j < FIELD_WIDTH; j++) { if (j == 0 || j == FIELD_WIDTH - 1 || i == FIELD_HEIGHT - 1) { printf("■"); } else { printf(" "); } } printf("\n"); } }
# 整理した方 #include #include #define FIELD_WIDTH 12 #define FIELD_HEIGHT 22 char field[FIELD_HEIGHT][FIELD_WIDTH]; int main() { memset(field, 0, sizeof(field)); // 左右の壁 for (int i = 0; i < FIELD_HEIGHT; i++) { field[i][0] = 1; field[i][FIELD_WIDTH - 1] = 1; } // 下の壁 for (int i = 0; i < FIELD_WIDTH; i++) { field[FIELD_HEIGHT - 1][i] = 1; } // 描画 for (int i = 0; i < FIELD_HEIGHT; i++) { for (int j = 0; j < FIELD_WIDTH; j++) { printf(field[i][j] ? "■" : " "); } printf("\n"); } }
ミノを表示する
枠が表示できたら次は、ミノを表示します。ミノは下図のように7種類ありますが、ここではテトリス棒を表示することをやってみます。
枠を表示したField
を元にし、あらたにミノを表示するdisplayBuffer
領域を確保しミノを表示します。最初にミノを表示する箇所、右の黒枠箇所となります。
#define FIELD_WIDTH 12 #define FIELD_HEIGHT 22 #define MINO_TYPE_MAX 7 #define MINO_ANGLE_MAX 4 #define MINO_WIDTH 4 #define MINO_HEIGHT 4 char field[FIELD_HEIGHT][FIELD_WIDTH]; char displayBuffer[FIELD_HEIGHT][FIELD_WIDTH]; char minoShapes[MINO_TYPE_MAX][MINO_ANGLE_MAX][MINO_HEIGHT][MINO_WIDTH] = { // MINO_TYPE_I { // MINO_ANGLE_0 { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, }, } }; void display() { memcpy(displayBuffer, field, sizeof(field)); for (int i = 0; i < MINO_HEIGHT; i++) { for (int j = 0; j < MINO_WIDTH; j++) { displayBuffer[minoY + i][minoX + j] |= minoShapes[minoType][minoAngle][i][j]; } } system("clear"); for (int i = 0; i < FIELD_HEIGHT; i++) { for (int j = 0; j < FIELD_WIDTH; j++) { printf(displayBuffer[i][j] ? "■" : " "); } printf("\n"); } } int main() { memset(field, 0, sizeof(field)); for (int i = 0; i < FIELD_HEIGHT; i++) { field[i][0] = field[i][FIELD_WIDTH - 1] = 1; } for (int i = 0; i < FIELD_WIDTH; i++) { field[FIELD_HEIGHT - 1][i] = 1; } display(); }
1秒に1回更新する
以下のコードを使えば1秒に1回更新することができます。
#include time_t t = time(NULL); while (true) { if (t != time(NULL)) { t = time(NULL); printf("%ld\n", t); } }
キーボード入力を取得する
Windowsとは違いLinux環境ではkbhit()
に相当するものが内容なのでこちらのサイトにある関数をそのまま利用しました。また、動画にはなかったですが、boolean
を扱うために#include <stdbool.h>
を追記しています。
#include #include #include #include bool kbhit() { struct termios oldt, newt; int ch; int oldf; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); oldf = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); fcntl(STDIN_FILENO, F_SETFL, oldf); if (ch != EOF) { ungetc(ch, stdin); return true; } return false; }
aキー
で左、sキー
で下、dキー
で右、スペースキー
で回転するようになっています。
if (kbhit()) { switch (getchar()) { case 's': if (!isHit(minoX, minoY + 1, minoType, minoAngle)) { minoY++; } break; case 'a': if (!isHit(minoX - 1, minoY, minoType, minoAngle)) { minoX--; } break; case 'd': if (!isHit(minoX + 1, minoY, minoType, minoAngle)) { minoX++; } break; case 0x20: if (!isHit(minoX, minoY, minoType, (minoAngle + 1) % MINO_ANGLE_MAX)) { minoAngle = (minoAngle + 1) % MINO_ANGLE_MAX; } break; } display(); }
ミノが壁にあたるかを判定する
ミノが壁にあたるかを判定するには以下のような関数でチェックします。
bool isHit(int _minoX, int _minoY, int _minoType, int _minoAngle) { for (int i = 0; i < MINO_HEIGHT; i++) { for (int j = 0; j < MINO_WIDTH; j++) { if (minoShapes[_minoType][_minoAngle][i][j] && field[_minoY + i][_minoX + j]) { return true; } } } return false; }
行が揃ったら消す
if (t != time(NULL)) { t = time(NULL); if (isHit(minoX, minoY + 1, minoType, minoAngle)) { for (int i = 0; i < MINO_HEIGHT; i++) { for (int j = 0; j < MINO_WIDTH; j++) { field[minoY + i][minoX + j] |= minoShapes[minoType][minoAngle][i][j]; } } for (int i = 0; i < FIELD_HEIGHT - 1; i++) { int lineFill = 1; for (int j = 1; j < FIELD_WIDTH - 1; j++) { if (!field[i][j]) { lineFill = 0; } } if (lineFill) { for (int j = i; 0 < j; j--) { memcpy(field[j], field[j - 1], FIELD_WIDTH); } } } resetMino(); } else { minoY++; } display(); }
まとめ
まだWindowsで実行していないのでわかりませんが、Windowsとの違いは、kbhit()
と#include <stdbool.h>
をinclude
するところあたりかと思います。また、Macのコンソール場合はclear
コマンドで描画し直しているので履歴には残ってしまいます。動画を見て写経しただけですがテトリスってこんな感じで作れるんだなと思い楽しむことができました。改善や機能追加する点はまだたくさんあるので時間をみて更新していこうと思います。