Lab 12-2: Text interface Poker game:
        center deck, the game and the player

 
實習目標 練習設計及規劃物件系統
練習 bottom-up 的物件實作方式
 
步驟一 上一個實習中, 我們還沒有實作到牌桌上看到的一切, 包括:

  • 哪些牌是合法可以出的,

  • 該如何讓四個 Player 輪流出牌,

  • 手上那麼多張牌該自動出哪一張,

  • 或是該問操作者到底要出哪一張,

  • 遊戲結束時該怎樣算分等等
這些都要在這個實習中完成。
步驟二 CenterDeck 類別

首先我們來實作牌桌上四疊不同花色的牌:

  • 這個類別內部當然應該要能夠存放 4 疊不同花色的牌, 每一疊最多可以有 13 張順序接起來而且花色相同的牌 (或許你也可以嘗試設計四個 CenterDeck 的物件, 每一個物件裡儲存一種花色的 13 張牌)
  • 應該要有一個界面可以將 Player 所出的牌接上來, 不過這個界面應該要能夠自動判斷是否所出的牌是可以接上去的, 例如: bool add(Card card), 如果成功會傳回 true, 失敗會傳回 false
  • 為了進行測試, 我們替這個類別加入 display(ostream &) 的界面
    int i;
    CenterDeck centerDeck;
    centerDeck.reset();
    cout << "Before the test\n";
    centerDeck.display(cout);

    // 測試按照順序出牌
    for (i=7; i>1; i--)
    {
        centerDeck.add(Card(Spade, i));
        centerDeck.add(Card(Heart, i));
        centerDeck.add(Card(Diamond, i));
        centerDeck.add(Card(Club, i));
    }

    // 故意測試不按照順序出牌
    if (centerDeck.add(Card(Spade, 11)))  
        cout << "SJ added successfully\n";
    else
        cout << "SJ fail to add to center deck\n";

    for (i=8; i<=12; i++)
    {
        centerDeck.add(Card(Spade, i));
        centerDeck.add(Card(Heart, i));
        centerDeck.add(Card(Diamond, i));
        centerDeck.add(Card(Club, i));
    }
    centerDeck.add(Card(Heart, 1));
    centerDeck.add(Card(Club, 1));
    centerDeck.add(Card(Spade, 13));
    centerDeck.add(Card(Diamond, 13));

    cout << "After adding 48 cards\n";
    centerDeck.display(cout);
範例執行程式

測試結果如下:

    Before the test
    Center Deck #0: -- -- -- -- -- -- -- -- -- -- -- -- --
    Center Deck #1: -- -- -- -- -- -- -- -- -- -- -- -- --
    Center Deck #2: -- -- -- -- -- -- -- -- -- -- -- -- --
    Center Deck #3: -- -- -- -- -- -- -- -- -- -- -- -- --
    SJ fail to add to center deck
    After adding all 52 cards
    Center Deck #0: -- S2 S3 S4 S5 S6 S7 S8 S9 ST SJ SQ SK
    Center Deck #1: H1 H2 H3 H4 H5 H6 H7 H8 H9 HT HJ HQ --
    Center Deck #2: -- D2 D3 D4 D5 D6 D7 D8 D9 DT DJ DQ DK
    Center Deck #3: C1 C2 C3 C4 C5 C6 C7 C8 C9 CT CJ CQ --
步驟三 接下來請替 CenterDeck 增加一個很重要的界面函式,
    	int queryNextMoves(Card nextMoves[]);
這個函式可以判斷接下來 CenterDeck 究竟可以接受哪些牌。 每一個 Player 基本上只知道自己手上的牌, 必須要呼叫 CenterDeck 的這個界面函式來知道下一步可以出哪些牌, 每次呼叫時需要傳進一個 Card nextMoves[8] 的陣列, 因為最多可能有八種可以出的牌, queryNextMoves() 則會傳回可以出的牌的數目, Player 再根據這些資訊以及自己手上的牌來決定自己該出哪一張牌。

你製作完 queryNextMoves() 函式後, 可以用下面的程式碼來測試

    CenterDeck centerDeck;
    Card nextMoves[8];

    centerDeck.add(Card(Spade, 7));
    centerDeck.add(Card(Spade, 8));
    centerDeck.add(Card(Spade, 9));
    centerDeck.add(Card(Heart, 7));
    centerDeck.add(Card(Heart, 6));
    centerDeck.add(Card(Club, 7));
    centerDeck.display(cout);

    int nMoves = centerDeck.queryNextMoves(nextMoves);
    int i;
    cout << "Possible next moves:\n";
    for (i=0; i<nMoves; i++)
        nextMoves[i].display(cout);
    cout << endl;
範例執行程式

測試結果如下:

    Center Deck #0: -- -- -- -- -- -- S7 S8 S9 -- -- -- --
    Center Deck #1: -- -- -- -- -- H6 H7 -- -- -- -- -- --
    Center Deck #2: -- -- -- -- -- -- -- -- -- -- -- -- --
    Center Deck #3: -- -- -- -- -- -- C7 -- -- -- -- -- --
    Possible next moves:
    S6 ST H5 H8 D7 C6 C8
步驟四 Player::nextMove()

接下來我們要來撰寫 Player 出牌的動作了, 每次 Game 物件送一個 nextMove 的訊息過來時, Player 物件會執行這個成員函式來回應, 這個函式應該要先去詢問 CenterDeck 物件到底可以出些什麼牌, 然後根據 Player 自己手上有哪些牌來判斷是否有牌可以出, 如果有很多張牌可以出的話, 選擇到底要出哪一張牌, 如果沒有牌可以出的話, 也需要選擇一張牌來蓋牌。

上面的這些動作你可以選擇最簡單的方式來實作, 例如說如果有多張牌可以出的話, Player 就出第一張牌, 如果需要蓋牌的話, Player 也蓋手上的第一張牌, 我們可以先把程式架構起來以後再回頭來修改這些部份, 讓 Player 變得比較聰明。

哪些是程式的基本架構呢? 你再仔細一點來看這個 nextMove() 成員函式, 你會發現它需要和其它的物件有一些互動, 比方說詢問 CenterDeck 有哪些牌是可以出的, 比方說出牌的動作, 比方說蓋牌的動作應該也需要和桌面上你專屬的牌堆互動, 你需要思考 Player 這個物件是否需要和相關的物件有靜態的關係, 如果要有的話該在什麼時候建立, 該如何建立? 或者不需要有靜態的關係, 你可以在 nextMove() 的參數裡動態地傳進來, 比方說 nextMove(CenterDeck *centerDeck)

接下來又到了設計測試的部份了, 簡單起見, 我們可以寫一個迴圈輪流讓四個 Player 出牌, 而且你知道只要每個人出十三次遊戲就結束了, 例如:

    for (i=0; i<13; i++)
        for (j=0; j<4; j++)
        {
            player[j].nextMove(centerDeck);
            centerDeck.display(cout);
        }
如此你就可以看到四個人自動把牌通通出完了, 不過你可能也希望在過程中把每一個 Player 手上所有的牌都印出來, 把每一個 Player 所蓋的牌都印出來, 這樣子可以檢查程式是不是正確地執行。
步驟五 上個步驟中你也許已經實作了給每個 Player 蓋牌的那四個容器, 如果沒有的話, 在這個步驟中你可以這樣子修改你的程式。

接下來還有不少事情需要一一處理的, 你可以在作業裡慢慢完成, 例如:

  1. 你可以做一個 Player 的抽象基礎類別, 繼承它作出不同的 Player, 這些 Player 可以有不同的出牌方式
  2. 還可以繼承 Player 抽象基礎類別, 做一個 RealPlayer 的類別, 這個類別的 nextMove() 函式很好寫, 基本上就直接詢問執行這個程式的人希望出什麼牌就好了
  3. 為了配合上面 1, 2 兩項的繼承架構, 你也應該使用 virtual function 和 polymorphic pointer, 例如:
        Player *player[4];
        ...
        player[0] = new RealPlayer(...);
        player[1] = new DumbPlayer(...);
        ...
        for (i=0; i<13; i++)
            for (j=0; j<4; j++)
            {
                player[j]->nextMove(centerDeck);
                centerDeck.display(cout);
            }
  4. 算分數的程式, 詢問有沒有 Spade 7 的程式 ...
  5. 更高級一點的話你也可以有透過網路的 NetPlayer, 它的 nextMove() 基本上會透過網路和另外一個程式溝通, 不過在這之前你很可能需要寫程式來同步兩個程式的顯示...有興趣的話可以在暑假時設計這樣的功能, 你需要的工具也許我可以提供給你
步驟六 請將所完成的 project (去掉 debug/ 資料匣下的所有內容) 壓縮起來儲存在 cyber 上你的帳號內, 後面的實習課程可能需要使用這裡所完成的程式

C++ 物件導向程式設計課程 首頁

製作日期: 04/27/2004 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程系 Lagoon