java写出麻将和牌的算法,麻将胡牌算法

犹豫工作和自己学习了一些新的东西,今天打开博客吓自己一跳,原来自己这么久没有更新博客了。看来以后还是要坚持每周最少写一篇博客啊。

在讲解麻将胡牌算法之前,先说说为什么写这么一篇博客吧。在做项目中,其实前辈们早就封装好了一些胡牌的检测算法,不过我还算是一个比较喜欢刨根问到底的人,每次调用别人写好的算法的时候总是想知道算法的具体实现。然而在看算法具体实现的时候,发现里面一个二维矩阵有点复杂,并且没有没有注释。所以改换另一条路去研究胡牌算法,先去网上找了找一般胡牌算法实现的原理然后自己写了写,就是下面即将展示给大家的胡牌算法(不含特殊牌型的检测)

1.名词解释

名词

解释

即胡牌必须具备的一个对子

克子

即三个相同的牌

顺子

即同种牌型三个连续的牌

胡牌

即手中的牌除了一对将之外全部能组成克子或者顺子

2.数据结构

2.1.麻将矩阵(二维矩阵)

mahjongMatrix = {

[0] = {[-1]=0,[0]=0,[1]=0,[2]=0,[3]=0,[4]=0,[5]=0,[6]=0,[7]=0,[8]=0,[9]=0,[10]=0,[11] = 0}, --万

[1] = {[-1]=0,[0]=0,[1]=0,[2]=0,[3]=0,[4]=0,[5]=0,[6]=0,[7]=0,[8]=0,[9]=0,[10]=0,[11] = 0}, --筒

[2] = {[-1]=0,[0]=0,[1]=0,[2]=0,[3]=0,[4]=0,[5]=0,[6]=0,[7]=0,[8]=0,[9]=0,[10]=0,[11] = 0}, --条

}

麻将矩阵的第一维

Key :表示麻将的类型,比如万,筒,条。当然有一些地区麻将含有东南西北等,这样也只需要扩展麻将矩阵的一维数据而已

Value : 即这个类型的麻将集合cardList

麻将矩阵的第二维

Key : 表示麻将的值,1~9。可能有同学有疑问 为什么会有-1,0,10,11。其实我在这里添加这几个值 单纯是为了在检测顺子的时候不需要考虑边界情况简化算法

"Value" : 即这个麻将值的牌有几个

2.2.麻将

card = 0x0101 --四位的16进制的数表示一张牌

第一位表示这张牌的类型 如:0->万 1->筒 2->条

第二位表示表示这张牌的值 如:1表示牌值是1,结合第一位就能推断出这张牌是1万,1筒还是1条

第三四位表示这张牌的索引,这样可以确保每一张牌都有唯一的编号,如:0x0201表示第一张2万

3.算法核心流程

玩过麻将都应该知道,一般判断手上的牌是否能胡,就是检测手牌除了一对将之后剩下的牌是否能组成克字或顺子并且没有剩余的手牌。

3.1.定义手牌

local cardList = {0x0101,0x0201,0x0202,0x0301,0x0302,0x0303,0x0401,0x0402,0x0501,0x1601,0x1701,0x1801,0x0901,0x0902}

3.2.将手牌转换成麻将矩阵

上面我们也提到了检测胡牌时依据麻将矩阵,因此我们要将手牌转换成麻将矩阵

-- 麻将类型定义

local mahjongType = {

[0] = "万",

[1] = "筒",

[2] = "条",

}

-- 初始化一个麻将矩阵

function initMahjongMatrix()

local mahjongMatrix = {}

for i = 0, 3 do

mahjongMatrix[i] = {}

for j = -1, 11 do

mahjongMatrix[i][j] = 0

end

end

return mahjongMatrix;

end

-- 打印麻将矩阵

function dumpMahjongMatrix(mahjongMatrix)

local dumpInfo = "{ "

for cardType, mahjongList in pairs(mahjongMatrix) do

for mahjongValue, count in pairs(mahjongList) do

for i = 1, count do

dumpInfo = dumpInfo .. mahjongValue .. mahjongType[cardType] .. " "

