天天看點

數組怎麼用getchar_用C語言實作井字棋

數組怎麼用getchar_用C語言實作井字棋

用C語言實作井字棋

數組怎麼用getchar_用C語言實作井字棋

         在學習完電磁炮的二連載之後,今天我将帶領大家學習用c語言編寫雙人井字棋的遊戲。

01

遊戲狀态的表示

首先,我認為表示方法(representation)是程式設計中應最先要考慮的事情。對于回合制遊戲,我們需要存儲一個回合中的遊戲狀态(game state)。

以下用一個結構體表示井字棋一個回合中的狀态,并加入函數作初始化:

typedef struct

{

int board[3][3]; // -1 = empty, 0 = O,1 = X

int turn;          // O first

} state;

void init(state* s)

{

    int i, j;

    for (j = 0; j < 3; j++)

    for (i = 0; i < 3; i++)

    s->board[j][i] = -1;

    s->turn = 0;

}

以上用二維數組存儲棋盤(board)是其中一種表示方式,另一種方式則是記錄每個回合下棋子的位置。我們采用前者是因為它較容易實作勝負判定。有些回合制遊戲可能使用備援的表示方式,以友善實作各種規則。

而使用結構體而不是直接用全局變量,可帶來一些優點,例如增強可讀性及内聚性。

02

顯示遊戲狀态

編寫遊戲時,我們通常希望先顯示遊戲狀态,之後才加入其他規則,因為這樣可以友善測試。

我希望用這樣的文本顯示遊戲狀态,當空置時寫上位置編号(1-9),以友善玩家輸入下棋位置:

 1 | 2 | 3

---+---+---

 4 | 5 | 6

---+---+---

 7 | 8 | 9

簡單直白地編寫代碼的話:

void display(const state* s)

{   int i, j;

for (j = 0; j < 3; j++) 

{

 for (i = 0; i < 3; i++)

{ switch (s->board[j][i]) 

   {

    case -1: printf(" %d ", j*3+i+1); break;

    case  0: printf(" O "); break;

    case  1: printf(" X "); break;

   }

            if (i < 2)

                printf("|");

            else

                printf("\n");

  }

        if (j < 2)

            printf("---+---+---\n");

        else

            printf("\n");

}

}

由于 display() 隻讀而不改變遊戲狀态,是以其參數類型為 const state*。

我們稍壓縮一下代碼:

void display(const state* s) 

{

    int i, j;

    for (j = 0; j < 3; printf(++j < 3 ? "---+-- -+---\n" : "\n"))

    for (i = 0; i < 3; putchar("||\n"[i++]))

    printf(" %c ", s->board[j][i] == -1? '1'+j * 3 + i : "OX"[s->board[j][i]]);

}

我們可以加入 main() 函數去顯示初始化的狀态:

int main()

 {

    state s;

    init(&s);

    display(&s);

}

03

實作下棋

然後,我們加入第一個遊戲規則,就是下棋:

int move(state* s, int i, int j) 

{

    if (s->board[j][i] != -1)

        return 0;

    s->board[j][i] = s->turn++ % 2;

        return 1;

}

函數内做了一個合法性判斷,如果該位置已有棋子,則傳回 0 表示失敗。成功的話,在偶數回合填入 0,表示 O;奇數回合填入 1,表示 X;然後都把回合加一。

更改 main() 簡單測試:

int main()

 {

    state s;

    init(&s);

    display(&s);

    move(&s, 1, 1);

    display(&s);

    move(&s, 0, 1);

    display(&s);

}

04

處理輸入

在每一回合中,提示目前玩家(O 或 X),并讓玩家輸入一個下棋位置(1-9),如果位置不合法,則重新輸入:

void human(state* s)

{

    char c;

    do

  {

     printf("%c: ", "OX"[s->turn % 2]);

     c = getchar();

     while (getchar() != '\n');

     printf("\n");

  }

    while (c '9' || !move(s,(c - '1') % 3,(c - '1') / 3));

}

在标準輸入中,要到Enter鍵才能處理輸入,是以這裡我們讀了第一個輸入字元後,就忽略其他字元直到讀到換行符。我們把表示位置的字元轉換成二維數組索引。

然後,就可以修改 main() 實作二人下棋的流程:

int main()

 {

    state s;

    init(&s);

    display(&s);

    while (s.turn < 9)

    {

        human(&s);

        display(&s);

    }

05

勝負判定

衆所周知,井字棋的勝利條件,是有三個棋子在橫線、直線或斜線連成一線。我們實作一個evaluate() 函數去評估棋局的狀态,如果 O 勝出則傳回 1,X 勝出則傳回 -1,不分勝負則傳回 0:

#define CHECK(j1, i1, j2, i2, j3, i3)

\ if (s->board[j1][i1] != -1 && s->board[j1][i1] == s->board[j2][i2] && s->board[j1][i1] == s->board[j3][i3]) \

return s->board[j1][i1] == 0 ? 1 : -1;

int evaluate(const state* s)

{

 int i;

 for (i = 0; i < 3; i++) 

  {

   CHECK(i, 0, i, 1, i, 2);    // horizontal

   CHECK(0, i, 1, i, 2, i);    // vertical

  }

   CHECK(0, 0, 1, 1, 2, 2);  // diagonal

   CHECK(0, 2, 1, 1, 2, 0);  // diagonal

   return 0;

}

上面的代碼使用了一個宏 CHECK() 去檢測三個位置是否都為相同的棋子,如是則直接傳回勝方。

最後,我們在 main() 中,待每次下棋及顯示狀态後, 判定是否出現勝方,如果到達第 9 個回合(回合從 0 開始),則判定是平局(draw):

int main()

 {

    state s;

    init(&s);

    display(&s);

    while (s.turn < 9)

  {

       human(&s);

       display(&s);

       switch (evaluate(&s))

   {

      case  1: printf("O win\n"); return 0;

      case -1: printf("X win\n"); return 0;

   }

  }

      printf("Draw\n");

}

06

總結

本篇實作了二人井字棋,它是一個簡單的回合制遊戲。我們先選擇了遊戲的狀态表示方式(state結構體及init()函數),然後把狀态以文本形式顯示(display()函數),加入每回合下棋規則(move()函數),以及人類玩家的輸入處理(human()函數),并作勝負判定(evaluate()函數),最後在main()裡則實作了按回合的循環及輸出勝負結果。

雖然這個遊戲本身以及 60 行的示例代碼都很簡單,但這個架構可以用于實作其他(更複雜的)回合制遊戲。實時遊戲(如動作遊戲)的主要差別,其實也隻在于把輸入部分做成非阻塞的函數,而該循環則稱為遊戲循環(game loop)。

看完這篇推文,大家可以快去自己動手實踐一下啦!

關注獵狐有驚喜!

數組怎麼用getchar_用C語言實作井字棋
數組怎麼用getchar_用C語言實作井字棋

文案|丁一如

排版|王金婷