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

PHP 實作撲克牌「炸金花」三張牌大小比較邏輯完整解析

     編輯:PHP 綜合知識

前言

「炸金花」(三張牌)是流行的撲克牌遊戲,其核心邏輯在於兩副三張牌之間的大小比較。本文將詳細解析如何用 PHP 實現完整的牌型判斷與大小比較算法,涵蓋單張、對子、順子、金花、順金、豹子(筒子)六大牌型,並採用「分值編碼法」將複雜的比較規則轉化為單一整數比較,邏輯清晰、效能極高。

一、遊戲規則與牌型層級

炸金花牌型由大到小排序:

  1. 豹子(筒子/三條):三張點數相同,如 AAA、999。
  2. 順金(同花順):同花色且點數連續,如 ♠A♠K♠Q。
  3. 金花(同花):同花色,不要求連續。
  4. 順子(斜蛇):點數連續,花色不限。特殊規則:A-2-3 > A-K-Q > K-Q-J(A 可作最大也可作最小)。
  5. 對子:兩張點數相同。
  6. 單張(散牌):以上皆非,比較最大單張,次之次大,再次之最小。

二、核心設計:分值編碼策略

將每種牌型映射為一個 7 位數整數,高位代表牌型等級,低位代表牌面具體大小,直接用整數比較即可得出勝負。

編碼規則

牌型 基礎分值(6位) 牌型加權(×100,000) 最終分值範圍
單張 三張點數降序拼成 6 位數(如 A-K-8 → 141308) +0 141308 ~ 020202
對子 對子點數置前兩位,單張置後兩位(如 7-7-A → 070714) +10×100,000 = 1,000,000 1,020,202 ~ 1,141,414
順子 三張點數降序拼成 6 位數(A-2-3 → 140302) +20×100,000 = 2,000,000 2,020,304 ~ 2,141,312
金花 三張點數降序拼成 6 位數 +30×100,000 = 3,000,000 3,020,202 ~ 3,141,311
順金 三張點數降序拼成 6 位數 +50×100,000 = 5,000,000 5,020,304 ~ 5,141,312
豹子 三張相同點數拼成 6 位數(如 666 → 060606) +60×100,000 = 6,000,000 6,020,202 ~ 6,141,414

設計優勢:

  • 牌型層級由十萬位決定,絕不發生「順子贏過金花」邏輯錯誤。
  • 同牌型內部大小由低 6 位自然比較,符合直覺。
  • 單一整數比較,無需複雜的 if-else 巢狀結構。

三、完整 PHP 類別實現

<?php
class PlayCards {
    // 花色順序:黑桃 > 紅心 > 方塊 > 梅花(僅作顯示用,不影響比大小)
    public $suits = ['Spade', 'Heart', 'Diamond', 'Club'];
    // 點數順序:2 3 4 5 6 7 8 9 10 J Q K A
    public $figures = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
    private $cards = [];

    public function __construct() {
        $this->resetDeck();
    }

    // 重置並洗牌
    public function resetDeck() {
        $cards = [];
        foreach ($this->suits as $suit) {
            foreach ($this->figures as $figure) {
                $cards[] = [$suit, $figure];
            }
        }
        $this->cards = $cards;
    }

    // 發 3 張牌
    public function getCard(): array {
        shuffle($this->cards);
        return [array_pop($this->cards), array_pop($this->cards), array_pop($this->cards)];
    }

    // 比較兩手牌:返回 1(贏) / -1(輸) / 0(平)
    public function compareCards(array $card1, array $card2): int {
        $score1 = $this->calculateScore($card1);
        $score2 = $this->calculateScore($card2);
        return ($score1 > $score2) ? 1 : (($score1 < $score2) ? -1 : 0);
    }

