「炸金花」(三張牌)是流行的撲克牌遊戲,其核心邏輯在於兩副三張牌之間的大小比較。本文將詳細解析如何用 PHP 實現完整的牌型判斷與大小比較算法,涵蓋單張、對子、順子、金花、順金、豹子(筒子)六大牌型,並採用「分值編碼法」將複雜的比較規則轉化為單一整數比較,邏輯清晰、效能極高。
炸金花牌型由大到小排序:
A-2-3 > A-K-Q > K-Q-J(A 可作最大也可作最小)。將每種牌型映射為一個 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 |
設計優勢:
<?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>";
?>
將點數映射為 2~14,並以兩位數字串儲存(str_pad),確保 10 不會被誤判為 1 + 0。降序排列(rsort)使最大牌在前,便於後續拼接比較。
預設降序下對子會落在後兩位(如 [14, 07, 07]),但比較對子時應優先比較對子點數。透過交換位置使對子點數佔據高位([07, 07, 14]),配合整數比較即可自動實現「先比對子大小,再比單張」的規則。
炸金花規則中 A-2-3 為最小順子(次於 A-K-Q)。編碼後為 140302,若按數值大小會錯誤判定為最大。因此在 isStraight() 中額外檢查此字串,確保邏輯正確。
判斷順序必須從高到低:豹子 → 順金 → 金花 → 順子 → 對子。因為順金同時滿足金花與順子條件,若先判斷金花會錯誤歸類。使用 elseif 鏈確保只匹配最高牌型。
$cards 擴充為多副混洗,修改 getCard() 邏輯即可。compareCards 改為接受陣列,返回排序後的索引順序。array_search 在循環中較慢,可預建立 figureToValue 映射陣列(['2'=>2, ..., 'A'=>14])直接查找。本文提出的「分值編碼法」將炸金花複雜的多層比較規則,優雅地轉化為單一整數的大小比較。核心類別 PlayCards 結構清晰、邏輯完整、易於擴展,可直接用於遊戲原型開發、教學示範或後端邏輯驗證。理解此設計模式,對於撲克類、麻將類遊戲的牌型判斷系統均有極高的參考價值。