至今为止,目前开发的西昌麻将已经根据客户的需求,基本完成了此游戏的开发,由于还正在内测,所以可能算法会有一些小问题,先奉上目前的算法实现。
此算法是用一个数字表示着某一张牌,1-9筒子、11-19条子、21-29万子,所以算法比较简单:
如判胡:一对将牌、三个成顺、三个成砍 满足此条件就为胡,还有种特殊情况是小七对、暗七对,所以具体的算法实现如下:
= 2){
return array($pai);
}
else{
return array();
}
}
/**
* 检查打牌能否杠牌的函数
* @param $playerPai 玩家手牌
* @param $pai 打的牌
* @return bool true/false
*/
function checkDaPaiGang($playerPai, $pai){
array_push($playerPai, $pai);
sort($playerPai);
$pai_count = array_count_values($playerPai);
foreach($pai_count as $key => $value){
if($value == 4 && $pai == $key){
return array($pai);
}
}
return array();
}
/**
* 检查摸牌后能否杠牌的函数
* @param $playerPai 玩家的手牌
* @param $playerPeng 玩家碰过的牌
* @param $pai 打出来的具体牌
* @return 如果能杠,返回('an'=>array(牌)或为空, 'ming'=>pai或false)
*/
function checkMoPaiGang($playerPai, $playerPeng, $pai){
$result = array();
$result['an'] = $this->checkAnGang($playerPai, $pai);
$result['ming'] = $this->checkMingGang($playerPai, $playerPeng, $pai);
// print_r($playerPeng);
return $result;
}
/**
* 检查玩家摸牌后是否能够暗杠
* @param $playerPai 玩家初始牌
* @param $pai 玩家摸到的牌
* @return array 返回可以暗杠的数组
*/
private function checkAnGang($playerPai, $pai){
array_push($playerPai, $pai);
sort($playerPai);
$pai_count = array_count_values($playerPai);
$result = array();
foreach($pai_count as $key => $value){
if($value == 4){
array_push($result, $key);
}
}
return $result;
}
/**
* 检查玩家摸牌后是否能够明杠
* @param $playerPai 玩家手牌
* @param $playerPengPai 玩家碰的牌
* @return 如果有,返回该牌,否则返回false;
*/
private function checkMingGang($playerPai, $playerPengPai, $pai){
//如果摸得牌可以杠
for($i = 0; $i < count($playerPengPai); $i++){
if($playerPengPai[$i] == $pai){
return array($pai);
}
}
//否则,还要检查手中的牌是否可以杠
for($i = 0; $i < count($playerPengPai); $i++){
if(in_array($playerPengPai[$i], $playerPai)){
return array($playerPengPai[$i]);
}
}
return array();
}
/**
* 处理玩家手牌的函数,将打过来的牌或者摸得牌放入数组并排序
* @param $playerPai,玩家的手牌
* @param $pai,玩家摸到或者打给玩家的牌
* @return array
*/
private function processPlayerPai($playerPai, $pai){
if($pai)
array_push($playerPai, $pai);
sort($playerPai);
return $playerPai;
}
/**
* 检查能否胡牌的函数
* @param $playerPai,玩家当前的手牌
* @param $pai,玩家摸到或者打给玩家的牌
* @return false / true
*/
function checkHu($playerPai, $pai){
$huPaiCheck = $this->processPlayerPai($playerPai, $pai);
//普通胡牌检测
$r = $this->doCheckHu($huPaiCheck);
// print_r($huPaiCheck);
if(!$r)
//七对胡牌检测
$r = $this->canSevenHu($huPaiCheck);
if($r)
return array($pai);
else
return array();
}
/**
* 辅助checkHu检查胡牌的函数
* @param $checkArray 由checkHu组合的字符串
* @return bool 返回是否能胡牌
*/
private function doCheckHu($checkArray){
//如果只有两张牌
if(count($checkArray) == 2){
return $checkArray[0] == $checkArray[1];
}
$canHu = false;
//找出将牌
for($i = 0; $i < count($checkArray) - 1; $i++){
//如果当前牌和后一张一样,则可为将
if($checkArray[$i] == $checkArray[$i+1]){
//如果将牌所在位置位于倒数第二个位置,直接截取前面所有的牌作为判断
if($i == count($checkArray) - 2){
$tempArray = array_slice($checkArray, 0, $i);
}
//如果将牌在一开始的位置,只截取后面的牌作为判断
else if($i == 0){
$tempArray = array_slice($checkArray, $i+2, count($checkArray) - 2);
}
//否则,从0到将牌所在位置,并从跳过将牌的位置到最后分别截取数组,合并成一个判断数组
else{
$tempArray1 = array_slice($checkArray, $i+2, count($checkArray) - $i - 2);
$tempArray2 = array_slice($checkArray, 0, $i);
$tempArray = array_merge($tempArray1, $tempArray2);
sort($tempArray);
}
//判断剩余牌组是否符合AAA 或ABC组合,则可以得出能否胡牌
if($this->canHu($tempArray)){
return true;
}
}
}
return false;
}
/**
* 辅助doCheckHu检查胡牌的函数
* @param $checkPais 由doCheckHu分割形成的除了将牌的手牌
* @return bool 返回是否能胡牌
*/
private function canHu($checkPais){
//如果截取的牌组为0,则已经可以胡牌了,这种情况不会发生
if(count($checkPais) == 0){
return true;
}
//保存一个临时数组,用于处理
$tempPais = $checkPais;
//得到数组中所有元素出现的次数
$count = array_count_values($checkPais);
foreach($count as $key => $value){
//如果次数大于3
if($value >= 3){
//则删除次数对应的KEY对应的砍牌
$this->removeKan($tempPais, $key);
}
}
//删除砍以后,如果没有牌,则能胡
if(count($tempPais) == 0){
return true;
}
//如果删除砍以后,只有三张一下的牌,不能胡
if(count($tempPais) < 3){
return false;
}
//判断能否形成顺子
//将删除完坎子的数组,保存一份备份
$checkPais = $tempPais;
for($i = 0; $i < count($checkPais); $i++){
//移除顺子
$this->removeShun($tempPais, $checkPais[$i]);
//如果一张牌不剩,则能胡
if(count($tempPais) == 0){
return true;
}
//如果剩余牌小于3,则不能胡
if(count($tempPais) < 3){
return false;
}
}
return false;
}
/**
* 辅助函数,用于删除牌中的一砍牌
* @param $pais
* @param $value
*/
private function removeKan(&$pais, $value){
for($i = 0; $i < count($pais); $i++){
if($pais[$i] == $value){
array_splice($pais, $i, 3);
break;
}
}
}
/**
* 辅助函数,用于删除牌中的顺子牌
* @param $pais
* @param $value
*/
private function removeShun(&$pais, $value){
//先找第一张牌
for($pai1Start = 0; $pai1Start < count($pais); $pai1Start++){
//如果找到第一张牌
if($pais[$pai1Start] == $value){
//从找到第一张牌开始后面的位置,找第二张牌
for($pai2Start = $pai1Start+1; $pai2Start < count($pais); $pai2Start++){
//如果找到第二张牌
if($pais[$pai2Start] == $value + 1){
//从找到第二张牌开始后面的位置,找第三张牌
for($pai3Start = $pai2Start + 1; $pai3Start < count($pais); $pai3Start++){
//如果找到第三张牌
if($pais[$pai3Start] == $value + 2){
//删除所有数据
array_splice($pais, $pai3Start, 1);
array_splice($pais, $pai2Start, 1);
array_splice($pais, $pai1Start, 1);
break;
}
}
}
}
}
}
}
/**
* 检查七小队/龙七对能胡的函数
* @param $checkPai
* @return bool true or false表示能否胡牌
*/
private function canSevenHu($checkPai){
if(count($checkPai) != 14){
return false;
}
$pairCount = 0;
for($i = 0; $i < count($checkPai); $i += 2){
if($checkPai[$i] == $checkPai[$i+1]){
$pairCount++;
}
}
return $pairCount == 7;
}
/**
* 检查红五胡的函数
* @param $checkPai
* @return bool true or false表示能否胡牌
*/
private function checkHongWu($checkPai,$pai){
if($pai==5||$pai==15||$pai==25){
$tempArray=array_count_values($checkPai);//得到每个值出现的次数
$fourNum=0;
$sixNum=0;
if(!empty($tempArray[$pai-1])){
$fourNum=$tempArray[$pai-1];
}
if(!empty($tempArray[$pai+1])){
$sixNum=$tempArray[$pai+1];
}
if(($fourNum==1||$fourNum==3||$fourNum==4)&&($sixNum==1||$sixNum==3||$sixNum==4)){
echo "MaJongLogic-checkHongWu OK!\n";
return true;
}
return false;
}else{
return false;
}
}
/**
* 检查是否能够推牌的函数,其实就是查叫,看看当玩家摸到一张牌后,打出任意一张牌后,是否能听牌
* @param $playerPai 玩家的手牌
* @param $pai 玩家摸到的牌
* @return array (i=>array(打的什么牌, array(a, b, c)具体胡的牌)
*/
function canTingPai($playerPai, $pai){
$tingArray = array();
$checkTingPai = $this->processPlayerPai($playerPai, $pai);
//基本思路,假设打出任意一张牌,且加入任意一张牌能胡牌,那这张牌就可以听牌
for($i = 0; $i < count($checkTingPai); $i++){
//如果已经判断过这张牌,跳过
if($this->isTingPai($tingArray, $checkTingPai[$i])){
continue;
}
//保存现有牌组
$tempArray = $checkTingPai;
//按照顺序,从牌组中移除第i张牌,相当于打了这张牌
array_splice($tempArray, $i, 1);
//检查能否听牌
$r = $this->doCheckTingPai($tempArray);
if(count($r) > 0){
array_push($tingArray, array($checkTingPai[$i], $r));
}
}
return $tingArray;
}
/**
* 辅助函数,用于判断当前这张牌是否判断过听牌
* @param $tingPaiArray 已经听牌的结果
* @param $pai 判断的牌
* @return bool TRUE/FALSE
*/
private function isTingPai($tingPaiArray, $pai){
for($i = 0; $i < count($tingPaiArray); $i++){
if($tingPaiArray[$i][0] == $pai){
return true;
}
}
return false;
}
/**
* 实际检测听牌的函数,基本思路为遍历所有牌型
* @param $processedPai
* @return array
*/
public function doCheckTingPai($processedPai){
//记录能胡牌的数组,也就是在打出某张牌后能听的牌
$result = array();
//便利所有牌
for($i = 0; $i < 3; $i++){
for($j = 1; $j <= 9; $j++){
//先保存现有牌型
$tempArray = $processedPai;
//压入任意牌型
array_push($tempArray, $i * 10 + $j);
//排序
sort($tempArray);
//检查当前牌组能否胡牌
$r = $this->doCheckHu($tempArray);
//如果可以,则为所听的牌
if($r){
array_push($result, $i*10+$j);
}
//检查特殊牌型能不能胡牌
$r = $this->canSevenHu($tempArray);
if($r)
array_push($result, $i * 10 + $j);
}
}
return $result;
}
/**
* 获取胡牌情况的函数,
* @param $playerPai 玩家手牌
* @param $pengPai 玩家碰的牌
* @param $gangPai 玩家杠的牌
* @return array(QING_YI_SE=> 0/1, DUI_ZI_HU=>0/1, JIANG_DUI=>0/1, 'GOU'=>int, 'QUE'=>0/1)
*/
public function getHuInfo($playerPai, $pengPai, $gangPai,$pai){
$tempPai = array($playerPai, $pengPai, $gangPai);
$returnArray = array(
QING_YI_SE => $this->checkQing($tempPai),
DUI_ZI_HU => $this->checkDuiZiHu($tempPai),
JIANG_DUI => $this->checkJiangDui($tempPai),
AN_QI_DUI => $this->canSevenHu($playerPai),
HONG_WU => $this->checkHongWu($playerPai,$pai),
'yaoJiuDui'=>$this->checkYaoJiuDui($tempPai),
'GOU' => $this->checkGou($tempPai),
'QUE' => $this->isQue($tempPai),
);
return $returnArray;
}
/**
* 检查缺的函数
* @param $pai,打包过的牌,[0]玩家手牌,[1]碰牌,[2]杠牌
* @return int 1/0
*/
function isQue($pai){
$hasTong = false;
$hasWan = false;
$hasTiao = false;
$temp = array_merge($pai[0], $pai[1], $pai[2]);
sort($temp);
for($i = 0; $i < count($temp); $i++){
if($temp[$i] > 0 && $temp[$i] <= 10){
$hasTong = true;
}
if($temp[$i] >= 11 && $temp[$i] < 20){
$hasTiao = true;
}
if($temp[$i] >= 21 && $temp[$i] < 30){
$hasWan = true;
}
}
return !($hasWan && $hasTong && $hasTiao);
}
/**
* 检查杠牌加翻的情况
* @param $tempPai
* @return int
*/
public function checkGou($tempPai){
//首先得到手牌的个数,如果有手牌个数为4,即有杠加翻情况
$hand_count = array_count_values($tempPai[0]);
$gouCount = 0;
foreach($hand_count as $pai => $paiCount){
if($paiCount == 4){
$gouCount++;
}
}
//然后检查碰牌,是否有勾的情况,检查方法为碰的牌在手牌中出现的牌
for($i = 0; $i < count($tempPai[1]); $i++){
foreach($hand_count as $pai => $paiCount){
if($paiCount == 1 && $pai == $tempPai[1][$i]){
$gouCount++;
}
}
}
//最后看杠的牌的情况
return $gouCount + count($tempPai[2]);
}
/**
* 检查是否出现2、5、8将对胡的情况
* @param $tempPai, 打包过的牌
* @return int 0/1
*/
public function checkJiangDui($tempPai){
//先检查是否为对子胡
$r = $this->checkDuiZiHu($tempPai);
if($r != DUI_ZI_HU){
return 0;
}
//如果是对子胡
else{
//检查手牌是否都为将牌
$handPai = $tempPai[0];
for($i = 0; $i < count($handPai); $i++){
if(!$this->checkJiangPai($handPai[$i])){
return 0;
}
}
}
//检查玩家碰过的牌是否都为将牌
for($i = 0; $i < count($tempPai[2]); $i++){
if(!$this->checkJiangPai($tempPai[2][$i]))
return 0;
}
//检查玩家杠过的牌是否都为将牌
for($i = 0; $i < count($tempPai[3]); $i++){
if(!$this->checkJiangPai($tempPai[3][$i])){
return 0;
}
}
return 1;
}
/**
* 检查是否出现1、9对胡的情况
* @param $tempPai, 打包过的牌
* @return int 0/1
*/
public function checkYaoJiuDui($tempPai){
//先检查是否为对子胡
$r = $this->checkDuiZiHu($tempPai);
if($r != DUI_ZI_HU){
return 0;
}
//如果是对子胡
else{
//检查手牌是否都为将牌
$handPai = $tempPai[0];
for($i = 0; $i < count($handPai); $i++){
if(!$this->checkYaoJiuPai($handPai[$i])){
return 0;
}
}
}
//检查玩家碰过的牌是否都为将牌
for($i = 0; $i < count($tempPai[2]); $i++){
if(!$this->checkYaoJiuPai($tempPai[2][$i]))
return 0;
}
//检查玩家杠过的牌是否都为将牌
for($i = 0; $i < count($tempPai[3]); $i++){
if(!$this->checkYaoJiuPai($tempPai[3][$i])){
return 0;
}
}
return 1;
}
/**
* 辅助函数,检查某张牌是否为1、9将牌
* @param $pai
* @return bool
*/
public function checkYaoJiuPai($pai){
$reminder = $pai % 10;
if($reminder != 1 || $reminder != 9){
return false;
}
return true;
}
/**
* 辅助函数,检查某张牌是否为2、5、8将牌
* @param $pai
* @return bool
*/
public function checkJiangPai($pai){
$reminder = $pai % 10;
if($reminder != 2 || $reminder != 5 || $reminder != 8){
return false;
}
return true;
}
/**
* 检查是否为清一色的函数
* @param $tempPai
* @return int
*/
public function checkQing($tempPai){
// print_r($tempPai);
//做一个类型的循环
for($type = 1; $type <= 3; $type++){
$r = $this->doCheckQing($tempPai, $type);
//如果某一种类型检查出为清一色,则返回结果,否则继续检查
if($r){
return 1;
}
}
//最后则不为清一色
return 0;
}
/**
* 实际检查是否为清一色的辅助函数
* @param $tempPai,打包后的牌
* @param $type,要检查的类型:1、筒子,2、条子,3、万子
* @return bool
*/
public function doCheckQing($tempPai, $type){
//分别检查手牌,碰的牌,杠的牌
for($i = 0; $i < 3; $i++){
$paiArray = $tempPai[$i];
for($j = 0; $j < count($paiArray); $j++){
//取牌的取整结果
$reminder = ceil($paiArray[$j] / 10);
if($reminder != $type){
return false;
}
}
}
return true;
}
/**
* 检查是否为对子胡的函数
* 对子胡理论上的样式为:AA + n * BBB
* @param $tempPai
* @return bool
*/
public function checkDuiZiHu($tempPai){
$playerPai = $tempPai[0];
$firstJiang = false;
$pai_count = array_count_values($playerPai);
foreach($pai_count as $pai => $count){
//如果某个牌的数量<2,不可能是对子胡
if($count < 2){
return 0;
}
//如果有牌的数量为2
if($count == 2){
//且是唯一一个,则还能继续判断
if(!$firstJiang){
$firstJiang = true;
}
//否则,不可能是对子胡
else{
return 0;
}
}
//如果手中的牌有4张一样的,也不可能是对子胡
if($count == 4){
return 0;
}
}
return 1;
}
/**
* 检查能否胡龙七对的函数
* @param $tempPai:打包过的牌
* @return bool
*/
public function checkLongQiDui($tempPai){
//先检查是否为七小队牌型
$r = $this->canSevenHu($tempPai[0]);
//如果是
if($r){
//检查是否有两个勾,如果有,则为龙七对
$pai_count = array_count_values($tempPai[0]);
$gouCount = 0;
foreach($pai_count as $pai => $count){
if($count == 4){
$gouCount++;
}
}
if($gouCount == 2){
return true;
}
else{
return false;
}
}
else{
return false;
}
}
}
?>