今日の雑記

生きることでいっぱいいっぱい

curses版テトリス

以前書いた「cursesテトリス」がまあ形になったので公開する。コンソール環境上でC言語環境とcursesライブラリがインストールされていれば動かせる。
操作方法は「左右」でブロック移動。「上」でブロック回転(時計回り固定)。「下」でブロック落下。「Q」キーで終了。

#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

#define FIELD_W (12)
#define FIELD_H (20)

#define BLOCK_N (4)

#define NONE  (0)
#define VWALL (1)
#define HWALL (2)
#define BLOCK (3)
#define V     (VWALL)
#define H     (HWALL)

static int field[FIELD_H][FIELD_W] = {
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { V,0,0,0,0,0,0,0,0,0,0,V },
  { H,H,H,H,H,H,H,H,H,H,H,H }
};

static int block_base[][BLOCK_N][2] = {
  { { +0, +0 }, { -1, +0 }, { +0, +1 }, { +0, +2 } },
  { { +0, +0 }, { +1, +0 }, { +0, +1 }, { +0, +2 } },
  { { +0, +0 }, { +1, +0 }, { +0, +1 }, { -1, +1 } },
  { { +0, +0 }, { +1, +0 }, { +0, -1 }, { -1, -1 } },
  { { +0, +0 }, { +0, +1 }, { +0, -1 }, { +0, -2 } },
  { { +0, +0 }, { +0, +1 }, { -1, +0 }, { -1, +1 } },
  { { +0, +0 }, { +0, +1 }, { +0, -1 }, { -1, +0 } },
};

static int block_tbl[BLOCK_N][2];

static int key;

static int lines;
static int score;
static int px;
static int py;
static int fall_time;
static int fall_wait;
static int block_num;

static void initApp(void)
{
  srand(time(NULL));
  initscr();
  noecho();
  cbreak();
  timeout(16);
  keypad(stdscr, TRUE);
  curs_set(0);
  lines = 0;
  score = 0;
  block_num = -1;
}

static void exitApp(void)
{
  curs_set(1);
  endwin();
}

static void initField(void)
{
  int i, j;

  for(i = 0; i < (FIELD_H - 1); i++){
    for(j = 1; j < (FIELD_W - 1); j++){
      field[i][j] = NONE;
    }
  }
}

static void drawField(void)
{
  int i, j;

  for(i = 0; i < FIELD_H; i++){
    for(j = 0; j < FIELD_W; j++){
      if(field[i][j] == VWALL){
        mvaddch(i, j, '|');
      }
      if(field[i][j] == HWALL){
        mvaddch(i, j, '-');
      }
    }
  }
}

static void initBlock(void)
{
  int i, n;

  do{
    n = rand() % 7;
    if(block_num == n){
      continue;
    }
    block_num = n;
    break;
  }while(TRUE);

  for(i = 0; i < BLOCK_N; i++){
    block_tbl[i][0] = block_base[block_num][i][0];
    block_tbl[i][1] = block_base[block_num][i][1];
  }

  px = 5;
  py = 1;
  fall_time = 30;
  fall_wait = 0;
}

static void eraseLine(int y)
{
  int i, j;

  for(i = y; i >= 0; i--){
    for(j = 1; j < (FIELD_W - 1); j++){
      if(i > 0){
        field[i][j] = field[i - 1][j];
        if(field[i][j] == NONE){
          mvaddch(i, j, ' ');
        }else{
          mvaddch(i, j, '#');
        }
      }else{
        field[i][j] = NONE;
        mvaddch(i, j, ' ');
      }
    }
  }
}

static void eraseCheck(int y)
{
  int i, j, add;
  bool erase;

  add = 0;
  for(i = y; i >= 0; i--){
    erase = TRUE;
    for(j = 1; j < (FIELD_W - 1); j++){
      if(field[i][j] != BLOCK){
        erase = FALSE;
      }
    }
    if(erase == TRUE){
      lines++;
      add += add + 10;
      eraseLine(i);
      i++;
    }
  }
  score += add;
}

static void fixBlock(int y, int x)
{
  int i, cx, cy;

  for(i = 0; i < BLOCK_N; i++){
    cy = y + block_tbl[i][0];
    cx = x + block_tbl[i][1];
    field[cy][cx] = BLOCK;
    mvaddch(cy, cx, '#');
  }
  eraseCheck(FIELD_H - 2);
}

static bool collideBlock(int y, int x)
{
  bool ret = FALSE;
  int i, cx, cy;

  for(i = 0; i < BLOCK_N; i++){
    cy = y + block_tbl[i][0];
    cx = x + block_tbl[i][1];
    if(field[cy][cx] != 0){
      ret = TRUE;
      break;
    }
  }

  return ret;
}

static void fallBlock(void)
{
  int fy = 0;
  bool down = FALSE;

  fall_wait++;
  if(fall_wait >= fall_time){
    fy = +1;
    fall_wait = 0;
  }else if(key == KEY_DOWN){
    fy = +1;
    down = TRUE;
  }else{
    return;
  }

  do{
    if(collideBlock(py + fy, px) == TRUE){
      fixBlock(py, px);
      initBlock();
      down = FALSE;
      break;
    }
    py += fy;
    if(down == TRUE){
      score++;
    }
  }while(down == TRUE);
}

static void rotateBlock(void)
{
  int i, x, y;
  int tbl[BLOCK_N][2];

  for(i = 0; i < BLOCK_N; i++){
    tbl[i][0] = block_tbl[i][0];
    tbl[i][1] = block_tbl[i][1];
  }

  for(i = 0; i < BLOCK_N; i++){
    y = +1 * block_tbl[i][1];
    x = -1 * block_tbl[i][0];
    block_tbl[i][0] = y;
    block_tbl[i][1] = x;
  }

  if(collideBlock(py, px) == TRUE){
    for(i = 0; i < BLOCK_N; i++){
      block_tbl[i][0] = tbl[i][0];
      block_tbl[i][1] = tbl[i][1];
    }
  }
}

static void moveBlock(void)
{
  int vx = 0, vy = 0;

  if(key == KEY_UP)    rotateBlock();
  if(key == KEY_LEFT)  vx = -1;
  if(key == KEY_RIGHT) vx = +1;

  if(collideBlock(py + vy, px + vx) == FALSE){
    px += vx;
    py += vy;
  }

  fallBlock();
}

static void drawBlock(void)
{
  int i, x, y;

  for(i = 0; i < BLOCK_N; i++){
    y = block_tbl[i][0];
    x = block_tbl[i][1];
    mvaddch(py + y, px + x, '#');
  }
}

static void clearBlock(void)
{
  int i, x, y;

  for(i = 0; i < BLOCK_N; i++){
    y = block_tbl[i][0];
    x = block_tbl[i][1];
    mvaddch(py + y, px + x, ' ');
  }
}

static void drawApp(void)
{
  drawBlock();
  mvprintw(1, FIELD_W + 1, "LINE  %d", lines);
  mvprintw(2, FIELD_W + 1, "SCORE %d", score);
  mvaddch(FIELD_H, px - 1, ' ');
}

static bool isExecApp(void)
{
  if(key == 'q'){
    return FALSE;
  }

  return TRUE;
}

static bool execApp(void)
{
  bool exec = TRUE;

  drawField();
  drawApp();

  while(exec){
    key = getch();
    exec = isExecApp();
    clearBlock();
    moveBlock();
    drawApp();
  }

  return exec;
}

int main(int argc,  char *argv[])
{
  initApp();
  atexit(exitApp);

  while(TRUE){
    initField();
    initBlock();
    if(execApp() == FALSE){
      break;
    }
  }

  return 0;
}