end

end

end

dumpInfo = dumpInfo .. "}"

print(dumpInfo)

end

-- 将手牌转换成麻将矩阵

function cardListConvertToMatrix(cardList)

local mahjongMatrix = initMahjongMatrix()

for _, card in pairs(cardList) do

local cardType = card >> 12

local cardValue = (card >> 8) & 0x0F

mahjongMatrix[cardType][cardValue] = mahjongMatrix[cardType][cardValue] + 1

end

dumpMahjongMatrix(mahjongMatrix)

return mahjongMatrix

end

函数initMahjongMatrix即使用双重for循环初始化一个麻将举证,

函数cardListConvertToMatrix将手牌转换成麻将矩阵

首先根据我们对牌的数据结构的定义,使用card >> 12得到16进制的第一位,即牌的类型

同样card >> 8得到16进制的第一和二位,然后位与0x0F结果即为card的值

根据我们对麻将矩阵的定义,知道card的type和value也就是确定了这张牌在麻将矩阵中的位置,因此我们只需要将这张牌在麻将矩阵中对应的个数加一,即表示这张牌被存储在了麻将矩阵中

函数dumpMahjongMatrix将麻将矩阵转换成很容易看懂的数据输出

{ 6筒 7筒 8筒 1万 2万 2万 3万 3万 3万 4万 4万 5万 9万 9万 }

3.3.检测将

有了麻将矩阵,我们先检测将(为什么第一步要检测将而不是检测克字和顺子,稍后我们再来解释)

-- 深拷贝

function deepCopy(sourceData)

if type(sourceData) == "table" then

local temp = {}

for key, value in pairs(sourceData) do

temp[key] = deepCopy(value)

end

return temp

end

return sourceData

end

-- 通过去除麻将矩阵中一个将之后的麻将矩阵列表

function getMahjongMatrixListByRemoveTwoCards(mahjongMatrix)

local mahjongMatrixList = {}

for cardType, mahjongList in pairs(mahjongMatrix) do

for mahjongValue, count in pairs(mahjongList) do

if count >= 2 then

local temp = deepCopy(mahjongMatrix)

temp[cardType][mahjongValue] = temp[cardType][mahjongValue] - 2

table.insert(mahjongMatrixList, temp);

end

end

end

return mahjongMatrixList

end

函数getMahjongMatrixListByRemoveTwoCards去除麻将矩阵中一个将之后的麻将矩阵列表

因为在麻将矩阵中找到一个对子作为将,有多个可能性。但是在这个阶段不能确定那一种可能性可以胡牌,哪一种可能性不能胡牌,因此要将每一种可能性都保存起来,后续继续检测

函数deepCopy深拷贝,在去除麻将矩阵中一个将之后,不能影响下一种可能性中的数据,因此在去除一个将之前都要对麻将矩阵深拷贝一次

看一下上面的测试手牌,有几种可能性

{ 6筒 7筒 8筒 1万 3万 3万 3万 4万 4万 5万 9万 9万 }

{ 6筒 7筒 8筒 1万 2万 2万 3万 4万 4万 5万 9万 9万 }

{ 6筒 7筒 8筒 1万 2万 2万 3万 3万 3万 5万 9万 9万 }

{ 6筒 7筒 8筒 1万 2万 2万 3万 3万 3万 4万 4万 5万 }

3.4.检测句子和克字

现在便利麻将矩阵,如果只有一张的牌那么这张牌A就只能当作顺子的开头;如果有两张的牌,因为已经有将而这两张也不能组成克子,所以这两张只能当作两个顺子的开头;如果有三张这样的牌,可以组成克子,但是如果让他组成顺子则要求为AAABBBCCC与后面的三张也能组成克子,所以组成顺子或者克子本质是相同的。但是组成克子AAA的通用性要高于组成顺子AAABBBCCC所以当有三个及以上这样牌的时候优先组成克子AAA;如果有四张这样的牌,要能胡牌则需要AAAABBBBCCCC或者AAAABC,对于是先组一个顺子还是一个克子都会回到上述的情况

3.4.1.检测句子

