テトリス当たり判定について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
やまんちゅ

テトリス当たり判定について

#1

投稿記事 by やまんちゅ » 6年前

今lunux環境の端末(emacs)でテトリスをある程度作ったのですが、横の当たり判定はうまく実装できているのですが、ブロックがあるないに関わらず、した判定がうまく実装出来ません。うまく着地する時もあれば、そのまましたの壁を透過してセグメントエラーが出てしまいます。どのように判定をしていけばいいでしょうか?
c言語はまだ習って一年ほどなので分からないことが多々ありますがよろしくお願いします。

コード:

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

typedef struct{
  char name[20];
  char score[20];
}rank;


rank r[5];//ランキングを管理する構造体
int Time, s = 0;//時間、スコア
char str[256], name[20]; //文字列汎用変数,プレイヤー名
time_t current, start;//経過時間
FILE *fp;
int ch;//キー入力
int level;//レベル
int next, nextnext, d_lines = 0;//次と次の次のブロック、消した行

void go();


#define PATNUM 7
#define PATSIZEW 3
#define PATSIZEH 3
#define PATSPIN 4

static char blockPattern[PATNUM][PATSPIN][PATSIZEH][PATSIZEW] =
  { 
    {
      {{'#','#',' '}, {'#','#',' '}, {' ',' ',' '}},//pt ##
      {{'#','#',' '}, {'#','#',' '}, {' ',' ',' '}},//   ##
      {{'#','#',' '}, {'#','#',' '}, {' ',' ',' '}},//   
      {{'#','#',' '}, {'#','#',' '}, {' ',' ',' '}},//
    },{
      {{' ','#',' '}, {' ','#',' '}, {' ','#',' '}},//pt 
      {{' ',' ',' '}, {'#','#','#'}, {' ',' ',' '}},//   ###
      {{' ','#',' '}, {' ','#',' '}, {' ','#',' '}},//
      {{' ',' ',' '}, {'#','#','#'}, {' ',' ',' '}},//
    },{
      {{' ',' ',' '}, {' ','#',' '}, {'#','#','#'}},//pt 
      {{' ',' ','#'}, {' ','#','#'}, {' ',' ','#'}},//    #
      {{'#','#','#'}, {' ','#',' '}, {' ',' ',' '}},//   ###
      {{'#',' ',' '}, {'#','#',' '}, {'#',' ',' '}},//
    },{
      {{'#',' ',' '}, {'#','#','#'}, {' ',' ',' '}},//pt
      {{' ','#',' '}, {' ','#',' '}, {'#','#',' '}},//   #
      {{' ',' ',' '}, {'#','#','#'}, {' ',' ','#'}},//   ###
      {{' ','#','#'}, {' ','#',' '}, {' ','#',' '}},//
    },{
      {{' ',' ','#'}, {'#','#','#'}, {' ',' ',' '}},//pt
      {{'#','#',' '}, {' ','#',' '}, {' ','#',' '}},//     #
      {{' ',' ',' '}, {'#','#','#'}, {'#',' ',' '}},//   ###
      {{' ','#',' '}, {' ','#',' '}, {' ','#','#'}},//
    },{
      {{' ',' ',' '}, {' ','#','#'}, {'#','#',' '}},//pt 
      {{' ','#',' '}, {' ','#','#'}, {' ',' ','#'}},//    ##
      {{' ',' ',' '}, {' ','#','#'}, {'#','#',' '}},//   ##
      {{' ','#',' '}, {' ','#','#'}, {' ',' ','#'}},//
    },{
      {{' ',' ',' '}, {'#','#',' '}, {' ','#','#'}},//pt 回転軸は中央ではない(pt6と一致してしまう)
      {{' ',' ','#'}, {' ','#','#'}, {' ','#',' '}},//    ##
      {{' ',' ',' '}, {'#','#',' '}, {' ','#','#'}},//     ##
      {{' ',' ','#'}, {' ','#','#'}, {' ','#',' '}},//
    }
  };


