回精華區
作者hightman@feeling-NOsmthSPAM-org (xtt), 看板: plan
標題[M3] 俄羅斯方塊(雙人版) tetris2.c
時間溫馨小屋 (Thu Jan 2 12:30:36 2003) Updated: 2005/03/20
: talk.c:talk_speak()

#ifdef HAVE_GAME
    else if (ch == Ctrl('O'))
    { /* Thor.990219: 呼叫外掛棋盤 */
      data[0] = ch;
      if (send(fd, data, 1, 0) != 1)
        break;
      if (DL_func("bin/bwboard.so:vaBWboard", fd, 0) == -2)
        break;
    }

+   else if (ch == Ctrl('L'))      /* Hightman.021229: 俄羅斯方塊對戰 */
+   {
+     data[0] = ch;
+     if (send(fd, data, 1, 0) != 1)
+       break;
+     if (DL_func("bin/tetris2.so:vaTetris", fd, 0) == -2)
+       break;
+   }
#endif

: src/game/Makefile

SO =    .... tetris2.so

: src/game/tetris2.c 新增此程式

/*-------------------------------------------------------*/
/* tetris2.c    ( NTHU CS MapleBBS Ver 3.10 )            */
/*-------------------------------------------------------*/
/* target : 雙人俄羅斯方塊                               */
/* create : 02/12/29                                     */
/* update :   /  /                                       */
/* author : hightman.bbs@bbs.hightman.net                */
/*-------------------------------------------------------*/


#include "bbs.h"


#ifdef HAVE_GAME

#define BLOCK_CHAR      "■"

static int cfd;         /* socket number */

typedef struct
{
  int ypos;
  int xpos;
  int style;
  int direction;
  int last_y;
  int last_x;
  int last_d;
  int newstyle;
  int level;
  int score;
  int lines;
  int lines2;           /* 送出行數 */
  int map[21][12];
}      TetrisScreen;

static TetrisScreen ts[2];

/* 每種方塊都是 4*4 的,然後可以轉 4 個方向 */

static int style_x[7][4][4] =
{
  0, 1, 0, 1,    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
  0, 1, 2, 3,    1, 1, 1, 1,   0, 1, 2, 3,   1, 1, 1, 1,
  0, 1, 1, 2,    1, 0, 1, 0,   0, 1, 1, 2,   1, 0, 1, 0,
  1, 2, 0, 1,    0, 0, 1, 1,   1, 2, 0, 1,   0, 0, 1, 1,
  0, 1, 2, 0,    0, 1, 1, 1,   2, 0, 1, 2,   0, 0, 0, 1,
  0, 1, 2, 2,    1, 1, 0, 1,   0, 0, 1, 2,   0, 1, 0, 0,
  0, 1, 2, 1,    1, 0, 1, 1,   1, 0, 1, 2,   0, 0, 1, 0
};

static int style_y[7][4][4] =
{
  0, 0, 1, 1,    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
  1, 1, 1, 1,    0, 1, 2, 3,   1, 1, 1, 1,   0, 1, 2, 3,
  0, 0, 1, 1,    0, 1, 1, 2,   0, 0, 1, 1,   0, 1, 1, 2,
  0, 0, 1, 1,    0, 1, 1, 2,   0, 0, 1, 1,   0, 1, 1, 2,
  0, 0, 0, 1,    0, 0, 1, 2,   0, 1, 1, 1,   0, 1, 2, 2,
  0, 0, 0, 1,    0, 1, 2, 2,   0, 1, 1, 1,   0, 0, 1, 2,
  0, 0, 0, 1,    0, 1, 1, 2,   0, 1, 1, 1,   0, 1, 1, 2
};


static int GameOver;        /* 1:I-win  2:I-fail  3:He-quit 4:I-quit 5:unknow-reson 0:not-over */
static int GameTimes;       /* 第幾局了? */
static int clocker, delay, gmode;


#define GAME_OVER       -5
#define GAME_WAIT       -4
#define GAME_START      -3
#define GAME_PREP       -2
#define GAME_REFRESH    -1
#define GAME_ON         0
#define GAME_WIN        1
#define GAME_FAIL       2
#define GAME_IQUIT      3
#define GAME_HQUIT      4
#define GAME_DISCONN    5