通过上面的分析我们先检测麻将矩阵中的句子

-- 移除麻将矩阵中的句子

function removeThreeLinkCards(mahjongMatrix)

for cardType, mahjongList in pairs(mahjongMatrix) do

for mahjongValue, count in pairs(mahjongList) do

for i=1, count do

local mahjongValuePlusOneCount = mahjongList[mahjongValue+1]

local mahjongValuePlusTwoCount = mahjongList[mahjongValue+2]

if count > 0 and mahjongValuePlusOneCount > 0 and mahjongValuePlusTwoCount > 0 then

mahjongList[mahjongValue] = mahjongList[mahjongValue] - 1

mahjongList[mahjongValue+1] = mahjongList[mahjongValue+1] - 1

mahjongList[mahjongValue+2] = mahjongList[mahjongValue+2] - 1

end

end

end

end

end

函数removeThreeLinkCards移除麻将矩阵中的句子

在上面我们解释了麻将矩阵二维的含义,因此我们判断牌值为mahjongValue的这张牌,在剩余牌中是否存在牌与这张牌组成句子,我们只需要判断,在麻将矩阵中是否存在牌值为(mahjongValue+1)和牌值为(mahjongValue+2)的牌。

如果牌值为(mahjongValue+1)和牌值为(mahjongValue+2)的牌都存在,则能组成句子,我们将这三张牌从麻将矩阵中移除掉

当然牌值为mahjongValue可能存在多个,但是每次检测到能组成句子,只去处一张牌,因此这张牌有几张我们就检测几次,防止漏掉

便利麻将矩阵的时候,是从牌值为1开始找的,因此在检测中间某一牌值mahjongValue的时候,不必回头检测是否存在牌值为(mahjongValue-1)和(mahjongValue-2)的牌

3.4.2.检测克子

检测克子比检测句子就更加简单了

-- 检测克子

function removeTheSameThreeCards(mahjongMatrix)

for cardType, mahjongList in pairs(mahjongMatrix) do

for mahjongValue, count in pairs(mahjongList) do

if count >= 3 then

mahjongList[mahjongValue] = mahjongList[mahjongValue] - 3

end

end

end

end

只需要检测牌值为mahjongValue的牌在麻将矩阵中的数量是否大于等于三

3.5.是否胡牌

有上面分析可知,如果麻将矩阵中所有的元素全部为0表示手中的牌除了一对将之外全部能组成克子或者顺子,即可以胡牌

-- 检测矩阵中元素是否全部为0

function checkMatrixAllElemEqualZero(mahjongMatrix)

for cardType, mahjongList in pairs(mahjongMatrix) do

for mahjongValue, count in pairs(mahjongList) do

if count ~= 0 then

return false

end

end

end

return true

end

-- 检测是否胡牌

function checkHu(cardList)

local mahjongMatrix = cardListConvertToMatrix(cardList)

local mahjongCardList = getMahjongMatrixListByRemoveTwoCards(mahjongMatrix)

for _, matrix in ipairs(mahjongCardList) do

removeThreeLinkCards(matrix)

removeTheSameThreeCards(matrix)

local result = checkMatrixAllElemEqualZero(matrix)

if result == true then

return true

end

end

return false

end

函数checkMatrixAllElemEqualZero便利麻将矩阵判断所有元素是否为0

函数checkHu检测是否胡牌

3.6.回答上面的疑问

上面分析算法的时候留下了一个问题,就是为什么第一步要检测将而不是检测克字和顺子

第一,因为如果要糊牌,必须要有唯一的一个将。而克子和顺子是非必要的

第二,如果出现ABCCCDEF这样的牌型的时候,先检测顺子或者先检测克子都不会将CC作为将