//下+左右の当たり判定を調べる関数(下、左、右それぞれにブロック、壁がある場合に2、3、4を返すように設定)
int collision(int blocY, int blocX, int bottom, int pattern, int spin, char buffer[LINES][COLS/2] )
{
  int ii, jj;
  int j = 0;

  for( jj=0; jj<3; jj++){
    for( ii=0; ii<3; ii++){
      if(blockPattern[pattern][spin][jj][ii] == '#'){
	if((buffer[blocY + jj + 1][blocX + ii] == '#') || (blocY + jj + 1 == bottom))//下の当たり判定
	  j = 2;
	if((buffer[blocY + jj][blocX + ii - 1] == '#') || (blocX + ii - 1 == COLS/4))//左の当たり判定
	  j = 3;
	if((buffer[blocY + jj][blocX + ii + 1] == '#') || (blocX + ii + 1 == COLS/2))//右の当たり判定
	  j = 4;
      }
    }
  }
  return j;
}


//ランキングを書き込むか、新たにランキングのファイルを作成する関数
void ranking(int n)
{
  int i;
  
  //nが1ならランキングをファイルに書き込む
  if(n == 1){
    if((fp = fopen("highscore.txt", "w+")) == NULL){
      printf("ランキングファイルの作成に失敗しました。\n");
      exit(1);
    }
    for( i=0; i<5; i++){
      sprintf(str, "%s\n", r[i].name);
      if(fputs(str, fp) == EOF){
	printf("書き込みにに失敗しました。\n");
	exit(1);
      }
      sprintf(str, "%s\n", r[i].score);
      if(fputs(str, fp) == EOF){
	printf("書き込みに失敗しました\n");
	exit(1);
      }
    }
  }
  
  //nが0ならランキングを読み込めないとき
  if(n == 0){
    printf("ランキングを読み込めません。新しいファイルを作成します。\n");
    if((fp = fopen("highscore.txt", "w+")) == NULL){
      printf("ランキングファイルの作成に失敗しました。\n");
      exit(1);
    }
    for(i=0; i<5; i++){
      if(fputs("No name\n", fp) == EOF){
	printf("書き込みに失敗しました。\n");
	exit(1);
      }
      if(fputs("0\n", fp) == EOF){
	printf("書き込みに失敗しました。\n");
	exit(1);
      }
    }
  }
  return;
}


