如何计算麻将中的数字?

时间:2010-11-21 16:55:24

标签: algorithm mahjong

这是我earlier question about deciding if a hand is ready的后续内容。

对麻将规则的了解非常好,但基于扑克或罗姆的背景也足以理解这个问题。

  

在Mahjong 14瓷砖(瓷砖就像   扑克中的牌)被安排到4集   和一对。一直(“123”)   正好使用3个瓷砖,而不是更多而不是   减。一套同类(“111”)   也包括3个瓷砖。这个   导致3 * 4 + 2 = 14的总和   瓦片。

     

像Kan这样的例外   还是十三个没有的孤儿   相关的。颜色和价值范围   (1-9)也不重要   算法

一只手由13块瓷砖组成,每次轮到我们选择一块新瓷砖并且必须丢弃任何瓷砖,所以我们留在13块瓷砖上 - 除非我们能够使用新挑选的瓷砖获胜。

可以安排形成4组和一对的手是“准备好”的。只需要交换1块瓷砖的手称为“tenpai”,或“1 ready from ready”。任何其他手牌都有一个shanten数字,表示需要交换多少牌块才能在tenpai中。因此,具有shanten数量为1的手需要1个瓷砖为tenpai(并且相应地准备2个瓷砖)。一个shanten数为5的手需要5个瓷砖才能成为tenpai等等。

我正在尝试计算一只手的数字。谷歌搜索了几个小时,阅读了关于这个主题的多篇文章和论文,这似乎是一个未解决的问题(蛮力方法除外)。我能找到的最接近的算法依赖于偶然性,即它无法在100%的时间内检测到正确的数字。

规则

我将解释一下实际规则(简化),然后我的想法如何解决这个任务。在麻将中,有4种颜色,3种普通颜色,如纸牌游戏(王牌,心脏......),被称为“男人”,“别针”和“苏”。这些颜色每个从1到9,可用于形成直道和同类组。第四种颜色称为“荣誉”,仅可用于同类组,但不适用于直道。七个荣誉称为“E,S,W,N,R,G,B”。

让我们看看一个tenpai手的例子:2p, 3p, 3p, 3p, 3p, 4p, 5m, 5m, 5m, W, W, W, E。接下来我们选择一个E。这是一个完整的麻将牌(准备好),由2-4针街道组成(请记住,针脚可用于直道),3针三联,5人三人,W三人和E对。

将我们原来的手稍微改为2p, 2p, 3p, 3p, 3p, 4p, 5m, 5m, 5m, W, W, W, E,我们得到了一个单手,即它需要一个额外的瓷砖是十派。在这种情况下,换一个2p换3p会让我们回到tenpai所以通过画一个3p和一个E我们赢了。

1p, 1p, 5p, 5p, 9p, 9p, E, E, E, S, S, W, W是2-shanten的一手牌。有1个完成的三联体和5对。我们最终需要一对,所以一旦我们选择1p,5p,9p,S或W中的一个,我们需要丢弃其中一对。示例:我们选择一个1针并丢弃一个W.现在手是1-shanten,看起来像这样:1p, 1p, 1p, 5p, 5p, 9p, 9p, E, E, E, S, S, W。接下来,我们等待5p,9p或S.假设我们选择5p并丢弃剩余的W,我们得到:1p, 1p, 1p, 5p, 5p, 5p, 9p, 9p, E, E, E, S, S。这只手在十柱中可以在9针或S上完成。

为避免更长时间地绘制此文字,您可以在wikipedia或使用google上的各种搜索结果之一阅读更多示例。所有这些都有点技术性,所以我希望上面的描述就足够了。

算法

如上所述,我想计算一只手的数字。我的想法是根据颜色将瓷砖分成4组。接下来,所有图块在其各自的组内被分类成集合,以便在荣誉组中以三元组,对或单个图块结束,或者另外,在3个正常组中结合。已完成的集将被忽略。对数进行计数,最终数量减少(最后我们需要1对)。单个图块将添加到此编号。最后,我们将数字除以2(因为每次我们选择一个能让我们接近tenpai的好瓷砖时,我们可以摆脱另一个不需要的瓷砖)。