4.欢迎讨论

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
麻将胡牌算法涉及到很多细节和规则,下面是一个基本的JAVA实现: 1. 首先,我们需要一个表示的数据结构,可以使用一个数组或者列表来存储玩家手型。 ```java public class Mahjong { public static final int MAX_COUNT = 34; // 麻将的总数 public static final int MAX_WEAVE = 4; // 最大组合数 public static final int COLOR_WAN = 0; // 万 public static final int COLOR_TIAO = 1; // 条 public static final int COLOR_TONG = 2; // 筒 public static final int COLOR_FENG = 3; // 风 public static final int COLOR_JIAN = 4; // 箭 public static final int CARD_FENG_EAST = 27; // 东 public static final int CARD_FENG_SOUTH = 28; // 南 public static final int CARD_FENG_WEST = 29; // 西 public static final int CARD_FENG_NORTH = 30; // 北 public static final int CARD_JIAN_ZHONG = 31; // 中 public static final int CARD_JIAN_FA = 32; // 发 public static final int CARD_JIAN_BAI = 33; // 白 private int[] cards = new int[MAX_COUNT]; private int count = 0; // 添加一张 public void addCard(int card) { cards[card]++; count++; } // 移除一张 public void removeCard(int card) { if (cards[card] > 0) { cards[card]--; count--; } } // 获取指定的数量 public int getCardCount(int card) { return cards[card]; } // 清除手 public void clear() { for (int i = 0; i < MAX_COUNT; i++) { cards[i] = 0; } count = 0; } // 获取所有的数量 public int getCount() { return count; } } ``` 2. 接下来,我们需要实现一个判断是否能够胡牌的函数。这个函数需要考虑到麻将的基本规则,如顺子、刻子、将等。 ```java public class MahjongUtils { public static final int MAX_HU_COUNT = 14; // 最大胡牌数 // 判断是否能够胡牌 public static boolean checkHu(Mahjong mahjong) { int[] cards = mahjong.getCards(); int count = mahjong.getCount(); // 如果的数量不是3n+2,则不能胡牌 if ((count % 3) != 2) { return false; } // 拆分型,分别判断顺子、刻子、将 int[] cardsCopy = Arrays.copyOf(cards, cards.length); int[] huCards = new int[MAX_HU_COUNT]; int huCount = 0; for (int i = 0; i < Mahjong.MAX_COUNT; i++) { if (cardsCopy[i] > 0) { // 判断顺子 if (i < Mahjong.CARD_FENG_EAST) { if (i % 9 <= 6 && cardsCopy[i + 1] > 0 && cardsCopy[i + 2] > 0) { cardsCopy[i]--; cardsCopy[i + 1]--; cardsCopy[i + 2]--; } } // 判断刻子 if (cardsCopy[i] >= 3) { cardsCopy[i] -= 3; } // 判断将 if (cardsCopy[i] == 2) { huCards[huCount++] = i; cardsCopy[i] -= 2; } } } // 如果剩余的型能够组成完整的顺子和刻子,则说明可以胡牌 for (int i = 0; i < Mahjong.MAX_COUNT; i++) { if (cardsCopy[i] != 0) { return false; } } return true; } } ``` 3. 最后,我们可以编写一个测试函数来验证算法的正确性。 ```java public class MahjongTest { public static void main(String[] args) { Mahjong mahjong = new Mahjong(); mahjong.addCard(Mahjong.CARD_WAN_1); mahjong.addCard(Mahjong.CARD_WAN_1); mahjong.addCard(Mahjong.CARD_WAN_1); mahjong.addCard(Mahjong.CARD_WAN_2); mahjong.addCard(Mahjong.CARD_WAN_3); mahjong.addCard(Mahjong.CARD_WAN_4); mahjong.addCard(Mahjong.CARD_WAN_5); mahjong.addCard(Mahjong.CARD_WAN_6); mahjong.addCard(Mahjong.CARD_WAN_7); mahjong.addCard(Mahjong.CARD_WAN_8); mahjong.addCard(Mahjong.CARD_WAN_9); mahjong.addCard(Mahjong.CARD_TONG_1); mahjong.addCard(Mahjong.CARD_TONG_1); boolean isHu = MahjongUtils.checkHu(mahjong); System.out.println("是否胡牌:" + isHu); } } ``` 这样,我们就实现了一个简单的麻将胡牌算法。当然,实际上麻将的规则非常复杂,这个算法只是一个基本的实现,还需要根据具体的麻将规则进行进一步的优化和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值