void end(int n)//ゲーム終了時の関数
{
  int i,j;
  
  mvaddstr(LINES / 2 - 7, ((COLS - 20) / 2) - 16, "                                      ");
  mvaddstr((LINES / 2) - 6, ((COLS - 20) / 2) - 16, "                                     ");
  mvaddstr((LINES / 2) - 5, ((COLS - 20) / 2) - 16, "                                     ");
  mvaddstr((LINES / 2) - 4, ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) - 3, ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) - 2, ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) - 1, ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) , ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) + 1, ((COLS - 20) / 2) - 16, "                                     "); 
  mvaddstr((LINES / 2) + 2, ((COLS - 20) / 2) - 16, "                                     ");
  mvaddstr((LINES / 2) + 3, ((COLS - 20) / 2) - 16, "                                     ");
  
  mvaddstr(LINES / 2 - 7, ((COLS - 20) / 2) - 3, "GameOver...");
  mvprintw((LINES / 2) - 6, ((COLS - 20) / 2) - 7, "Your Score %7d !!", s);
  if (s > atoi(r[4].score)){
    mvaddstr((LINES / 2) - 5, ((COLS - 20) / 2) - 4, "High Score !!");
    mvaddstr((LINES / 2) - 4, ((COLS - 20) / 2) - 8, "Your Name -> ");
    
    
    curs_set(1);
    nocbreak();
    echo();
    timeout(-1);
    getnstr(name, 15);
    curs_set(0);
    cbreak();
    noecho();
    timeout(0);
    
    //ランキングに追加
    for (i = 4; i >= 0; i--){
      if (atoi(r[i].score) < s){
	if (i == 0){
	  strcpy(r[i].name,name);
	  sprintf(r[i].score, "%d", s);
	  break;
	}
	if (atoi(r[i - 1].score) >= s){
	  strcpy(r[i].name,name);
	  sprintf(r[i].score, "%d", s);
	  break;
	}else{
	  strcpy(r[i].name,r[i - 1].name);
	  strcpy(r[i].score,r[i - 1].score);
	}
      }
    }
    mvaddstr((LINES / 2) - 3 + i, ((COLS - 20) / 2) - 16, "You! -> "); 
    //ファイルに書き込み
    ranking(1);
    fclose(fp);
  }
  mvaddstr((LINES / 2) - 4, ((COLS - 20) / 2) - 8, "        Ranking              "); 
  for (i = 0; i < 5; i++){  
    mvprintw((LINES / 2) - 3 + i, ((COLS - 20) / 2) - 8,"%d:", i + 1);
    j = strlen(r[i].name);
    mvprintw((LINES / 2) - 3 + i, ((COLS - 20) / 2) + 6 - j,"%s", r[i].name);
    j = strlen(r[i].score);
    mvprintw((LINES / 2) - 3 + i, ((COLS - 20) / 2) + 14 - j,"%s", r[i].score);
  }
  
  mvaddstr((LINES / 2) + 3, ((COLS - 20) / 2) - 2, "q : Quit");
  
  while(1){    
    ch = getch();
    if (ch == 'q'){
      endwin();
      exit(1);
    }
  }
  return;
}


void deleteLine(char buffer[LINES][COLS/2])//揃った行を消す関数
{
  int i, j, q;
  int hantei;

  for(j = 0; j < LINES ; j++){
    hantei = 1;
    for(i = COLS/4 + 1; i < COLS/2; i++){
      if(buffer[j][i] == ' ')
	hantei = 0;
    }
    if(hantei){
      for(q = j; q>0; q--){
	for(i = COLS/4 + 1; i < COLS/2; i++){
	  buffer[q][i] = buffer[q-1][i];
	  //1段下げる
	}
      }
      s += 100 * level;
      d_lines++;
    }
  }
}


//画面横に情報を表示する関数
void info()
{
  int i,j;
  //経過時間の表示
  time(&current);
  current -= start;
  j = COLS/4 * 3;
  mvaddstr(5, j, "Time:");
  mvprintw(6, j, "              %5d", current + Time);
  //現在のレベルの表示
  mvaddstr(3, j, "Level:");
  mvprintw(4, j, "                  %d", level);
  //消した行数の表示
  mvaddstr(7, j, "deleted_lines:");
  mvprintw(8,j, "            %7d", d_lines);
  //スコアの表示
  mvaddstr(9, j, "Score:");
  mvprintw(10,j, "            %7d", s);
  //ハイスコアの表示
  mvaddstr(11,j, "High Score:");
  i = strlen(r[0].score);
  mvprintw(12,j + 19 - i, "%s", r[0].score);
  //ハイスコアプレイヤ―の表示 
  mvaddstr(13,j, "High Score Player:");
  i = strlen(r[0].name);
  mvprintw(14,j + 19 - i, "%s", r[0].name);
  //次と次の次ののブロックの表示
  mvaddstr(15,j, "Next Blocks");
  mvaddstr(16,j, "First    Second");
  mvaddstr(17,j, "+---+    +---+");
  sprintf(str, "|%c%c%c|    |%c%c%c|",blockPattern[next][0][0][0],blockPattern[next][0][0][1],blockPattern[next][0][0][2],blockPattern[nextnext][0][0][0],blockPattern[nextnext][0][0][1],blockPattern[nextnext][0][0][2] );
  mvaddstr(18,j, str);
  sprintf(str, "|%c%c%c|    |%c%c%c|",blockPattern[next][0][1][0],blockPattern[next][0][1][1],blockPattern[next][0][1][2],blockPattern[nextnext][0][1][0],blockPattern[nextnext][0][1][1],blockPattern[nextnext][0][1][2] );
  mvaddstr(19,j, str);
  sprintf(str, "|%c%c%c|    |%c%c%c|",blockPattern[next][0][2][0],blockPattern[next][0][2][1],blockPattern[next][0][2][2],blockPattern[nextnext][0][2][0],blockPattern[nextnext][0][2][1],blockPattern[nextnext][0][2][2] );
  mvaddstr(20,j,  str);
  mvaddstr(21,j, "+---+    +---+");
  
  return; 
}

