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

2017-07-20 19:03:55 +08:00
 goodspb

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

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

参考了如下几个地方:

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

代码:

<?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 了。哈哈哈哈

2585 次点击
所在节点    PHP
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/376788

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX