1000行代码实现的斗地主算法

斗地主的算法类来自GitHub某位网友分享,波波碰到了顺便记录一下。

这个斗地主的算法类库主要实现的功能是构造牌、发牌、检测牌面、出牌等。具体不多介绍。源码如下:

  1. <?php
  2. namespace App\Game\Core;
  3. /**
  4.  * 斗地主 poker 算法逻辑
  5.  * @author:jiagnxinyu
  6.  */
  7. class DdzPoker
  8. {
  9.     //花色类型
  10.     const COLOR_TYPE_HEITAO = 0;//黑桃
  11.     const COLOR_TYPE_HONGTAO = 1;//红桃
  12.     const COLOR_TYPE_MEIHUA = 2;//梅花
  13.     const COLOR_TYPE_FANGKUAI = 3;//方块
  14.     const COLOR_TYPE_XIAOWANG = 4;//小王
  15.     const COLOR_TYPE_DAWANG = 5;//大王
  16.     //牌显示出来的值
  17.     const CARD_SAN = '3'; //牌值3
  18.     const CARD_SI = '4'; //牌值4
  19.     const CARD_WU = '5'; //牌值5
  20.     const CARD_LIU = '6'; //牌值6
  21.     const CARD_QI = '7'; //牌值7
  22.     const CARD_BA = '8'; //牌值8
  23.     const CARD_JIU = '9'; //牌值9
  24.     const CARD_SHI = '10'; //牌值10
  25.     const CARD_J = 'J'; //牌值J
  26.     const CARD_Q = 'Q'; //牌值Q
  27.     const CARD_K = 'K'; //牌值K
  28.     const CARD_A = 'A'; //牌值A
  29.     const CARD_ER = '2'; //牌值2
  30.     const CARD_XIAOWANG = 'SJ'; //牌值小王
  31.     const CARD_DAWANG = 'BJ'; //牌值大王
  32.     //牌型
  33.     const CARD_TYPE_DAN = 1; //单张
  34.     const CARD_TYPE_DUI = 2; //对子
  35.     const CARD_TYPE_SAN = 3; //三张
  36.     const CARD_TYPE_SANDAIYI = 4; //三代一
  37.     const CARD_TYPE_SANDAIER = 5; //三代二
  38.     const CARD_TYPE_SHUNZI = 6; //顺子
  39.     const CARD_TYPE_LIANDUI = 7; //连对
  40.     const CARD_TYPE_FEIJIBUDAI = 8; //飞机不带
  41.     const CARD_TYPE_FEIJIDAIDAN = 9; //飞机带单
  42.     const CARD_TYPE_FEIJIDAISHUANG = 10; //飞机带双
  43.     const CARD_TYPE_SIDAIYI = 11; //四带一, 指的是四带一对
  44.     const CARD_TYPE_SIDAIER = 12; //四带二, 指的是四带两队
  45.     const CARD_TYPE_ZHADAN = 13; //炸弹
  46.     const CARD_TYPE_HUOJIAN = 14; //火箭
  47.     /**
  48.      * 构造花色值
  49.      */
  50.     public static $card_color = array(
  51.         self::COLOR_TYPE_HEITAO => '黑桃',
  52.         self::COLOR_TYPE_HONGTAO => '红桃',
  53.         self::COLOR_TYPE_MEIHUA => '梅花',
  54.         self::COLOR_TYPE_FANGKUAI => '方块',
  55.         self::COLOR_TYPE_XIAOWANG => '小王',
  56.         self::COLOR_TYPE_DAWANG => '大王',
  57.     );
  58.     /**
  59.      * 构造扑克牌值列表(54张牌,采用16进制的模式, 每16位一种花色牌型,花色不一样, 大小王,固定值,这样设计,一个数字,既可以表示出牌值, 也能表示出花色)
  60.      * @var array
  61.      */
  62.     public static $card_value_list = array(
  63.         1 => self::CARD_SAN, 2 => self::CARD_SI, 3 => self::CARD_WU, 4 => self::CARD_LIU, 5 => self::CARD_QI, 6 => self::CARD_BA, 7 => self::CARD_JIU, 8 => self::CARD_SHI, 9 => self::CARD_J, 10 => self::CARD_Q, 11 => self::CARD_K, 12 => self::CARD_A, 13 => self::CARD_ER,
  64.         17 => self::CARD_SAN, 18 => self::CARD_SI, 19 => self::CARD_WU, 20 => self::CARD_LIU, 21 => self::CARD_QI, 22 => self::CARD_BA, 23 => self::CARD_JIU, 24 => self::CARD_SHI, 25 => self::CARD_J, 26 => self::CARD_Q, 27 => self::CARD_K, 28 => self::CARD_A, 29 => self::CARD_ER,
  65.         33 => self::CARD_SAN, 34 => self::CARD_SI, 35 => self::CARD_WU, 36 => self::CARD_LIU, 37 => self::CARD_QI, 38 => self::CARD_BA, 39 => self::CARD_JIU, 40 => self::CARD_SHI, 41 => self::CARD_J, 42 => self::CARD_Q, 43 => self::CARD_K, 44 => self::CARD_A, 45 => self::CARD_ER,
  66.         49 => self::CARD_SAN, 50 => self::CARD_SI, 51 => self::CARD_WU, 52 => self::CARD_LIU, 53 => self::CARD_QI, 54 => self::CARD_BA, 55 => self::CARD_JIU, 56 => self::CARD_SHI, 57 => self::CARD_J, 58 => self::CARD_Q, 59 => self::CARD_K, 60 => self::CARD_A, 61 => self::CARD_ER,
  67.         78 => self::CARD_XIAOWANG,
  68.         79 => self::CARD_DAWANG,
  69.     );
  70.     /**
  71.      * 构造牌型值
  72.      * @var array
  73.      */
  74.     public static $card_type = array(
  75.         self::CARD_TYPE_DAN => '单张',
  76.         self::CARD_TYPE_DUI => '对子',
  77.         self::CARD_TYPE_SAN => '三张',
  78.         self::CARD_TYPE_SANDAIYI => '三带一',
  79.         self::CARD_TYPE_SANDAIER => '三带二',
  80.         self::CARD_TYPE_SHUNZI => '顺子',
  81.         self::CARD_TYPE_LIANDUI => '连对',
  82.         self::CARD_TYPE_FEIJIBUDAI => '飞机不带',
  83.         self::CARD_TYPE_FEIJIDAIDAN => '飞机带单',
  84.         self::CARD_TYPE_FEIJIDAISHUANG => '飞机带双',
  85.         self::CARD_TYPE_SIDAIYI => '四带一对',
  86.         self::CARD_TYPE_SIDAIER => '四带二对',
  87.         self::CARD_TYPE_ZHADAN => '炸弹',
  88.         self::CARD_TYPE_HUOJIAN => '火箭',
  89.     );
  90.     /**
  91.      * 洗好的牌
  92.      */
  93.     public $washed_cards;
  94.     /*
  95.      * 洗牌
  96.      */
  97.     public function washCards()
  98.     {
  99.         $cards = array_keys(self::$card_value_list);
  100.         shuffle($cards);
  101.         $show_cards = $this->crateCard($cards);
  102.         $this->washed_cards = array('cards' => $cards, 'show_cards' => $show_cards);
  103.     }
  104.     /*
  105.      * 发牌
  106.      */
  107.     public function dealCards()
  108.     {
  109.         $cards = $this->washed_cards['cards'];
  110.         $user_card1 = $user_card2 = $user_card3 = $hand = array();
  111.         //每人发17张牌
  112.         $chuank = array_chunk($cards, 51);
  113.         $hand = $chuank[1];
  114.         $cards = $chuank[0];
  115.         $cnt = count($cards);
  116.         for ($i = 0; $i < $cnt$i += 3) {
  117.             $user_card1[] = $cards[$i];
  118.             $user_card2[] = $cards[$i + 1];
  119.             $user_card3[] = $cards[$i + 2];
  120.         }
  121.         $user_card1 = $this->_sortCardByGrade($user_card1);
  122.         $user_card2 = $this->_sortCardByGrade($user_card2);
  123.         $user_card3 = $this->_sortCardByGrade($user_card3);
  124.         $card = array('user1' => $user_card1, 'user2' => $user_card2, 'user3' => $user_card3, 'hand' => $hand);
  125.         $show_card = array('user1' => $this->crateCard($user_card1), 'user2' => $this->crateCard($user_card2), 'user3' => $this->crateCard($user_card3), 'hand' => $this->crateCard($hand));
  126.         return array('card' => $card, 'show_card' => $show_card);
  127.     }
  128.     /**
  129.      * 根据牌值构建牌
  130.      * @param $card
  131.      * @return array
  132.      */
  133.     public function crateCard($card)
  134.     {
  135.         $data = array();
  136.         foreach ($card as $v) {
  137.             if ($v == 78) {
  138.                 $color_type = self::COLOR_TYPE_XIAOWANG;
  139.             } elseif ($v == 79) {
  140.                 $color_type = self::COLOR_TYPE_DAWANG;
  141.             } else {
  142.                 $color_type = intval($v / 16);
  143.             }
  144.             $color = self::$card_color[$color_type];
  145.             $data[$v] = $color . '_' . self::$card_value_list[$v];
  146.         }
  147.         return $data;
  148.     }
  149.     /**
  150.      * 判断是否为单张牌
  151.      * @param $arr_card
  152.      * @return bool
  153.      */
  154.     public function isDan($arr_card)
  155.     {
  156.         if (count($arr_card) == 1) {
  157.             return true;
  158.         } else {
  159.             return false;
  160.         }
  161.     }
  162.     /**
  163.      * 判断是否为对子
  164.      * @param $arr_card
  165.      * @return bool
  166.      */
  167.     public function isDui($arr_card)
  168.     {
  169.         if (count($arr_card) == 2 && ($this->_getModVal($arr_card[0]) == $this->_getModVal($arr_card[1]))) {
  170.             return true;
  171.         } else {
  172.             return false;
  173.         }
  174.     }
  175.     /**
  176.      * 判断是否为三张
  177.      * @param $arr_card
  178.      * @return bool
  179.      */
  180.     public function isSan($arr_card)
  181.     {
  182.         $value = $this->_getModVal($arr_card[0]);
  183.         if (count($arr_card) == 3 && ($this->_getModVal($arr_card[1]) == $value && $this->_getModVal($arr_card[2]) == $value)) {
  184.             return true;
  185.         } else {
  186.             return false;
  187.         }
  188.     }
  189.     /**
  190.      * 判断是否为三带一
  191.      * @param $arr_card
  192.      * @return bool
  193.      */
  194.     public function isSanDaiYi($arr_card)
  195.     {
  196.         $back = false;
  197.         if (count($arr_card) == 4) {
  198.             //排序
  199.             $arr_card = $this->_sortCardByGrade($arr_card);
  200.             if ($this->_getModVal($arr_card[0]) == $this->_getModVal($arr_card[1]) && $this->_getModVal($arr_card[1]) == $this->_getModVal($arr_card[2]) && $this->_getModVal($arr_card[2]) != $this->_getModVal($arr_card[3])) {
  201.                 //带单在后面
  202.                 $back = true;
  203.             } elseif ($this->_getModVal($arr_card[0]) != $this->_getModVal($arr_card[1]) && $this->_getModVal($arr_card[1]) == $this->_getModVal($arr_card[2]) && $this->_getModVal($arr_card[2]) == $this->_getModVal($arr_card[3])) {
  204.                 //带单在前面
  205.                 $back = true;
  206.             }
  207.         }
  208.         return $back;
  209.     }
  210.     /**
  211.      * 判断是否为三带二
  212.      * @param $arr_card
  213.      * @return bool
  214.      */
  215.     public function isSanDaiEr($arr_card)
  216.     {
  217.         $back = false;
  218.         if (count($arr_card) == 5) {
  219.             //排序
  220.             $arr_card = $this->_sortCardByGrade($arr_card);
  221.             if ($this->_getModVal($arr_card[0]) == $this->_getModVal($arr_card[1]) && $this->_getModVal($arr_card[1]) == $this->_getModVal($arr_card[2]) && $this->_getModVal($arr_card[2]) != $this->_getModVal($arr_card[3]) && $this->_getModVal($arr_card[3]) == $this->_getModVal($arr_card[4])) {
  222.                 //带单在后面
  223.                 $back = true;
  224.             } elseif ($this->_getModVal($arr_card[0]) == $this->_getModVal($arr_card[1]) && $this->_getModVal($arr_card[1]) != $this->_getModVal($arr_card[2]) && $this->_getModVal($arr_card[2]) == $this->_getModVal($arr_card[3]) && $this->_getModVal($arr_card[3]) == $this->_getModVal($arr_card[4])) {
  225.                 //带单在前面
  226.                 $back = true;
  227.             }
  228.         }
  229.         return $back;
  230.     }
  231.     /**
  232.      * 判断是否为顺子
  233.      * @param $arr_card
  234.      * @return bool
  235.      */
  236.     public function isShunZi($arr_card)
  237.     {
  238.         $cnt = count($arr_card);
  239.         if ($cnt < 5 || $cnt > 12) {
  240.             return false;
  241.         } else {
  242.             //排序
  243.             $arr_card = $this->_sortCardByGrade($arr_card);
  244.             for ($i = 0; $i < $cnt - 1; $i++) {
  245.                 //过滤掉2,小王,大王
  246.                 if (in_array($this->_getModVal($arr_card[$i]), array(13, 14, 15))) {
  247.                     return false;
  248.                 }
  249.                 if ($this->_getModVal($arr_card[$i + 1]) - $this->_getModVal($arr_card[$i]) != 1) {
  250.                     return false;
  251.                 }
  252.             }
  253.             return true;
  254.         }
  255.     }
  256.     /**
  257.      * 判断是否为连对
  258.      * @param $arr_card
  259.      * @return bool
  260.      */
  261.     public function isLianDui($arr_card)
  262.     {
  263.         $cnt = count($arr_card);
  264.         if ($cnt < 6 || $cnt % 2 != 0) {
  265.             return false;
  266.         } else {
  267.             //排序
  268.             $arr_card = $this->_sortCardByGrade($arr_card);
  269.             for ($i = 0; $i < $cnt - 1; $i = $i + 2) {
  270.                 //过滤掉2,小王,大王
  271.                 if (in_array($this->_getModVal($arr_card[$i]), array(13, 14, 15))) {
  272.                     return false;
  273.                 }
  274.                 if ($this->_getModVal($arr_card[$i]) != $this->_getModVal($arr_card[$i + 1])) {
  275.                     return false;
  276.                 }
  277.                 if ($i < $cnt - 2) {
  278.                     if ($this->_getModVal($arr_card[$i]) - $this->_getModVal($arr_card[$i + 2]) != -1) {
  279.                         return false;
  280.                     }
  281.                 }
  282.             }
  283.             return true;
  284.         }
  285.     }
  286.     /**
  287.      * 判断是否为飞机不带
  288.      * @param $arr_card
  289.      * @return bool
  290.      */
  291.     public function isFeiJiBuDai($arr_card)
  292.     {
  293.         $cnt = count($arr_card);
  294.         if ($cnt < 6 || $cnt % 3 != 0) {
  295.             return false;
  296.         } else {
  297.             //排序
  298.             $arr_card = $this->_sortCardByGrade($arr_card);
  299.             $index = array();
  300.             for ($i = 0; $i < $cnt - 2; $i = $i + 3) {
  301.                 //过滤掉2,小王,大王
  302.                 if (in_array($this->_getModVal($arr_card[$i]), array(13, 14, 15))) {
  303.                     return false;
  304.                 }
  305.                 if ($i != $cnt) {
  306.                     if (!$this->isSan(array($arr_card[$i], $arr_card[$i + 1], $arr_card[$i + 2]))) {
  307.                         return false;
  308.                     }
  309.                     $index[] = $arr_card[$i];
  310.                 }
  311.             }
  312.             //排序
  313.             $index = $this->_sortCardByGrade($index);
  314.             $index_cnt = count($index);
  315.             for ($i = 0; $i < $index_cnt - 1; $i++) {
  316.                 if ($this->_getModVal($index[$i]) - $this->_getModVal($index[$i + 1]) != -1) {
  317.                     return false;
  318.                 }
  319.             }
  320.             return true;
  321.         }
  322.     }
  323.     /**
  324.      * 是非为飞机带单
  325.      * @param $arr_card
  326.      * @param int $flag 默认为1时, 飞机带单, 等于2是,判断飞机带双
  327.      * @return bool
  328.      */
  329.     public function isFeiJiDaiDan($arr_card$flag = 1)
  330.     {
  331.         $cnt = count($arr_card);
  332.         if ($cnt < 8) {
  333.             return false;
  334.         }
  335.         //对牌值进行取模运算
  336.         $card_value = array();
  337.         array_walk(
  338.             $arr_card,
  339.             function ($item$keyuse (&$card_value) {
  340.                 $card_value[$key] = $this->_getModVal($item);
  341.             }
  342.         );
  343.         $arrs = array_count_values($card_value);
  344.         $list = [];
  345.         foreach ($arrs as $k => $v) {
  346.             if ($v == 3) {
  347.                 $list[] = $k;
  348.             }
  349.         }
  350.         //排序
  351.         $list = $this->_sortCardByGrade($list);
  352.         for ($i = 0; $i < count($list) - 1; $i++) {
  353.             if ($list[$i] - $list[$i + 1] != -1) {
  354.                 return false;
  355.             }
  356.         }
  357.         $list_cnt = count($list);
  358.         $pokers = $list_cnt * 3 + $list_cnt * $flag;
  359.         if ($pokers != count($arr_card)) {
  360.             return false;
  361.         }
  362.         return true;
  363.     }
  364.     /**
  365.      * 判断是否为飞机带双
  366.      * @param $arr_card
  367.      * @return bool
  368.      */
  369.     public function isFeiJiDaiShuang($arr_card)
  370.     {
  371.         return $this->isFeiJiDaiDan($arr_card$flag = 2);
  372.     }
  373.     /**
  374.      * 判断是否为四带一, 表示四带一对
  375.      * @param $arr_card
  376.      * @return bool
  377.      */
  378.     public function isSiDaiYi($arr_card)
  379.     {
  380.         //排序
  381.         $arr_card = $this->_sortCardByGrade($arr_card);
  382.         $cnt = count($arr_card);
  383.         $back = false;
  384.         if ($cnt == 6) {
  385.             $grade0 = $this->_getModVal($arr_card[0]);
  386.             $grade1 = $this->_getModVal($arr_card[1]);
  387.             $grade2 = $this->_getModVal($arr_card[2]);
  388.             $grade3 = $this->_getModVal($arr_card[3]);
  389.             $grade4 = $this->_getModVal($arr_card[4]);
  390.             $grade5 = $this->_getModVal($arr_card[5]);
  391.             if ($grade0 == $grade1 && $grade1 == $grade2 && $grade2 == $grade3 && $grade3 != $grade4 && $grade4 == $grade5) {
  392.                 $back = true;
  393.             } else if ($grade0 == $grade1 && $grade1 != $grade2 && $grade2 == $grade3 && $grade3 == $grade4 && $grade4 == $grade5) {
  394.                 $back = true;
  395.             }
  396.         }
  397.         return $back;
  398.     }
  399.     /**
  400.      * 判断是否为四带二, 表示4带2对
  401.      * @param $arr_card
  402.      * @return bool
  403.      */
  404.     function isSiDaiEr($arr_card)
  405.     {
  406.         //排序
  407.         $arr_card = $this->_sortCardByGrade($arr_card);
  408.         $cnt = count($arr_card);
  409.         $back = false;
  410.         if ($cnt == 8) {
  411.             $grade0 = $this->_getModVal($arr_card[0]);
  412.             $grade1 = $this->_getModVal($arr_card[1]);
  413.             $grade2 = $this->_getModVal($arr_card[2]);
  414.             $grade3 = $this->_getModVal($arr_card[3]);
  415.             $grade4 = $this->_getModVal($arr_card[4]);
  416.             $grade5 = $this->_getModVal($arr_card[5]);
  417.             $grade6 = $this->_getModVal($arr_card[6]);
  418.             $grade7 = $this->_getModVal($arr_card[7]);
  419.             if ($grade0 == $grade1 && $grade1 == $grade2 && $grade2 == $grade3 && $grade3 != $grade4 && $grade4 == $grade5 && $grade6 == $grade7) {
  420.                 $back = true;
  421.             } elseif ($grade0 == $grade1 && $grade1 != $grade2 && $grade2 == $grade3 && $grade4 == $grade5 && $grade5 == $grade6 && $grade6 == $grade7) {
  422.                 $back = true;
  423.             } elseif ($grade0 == $grade1 && $grade1 != $grade2 && $grade2 == $grade3 && $grade3 == $grade4 && $grade4 == $grade5 && $grade5 != $grade6 && $grade6 == $grade7) {
  424.                 $back = true;
  425.             }
  426.         }
  427.         return $back;
  428.     }
  429.     /**
  430.      * 是否为炸弹
  431.      * @param $arr_card
  432.      * @return bool
  433.      */
  434.     public function isZha($arr_card)
  435.     {
  436.         if (count($arr_card) == 4 && $this->_getModVal($arr_card[0]) == $this->_getModVal($arr_card[1]) && $this->_getModVal($arr_card[1]) == $this->_getModVal($arr_card[2]) && $this->_getModVal($arr_card[2]) == $this->_getModVal($arr_card[3])) {
  437.             return true;
  438.         } else {
  439.             return false;
  440.         }
  441.     }
  442.     /**
  443.      * 判断是否为火箭
  444.      * @param $arr_card
  445.      * @return bool
  446.      */
  447.     public function isHuojian($arr_card)
  448.     {
  449.         if (count($arr_card) == 2 && (($arr_card[0] == 78 && $arr_card[1] == 79) || ($arr_card[1] == 79 && $arr_card[2] == 80))) {
  450.             return true;
  451.         } else {
  452.             return false;
  453.         }
  454.     }
  455.     /**
  456.      * 检测牌型
  457.      * @param $arr_card
  458.      * @return array
  459.      */
  460.     public function checkCardType($arr_card)
  461.     {
  462.         $type = 0;
  463.         if ($this->isDan($arr_card)) {
  464.             $type = self::CARD_TYPE_DAN;
  465.         } elseif ($this->isDui($arr_card)) {
  466.             $type = self::CARD_TYPE_DUI;
  467.         } elseif ($this->isHuojian($arr_card)) {
  468.             $type = self::CARD_TYPE_HUOJIAN;
  469.         } elseif ($this->isSan($arr_card)) {
  470.             $type = self::CARD_TYPE_SAN;
  471.         } elseif ($this->isZha($arr_card)) {
  472.             $type = self::CARD_TYPE_ZHADAN;
  473.         } elseif ($this->isSandaiyi($arr_card)) {
  474.             $type = self::CARD_TYPE_SANDAIYI;
  475.         } elseif ($this->isShunzi($arr_card)) {
  476.             $type = self::CARD_TYPE_SHUNZI;
  477.         } elseif ($this->isLiandui($arr_card)) {
  478.             $type = self::CARD_TYPE_LIANDUI;
  479.         } else if ($this->isFeijiBuDai($arr_card)) {
  480.             $type = self::CARD_TYPE_FEIJIBUDAI;
  481.         } elseif ($this->isSiDaiYi($arr_card)) {
  482.             $type = self::CARD_TYPE_SIDAIYI;
  483.         } elseif ($this->isSiDaiEr($arr_card)) {
  484.             $type = self::CARD_TYPE_SIDAIER;;
  485.         } elseif ($this->isSandaiEr($arr_card)) {
  486.             $type = self::CARD_TYPE_SANDAIER;;
  487.         } elseif ($this->isFeijiDaiDan($arr_card)) {
  488.             $type = self::CARD_TYPE_FEIJIDAIDAN;;
  489.         } elseif ($this->isFeijiDaiShuang($arr_card)) {
  490.             $type = self::CARD_TYPE_FEIJIDAISHUANG;
  491.         }
  492.         if (array_key_exists($type, self::$card_type)) {
  493.             $back = array('type' => $type, 'type_msg' => self::$card_type[$type]);
  494.         } else {
  495.             $back = array('type' => $type, 'type_msg' => 'unknow'); //未知牌型
  496.         }
  497.         return $back;
  498.     }
  499.     /**
  500.      * 检测牌的大小
  501.      * 比较2家的牌,主要有2种情况:
  502.      * 1.我出和上家一种类型的牌,即对子管对子;
  503.      * 2.我出炸弹,此时,和上家的牌的类型可能不同
  504.      * 王炸的情况先排除
  505.      * @param array $my 我的出牌, 格式:array()
  506.      * @param array $prev 上手出牌, 格式:array()
  507.      * @return bool,true是可以出牌, false不能出牌
  508.      */
  509.     public function checkCardSize($my = array(), $prev = array())
  510.     {
  511.         //判断我的牌数据是否非法
  512.         if (!$my) {
  513.             return false;
  514.         }
  515.         //计算出我的牌型
  516.         $my_arr = $this->checkCardType($my);
  517.         $my_type = isset($my_arr['type']) ? $my_arr['type'] : 0;
  518.         //我先出牌上家没牌, 我大
  519.         if (!$prev) {
  520.             return true;
  521.         }
  522.         $prev_arr = $this->checkCardType($prev);
  523.         $prev_type = isset($prev_arr['type']) ? $prev_arr['type'] : 0;
  524.         //集中判断是否为火箭,免得多次判断火箭
  525.         if ($my_type == self::CARD_TYPE_HUOJIAN) {
  526.             return true;
  527.         } elseif ($prev_type == self::CARD_TYPE_HUOJIAN) {
  528.             return false;
  529.         }
  530.         //集中判断上家不是炸弹,我出炸弹的情况
  531.         if ($my_type == self::CARD_TYPE_ZHADAN && $prev_type != self::CARD_TYPE_ZHADAN) {
  532.             return true;
  533.         }
  534.         $my_card = $this->_sortCardByGrade($my);
  535.         $prev_card = $this->_sortCardByGrade($prev);
  536.         $my_cnt = count($my_card);
  537.         $prev_cnt = count($prev_card);
  538.         if ($my_type == self::CARD_TYPE_DAN && $prev_type == self::CARD_TYPE_DAN && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  539.             //单张
  540.             return true;
  541.         } elseif ($my_type == self::CARD_TYPE_DUI && $prev_type == self::CARD_TYPE_DUI && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  542.             //对子
  543.             return true;
  544.         } elseif ($my_type == self::CARD_TYPE_SAN && $prev_type == self::CARD_TYPE_SAN && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  545.             //三张
  546.             return true;
  547.         } elseif ($my_type == self::CARD_TYPE_ZHADAN && $prev_type == self::CARD_TYPE_ZHADAN && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  548.             //炸弹
  549.             return true;
  550.         } elseif ($my_type == self::CARD_TYPE_SANDAIYI && $prev_type == self::CARD_TYPE_SANDAIYI && $this->_getModVal($my_card[1]) > $this->_getModVal($prev_card[1])) {
  551.             //三带一,只比较第二张牌的大小
  552.             return true;
  553.         } elseif ($my_type == self::CARD_TYPE_SANDAIER && $prev_type == self::CARD_TYPE_SANDAIER && $this->_getModVal($my_card[2]) > $this->_getModVal($prev_card[2])) {
  554.             //三带二,只比较第三张牌的大小
  555.             return true;
  556.         } elseif ($my_type == self::CARD_TYPE_SIDAIYI && $prev_type == self::CARD_TYPE_SIDAIYI && $this->_getModVal($my_card[2]) > $this->_getModVal($prev_card[2])) {
  557.             //四带一对,只比较第二张牌的大小
  558.             return true;
  559.         } elseif ($my_type == self::CARD_TYPE_SIDAIER && $prev_type == self::CARD_TYPE_SIDAIER) {
  560.             //对牌值进行取模运算
  561.             $my_card_grade = $prev_card_grade = array();
  562.             array_walk(
  563.                 $my_card,
  564.                 function ($item$keyuse (&$my_card_grade) {
  565.                     $my_card_grade[$key] = $this->_getModVal($item);
  566.                 }
  567.             );
  568.             array_walk(
  569.                 $prev_card,
  570.                 function ($item$keyuse (&$prev_card_grade) {
  571.                     $prev_card_grade[$key] = $this->_getModVal($item);
  572.                 }
  573.             );
  574.             $m = array_flip(array_count_values($my_card_grade));
  575.             $p = array_flip(array_count_values($prev_card_grade));
  576.             if (isset($m[4]) && isset($p[4]) && $m[4] > $p[4]) {
  577.                 return true;
  578.             }
  579.         } elseif ($my_type == self::CARD_TYPE_SHUNZI && $prev_type == self::CARD_TYPE_SHUNZI && $my_cnt == $prev_cnt && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  580.             //顺子
  581.             return true;
  582.         } elseif ($my_type == self::CARD_TYPE_LIANDUI && $prev_type == self::CARD_TYPE_LIANDUI && $my_cnt == $prev_cnt && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  583.             //连对
  584.             return true;
  585.         } elseif ($my_type == self::CARD_TYPE_FEIJIBUDAI && $prev_type == self::CARD_TYPE_FEIJIBUDAI && $my_cnt == $prev_cnt && $this->_getModVal($my_card[0]) > $this->_getModVal($prev_card[0])) {
  586.             //飞机不带
  587.             return true;
  588.         } elseif ($my_type == self::CARD_TYPE_FEIJIDAIDAN && $prev_type == self::CARD_TYPE_FEIJIDAIDAN && $my_cnt == $prev_cnt && $this->_getModVal($my_card[1]) > $this->_getModVal($prev_card[1])) {
  589.             //飞机带单
  590.             return true;
  591.         } elseif ($my_type == self::CARD_TYPE_FEIJIDAISHUANG && $prev_type == self::CARD_TYPE_FEIJIDAISHUANG && $my_cnt == $prev_cnt && $this->_getModVal($my_card[2]) > $this->_getModVal($prev_card[2])) {
  592.             //飞机带双
  593.             return true;
  594.         }
  595.         return false;
  596.     }
  597.     /**
  598.      * 是否可以出牌
  599.      * @param $my_card
  600.      * @param $prev_type
  601.      * @param $prev_card
  602.      * @return bool
  603.      */
  604.     public function isPlayCard($my_card$prev_card)
  605.     {
  606.         //我的手牌和上家手牌不能为空
  607.         if (emptyempty($my_card) || emptyempty($prev_card)) {
  608.             return false;
  609.         }
  610.         $arr = $this->checkCardType($prev_card);
  611.         $prev_type = isset($arr['type']) ? $arr['type'] : 0;
  612.         //手牌类型
  613.         if (!array_key_exists($prev_type, self::$card_type)) {
  614.             return false;
  615.         }
  616.         $my_card = $this->_sortCardByGrade($my_card);
  617.         $prev_card = $this->_sortCardByGrade($prev_card);
  618.         $my_cnt = count($my_card);
  619.         $prev_cnt = count($prev_card);
  620.         //我先出牌, 上家没有牌
  621.         if ($prev_cnt == 0 && $my_cnt > 0) {
  622.             return true;
  623.         }
  624.         //集中判断是否为火箭, 免得后面多次判断
  625.         if ($prev_type == self::CARD_TYPE_HUOJIAN) {
  626.             return false;
  627.         }
  628.         //判断我的牌里是否有飞机, 如果有飞机, 肯定可以出牌
  629.         if ($my_cnt >= 2 && $this->isHuojian(array($my_card[$my_cnt - 1]), $my_card[$my_cnt - 2])) {
  630.             return true;
  631.         }
  632.         //集中判断对方不是炸弹,我出炸弹的情况
  633.         if ($prev_type != self::CARD_TYPE_ZHADAN) {
  634.             if ($my_cnt < 4) {
  635.                 return false;
  636.             } else {
  637.                 //循环判断, 我方是否有炸弹
  638.                 for ($i = 0; $i < $my_cnt - 3; $i++) {
  639.                     $grade0 = $this->_getModVal($my_card[$i]);
  640.                     $grade1 = $this->_getModVal($my_card[$i + 1]);
  641.                     $grade2 = $this->_getModVal($my_card[$i + 2]);
  642.                     $grade3 = $this->_getModVal($my_card[$i + 3]);
  643.                     if ($grade0 == $grade1 && $grade2 == $grade0 && $grade3 == $grade0) {
  644.                         return true;
  645.                     }
  646.                 }
  647.             }
  648.         }
  649.         //上家出单张
  650.         if ($prev_type == self::CARD_TYPE_DAN) {
  651.             //最要判断最后一张牌是否能大过上家的牌就行
  652.             if ($this->_getModVal($my_card[$my_cnt - 1]) > $this->_getModVal($prev_card[0])) {
  653.                 return true;
  654.             }
  655.         } //上家出对子
  656.         else if ($prev_type == self::CARD_TYPE_DUI) {
  657.             // 2张牌可以大过上家的牌
  658.             for ($i = $my_cnt - 1; $i >= 1; $i--) {
  659.                 $grade0 = $this->_getModVal($my_card[$i]);
  660.                 $grade1 = $this->_getModVal($my_card[$i - 1]);
  661.                 if ($grade0 == $grade1 && $grade0 > $this->_getModVal($prev_card[0])) {
  662.                     return true;
  663.                 }
  664.             }
  665.         } //上家出三不带, 三带一, 三带二
  666.         else if (in_array($prev_typearray(self::CARD_TYPE_SAN, self::CARD_TYPE_SANDAIYI, self::CARD_TYPE_SANDAIER))) {
  667.             //区别在于三带一喝三带二要判断牌数
  668.             if ($prev_type == self::CARD_TYPE_SANDAIYI && $my_cnt < 4) {
  669.                 return false;
  670.             }
  671.             if ($prev_type == self::CARD_TYPE_SANDAIER && $my_cnt < 5) {
  672.                 return false;
  673.             }
  674.             // 3张牌可以大过上家的牌
  675.             for ($i = $my_cnt - 1; $i >= 2; $i--) {
  676.                 $grade0 = $this->_getModVal($my_card[$i]);
  677.                 $grade1 = $this->_getModVal($my_card[$i - 1]);
  678.                 $grade2 = $this->_getModVal($my_card[$i - 2]);
  679.                 if ($grade0 == $grade1 && $grade0 == $grade2 && $grade0 > $this->_getModVal($prev_card[2])) {
  680.                     return true;
  681.                 }
  682.             }
  683.         } //上家出炸弹,四带一对,四带二对, 只要手牌有炸弹,或有对,就可以出牌就可以出牌
  684.         else if ($prev_type == self::CARD_TYPE_ZHADAN || $prev_type == self::CARD_TYPE_SIDAIYI || $prev_type == self::CARD_TYPE_SIDAIER) {
  685.             // 4张牌可以大过上家的牌
  686.             $dui_cnt = 0; //手牌对子计数
  687.             $si_arr = array(); //四张牌值grade
  688.             for ($i = $my_cnt - 1; $i >= 3; $i--) {
  689.                 $grade0 = $this->_getModVal($my_card[$i]);
  690.                 $grade1 = $this->_getModVal($my_card[$i - 1]);
  691.                 $grade2 = $this->_getModVal($my_card[$i - 2]);
  692.                 $grade3 = $this->_getModVal($my_card[$i - 3]);
  693.                 //记录四张相同的牌值
  694.                 if ($grade0 == $grade1 && $grade0 == $grade2 && $grade0 == $grade3) {
  695.                     $si_arr[] = $grade0;
  696.                 } else {
  697.                     //统计对子数量
  698.                     if ($grade0 == $grade1 || $grade1 == $grade2 || $grade2 == $grade3) {
  699.                         $dui_cnt++;
  700.                     }
  701.                 }
  702.             }
  703.             //和上级比较四张的牌值
  704.             foreach ($si_arr as $v) {
  705.                 if ($prev_type == self::CARD_TYPE_ZHADAN && $v > $this->_getModVal($prev_card[0])) {
  706.                     return true;
  707.                 } elseif ($prev_type == self::CARD_TYPE_SIDAIYI && $v > $this->_getModVal($prev_card[2]) && $dui_cnt > 0) {
  708.                     return true;
  709.                 } elseif ($prev_type == self::CARD_TYPE_SIDAIER && $dui_cnt > 1) {
  710.                     //四带两对的三种情况
  711.                     $prev_grade1 = $this->_getModVal($prev_card[1]);
  712.                     $prev_grade2 = $this->_getModVal($prev_card[2]);
  713.                     $prev_grade3 = $this->_getModVal($prev_card[3]);
  714.                     $prev_grade5 = $this->_getModVal($prev_card[5]);
  715.                     $prev_grade6 = $this->_getModVal($prev_card[6]);
  716.                     if ($prev_grade1 == $prev_grade2 && $v > $prev_grade1) {
  717.                         return true;
  718.                     } elseif ($prev_grade5 == $prev_grade6 && $v > $prev_grade5) {
  719.                         return true;
  720.                     } elseif ($prev_grade1 != $prev_grade2 && $prev_grade5 != $prev_grade6 && $v > $prev_grade3) {
  721.                         return true;
  722.                     }
  723.                 }
  724.             }
  725.         } //上家出顺子, 出连对, 飞机
  726.         else if ($prev_type == self::CARD_TYPE_SHUNZI || $prev_type == self::CARD_TYPE_LIANDUI || $prev_type == self::CARD_TYPE_FEIJIBUDAI || $prev_type == self::CARD_TYPE_FEIJIDAIDAN || $prev_type == self::CARD_TYPE_FEIJIDAISHUANG) {
  727.             if ($my_cnt < $prev_cnt) {
  728.                 return false;
  729.             } else {
  730.                 $my_card_grade = $this->_getCardGrade($my_card);
  731.                 $prev_card_grade = $this->_getCardGrade($prev_card);
  732.                 $tmp_my_cnt = array_count_values($my_card_grade); //统计出牌的grade值相同张数
  733.                 $my_card_grade = array_keys(array_flip($my_card_grade)); //去重
  734.                 //飞机带单和飞机带双要特殊处理一下
  735.                 if ($prev_type == self::CARD_TYPE_FEIJIDAIDAN || $prev_type == self::CARD_TYPE_FEIJIDAISHUANG) {
  736.                     $tmp_prev_cnt = array_count_values($prev_card_grade);
  737.                     $prev_card_grade = array();
  738.                     foreach ($tmp_prev_cnt as $k => $v) {
  739.                         if ($v == 3) {
  740.                             $prev_card_grade[] = $k;
  741.                         }
  742.                     }
  743.                 } else {
  744.                     $prev_card_grade = array_keys(array_flip($prev_card_grade)); //去重
  745.                 }
  746.                 $my_cnt = count($my_card_grade);
  747.                 $prev_cnt = count($prev_card_grade);
  748.                 for ($i = $my_cnt - 1; $i >= $prev_cnt - 1; $i--) {
  749.                     $my_tmp_cards = array();
  750.                     for ($j = 0; $j < $prev_cnt$j++) {
  751.                         $my_tmp_cards[] = $my_card_grade[$i - $j];
  752.                     }
  753.                     $my_tmp_cards = $this->_sortCardByGrade($my_tmp_cards);
  754.                     if ($prev_type == self::CARD_TYPE_SHUNZI) {
  755.                         //检查牌的类型
  756.                         $tmp_type = $this->checkCardType($my_tmp_cards);
  757.                         $my_type = isset($tmp_type['type']) ? $tmp_type['type'] : 0;
  758.                         $grade = $my_tmp_cards[count($my_tmp_cards) - 1];// 最大的牌在最后
  759.                         $prev_grade = $prev_card_grade[$prev_cnt - 1];// 最大的牌在最后
  760.                         if ($my_type == $prev_type && $grade > $prev_grade) {
  761.                             return true;
  762.                         }
  763.                     } elseif ($prev_type == self::CARD_TYPE_LIANDUI) {
  764.                         if ($this->_isContinuous($my_tmp_cards) && $this->_isNumOk($tmp_my_cnt$my_tmp_cards, 2)) {
  765.                             return true;
  766.                         }
  767.                     } elseif ($prev_type == self::CARD_TYPE_FEIJIBUDAI) {
  768.                         //判断连续性
  769.                         if ($this->_isContinuous($my_tmp_cards) && $this->_isNumOk($tmp_my_cnt$my_tmp_cards, 3)) {
  770.                             return true;
  771.                         }
  772.                     } elseif ($prev_type == self::CARD_TYPE_FEIJIDAIDAN) {
  773.                         //判断连续性
  774.                         if ($this->_isContinuous($my_tmp_cards) && $this->_isNumOk($tmp_my_cnt$my_tmp_cards, 3) && $this->_isDaiNumOk($tmp_my_cnt$my_tmp_cards)) {
  775.                             return true;
  776.                         }
  777.                     } elseif ($prev_type == self::CARD_TYPE_FEIJIDAISHUANG) {
  778.                         //判断连续性
  779.                         if ($this->_isContinuous($my_tmp_cards) && $this->_isNumOk($tmp_my_cnt$my_tmp_cards, 3) && $this->_isDaiNumOk($tmp_my_cnt$my_tmp_cards, 2)) {
  780.                             return true;
  781.                         }
  782.                     }
  783.                 }
  784.             }
  785.         }
  786.         return false;
  787.     }
  788.     /**
  789.      * 最值进行取模运算, 获取到牌的值
  790.      * @param $val
  791.      * @return int
  792.      */
  793.     private function _getModVal($val)
  794.     {
  795.         return $val % 16;
  796.     }
  797.     /**
  798.      * 对手牌进行排序处理
  799.      * @param array $card
  800.      * @return array
  801.      */
  802.     private function _sortCardByGrade($card = array())
  803.     {
  804.         //牌进行排序
  805.         $new_card = array();
  806.         foreach ($card as $v) {
  807.             $new_card[$v] = $this->_getModVal($v);
  808.         }
  809.         //对数组按值排序, 并保留键值
  810.         asort($new_card);
  811.         $card = array();
  812.         foreach ($new_card as $k => $v) {
  813.             $card[] = $k;
  814.         }
  815.         return $card;
  816.     }
  817.     /**
  818.      * 获取牌的grade值
  819.      * @param $cards
  820.      * @return array
  821.      */
  822.     private function _getCardGrade($cards) {
  823.         $new_card = array();
  824.         foreach ($cards as $v) {
  825.             $new_card[] = $this->_getModVal($v);
  826.         }
  827.         return $new_card;
  828.     }
  829.     /**
  830.      * 判断牌值是否连续
  831.      * @param $card
  832.      * @return bool
  833.      */
  834.     private function _isContinuous($card)
  835.     {
  836.         $card = $this->_sortCardByGrade($card);
  837.         $cnt = count($card);
  838.         for ($i = 0; $i < $cnt - 1; $i++) {
  839.             if ($card[$i] - $card[$i + 1] != -1) {
  840.                 return false;
  841.             }
  842.         }
  843.         return true;
  844.     }
  845.     /**
  846.      * 判断数量是否正确, 顺子,连对,飞机使用判断
  847.      * @param $cnt_card
  848.      * @param $card
  849.      * @param int $num
  850.      * @return bool
  851.      */
  852.     private function _isNumOk($cnt_card$card$num = 2)
  853.     {
  854.         //判断数量
  855.         foreach ($card as $v) {
  856.             if ($cnt_card[$v] < $num) {
  857.                 return false;
  858.             }
  859.         }
  860.         return true;
  861.     }
  862.     /**
  863.      * 判断戴牌的数量是否ok, 主要用户飞机带单和飞机带双使用
  864.      * @param $cnt_card
  865.      * @param $card
  866.      * @param int $num 1表示带单, 2表示带双
  867.      * @return bool
  868.      */
  869.     private function _isDaiNumOk($cnt_card$card$num = 1)
  870.     {
  871.         //判断数量
  872.         $count = 0;
  873.         foreach ($cnt_card as $k=>$v) {
  874.             if (!in_array($k$card) && $v >= $num) {
  875.                 $count++;
  876.             }
  877.         }
  878.         if($count >= count($card)) {
  879.             return true;
  880.         } else {
  881.             return false;
  882.         }
  883.     }
  884. }

项目地址:https://github.com/jxy918/ddzpoker

你想把广告放到这里吗?

发表评论

您必须 登录 才能发表留言!