static void
map_move(x, y)      /* 在遊戲畫面上的相對移動 */
  int x, y;
{
  move(x + 2, 2 * y);
}


static void
map_show(m)     /* 根據 (m: 0-我方  1-對方) 秀出畫面 */
  int m;
{
  int x, y;

  for (x = 0; x < 21; x++)
  {
    map_move(x, m * 28);
    for (y = 0; y < 12; y++)
    {
      if (ts[m].map[x][y] == 8)
        outs("▓");
      else if (ts[m].map[x][y])
        outs(BLOCK_CHAR);
      else
        outs("  ");
    }
  }
}


static void
show_block(f, m)    /* 秀出或清除一個方塊組 */
  int f, m;     /* f=1:秀出 f=0:清除 */
{
  int i, b, x, y, d, s;

  b = m * 28;
  d = ts[m].last_d;
  y = ts[m].last_y;
  x = ts[m].last_x;
  s = ts[m].style;

  if (d == -1)
    return;
  for (i = 0; i < 4; i++)
  {
    map_move(x + style_x[s][d][i], y + b + style_y[s][d][i]);
    if (f)
      outs(BLOCK_CHAR);
    else
      outs("  ");
  }
  move(1, 0);
}


static void
map_raise(n, m)     /* 對方迫使我方地圖上升 */
  int n, m;
{
  int x, y, k;

  if (n < 1)
    return;

  k = (m + 1) % 2;

  for (x = 0; x < 20; x++)
  {
    for (y = 1; y < 11; y++)
    {
      ts[k].map[x][y] = ts[k].map[x + n][y];
    }
  }

  ts[k].ypos -= n;

  if (ts[k].ypos < 0)
    ts[k].ypos = 0;

  do
  {
    for (y = 1; y < 11; y++)
    {
      x = 20 - n;
      ts[k].map[x][y] = ((x * ts[m].score) >> (int)(y / n)) % 3;
    }
  }
  while (--n > 0);

  map_show(k);
  show_block(1, k);
}


static void
new_block(m)        /* 顯示一個新方塊組 */
  int m;
{
  int i, b, s;

  b = m * 12;
  s = ts[m].newstyle;

  map_move(0, b + 12);
  outs("        ");
  map_move(1, b + 12);
  outs("        ");

  for (i = 0; i < 4; i++)
  {
    map_move(style_x[s][0][i], 12 + b + style_y[s][0][i]);
    outs(BLOCK_CHAR);
  }
  move(1, 0);
}


static void
move_block(m)       /* 方塊組的移動 */
  int m;
{
  show_block(0, m);
  ts[m].last_x = ts[m].xpos;
  ts[m].last_y = ts[m].ypos;
  ts[m].last_d = ts[m].direction;
  show_block(1, m);
}


static int
crash(x, y, s, d, m)    /* 判斷是否撞到了 */
  int x, y, s, d, m;
{
  int i;

  for (i = 0; i < 4; i++)
  {
    if (ts[m].map[x + style_x[s][d][i]][y + style_y[s][d][i]])
      return 1;
  }
  return 0;
}

/* 檢查是否有需要消去的 */
static void
check_lines(m)
  int m;
{
  int y, x, s = 0;

  for (y = 0; y < 20; y++)
  {
    for (x = 1; x < 11; x++)
    {
      if (ts[m].map[y][x] == 0)
        break;
    }

    if (x == 11)
    {
      int y2;

      /* 消去行上部全部下移 */
      for (y2 = y; y2 > 0; y2--)
      {
        for (x = 1; x < 11; x++)
          ts[m].map[y2][x] = ts[m].map[y2 - 1][x];
      }

      for (x = 1; x < 11; x++)
        ts[m].map[0][x] = 0;

      ts[m].lines++;
      s++;

      if (ts[m].lines % 30 == 0)
      {
        if (m == 0)
        {
          delay *= 0.9;
          bell();
        }
        ts[m].level++;
      }
    }
  }

  y = s;
  s = (1 << y) - 1;

  if (s > 0)
  {
    s = s * (10 + ts[m].level) / 10;
    ts[m].score += s;
    map_show(m);

    /* 上升吧 */
    if (y > 1)
      map_raise(y - 1, m);

    /* update lines, score, lines2 */
    ts[m].lines2 += (y - 1);

    move(9, 27 + m * 24);
    prints("%d", ts[m].lines);
    move(11, 27 + m * 24);
    prints("%d", ts[m].lines2);
    move(13, 27 + m * 24);
    prints("%d", ts[m].score);
    move(15, 27 + m * 24);
    prints("%d", ts[m].level);
    move(1, 0);
  }
}