    // 核心:計算單手牌分值
    private function calculateScore(array $card): int {
        $suits = [];
        $figures = [];

        // 提取花色與點數索引(2→2, ..., A→14)
        foreach ($card as $c) {
            $suits[] = $c[0];
            $figures[] = array_search($c[1], $this->figures) + 2;
        }

        // 補齊前導零:每張牌佔 2 位數
        $figures = array_map(fn($v) => str_pad($v, 2, '0', STR_PAD_LEFT), $figures);
        rsort($figures); // 降序排列

        // 【關鍵】對子特殊處理:將對子點數移至前兩位
        // 例:[14, 07, 07] (A,7,7) → [07, 07, 14]
        if ($figures[1] === $figures[2]) {
            $temp = $figures[0];
            $figures[0] = $figures[2];
            $figures[2] = $temp;
        }

        // 基礎分值:拼成 6 位數字串再轉整數
        $score = (int)implode('', $figures);

        // 判斷牌型並加權(順序不可亂:豹子 > 順金 > 金花 > 順子 > 對子)
        $isFlush = ($suits[0] === $suits[1] && $suits[1] === $suits[2]);
        $isStraight = $this->isStraight($figures);
        $isThree = ($figures[0] === $figures[1] && $figures[1] === $figures[2]);
        $isPair = ($figures[0] === $figures[1] && $figures[1] !== $figures[2]);

        if ($isThree) {
            $score += 60 * 100000; // 豹子
        } elseif ($isFlush && $isStraight) {
            $score += 50 * 100000; // 順金
        } elseif ($isFlush) {
            $score += 30 * 100000; // 金花
        } elseif ($isStraight) {
            $score += 20 * 100000; // 順子
        } elseif ($isPair) {
            $score += 10 * 100000; // 對子
        }
        // 散牌不加權

        return $score;
    }

    // 判斷是否為順子(含 A-2-3 特例)
    private function isStraight(array $figures): bool {
        // 正常順子:最大 - 中間 = 1 且 中間 - 最小 = 1
        if ($figures[0] - $figures[1] == 1 && $figures[1] - $figures[2] == 1) {
            return true;
        }
        // 特例:A-2-3 (編碼為 14, 03, 02)
        return implode('', $figures) === '140302';
    }

    // 格式化輸出牌面
    public static function formatCard(array $card): string {
        $str = '(';
        foreach ($card as $c) $str .= $c[0].$c[1].',';
        return rtrim($str, ',').')';
    }
}

// ========== 測試範例 ==========
$game = new PlayCards();
$hand1 = $game->getCard();
$hand2 = $game->getCard();

echo "玩家1: " . PlayCards::formatCard($hand1) . "<br>";
echo "玩家2: " . PlayCards::formatCard($hand2) . "<br>";

$result = $game->compareCards($hand1, $hand2);
$msg = $result === 1 ? '玩家1 勝' : ($result === -1 ? '玩家2 勝' : '平局');
echo "結果: $msg<br>";
?>

四、關鍵邏輯深度解析

1. 點數編碼與排序

將點數映射為 2~14,並以兩位數字串儲存(str_pad),確保 10 不會被誤判為 1 + 0。降序排列(rsort)使最大牌在前,便於後續拼接比較。

2. 對子位置調整

預設降序下對子會落在後兩位(如 [14, 07, 07]),但比較對子時應優先比較對子點數。透過交換位置使對子點數佔據高位([07, 07, 14]),配合整數比較即可自動實現「先比對子大小,再比單張」的規則。

3. 順子特例 A-2-3

炸金花規則中 A-2-3 為最小順子(次於 A-K-Q)。編碼後為 140302,若按數值大小會錯誤判定為最大。因此在 isStraight() 中額外檢查此字串,確保邏輯正確。

4. 牌型加權順序

判斷順序必須從高到低:豹子 → 順金 → 金花 → 順子 → 對子。因為順金同時滿足金花與順子條件,若先判斷金花會錯誤歸類。使用 elseif 鏈確保只匹配最高牌型。

五、擴展與優化建議

  • 多副牌支援:將 $cards 擴充為多副混洗,修改 getCard() 邏輯即可。
  • 比較多家:將 compareCards 改為接受陣列,返回排序後的索引順序。
  • 效能優化:array_search 在循環中較慢,可預建立 figureToValue 映射陣列(['2'=>2, ..., 'A'=>14])直接查找。
  • 單元測試:針對各邊界牌型(A-2-3、A-K-Q、同點數對子踢腳牌大小等)編寫測試用例。

六、總結

本文提出的「分值編碼法」將炸金花複雜的多層比較規則,優雅地轉化為單一整數的大小比較。核心類別 PlayCards 結構清晰、邏輯完整、易於擴展,可直接用於遊戲原型開發、教學示範或後端邏輯驗證。理解此設計模式,對於撲克類、麻將類遊戲的牌型判斷系統均有極高的參考價值。

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