首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

劣質代碼評析:《C語言入門經典》附錄B 21點遊戲(七)—— 勝負判斷邏輯重構

     編輯:關於C語言

原始 findWinner() 的混亂邏輯


// 原書片段 (簡化)
void findWinner(int total[]) {
    if (total[DEALER] == 21) { printf("The house wins.\n"); return; }
    if (total[DEALER] > 21) {
        if (total[PLAYER] > 21) { printf("Nobody wins.\n"); return; }
    }
    if (total[DEALER] < 21) {
        if (total[PLAYER] > 21) { printf("The house wins.\n"); return; }
        if (total[DEALER] >= total[PLAYER]) { printf("The house wins.\n"); return; }
    }
    printf("You win!\n");
}

缺陷分析:

  1. 條件重疊與遺漏:DEALER > 21 且 PLAYER <= 21 時未顯式處理 (落入最後 You win),雖結果正確但邏輯隱晦
  2. 魔術索引:total[DEALER], total[PLAYER] 依賴全域常數定義
  3. 輸出耦合:直接 printf,無法複用於 GUI/網路版
  4. 平局邏輯模糊:雙方爆牌算平局,雙方 21 點?莊家 21 直接贏?規則未明確

重構目標:規則明確、可測試、可擴展

1. 定義遊戲結果枚舉


typedef enum {
    PLAYER_WIN,
    DEALER_WIN,
    PUSH  // 平局
} GAME_RESULT;

2. 純邏輯判斷函數 (無副作用)


GAME_RESULT judge_winner(int dealer_score, int player_score) {
    // 1. 莊家黑傑克 (21點) - 規則通常莊家黑傑克直接贏,除非玩家也有
    if (dealer_score == 21) {
        return (player_score == 21) ? PUSH : DEALER_WIN;
    }

    // 2. 莊家爆牌
    if (dealer_score > 21) {
        if (player_score > 21) return PUSH;      // 雙爆平局
        return PLAYER_WIN;                       // 莊家爆、玩家未爆 -> 玩家贏
    }

    // 3. 莊家未爆 (< 21)
    if (player_score > 21) return DEALER_WIN;    // 玩家爆 -> 莊家贏
    if (dealer_score > player_score) return DEALER_WIN; // 莊家點大 -> 莊家贏
    if (dealer_score < player_score) return PLAYER_WIN; // 玩家點大 -> 玩家贏
    return PUSH;                                 // 點數相同 -> 平局
}

優勢:

  • 所有分支互斥且窮盡,邏輯一目瞭然
  • 無 I/O,極易單元測試 (如 assert(judge_winner(22, 19) == PLAYER_WIN))
  • 規則修改只需改此函數 (如改為雙方 21 平局、或莊家軟 17 規則)

3. 顯示層分離


void declare_winner(GAME_RESULT result) {
    switch(result) {
        case PLAYER_WIN:  puts("你贏了!\a"); break;  // \a 蜂鳴器
        case DEALER_WIN:  puts("你輸了。"); break;
        case PUSH:        puts("平局。"); break;
    }
}

主流程調用:


GAME_RESULT result = judge_winner(dealer.score, player.score);
declare_winner(result);

4. 進階:支援 Blackjack (天生 21) 特殊賠率

若需區分「天生 21」(兩張牌 A+10點) 與「多張牌 21」,擴充 GAMER 結構體:


typedef struct {
    int low, high, score;
    int num_cards;      // 手牌數
    bool is_blackjack;  // 是否天生 21
} GAMER;

判斷邏輯相應調整:


if (dealer.is_blackjack) {
    return player.is_blackjack ? PUSH : DEALER_WIN;
}
if (player.is_blackjack) return PLAYER_WIN; // 玩家黑傑克通常賠 3:2

5. 原始代碼與重構版對比測試表

莊家點數玩家點數原始輸出重構結果備註
2120The house winsDEALER_WIN莊家 21 直接贏
2121The house winsPUSH重構修正:雙方 21 平局
2220You winPLAYER_WIN莊家爆牌
2223Nobody winsPUSH雙爆平局
1922The house winsDEALER_WIN玩家爆牌
1920You winPLAYER_WIN玩家點大
2019The house winsDEALER_WIN莊家點大 (含平局莊家贏規則)
1818The house winsPUSH重構修正:點數相同平局

關鍵修正:原始代碼在「莊家 < 21 且玩家 ≤ 莊家」時一律莊家贏,包含平局。標準 21 點規則通常點數相同為平局 (Push),重構版修正此點。

6. 單元測試範例


#include 

void test_judge_winner() {
    assert(judge_winner(21, 20) == DEALER_WIN);
    assert(judge_winner(21, 21) == PUSH);
    assert(judge_winner(22, 19) == PLAYER_WIN);
    assert(judge_winner(22, 23) == PUSH);
    assert(judge_winner(19, 22) == DEALER_WIN);
    assert(judge_winner(19, 20) == PLAYER_WIN);
    assert(judge_winner(20, 19) == DEALER_WIN);
    assert(judge_winner(18, 18) == PUSH);
    assert(judge_winner(17, 21) == PLAYER_WIN); // 玩家 21
    printf("All judge_winner tests passed.\n");
}

編譯時加 -DNDEBUG 可移除斷言,零成本生產環境部署。

總結

勝負判斷是遊戲核心規則,重構關鍵在於:

  1. 枚舉化結果:替代散落的 printf,語意明確
  2. 純函數化邏輯:judge_winner() 無副作用,輸入輸出確定,可測試
  3. 規則表驅動:條件分支對應規則書,易審計、易修改
  4. 顯示分離:declare_winner() 只負責呈現,支援多介面

這樣的設計,不僅修復了原始代碼的邏輯漏洞 (平局判斷錯誤),更為未來擴展 (側注、保險、分牌、多副牌、AI 對戰) 奠定堅實基礎。下一篇將呈現完整最終版代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved