V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
goodspb
V2EX  ›  PHP

分享一个牛仔游戏(开心牛仔)的算法

  •  
  •   goodspb · 2017-07-20 19:03:55 +08:00 · 2632 次点击
    这是一个创建于 2683 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在做小小的直播平台 (逃..., 里面有一个叫 牛仔游戏 的小游戏供玩家去玩。

    好了,闲话少说,上代码:

    参考了如下几个地方:

    分享一个牛牛算法 百度百科

    代码:

    <?php
    class NiuNiu
    {
       public static $cards = [
           //数字,花色,牛仔中的大小
           [1, 4, 1], [1, 3, 1], [1, 2, 1], [1, 1, 1],
           [2, 4, 2], [2, 3, 2], [2, 2, 2], [2, 1, 2],
           [3, 4, 3], [3, 3, 3], [3, 2, 3], [3, 1, 3],
           [4, 4, 4], [4, 3, 4], [4, 2, 4], [4, 1, 4],
           [5, 4, 5], [5, 3, 5], [5, 2, 5], [5, 1, 5],
           [6, 4, 6], [6, 3, 6], [6, 2, 6], [6, 1, 6],
           [7, 4, 7], [7, 3, 7], [7, 2, 7], [7, 1, 7],
           [8, 4, 8], [8, 3, 8], [8, 2, 8], [8, 1, 8],
           [9, 4, 9], [9, 3, 9], [9, 2, 9], [9, 1, 9],
           [10, 4, 10], [10, 3, 10], [10, 2, 10], [10, 1, 10],
           [11, 4, 10], [11, 3, 10], [11, 2, 10], [11, 1, 10],
           [12, 4, 10], [12, 3, 10], [12, 2, 10], [12, 1, 10],
           [13, 4, 10], [13, 3, 10], [13, 2, 10], [13, 1, 10],
       ];
       protected $nowLeftCards;
       protected $playersNumber;
       protected $playerCards = [];
       public function __construct()
       {
           //创建剩余的牌
           $this->nowLeftCards = self::$cards;
       }
       /**
        * 获取牌面数字
        * @param $card
        * @return mixed
        */
       protected function getCardNumber($card)
       {
           return $card[0];
       }
       /**
        * 获取花色
        * @param $card
        * @return mixed
        */
       protected function getCardColor($card)
       {
           return $card[1];
       }
       /**
        * 获取牛牛中数值
        * @param $card
        * @return mixed
        */
       protected function getCardValue($card)
       {
           return $card[2];
       }
       /**
        * 获取剩余的牌
        * @return array
        */
       public function getLeftCards()
       {
           return $this->nowLeftCards;
       }
       /**
        * 获取玩家的牌
        * @return array
        */
       public function getPlayersCards()
       {
           return $this->playerCards;
       }
       /**
        * 配置主播手牌
        * @param string $playerName
        * @param array $cards
        */
       public function setPlayerCard($playerName, array $cards)
       {
           $playerCards = [];
           foreach ($cards as $card) {
               $hasThisCard = false;
               $unsetKey = null;
               foreach ($this->nowLeftCards as $nowLeftCardKey => $nowLeftCard) {
                   if ($card == $nowLeftCard) {
                       $hasThisCard = true;
                       $unsetKey = $nowLeftCardKey;
                   }
               }
               if ($hasThisCard) {
                   $playerCards[] = $card;
                   unset($this->nowLeftCards[$unsetKey]);
               }
           }
           $this->playerCards[$playerName] = $playerCards;
       }
       /**
        * 设置忽略的牌
        * @param array $value
        * @return array
        */
       public function setExclude(array $value)
       {
           foreach ($this->nowLeftCards as $key => $nowLeftCard) {
               if ($nowLeftCard == $value) {
                   unset($this->nowLeftCards[$key]);
               }
           }
           return $this->nowLeftCards;
       }
       /**
        * 随机生成玩家 & 牌
        * @param int $playerNumbers
        * @return array
        */
       public function generate($playerNumbers = 3)
       {
           $this->playerCards = [];
           //洗牌
           shuffle($this->nowLeftCards);
           for ($i = 1; $i <= $playerNumbers; $i++) {
               $playerCard = [];
               $needToRand = 5;
               for ($j = 1; $j <= $needToRand; $j++) {
                   $playerCard[] = array_shift($this->nowLeftCards);
               }
               $this->playerCards["player_{$i}"] = $playerCard;
           }
           return $this->playerCards;
       }
       /**
        * 执行计算
        * @return array
        */
       public function execute()
       {
           $result = [];
           foreach ($this->playerCards as $player => &$playerCard) {
               //按照从大到小排序
               $this->cardsSort($playerCard);
               $result[] = [
                   'name' => $player,
                   'shape' => $this->judge($playerCard),
                   'cards' => $playerCard,
               ];
           }
           //计算结果
           $this->sortResult($result);
           return $result;
       }
       /**
        * 计算结果
        * @param $result
        */
       public function sortResult(&$result)
       {
           usort($result, function($value, $next) {
               //当牌型相同的时候,比较牌的大小和卡
               if ($value['shape'] == $next['shape']) {
                   return $this->compareNumberAndColor($value['cards'], $next['cards']);
               }
               return $value['shape'] < $next['shape'] ? 1 : -1;
           });
       }
       /**
        * 比较 2 张牌的大小,一次比较单牌的大小,如单牌牌面都相同,比较最大单牌的花色
        * @param $first
        * @param $second
        * @return bool|int
        */
       public function compareNumberAndColor($first, $second)
       {
           foreach ($first as $key => $value) {
               if (($firstNumber = $this->getCardNumber($value)) < ($secondNumber = $this->getCardNumber($second[$key]))) {
                   return 1;
               } elseif ($firstNumber > $secondNumber) {
                   return -1;
               }
           }
           //当所有的牌都是等于的时候,比较最大单牌的花色
           return $this->getCardColor($first[0]) < $this->getCardColor($second[0]) ? 1 : -1;
       }
       /**
        * 判断牌型
        * @param $cards
        * @return int 0:没有牛 | 牛 1~9:1~9 |  10:牛牛 |  11:4 花牛 | 12:5 花牛 | 13:炸弹 | 14:五小牛
        */
       public function judge($cards)
       {
           $numbers = [];
           $smallerThanFive = 0;
           foreach ($cards as $card) {
               $numbers[] = $cardNumber = $this->getCardNumber($card);
               if ($cardNumber < 5) {
                   $smallerThanFive++;
               }
           }
           // 5 小牛
           if (array_sum($numbers) < 10 && $smallerThanFive == 5) {
               return 14;
           }
           // 炸弹,4 张牌进行排列
           $fourCardArrangements = $this->arrangement($numbers, 4);
           foreach ($fourCardArrangements as $fourCardArrangement) {
               //去重值, 如果只剩下 1 个, 证明 4 张牌相同
               if (count(array_unique($fourCardArrangement)) == 1) {
                   return 13;
               }
           }
           //所有卡牌的数字总和
           $allCardSum = $this->cardsSum($cards);
           //计算牛牛,3 张牌一组进行排列
           $arrangements = $this->arrangement($cards, 3);
           //初始化结果,没有牛
           $result = 0;
           foreach ($arrangements as $arrangement) {
               $sum = $this->cardsSum($arrangement);
               //有牛
               if ($sum % 10 ==  0) {
                   $left = ($allCardSum - $sum) % 10;
                   //牛牛
                   if ($left == 0 ) {
                       $biggerThanEleven = 0;
                       $biggerThanTen = 0;
                       //所有牌的牌面数字
                       $numbers = [];
                       foreach ($cards as $card) {
                           $numbers[] = $cardNumber = $this->getCardNumber($card);
                           if ($cardNumber >= 11) {
                               $biggerThanEleven++;
                               $biggerThanTen++;
                           } elseif($cardNumber == 10) {
                               $biggerThanTen++;
                           }
                       }
                       // 5 花牛
                       if ($biggerThanEleven == 5) {
                           return 12;
                       }
                       // 4 花牛
                       if ($biggerThanTen == 5) {
                           return 11;
                       }
                       //普通牛牛
                       return 10;
                   }
                   //牛 1~9
                   else {
                       //当前值大于另外的组合的值,则代替
                       if ($left >= $result) {
                           $result = $left;
                       }
                   }
               }
           }
           return $result;
       }
       /**
        * 卡牌求和
        * @param $cards
        * @return int
        */
       protected function cardsSum($cards)
       {
           $sum = 0;
           foreach ($cards as $card) {
               $sum += $this->getCardValue($card);
           }
           return $sum;
       }
       /**
        * 从大到小的排序,包括牌面、花色
        * @param $cards
        */
       protected function cardsSort(&$cards)
       {
           usort($cards, function($value, $next) {
               //先判断牌面, 当牌面相同,再判断花色
               if ($value[0] == $next[0]) {
                   return $value[1] == $next[1] ? 0 : ($value[1] < $next[1] ? 1 : -1);
               }
               return $value[0] < $next[0] ? 1 : -1;
           });
       }
       /**
        * 排列
        * @param $array
        * @param $number
        * @return array
        */
       protected function arrangement($array, $number = 3)
       {
           $result = [];
           $count = count($array);
           if ($number <= 0 || $number > $count) {
               return $result;
           }
           for ($i = 0; $i < $count; $i++) {
               $_temp = $array;
               //每次取一个数字,并从数组中删除
               $single = array_splice($_temp, $i, 1);
               if ($number == 1) {
                   $result[] = $single;
               } else {
                   $deep = $this->arrangement($_temp, $number - 1);
                   foreach ($deep as $deepItem) {
                       $result[] = array_merge($single, $deepItem);
                   }
               }
           }
           return $result;
       }
    }
    

    使用例子:

    <?php
    include __DIR__ . 'NiuNiu.php';
    $time = microtime(true);
    //创建实例
    $niu = new NiuNiu();
    //随机生成 3 位玩家,2 种方式二选一,但是如果手工填入的话,需要自己管理随机性和牌的唯一性
    $niu->generate(3);
    //手工填入 2 位玩家
    //$niu->setPlayerCard('player_1', [[1, 4, 1], [2, 4, 2], [3, 4, 3], [4, 4, 4], [5, 2, 5]]);
    //$niu->setPlayerCard('player_2', [[1, 3, 1], [2, 3, 2], [3, 3, 3], [4, 3, 4], [5, 3, 5]]);
    //执行计算
    $result = $niu->execute();
    $players = $niu->getPlayersCards();
    $time2 = microtime(true);
    //输出剩余的牌
    //var_dump($niu->getLeftCards());
    //输出玩家牌
    var_dump($players);
    //输出结果
    echo '<pre>';
    print_r($result);
    //计算执行时间
    var_dump(round($time2 - $time, 4) . '秒');
    //计算占用内存
    var_dump(round(memory_get_usage() / (1024 * 1024), 4) . 'MB');
    

    Github 地址, 求 star https://github.com/goodspb/niuniu-algorithm

    有问题欢迎 issues 哦,感谢各位帮我 debug 了。哈哈哈哈

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2750 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 08:21 · PVG 16:21 · LAX 00:21 · JFK 03:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.