正则表达式一般用来描述字符串,所以我们把麻将和牌的组合先变成字符串。麻将中包含以下牌面:
- 条:幺鸡、二条、三条、四条、五条、六条、七条、八条、九条
- 筒:一筒、二筒、三筒、四筒、五筒、六筒、七筒、八筒、九筒
- 万:幺万、二万、三万、四万、五万、六万、七万、八万、九万
- 其他:红中
牌的摆放顺序和搭配也很重要,能影响人的判断,决定能否及时上听、避免漏和;同样,也能够简化正则匹配问题。所以在这里假设已经按搭配成「刻、顺子、对子、杠」,顺子中以上面列表顺序从小到大依次排好。
和牌后共计 14 张,再加上明杠或暗杠 0 到 3 个,所以必然符合 正则一——张数:
^(?:幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]){14,17}$
牌中必然有至少一个的刻或杠,符合 正则二——杠刻:
(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3}
牌中也可能有顺子,符合 正则三——顺子:
(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k))
另外还必须有一对,考虑到排序后对子前后的牌可能是顺子中的大牌或小牌,所以不做断言,只需符合 正则四——对子:
(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k
局部地区中不允许清一色,应该符合 正则五——两门:
(?:\[鸡条\].\*\[万筒\])|(?:筒.\*\[万条鸡\])|(?:万.\*\[筒条鸡\])
最后,有一个非常严肃的问题,这副牌必须符合 正则六——不断幺:
\[幺一九中\]
好,我们现在整体回顾一下,和牌应该:
- 张数符合;
- 整副牌符合「此处可能有对子,顺子或杠刻,此处可能有对子,顺子或杠刻,此处可能有对子,此处可能有对子,顺子或杠刻,此处可能有对子,顺子或杠刻,此处可能有对子」;
- 杠刻至少一个;
- 杠最多有三个;
- 对子只有一个;
- 最多缺一门;
- 不能断幺;
- 牌面相同的牌不得超过4张(有人会出老千出得这么明显么!)。
下面以之前的六个正则为基础,进行一些加强判断。
整副牌由被「对子」隔开的连续的「顺子、杠刻」组成,正则二三四可以归纳为 正则七——结构:
^(?:(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k)?(?:(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k)))|(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k)?)+$
杠刻前后可以是首尾,或者杠刻顺子对;正则七可以进化为 正则八——必有杠刻:
^(?:(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})|(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k))|(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k))\*(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})(?:(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})|(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k))|(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k))\*$
局部地区要求上听时手里至少要有四张,所以即使在可和对对碰时允许有两个相同牌面的对子,也只可能有最多三杠;前后可能是首尾或「顺子、刻、对子」的杠,允许有 0 到 3 个。3个杠时,张数17张,除去杠之外的5张牌应该由至少一个对子与一套「顺子、刻」组成,无论如何组合,都不会发生出现同样四张牌、却不是杠的情况;验证非4杠时也就不用考虑两对的情况,无需为对子再做负断言,正则有所简化;即符合 正则九——至多三杠:
^(?!(?:.\*(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{3}.\*){4,}).\*$
「幺鸡幺鸡幺鸡三条四条五条五条六条七条幺万幺万幺万九万九万」不应认为包含对五条;但「幺鸡幺鸡幺鸡三条四条五条五条五条五条六条七条二万二万二万」则应算做有对五条,所以对子两侧应该是首尾或顺子杠刻,符合 正则十——仅一对:
^(?:(?:(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k)))|((?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3}))\*(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k)(?:((?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k)))|((?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3}))\*$
综上,以相互独立的条件中任意一个做正则主体语句,其他条件做断言,即可构造出匹配麻将和牌的正则。例,正则十一——和牌:
^(?=^(?:幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]){14,17}$)(?=(?:.\*\[鸡条\].\*\[万筒\].\*)|(?:.\*筒.\*\[万条鸡\].\*)|(?:.\*万.\*\[筒条鸡\].\*))(?=.\*\[幺一九中\].\*)(?=^(?:(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})|(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k))|(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k))\*(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})(?:(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3})|(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k))|(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k))\*$)(?=^(?!(?:.\*(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{3}.\*){4,}).\*$)(?:(?:(?:(?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k)))|((?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3}))\*(?:(?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k)(?:((?:幺鸡二条三条|一筒二筒三筒|幺万二万三万|(?:二(?\[条筒万\])三\\k四\\k)|(?:三(?\[条筒万\])四\\k五\\k)|(?:四(?\[条筒万\])五\\k六\\k)|(?:五(?\[条筒万\])六\\k七\\k)|(?:六(?\[条筒万\])七\\k八\\k)|(?:七(?\[条筒万\])八\\k九\\k)))|((?(幺鸡|一筒|幺万|红中|\[二三四五六七八九\]\[条筒万\]))\\k{2,3}))\*)$
目测该表达式会有压缩精简的空间,但恐使其仅存不多的可读性丧失殆尽。那么有人会问了,这样只匹配了哈尔滨麻将,那么「吉津冀秦晋鄂皖沪浙湘蜀粤」各地的麻将和牌正则能不能都给写了 呢?对此,我的回答只有一个字:
格稳! : (
嗯,严肃一点,其实基于正则表达式的断言,可以像搭积木一样使用不同的组合来适应各种麻将的规则。适当的捕获分组甚至能解决番数的计算,不过这只是未验证过的猜测。
Webmention
Comment Form
路过看看火了的留言
笑喷了....
V2EX观光团
别人没感觉,反正我是真是看不惯你这种sb
见到此话之博主,一年之内代码全是bug无法调通
不通过or删此话着,你就等着一户口吧
@v2ex_tour 别人没感觉,反正我是真是看不惯你这种sb
https://www.v2ex.com/t/193957