目录
麻将基础
在介绍代码前先介绍一下麻将的基础:
一副麻将牌里分别有4张1-9万,4张1-9饼,4张1-9索和四张东南西北白发中
万、饼、索花色三张连续的牌叫顺子,例如:1万2万3万,5索6索7索
任何一种花色三张相同的牌叫刻字,例如:4饼4饼4饼,9索9索9索
任何一种花色两张相同的牌叫对子,例如:5万5万,东风东风
文章中定义1-9万为1-9m,1-9饼为1-9p,1-9索为1-9s,东南西北白发中分别为1-7z,例如:1饼2饼3饼为1p2p3p
这里介绍下胡牌的基本公式:m顺子+n刻子+1对子(其中m+n=4)
光看这个公式可能有点抽象,化成文字理解就是:手牌中有一个对子,且顺子和刻子数量相加等于4,就是胡牌了。
大部分地区都是这样的规则:手上有13张牌+一张摸进的牌,如果这张摸进的牌和剩下的13张牌符合上述胡牌公式,就可以推牌喊一声:“胡啦!”
为什么用对子法?
讲了那么多,现在开始进入正题:用c#如何判定一套14张牌是否符合胡牌牌型呢?那么就聊一聊我的方法—对子法, 以及对子法的好处
要让电脑判定是否胡牌,首先得让电脑知道手牌中有几个顺子、刻子、对子,对应刚刚已经介绍了的胡牌公式:m顺子+n刻子+1对子(其中m+n=4),那第一步要先分解手牌,例如4m5m6m 7p8p9p 6s6s6s就分解为两个顺子加一个刻子,但如果直接正向分解(即先分解出顺子刻子,再分解对子),就会出现这样的情况:3m4m4m4m4m5m6m可以分解为一个顺子3m4m5m 一个刻子4m4m4m和一张孤立牌6m或者两个对子4m4m 4m4m和孤立牌3m 5m6m等多种牌型,这样的话某些牌型可能衍生出非常多种分解方法,不利于程序快速运算。
顺子和刻子虽然有很多种情况,但是对子只有一种情况,因此先分解出对子,再分解顺子和对子,面对的情况就会减少,以刚刚的3m4m4m4m4m5m6m为例,先分解出一对4m4m,再分解出3m4m5m,剩下4m和6m孤立,这样就能马上判断出一套手牌是否满足胡牌牌型了。一套牌中最多也只会有7个对子,但顺子和刻子是能组合出很多出来的,一副牌没有对子,也马上就能判定不能胡牌,因此先找出对子,分解的情况最少,程序运行效率也最高。
程序思路
上文提到定义1-9万为1-9m,1-9饼为1-9p,1-9索为1-9s,东南西北白发中分别为1-7z,c#程序中也是用这个代码代替对应的牌。
先说一个对子法思路例子:1m1m1m2m3m4m5m6m7m8m9m9m9m9m
这个是大名鼎鼎的纯正九莲宝灯
用对子法先找出对子,再分解顺子刻子,这里只有有两个对子:一万和九万
找出一万对子的话,剩下的牌就分解为1m2m3m 4m5m6m 7m8m9m 9m9m9m,正好符合胡牌公式
找出九万对子的话,剩下的牌为1m1m1m 2m3m4m 5m6m7m和8m9m9m此时顺子加刻子的数量为3,不符合胡牌公式,但找出一万对子的话是符合的,这副牌也自然就是胡牌状态了。
再看一个例子:3m3m7p8p9p3s4s4s5s5s6s7z7z7z
这一套手牌有四个可找出来的对子,先找出三万,7p8p9p和7z7z7z这两个显而易见了,那一堆索子需要稍微分析一下也可以看出3s4s5s和4s5s6s这两个顺子,如果把四索或者五索当成对子,那么3s和6s就会被孤立,把红中当对子那就更不能组成胡牌了。由此可见先找对子这种方法能大大降低判定的次数和难度,如果要算番数的话,也可以覆盖全部情况。
源代码
代码实现原理:将分解出的顺子、刻子、对子加入temp_brand_arr中,最后判断temp_brand_arr是否有null,如果有,那就是不能胡牌,如果没有那就是手牌成功分解成了四面子+一对子的形式,可以胡牌
下面是c#实现的源代码,如果有不懂的地方可以私信我或者加我QQ,联系方式在文章末尾。
using System;
namespace mahjong
{
class Program
{
static void Main(string[] args)
{
while (true)
{
Console.Write("请输入牌型:");
string input = Console.ReadLine();
Console.WriteLine(IfRon(input));
}
}
// 主函数
private static string IfRon(string brand)
{
int count;
int brand_j;
int brand_k;
int brand_l;
string ifron = "No";
string[] brand_arr = new string[14]; //传进的牌的数组
string[] temp_brand_arr = new string[14]; //输出的牌的数组
for (int i = 0; i < 27; i += 2)
{
brand_arr[i / 2] = brand.Substring(i, 2); //将字符串分解
}
for (int i = 0; i < 13; i++)
{
count = 0;
//先找对子
if (Array.IndexOf(temp_brand_arr, null) != -1)
{
for (int j = 0; j < brand_arr.Length; j++)
{
temp_brand_arr[j] = brand_arr[j];
}
}
if (temp_brand_arr[i] == temp_brand_arr[i + 1])
{
temp_brand_arr[i] = null;
temp_brand_arr[i + 1] = null;
for (int j = 0; j < 12; j++)
{
if (temp_brand_arr[j] == null)
{
continue;
}
for (int k = j + 1; k < 13;)
{
if (temp_brand_arr[k] == null)
{
k++;
continue;
}
if (temp_brand_arr[k][1] == temp_brand_arr[j][1])
{
brand_k = int.Parse("" + string.Format("{0}", temp_brand_arr[k][0]));
brand_j = int.Parse("" + string.Format("{0}", temp_brand_arr[j][0]));
if (brand_k == brand_j + 1 && temp_brand_arr[k][1] != 'z')
{
for (int l = k + 1; l < 14;)
{
if (temp_brand_arr[l] == null)
{
l++;
continue;
}
if (temp_brand_arr[l][1] == temp_brand_arr[k][1])
{
brand_l = int.Parse("" + string.Format("{0}", temp_brand_arr[l][0]));
if (brand_l == brand_k + 1)
{
temp_brand_arr[j] = null;
temp_brand_arr[k] = null;
temp_brand_arr[l] = null;
break;
}
else
{
l++;
}
}
else
{
break;
}
}
break;
}
else if (brand_k == brand_j)
{
for (int l = k + 1; l < 14;)
{
if (temp_brand_arr[l] == null)
{
l++;
continue;
}
if (temp_brand_arr[l][1] == temp_brand_arr[k][1])
{
brand_l = int.Parse("" + string.Format("{0}", temp_brand_arr[l][0]));
if (brand_l == brand_k)
{
temp_brand_arr[j] = null;
temp_brand_arr[k] = null;
temp_brand_arr[l] = null;
break;
}
else
{
l++;
}
}
else
{
break;
}
}
break;
}
else
{
k++;
}
}
else
{
break;
}
}
}
for (int j = 0; j < temp_brand_arr.Length; j++)
{
if (temp_brand_arr[j] == null)
{
count++;
}
}
if (count == 14)
{
ifron = "Yes";
return ifron;
}
}
}
return ifron;
}
}
}
使用示例:
最后一个例子不能胡是因为3z4z5z为西风北风白板,不能构成顺子
到这里有关胡牌判定的算法介绍就结束了,如果对游戏开发感兴趣的朋友欢迎加入我们的小群或者加我QQ研究探讨!
QQ:792006305
群号:385075578