2004年度のソフトウェア演習IIの授業は終りました。 この授業のページは参照用の資料として保存しているものです。最新の情報・資料は江上の授業ページで。

[Back] [Index]    -    [EGAMIX トップ] > [授業のページ] > [2004年度 ソフトウェア演習II] > [授業資料・ノート]
 
サンプルプログラム(tetris.c)

テトリスゲーム

内容の一部は学習した範囲をこえてしまいますが、比較的簡単に配列・ポインタ・構造体の考え方を使ったゲームを考えてみましょう
 
 tetris.c
/* ----------------------------------------
    'tetris.c' for softe2
                       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;                 /*  */

/* ***** テトリスの形状等の構造体定義 ***** */
struct STONE_PATTERN {
    int next ;             /* 回転させたときの次のパターン */
    int stone1;            /* 1個目の石の場所 */
    int stone2;            /* 2個目の石の相対場所 */
    int stone3;            /* 3個目の石の相対場所 */
    int stone4;            /* 4個目の石の相対場所 */
} pattern[19] = {
    {  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].stone1 ]
         + screen[ value + pattern[n].stone2 ]
         + screen[ value + pattern[n].stone3 ]
         + screen[ value + pattern[n].stone4 ]  == 0) {
        return 1;      /* すきまあり */
    } else {
        return 0;      /* すきまなし */
    }
}

/* ***** テトリスのブロックをセット・リセットする関数 ***** */
set_block(int value, int n)
{
   screen[ pos + pattern[n].stone1 ] = value;
   screen[ pos + pattern[n].stone2 ] = value;
   screen[ pos + pattern[n].stone3 ] = value;
   screen[ pos + pattern[n].stone4 ] = 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();

        switch (c) {

          case '4':                    /* '4' で左へ動かす */
            if (is_empty(pos-1,n)) 
              pos--;
            break;
          case '5':                    /* '5' で回転 */
            if (is_empty(pos,pattern[n].next) )
              n = pattern[n].next;
            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 の範囲を超える(超えそう)な部分を赤い色で表示しています。
プログラムの大まかな流れと、通常変数・配列の利用状況の 把握が出来れば十分です。正しい理解のためには、構造体の学習が必要です。
プログラムが長くなってきましたが、こうしたゲームが比較的手頃な長さ・行数で実現できることを理解してください。
 
  
より進んだ学習のために
     
[Back] [Index]
Kunihiro Egami <egami@egamix.com>