int main(int argc, char **argv){
  
  int i, j;
  //ランキングファイルを読み込む
  for (;(fp = fopen("highscore.txt", "r")) == NULL;){
    ranking(0);
    fclose(fp);
  }
  for (i = 0; i < 5; i++){
    if (fgets(r[i].name, 20, fp) == NULL){
      ranking(0);
      printf("もう一度プログラムを実行しなおしてください\n");
      exit(1);
    }
    for (j = 0; r[i].name[j] != '\0'; j++);
    if (r[i].name[j - 1] == '\n')
      r[i].name[j - 1] = '\0';
    if (fgets(r[i].score, 20, fp) == NULL){
      ranking(0);
      printf("もう一度プログラムを実行しなおしてください\n");
      exit(1);
    }
    for (j = 0; r[i].score[j] != '\0'; j++);
    if (r[i].score[j - 1] == '\n')
      r[i].score[j - 1] = '\0';
  }
  fclose(fp);
  initscr();
  start_color();
  noecho();
  cbreak();
  curs_set(0);
  keypad(stdscr, TRUE);
  go();
  endwin();
  
  return 0;
}

void go()
{
  int blocX, blocY;
  
  int delay=0;
  int waitCount = 20000;
  int pattern;
  int spin = 0;
  char buffer[LINES][COLS/2];
  int i, j;
  int ii, jj;
  int a = 1;
  int t;
  
  level = 1;
  s = 0;
  Time = 0;
  timeout(0);  
  time(&start);  
  srand((unsigned) time(NULL));
  pattern = rand() % 7;
  next = rand() % 7;
  nextnext = rand() % 7;

  blocX = (COLS/8) * 3;
  blocY = 1;
  

  for( j=0; j<LINES; j++){
    for( i=0; i<COLS/2; i++){
      buffer[j][i]=' ';
    }
  }//buffer内を初期化
  
  timeout(0);
  
  while((ch=getch())!='q'){
    mvaddstr(blocY,blocX,"   ");
    mvaddstr(blocY+1,blocX,"   ");
    mvaddstr(blocY+2,blocX,"   ");
    
    for(a=0; a<LINES; a++){
      mvaddch( a, COLS/2, '|');
      mvaddch( a, COLS/4, '|');
    }
    for(a = COLS/4 + 1; a < COLS/2; a++){
      mvaddch(LINES-1, a, '-' );
    }
    
    if(delay%waitCount == 0){
      blocY += 1;
    }
    
    delay++;

    switch(ch){
    case KEY_LEFT://左に1マス移動
      if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) != 3){
	blocX -=1;
      }
      break;
    case KEY_RIGHT://右に1マス移動
      if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) != 4){
	blocX +=1;
      }
      break;
    case KEY_DOWN://真下に即落下させる
      while(1){
	blocY++;
	if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) == 2){
	  for(jj=0; jj<3; jj++){
	    for(ii=0; ii<3; ii++){
	      if(blockPattern[pattern][spin][jj][ii] == '#'){
		buffer[blocY+jj][blocX+ii] = blockPattern[pattern][spin][jj][ii];
	      }
	    }
	  }
	  pattern = next;
	  next = nextnext;
	  nextnext = rand() % 7;
	  blocY = 1;
	  blocX = (COLS/8) * 3;
	  beep();
	  break;
	}
      }
      break;
    case ' ':
      if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) <= 2){
	spin = (spin + 1) % PATSPIN;
      }
      break;
    default:
      break;
    }
    
    //buffer内のブロック
    for(j=0; j<LINES-1; j++){
      for(i= COLS/4 + 1; i<COLS/2; i++){
	mvaddch(j, i, buffer[j][i] );
      }
    }
    //操作中のブロック
    for(jj=0; jj<3; jj++){
      for(ii=0; ii<3; ii++){
	if(blockPattern[pattern][spin][jj][ii] == '#')
	mvaddch(blocY+jj, blocX+ii, blockPattern[pattern][spin][jj][ii]);
      }
    }
    
    
    //ブロックが着地した時にbufferに追加
    if( collision(blocY, blocX, LINES-1, pattern, spin, buffer) == 2){
      for(jj=0; jj<3; jj++){
	for(ii=0; ii<3; ii++){
	  if(blockPattern[pattern][spin][jj][ii] == '#'){
	    buffer[blocY+jj][blocX+ii] = blockPattern[pattern][spin][jj][ii];
	  }
	}
      }
      pattern = next;
      next = nextnext;
      nextnext = rand() % 7;
      blocX = (COLS/8) * 3;
      blocY = 1;
      beep();
      
    }
    if(s >= 500 * level){
      level++;
      waitCount = waitCount / 4 * 3;
    }
    
    deleteLine(buffer);
    
    t = 0;
    for(ii = 0; ii < 256; ii++)//ゲームオーバーの条件 
      if(buffer[1][ii] == '#')
	t = 1;
    if(t)break;
    
    info();

  }
  if(t)
    end(0);
  return;
}


 

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: テトリス当たり判定について