但是,我不能证明这个算法是正确的,而且我也很难将直道包含在近距离包含许多瓷砖的困难群体中。各种想法都值得赞赏。我正在使用.NET进行开发,但也欢迎使用伪代码或任何可读语言。

6 个答案:

答案 0 :(得分:5)

我已经考虑过这个问题了。要查看最终结果,请跳到上一部分。

第一个想法:蛮力方法

首先,我写了一个蛮力的方法。它能够在一分钟内识别出3-shanten,但它不是很可靠(有时候太长了,即使只有3-shanten也无法枚举整个空间)。

蛮力方法的改进

浮现在脑海中的一件事是为蛮力方法添加一些智慧。天真的方法是添加任何剩余的瓷砖,看它是否产生麻将,如果没有递归尝试,直到找到它。假设剩下大约30个不同的瓷砖,最大深度为6(我不确定7 + - 手是否可能 [编辑:根据后面开发的公式,最大可能的数字是( 13-1)* 2/3 = 8] ),我们得到(13 * 30)^ 6种可能性,这是大的(10 ^ 15范围)。

然而,没有必要将每个剩余的瓷砖放在手中的每个位置。由于每种颜色本身都必须完整,我们可以将瓷砖添加到相应的颜色组中,并记下该组本身是否完整。总共只有1对的细节并不难添加。这样,有大约(13 * 9)^ 6种可能性,即大约10 ^ 12,更可行。

更好的解决方案:修改现有的麻将检查器

我的下一个想法是使用我早期编写的代码来测试Mahjong并以两种方式修改它:

  • 在找到无效的手牌时不要停止但是记下缺失的牌
  • 如果有多种可能的方式来使用磁贴,请尝试所有这些方法

这应该是最佳的想法,并且添加一些启发式算法应该是最佳算法。但是,我发现它很难实现 - 但它绝对是可能的。我希望首先更容易编写和维护解决方案。

使用领域知识的高级方法

与经验丰富的玩家交谈时,似乎可以使用一些法律。例如,一组3个图块永远不需要被分解,因为它永远不会减少数字。但是,它可以以不同的方式使用(例如,对于111或123组合)。

枚举所有可能的3组并为每个组创建一个新的模拟。删除3组。现在在结果手中创建所有2组并模拟每个将它们改进为3组的区块。同时,模拟任何被移除的1组。继续这样做,直到所有3组和2组都消失。最后应该留下1套(即单个瓷砖)。

从实施和最终算法中学习

我实现了上述算法。为了便于理解,我用伪代码写下来了:

Remove completed 3-sets
If removed, return (i.e. do not simulate NOT taking the 3-set later)

Remove 2-set by looping through discarding any other tile (this creates a number of branches in the simulation)
If removed, return (same as earlier)

Use the number of left-over single tiles to calculate the shanten number

顺便说一句,这实际上非常类似于我自己计算数字时采用的方法,显然从来没有产生过高的数字。

这几乎适用于所有情况。但是,我发现有时候早期的假设(“删除已经完成的3组并不是一个坏主意”)是错误的。反例:23566M 25667P 159S。重要的部分是25667。通过删除567 3集,我们最终得到一个左侧6磁贴,导致5-shanten。最好使用两个单个图块来形成56x67x,从而导致整体为4-shanten。

要修复,我们必须删除错误的优化,导致此代码:

Remove completed 3-sets
Remove 2-set by looping through discarding any other tile
Use the number of left-over single tiles to calculate the shanten number

我相信这总能准确找到最小的数字,但我不知道如何证明这一点。所花时间处于“合理”范围内(在我的机器上最多10秒,通常为0秒)。

最后一点是计算剩余单个图块数量的shanten。首先,显而易见的是,数字的格式为3*n+1(因为我们从14个图块开始,总是减去3个图块)。