/* 撞到後的處理 */
static void
arrived(m)
  int m;
{
  int y, x, i, s, d;

  s = ts[m].style;
  d = ts[m].direction;

  for (i = 0; i < 4; i++)
  {
    y = ts[m].ypos + style_y[s][d][i];
    x = ts[m].xpos + style_x[s][d][i];
    ts[m].map[y][x] = s + 1;
  }

  check_lines(m);
  if (m == 0)
    GameOver = GAME_REFRESH;
}


static void
play_count(userid, total, win)  /* 回傳 userid 的總場數、勝場數 */
  char *userid;
  int *total, *win;
{
  char fpath[64];
  int fd;
  int grade[32];        /* 保留 32 個 int */

  *total = 0;
  *win = 0;

  usr_fpath(fpath, userid, "bwboard");
  if ((fd = open(fpath, O_RDONLY)) >= 0)
  {
    read(fd, grade, sizeof(grade));
    close(fd);

    *total = grade[10];
    *win = grade[11];
  }
}


static void
play_add(win)                   /* 我的總/勝場數 +1 */
  int win;
{
  char fpath[64];
  int fd;
  int grade[32];        /* 保留 32 個 int */

  usr_fpath(fpath, cuser.userid, "bwboard");
  if ((fd = open(fpath, O_RDWR | O_CREAT, 0600)) >= 0)
  {
    if (read(fd, grade, sizeof(grade)) != sizeof(grade))
      memset(grade, 0, sizeof(grade));

    grade[10]++;
    if (win)
      grade[11]++;

    lseek(fd, (off_t) 0, SEEK_SET);
    write(fd, grade, sizeof(grade));
    close(fd);
  }
}


static void
show_record()
{
  int myTotal, yourTotal, myWin, yourWin;

  /* 取得戰績 */
  play_count(cuser.userid, &myTotal, &myWin);
  play_count(cutmp->mateid, &yourTotal, &yourWin);

  move(1, 0);
  clrtoeol();
  prints("★%s [%d戰%d勝]", cuser.userid, myTotal, myWin);

  move(1, 38);
  outs("V.S.");

  move(1, 44);
  prints("☆%s [%d戰%d勝]", cutmp->mateid, yourTotal, yourWin);
}


/* 遊戲結束原因 */
static char *
over_reason(over)
  int over;
{
  if (over == GAME_WIN)
    return "◆ 我方獲勝!";
  else if (over == GAME_FAIL)
    return "◆ 我方失敗!";
  else if (over == GAME_HQUIT)
    return "◆ 對方中斷!";
  else if (over == GAME_IQUIT)
    return "◆ 我方中斷!";
  else
    return "◆ 不明原因!(網路問題?)";
}

/* 遊戲結果 */
static void
show_result()
{
  /* 記錄戰積,分數多者為勝者 */
  play_add(ts[0].score > ts[1].score);

  move(2, 0);
  clrtobot();
  refresh();

  prints(" ┌─────────────────────────┐\n");
  prints(" │         俄羅斯方塊對戰結果 第%3d局               │\n", GameTimes);
  prints(" │         ==========================               │\n");
  prints(" ├─────┬─────────┬─────────┤\n");
  prints(" │ 用戶名稱 │ [%-13s]  │ (%-13s)  │\n", cuser.userid, cutmp->mateid);
  prints(" ├─────┼─────────┼─────────┤\n");
  prints(" │ 總消行數 │  %-13d   │  %-13d   │\n", ts[0].lines, ts[1].lines);
  prints(" ├─────┼─────────┼─────────┤\n");
  prints(" │ 遣送行數 │  %-13d   │  %-13d   │\n", ts[0].lines2, ts[1].lines2);
  prints(" ├─────┼─────────┼─────────┤\n");
  prints(" │ 本局得分 │  %-13d   │  %-13d   │\n", ts[0].score, ts[1].score);
  prints(" ├─────┼─────────┴─────────┤\n");
  prints(" │ 結束原因 │ %-37s│\n", over_reason(GameOver));
  prints(" └─────┴───────────────────┘\n");
  prints(" 按 <\033[1;32mEnter\033[m> 繼續遊戲, <\033[1;32m^C ^D\033[m> 退出遊戲. ");

  delay = GameOver;  /* 借用 delay 保存真正 over 原因,以免斷線了仍然要重開 */
  GameOver = GAME_WAIT;

  add_io(cfd, 60);
}


/* 幫助畫面 */
static void
show_help()
{
  move(4, 33);
  prints("  第 %4d 局  ", ++GameTimes);
  move(5, 33);
  outs("╭─────╮");
  move(6, 33);
  outs("│ 計 分 牌 │");
  move(7, 33);
  outs("╰─────╯");
  move(8, 26);
  outs("---------== 行數 ==---------");
  move(9, 26);
  outs(" 0                       0");
  move(10, 26);
  outs("---------== 送出 ==---------");
  move(11, 26);
  outs(" 0                       0");
  move(12, 26);
  outs("---------== 得分 ==---------");
  move(13, 26);
  outs(" 0                       0");
  move(14, 26);
  outs("---------== 級別 ==---------");
  move(15, 26);
  outs(" 0                       0");
  move(16, 26);
  outs("---------== 幫助 ==---------");
  move(17, 26);         /* 12+16 = 28 */
  outs("左移 a, ← | 右移 d, →");
  move(18, 26);
  outs("旋轉 h, ↑ | 下降 s, ↓");
  move(19, 26);
  outs("快降 x,' ' | 認輸 ^F");
  move(20, 26);
  outs("中斷 ^C ^D 會扣分的 ..");
  move(21, 26);
  outs("切換對方屏幕即時模式 ^G");
}


/* 可傳送給對方的指令 */
static int
send_cmd(cmd)
  int cmd;
{
  int i;
  char valid[23] =
  {
    'a', 'A', 'd', 'D', 'h', 'H', 'k', 'K', ' ', 'n', 'N', 'y', 'Y',
    'j', 'J', 's', 'S', 'z', 'Z', 'x', 'X', KEY_ENTER, Ctrl('F')
  };

  if (cmd == KEY_UP)
    cmd = 'h';
  else if (cmd == KEY_DOWN)
    cmd = 's';
  else if (cmd == KEY_LEFT)
    cmd = 'a';
  else if (cmd == KEY_RIGHT)
    cmd = 'd';

  for (i = 0; i < 23; i++)
  {
    if (valid[i] == cmd)
    {
      char c;

      c = cmd;
      if (send(cfd, &c, 1, 0) != 1)
      {
        GameOver = GAME_DISCONN;
        return 0;
      }
      return 1;
    }
  }
  return 0;
}

static int tetris_start();