#2

投稿記事 by みけCAT » 6年前

オフトピック
インデントが乱れて見えます。
これは、デフォルトでスペースとタブが混ざったクソインデントを出力するemacsの糞仕様によるものであると思われます。
この糞仕様を回避するために、c-basic-offsetとtab-widthを同じ値に設定することをおすすめします。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: テトリス当たり判定について

#3

投稿記事 by みけCAT » 6年前

やまんちゅ さんが書きました:どのように判定をしていけばいいでしょうか?
実際には例えば左と下に同時に接することもあるはずなのに、collision関数が下、左、右のどれか1個の接触判定しか返さない仕様なのがまずいと思います。
例えば
  • 下、左、右を2、3、4ではなく1、2、4で表す
  • 接触を検出したら、その方向を表す数値を代入するのではなくjとビットORする
  • collision関数の戻り値を用いた接触しているかの判定は、等価ではなく判定したい方向を表す数値とのビットANDで行う
とすると良さそうだと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

かずま

Re: テトリス当たり判定について

#4

投稿記事 by かずま » 6年前

みけCATさんが的確なアドバイスをしているのに、
質問者は応答しませんね。
具体的な修正方法が分からないのでしょうか?
次のように 8行だけ修正すると、問題なく動くように思うのですが。

コード:

82行目:  j = 2; を j |= 1; に。
84行目:  j = 3; を j |= 2; に。
82行目:  j = 4; を j |= 4; に。
382行目:
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) != 3){ を
   if((collision(blocY, blocX, LINES-1, pattern, spin, buffer) & 2) == 0){ に。
387行目:
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) != 4){ を
   if((collision(blocY, blocX, LINES-1, pattern, spin, buffer) & 4) == 0){ に。
394行目:
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) == 2){ を
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) & 1){ に。
413行目:
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) <= 2){ を
   if(collision(blocY, blocX, LINES-1, pattern, spin, buffer) <= 1){ に
437行目:
   if( collision(blocY, blocX, LINES-1, pattern, spin, buffer) == 2){ を
   if( collision(blocY, blocX, LINES-1, pattern, spin, buffer) & 1){ に
話は変わりますが、テトリスなら、
### ではなく、#### にしないといけないのでは?
あと、# が正方形ではなく、縦長なので、回転させたときに
形が大きく変わることに違和感があります。

Linux や cygwin で動くテトリスにこんなものがあります。

コード:

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

struct itimerval h;

void t()
{
    h.it_value.tv_usec -= h.it_value.tv_usec / 3000;
    setitimer(0, &h, 0);
}

FILE *d;
struct sigaction v;
int c, l, s, I, K = 0, i = 276, j, k, q[276], Q[276], *n = q, *m, x = 17;
int f[] = {
     7, -13, -12,  1,    8, -11, -12, -1,    9,  -1,   1, 12,
     3, -13, -12, -1,   12,  -1,  11,  1,   15,  -1,  13,  1,
    18,  -1,   1,  2,    0, -12,  -1, 11,    1, -12,   1, 13,
    10, -12,   1, 12,   11, -12,  -1,  1,    2, -12,  -1, 12,
    13, -12,  12, 13,   14, -11,  -1,  1,    4, -13, -12, 12,
    16, -11, -12, 12,   17, -13,   1, -1,    5, -12,  12, 11,
     6, -12,  12, 24
};

void u(void)
{
    for (i = 11; ++i < 264; )
        if ((k = q[i]) - Q[i]) {
            Q[i] = k;
            if (i - ++I || i % 12 < 1)
                printf("\033[%d;%dH",
                    (I = i)/12, i % 12 * 2 + 28);
            printf("\033[%dm  " + (K - k ? 0 :5), k);
            K = k;
        }
    Q[263] = c = getchar();
}

int G(int b)
{
    for (i = 4; i--; )
        if (q[i ? b + n[i] :b]) return 0;
    return 1;
}

void g(int b) { for (i = 4; i--; q[i ? x + n[i] : x] = b) ; }

int main(int C, char **V)
{
    char *a;
    h.it_value.tv_usec = 1000000 / (l = C > 1 ? atoi(V[1]) : 2);
    for (a = C >2 ? V[2] : "jkl pq"; i ; i--)
        *n++ = i < 25 || i % 12 < 2 ? 7 : 0;
    srand(getpid());
    system("stty cbreak -echo stop u");
    v.sa_handler = t;
    sigaction(SIGALRM, &v, NULL);
    t();
    puts("\033[H\033[J");
    for (n = f + rand() % 7 * 4; ; g(7), u(), g(0)) {
        if (c < 0)
            if (G(x + 12)) x += 12;
            else {
                for (g(7), j = 0; j < 252; j = 12 * (j/12 + 1))
                    for (; q[++j]; )
                        if (j % 12 == 10) {
                            for (; j % 12; q[j--] = 0) ;
                            u();
                            for (; --j; q[j+12] = q[j]) ;
                            u();
                        }
                n = f + rand() % 7 * 4, G(x = 17) || (c = a[5]);
            }
        if (c == *a) G(--x) || ++x;
        if (c == a[1]) n = f + 4 * *(m = n), G(x) || (n = m);
        if (c == a[2]) G(++x) || --x;
        if (c == a[3]) while (G(x+12)) x += 12;
        if (c == a[4] || c == a[5]) {
            printf("\033[H\033[J\033[0m\n");
            if (c == a[5]) break;
            for (j = 264; j--; Q[j] = 0) ;
            while (getchar() - a[4]) ;
            puts("\033[H\033[J\033[7m");
        }
    }
    system("stty -cbreak echo stop \023");
    return 0;
}
第1引数は、スピード[大きいほど速い](デフォルトは 2)
第2引数は、キー配置[左、回転、右、落下、ポーズ、終了](デフォルトは "jkl pq")
例えば、./a.out 2 hkljpq などのように起動します。

返信

“C言語何でも質問掲示板” へ戻る