テトリスゲーム
内容の一部は学習した範囲をこえてしまいますが、基本的な考え方は比較的簡単です。2次元配列を積極的に利用したゲームを考えて みましょう
 
 tetris.c
/* ----------------------------------------
'tetris2.c' for softj2 (配列使用版)
by K.Egami
(いつかどこかで見たようなプログラム)
---------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

struct itimerval timer_val;
struct sigaction act;

void timer()
{
setitimer(ITIMER_REAL,&timer_val,(struct itimerval *)0);
}



int screen[12*23] = {0,}; /* ゲーム画面の表示保存用配列 1次元配列で保存*/
int old_screen[12*23]; /* 1つ前の画面を保存する */

int pos = 17; /* 落ちてくるテトリスの場所 */
int pos_y; /* */

/* ***** テトリスの形状等の定義 ***** */
/* 要素0: 回転させたときの次のパターン */
/* 1: 1個目の石の場所 */
/* 2: 2個目の石の相対場所 */
/* 3: 3個目の石の相対場所 */
/* 4: 4個目の石の相対場所 */
int pattern[19][5] = {
{ 7, 0, -13, -12, 1}, /* 0 -> 7 -> 0 */
{ 8, 0, -11, -12, -1}, /* 1 -> 8 -> 1 */
{ 9, 0, -1, 1, 12}, /* 2 -> 9 -> 10 -> 11 -> 2 */
{ 3, 0, -13, -12, -1}, /* 3 -> 3 */
{ 12, 0, -1, 11, 1}, /* 4 -> 12 -> 13 -> 14 -> 4 */
{ 15, 0, -1, 13, 1}, /* 5 -> 15 -> 16 -> 17 -> 5 */
{ 18, 0, -1, 1, 2}, /* 6 -> 18 -> 6 */
{ 0, 0, -12, -1, 11}, /* 7 */
{ 1, 0, -12, 1, 13}, /* 8 */
{ 10, 0, -12, 1, 12}, /* 9 */
{ 11, 0, -12, -1, 1}, /* 10 */
{ 2, 0, -12, -1, 12}, /* 11 */
{ 13, 0, -12, 12, 13}, /* 12 */
{ 14, 0, -11, -1, 1}, /* 13 */
{ 4, 0, -13, -12, 12}, /* 14 */
{ 16, 0, -11, -12, 12}, /* 15 */
{ 17, 0, -13, 1, -1}, /* 16 */
{ 5, 0, -12, 12, 11}, /* 17 */
{ 6, 0, -12, 12, 24} /* 18 */
};

/* ***** 画面の描画する為の関数 ***** */
void update_screen(void)
{
int i;

for (i = 12;i < 12*22;i++)
if (screen[i] != old_screen[i]) {
printf("\033[%d;%dH", i / 12, i % 12 * 2 + 10); /* 描画場所移動 */
if (screen[i] > old_screen[i]) {
printf("##"); /* 石・壁を書く */
} else {
printf(" "); /* 石を消す */
}
old_screen[i] = screen[i];
}
}

/* ***** 回転・落下させたときに空間があるのか?のチェックを行う関数 ***** */
int is_empty(int value, int n)
{
if ( screen[ value + pattern[n][1] ]
+ screen[ value + pattern[n][2] ]
+ screen[ value + pattern[n][3] ]
+ screen[ value + pattern[n][4] ] == 0) {
return 1; /* すきまあり */
} else {
return 0; /* すきまなし */
}
}

/* ***** テトリスのブロックをセット・リセットする関数 ***** */
set_block(int value, int n)
{
screen[ pos + pattern[n][1] ] = value;
screen[ pos + pattern[n][2] ] = value;
screen[ pos + pattern[n][3] ] = value;
screen[ pos + pattern[n][4] ] = value;
}

/* ********************** */
/* ***** メイン関数 ***** */
/* ********************** */
int main(void)
{
int i,j,k;
int c; /* キーボードからの入力用 */
int n; /* 落下するテトリスのパターン番号 */

srand(time(0));
/* ゲーム・落下速度・・・数を大きくするとユックリになる */
timer_val.it_value.tv_usec = 300*1000;
timer_val.it_value.tv_sec = 0;


system("stty cbreak -echo stop u");
act.sa_handler = timer;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, 0);
timer();


/* ゲームの枠データを設定 */
for (i = 0;i < 12*23; i++) {
if ((i % 12 == 0) || (i % 12 == 11) || (i > 12*21))
screen[i] = 1;
}

/* 画面の消去(クリア) */
printf("\033[H\033[J");

n = rand() % 7;

/* メインループ */
while (1) {
set_block(1,n);
update_screen(); /* 画面の更新 */
set_block(0,n);

c = getchar(); /* キーボードから1文字入力 */

switch (c) {

case '4': /* '4' で左へ動かす */
if (is_empty(pos-1,n))
pos--;
break;
case '5': /* '5' で回転 */
if (is_empty(pos,pattern[n][0]) )
n = pattern[n][0];
break;
case '6': /* '6' で右へ動かす */
if (is_empty(pos+1,n))
pos++;
break;
case ' ': /* ' ' で落とす */
while ( is_empty(pos + 12,n) ) {
pos += 12;
pos_y ++;
}
break;

default: /* それ以外のキー or 押さなかった */

if (is_empty(pos + 12,n)) {
pos += 12;
} else { /* 下まで落ちた場合 */
set_block(1,n);
pos ++;

for (j = 0; j < 12*21; j += 12) {
int k = 0;
for (i = 1;i <= 10; i++) {
k += screen[j + i];
}
if (k == 10) { /* 石が1段そろっていたら・・・ */
for (i = 1;i <= 10; i++)
screen[j + i] = 0; /* 1段全て消去 */
update_screen();
for (i = j;i > 0;i--)
screen[i + 12] = screen[i]; /* パターンを1段下げる */
update_screen();
}
}
n = rand() % 7;
pos = 17;

if (! is_empty(17,n)) {
system("stty sane");
exit(0);
/* ゲームオーバー */
}
} /* end of if */
} /* end of switch */
} /* end of while */
}
 
ソフトウェア実習II の範囲を超える(超えそう)な部分を赤い色で表示しています。
プログラムの大まかな流れと、通常変数・配列の利用状況の 把握が出来れば十分です。
プログラムが長くなってきましたが、こうしたゲームがこれまでのゲームと同じく比較的手頃な長さ・行数で実現できることを理解してください。
 
  
より進んだ学習のために


 

Kunihiro Egami <egami@egamix.com>