/* 操作指令集 m為0時若返回0才送出指令給對方 */
static int
tetris_cmd(cmd, m)
  int cmd, m;
{
  int x, y, s, d;

  if (cmd >= '0' && cmd < '7')
  {
    if (!m)
      return -1;
    if (GameOver == GAME_START)
    {
      ts[1].newstyle = cmd - '0';
      GameOver = GAME_REFRESH;
      return -1;
    }
    else
    {
      ts[1].style = ts[1].newstyle;
      ts[1].xpos = 3;
      ts[1].ypos = 0;
      ts[1].direction = 0;
      ts[1].newstyle = cmd - '0';
      ts[1].last_d = -1;

      new_block(1);
      move_block(1);
      return 0;
    }
  }

  /* game wait to over */
  if (GameOver == GAME_WAIT)
  {
    if (delay >= 0)
    {
      if (cmd == KEY_ENTER)
      {
        move(b_lines - 1, 0);
        if (m)
          outs("對方要求再來一盤,您衕意嗎?[Y/n]");
        else
          outs("等待對方應答中,請稍候 ... ^c ^d 退出");
        delay = -1 - m;     /* 等的: -1   回答的: -2 */
      }
      return 0;
    }

    if ((delay - m) != -2)
      return 0;         /* 這時 key 就不算 */

    if (cmd == 'n' || cmd == 'N')
      delay = 1;
    else
      delay = 0;

    /* disconn 的不會執行到這步 */
    GameOver = GAME_OVER;
    return -1;
  }

  if (GameOver != GAME_ON)
    return -1;

  x = ts[m].xpos;
  y = ts[m].ypos;
  s = ts[m].style;
  d = ts[m].direction;

  switch (cmd)
  {
  case Ctrl('F'):
    GameOver = GAME_FAIL - m;
    return -1;
  case Ctrl('G'):
    gmode ^= 1;         /* 即時模式切換 */
    break;
  case 'A':
  case 'a':
  case KEY_LEFT:
    if (!crash(x - 1, y, s, d, m))
    {
      ts[m].xpos--;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'D':
  case 'd':
  case KEY_RIGHT:
    if (!crash(x + 1, y, s, d, m))
    {
      ts[m].xpos++;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'H':
  case 'h':
  case KEY_UP:
    if (!crash(x, y, s, (d + 3) % 4, m))
    {
      ts[m].direction = (d + 3) % 4;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'K':
  case 'k':
    if (!crash(x, y, s, (d + 1) % 4, m))
    {
      ts[m].direction = (d + 1) % 4;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'J':
  case 'j':
    if (!crash(x, y, s, (d + 2) % 4, m))
    {
      ts[m].direction = (d + 2) % 4;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'S':
  case 's':
  case 'Z':
  case 'z':
  case KEY_DOWN:
    if (m == 0)
      clocker = times(0);
    if (crash(x, y + 1, s, d, m))
    {
      if (gmode && m)
        move_block(m);
      arrived(m);
      break;
    }
    else
    {
      ts[m].ypos++;
      if (!gmode || !m)
        move_block(m);
    }
    break;
  case 'x':
  case 'X':
  case ' ':
    while (!crash(x, ts[m].ypos + 1, s, d, m))
      ts[m].ypos++;
    move_block(m);
    arrived(m);
    break;
  default:
    break;
  }
  return 0;
}

/* 每場比賽的初始化 */
static int
tetris_init()
{
  int i;

  /* Tetris Screen initilize ... */
  memset(ts, 0, sizeof(ts));
  for (i = 0; i < 20; i++)
  {
    ts[0].map[i][0] = 8;
    ts[1].map[i][0] = 8;
    ts[0].map[i][11] = 8;
    ts[1].map[i][11] = 8;
  }

  for (i = 0; i < 12; i++)
  {
    ts[0].map[20][i] = 8;
    ts[1].map[20][i] = 8;
  }

  GameOver = GAME_PREP;
  delay = 40;

  clear();
  vs_bar("俄羅斯方塊 - 雙人版");
  map_show(0);
  map_show(1);
  show_record();
  show_help();

  return 0;
}

/* 一個字符一個字符的送過去 */
static int
tetris_start()
{
  uschar data[80];
  int i, ch;

  tetris_init();

  clocker = times(0);

  srand(time(0));
  add_io(cfd, 0);

  for (;;)
  {
    if (GameOver == GAME_PREP)
    {
      ts[0].newstyle = rand() % 7;

      data[0] = ts[0].newstyle + '0';

      if (send(cfd, data, 1, 0) != 1)
      {
        GameOver = GAME_DISCONN;
        continue;
      }

      GameOver = GAME_START;

      outz("對方還在準備中 ... 請稍後 \033[5m ... \033[m");
      refresh();

      continue;
    }

    if (GameOver == GAME_REFRESH)   /* need new block */
    {
      ts[0].style = ts[0].newstyle;
      ts[0].xpos = 3;
      ts[0].ypos = 0;
      ts[0].direction = 0;
      ts[0].newstyle = rand() % 7;
      ts[0].last_d = -1;

      data[0] = ts[0].newstyle + '0';
      if (send(cfd, data, 1, 0) != 1)
      {
        GameOver = GAME_DISCONN;
        continue;
      }

      move_block(0);
      new_block(0);

      if (crash(ts[0].xpos, ts[0].ypos, ts[0].style, ts[0].direction, 0))   /* game over */
      {
        data[0] = Ctrl('F');    /* 相當於認輸 */
        if (send(cfd, data, 1, 0) != 1)
          GameOver = GAME_DISCONN;  /* 網絡斷線? */
        else
          GameOver = GAME_FAIL; /* 我方先敗! */
        continue;
      }
      GameOver = GAME_ON;   /* 遊戲繼續. */
    }

    if (GameOver == GAME_OVER)
      break;            /* 遊戲結束2 */

    if (GameOver > 0)       /* 遊戲結束1 */
      show_result();        /* 此步將 GameOver 設為 GAME_WAIT */

    ch = vkey();

    /* 斷線了還做其它乾嘛 */
    if (GameOver == GAME_WAIT && delay == GAME_DISCONN)
      break;

    if (ch == I_OTHERDATA)  /* 有數據進來 */
    {
      ch = recv(cfd, data, 1, 0);
      if (ch <= 0)
      {
        GameOver = GAME_DISCONN;
        continue;
      }

      for (i = 0; i < ch; i++)
      {
        if (data[i] == Ctrl('C') || data[i] == Ctrl('D'))
        {
          if (GameOver == GAME_WAIT)
            GameOver = GAME_OVER;
          else
            GameOver = GAME_HQUIT;
          break;
        }
        else if (tetris_cmd(data[i], 1) != 0)
          break;
      }

    }

    if (ch == Ctrl('C') || ch == Ctrl('D'))
    {
      /* ^c ^d 是肯定要傳出去的 */
      data[0] = ch;
      if (send(cfd, data, 1, 0) != 1)
      {
        GameOver = GAME_DISCONN;/* 網絡斷線? */
        continue;
      }

      if (GameOver == GAME_WAIT)
        GameOver = GAME_OVER;
      else
        GameOver = GAME_IQUIT;
      continue;
    }

    if (GameOver == GAME_START)
      continue;

    /* 送出所有能送出的鍵 */
    /* 送出失敗本地也無需執行了 */
    if (send_cmd(ch))
      tetris_cmd(ch, 0);

    if (GameOver != GAME_ON)
      continue;

    /* Delay timeout  auto down */
    if ((times(0) - clocker) > delay)
    {
      /* 通知對方, 相當於 's' */
      data[0] = 's';
      if (send(cfd, data, 1, 0) != 1)
      {
        GameOver = GAME_DISCONN;
        continue;
      }

      clocker = times(0);
      if (crash(ts[0].xpos, ts[0].ypos + 1, ts[0].style, ts[0].direction, 0))
        arrived(0);
      else
      {
        ts[0].ypos++;
        move_block(0);
      }
    }
  }
  return GAME_OVER;
}


/* Tetris2 Main */
static int
Tetris2(sock, later)
  int sock;
  int later;
{
  screenline sl[T_LINES];
  char c;

  cfd = sock;
  vs_save(sl);

  if (!later)
  {
    c = vans("您確定要和對方單挑俄羅斯方塊嗎(Y/N)?[Y] ");
    if (send(cfd, &c, 1, 0) != 1)
      return -2;

    vs_restore(sl);
  }
  else
  {
    outz("對方要求進入俄羅斯方塊對戰模式, 選擇中請稍候.....");
    refresh();
    if (recv(cfd, &c, 1, 0) != 1)
      return -2;

    vs_restore(sl);
  }

  if (c == 'n')
    return -1;

  GameTimes = 0;
  delay = 0;            /* 借用 delay 決定程序是否終止 */
  gmode = 0;            /* 即時傳輸 */

  do
  {
    tetris_start();
  }
  while (!delay);

  add_io(cfd, 60);

  vs_restore(sl);
  return 0;
}


#include <stdarg.h>

int
vaTetris(va_list pvar)
{
  int sock, later;

  sock = va_arg(pvar, int);
  later = va_arg(pvar, int);
  return Tetris2(sock, later);
}
#endif  /* HAVE_GAME */

□ 本文章由 itocitoc.Dorm-GD2.NCTU.edu.tw 發表
精華選讀←離開[主題上]主題下(k)上篇(j)下篇S/a搜尋 G串列 TAB精華 ↑↓捲 Pg/Space翻