如果剩下1个瓷砖,我们已经很好了(我们只是在等待最后一对)。剩下4个瓷砖,我们必须丢弃其中的2个以形成3套,再次让我们留下一个瓷砖。这导致另外2个丢弃。有7个瓷砖,我们有2次2丢弃,增加4.等等。

这导致了简单的公式shanten_added = (number_of_singles - 1) * (2/3)

所描述的算法运行良好并通过了我的所有测试,因此我认为它是正确的。如上所述,我无法证明这一点。

由于该算法首先删除了最可能的切片组合,因此它具有内置优化功能。添加一个简单的检查if (current_depth > best_shanten) then return;即使对于高数字也很有效。

答案 1 :(得分:2)

我最好的猜测是A *灵感的方法。你需要找到一些永远不会过高估计shanten数的启发式算法,并使用它来搜索只有在可以足够快地进入就绪状态的区域中的强力树。

答案 2 :(得分:1)

更正算法示例:syanten.cpp

递归剪切形式从手按顺序:集,对,不完整的形式, - 并计算它。在所有变化中。结果是所有变种的Shanten值最小: Shanten = Min(Shanten,8 - * 2 - - )

C#sample(从c ++重写)可以找到here(俄语)。

答案 3 :(得分:1)

在这里看看:ShantenNumberCalculator。快速计算shanten。和一些相关的东西(日语,但有代码示例)http://cmj3.web.fc2.com

算法的本质:以所有可能的方式切出所有对,集合和未完成的形式,从而找到shanten数的最小值。 普通手的最大柄数:8。

也就是说,我们有4套和1对的起点,但每套只有1个图块(总共13-5 = 8)。 因此,一对将减少杆的数量,两个(与其余部分隔离)相邻的瓦片(预设)将使杆的数量减少一个, 一套完整的(3个相同或3个连续的图块)将使杆数减少2,因为两个合适的图块进入了一个孤立的图块。

Shanten = 8-组* 2-对-预设

答案 4 :(得分:0)

确定您的手是否已经在tenpai听起来像multi-knapsack problem。贪心算法不起作用 - 正如Dialecticus指出的那样,你需要考虑整个问题空间。

答案 5 :(得分:0)

我做了一点思考,并提出了一个与mafu相比略有不同的公式。首先,考虑一只手(一只非常可怕的手):

1s 4s 6s 1m 5m 8m 9m 9m 7p 8p West East North

通过使用mafu的算法我们所能做的就是抛出一对(9m,9m)。然后我们剩下11个单打。现在,如果我们应用mafu的公式,我们得到(11-1)* 2/3这不是一个整数,因此不能是一个shanten数。这是我想出来的地方:

  

N =((S + 1)/ 3) - 1

N代表shanten数字,S代表分数总和。 什么是得分?它需要一些拼贴才能完成不完整的设置。例如,如果您手中有(4,5),则需要3或6才能使其成为完整的3组,即只有一个图块。所以这个不完整的对得分为1.因此,(1,1)只需要1成为3组。任何单个图块显然需要2个图块才能成为3套并获得分数2.任何完整的套装当然都会得到0分。注意我们忽略了单身成对的可能性。现在,如果我们尝试在上面的手中找到所有不完整的集合,我们得到:

  

(4s,6s)(8m,9m)(7p,8p)1s 1m 5m 9m West East North

然后我们计算其得分之和= 1 * 3 + 2 * 7 = 17。 现在,如果我们将这个数字应用于上面的公式,我们得到(17 + 1)/ 3 - 1 = 5,这意味着这只手是5-shanten。它比阿列克谢更复杂,而且我没有证据,但到目前为止它似乎对我有用。请注意,这样的手可以用其他方式解析。例如:

  

(4s,6s)(9m,9m)(7p,8p)1s 1m 5m 8m West East North

然而,根据公式,它仍然得到17分和5分的得分。我也无法证明这一点,这比Alexey的公式稍微复杂一些,但也引入了可以应用(?)到别的东西的分数。