最近做了一个模拟计算的小程序,总体说来不难,关键地方就是优化,提高计算速度。
规则是用户知道自己的四张手牌,然后模拟出来自己的胜率和收益。
大概思路就是给模拟用户还有用户自己模拟发牌,最后计算所有人的权值,找出最大的权值来判断收益。
52张牌我由m=1-52数字表示,m%13表示每张牌的大小,m/13表示牌的花色,当然13,26,39,52,这种特殊数字返回特定的花色。
1.计算牌的花色
/// <summary>
/// 返回牌的花色
/// </summary>
/// <param name="brand"></param>
/// <returns></returns>
private static int BackColor(int brand)
{
if (brand == 13) return 0;
if (brand == 26) return 1;
if (brand == 39) return 2;
if (brand == 52) return 3;
return brand / 13;
}
2.计算牌的大小
/// <summary>
/// 返回牌的大小
/// </summary>
/// <param name="brand">牌</param>
/// <returns></returns>
private static int BackValue(int brand)
{
if (brand % 13 == 0) return 13;
return brand % 13;
}
因为牌的大小在做累加的时候,10,J,Q,K都是按照0计算的,所以需要累加的时候,需要另一个方法
/// <summary>
/// 返回牌的大小 如果 大于9 则返回0
/// </summary>
/// <param name="brand"></param>
/// <returns></returns>
private static int BackZero(int brand)
{
if (brand % 13 > 9)
{
return 0;
}
else
{
return brand % 13;
}
}
3.返回两张牌的大牌
两张牌一定有一张大一张小,因为规则是如果牌的大小相等,则比较牌的花色。
/// <summary>
/// 返回两张牌较大的牌
/// </summary>
/// <param name="Abrand"></param>
/// <param name="Bbrand"></param>
/// <returns></returns>
private static int BackCompareMax(int Abrand, int Bbrand)
{
if (BackValue(Abrand) > BackValue(Bbrand))
{
return Abrand;
}
else if (BackValue(Abrand) < BackValue(Bbrand))
{
return Bbrand;
}
else
{
if (BackColor(Abrand) > BackColor(Bbrand))
{
return Abrand;
}
else
{
return Bbrand;
}
}
}
4.将用户的四张手牌按照从小到大的顺序排序
因为权值除了比较值的大小之外还要比较牌的花色,所以不能直接用list自带的排序方法
/// <summary>
/// 将手牌按照值和花色大小排序 从小到大
/// </summary>
/// <returns></returns>
private static List<int> ListSort(List<int> lis)
{
for (int i = 0; i < lis.Count - 1; i++)
{
for (int j = i + 1; j < lis.Count; j++)
{
if (lis[i] == BackCompareMax(lis[i], lis[j]))
{
lis[i] = lis[i] + lis[j];
lis[j] = lis[i] - lis[j];
lis[i] = lis[i] - lis[j];
}
}
}
return lis;
}
5.获取手牌的和
获取和的时候10,J,Q,K都是按照0算的,把手牌作和是为了后面方便判断手牌的类型
/// <summary>
/// 获取手牌的和值(10,J,Q,K算0)
/// </summary>
/// <param name="lis"></param>
/// <returns></returns>
private static int Getsum(List<int> lis)
{
int sum = 0;
for (int i = 0; i < lis.Count; i++)
{
sum += BackZero(lis[i]);
}
return sum;
}
6.将用户输入的牌除开之后,剩下的牌组的牌作为一个list存放起来,然后随机选取牌组里面的一张牌分配给用户,再随机选取五张牌分配给一个模拟用户,然后计算五张牌的权值。
这里我的程勋是按照类型的大小从上往下排的,因为牌型都是选取最大的牌型来比较。
这里我把每个不同的牌型都是用四位数表示。
千位数表示牌的类型,百位数是专门为牛准备的,百位数就是牛X,后面的两位数就是五张牌里面的最大牌。因为规则比较大小的时候首先是比较类型,类型大的就赢,若类型相同则比较最大牌(牛除外),如果是牛的话类型相同就比较牛的大小,如果牛相同的话就比较最大牌的值,如果值相等则比较花色。
/// <summary>
/// 返回五张牌中最大的牌型 9-1 为五小牛 到散牌
/// </summary>
/// <param name="listbrand">5张手牌</param>
/// <returns></returns>
private static int BackMaxType(List<int> listbrand)
{
//ata[10]++;
listbrand = ListSort(listbrand);
int sums = Getsum(listbrand);
//判断是否是五小牛
int sum = 0;
if(sums==10 && BackValue(listbrand[4]) <5) return (listbrand[4] + 9000);
//判断是否是炸弹
if (BackValue(listbrand[0]) == BackValue(listbrand[3]))
{
return listbrand[3] + 8000;
}
if (BackValue(listbrand[1]) == BackValue(listbrand[4]))
{
return listbrand[4] + 8000;
}
//判断是否是葫芦牛 (AAABB or AABBB)
if (BackValue(listbrand[0]) == BackValue(listbrand[2]) && BackValue(listbrand[3]) == BackValue(listbrand[4]))
{
return listbrand[2] + 7000;
}
if (BackValue(listbrand[0]) == BackValue(listbrand[1]) && BackValue(listbrand[2]) == BackValue(listbrand[4]))
{
return listbrand[4] + 7000;
}
//判断是否是五花牛
if (BackValue(listbrand[0]) > 10) return listbrand[4] + 6000;
//判断是否是同花牛
if (BackColor(listbrand[0]) == BackColor(listbrand[1]) && BackColor(listbrand[1]) == BackColor(listbrand[2]) && BackColor(listbrand[2]) == BackColor(listbrand[3])
&& BackColor(listbrand[3]) == BackColor(listbrand[4]))
{
return listbrand[4] + 5000;
}
//判断是否是顺子牛
if (BackValue(listbrand[4]) - BackValue(listbrand[3]) == 1 && BackValue(listbrand[3]) - BackValue(listbrand[2]) == 1 &&
BackValue(listbrand[2]) - BackValue(listbrand[1]) == 1 && BackValue(listbrand[1]) - BackValue(listbrand[0]) == 1)
{
return listbrand[4] + 4000;
}
//判断是否是牛牛
//首先五张牌相加一定是10的倍数
if (sums % 10 == 0)
{
//再取其中两张相加为10的倍数则另外三张加起来也一定是10的倍数
//即牛牛
for (int i = 0; i < listbrand.Count; i++)
{
for (int j = i + 1; j < listbrand.Count; j++)
{
if ((BackZero(listbrand[i]) + BackZero(listbrand[j])) % 10 == 0)
{
return listbrand[4] + 3000;
}
}
}
}
//判断 是否有牛
sum = sums;
for (int i = 0; i < listbrand.Count - 1; i++)
{
for (int j = i + 1; j < listbrand.Count; j++)
{
if ((sum - BackZero(listbrand[i]) - BackZero(listbrand[j])) % 10 == 0)
{
int va = (BackZero(listbrand[i]) + BackZero(listbrand[j])) % 10; //获取牛值
return listbrand[4] + 2000+ va*100;
}
}
}
//判断散牌
return listbrand[4] + 1000;
}
7.根据五张牌的权值来比较大小
这里的比较很简单了,规则就想6里面说的那样比较,
/// <summary>
/// 找出两幅牌的大牌
/// </summary>
/// <param name="brand"></param>
/// <returns></returns>
private static int BackMaxBrand(int Abrand, int Bbrand)
{
if (Math.Abs(Abrand - Bbrand) > 51)//不属于同一类型
{
return Math.Max(Abrand, Bbrand);
}
else//属于同一类型
{
int a = Abrand;
int b = Bbrand;
//去除类型比较牌的大小
if (a > 2000 && a < 3000) //判断是否是牛
{
//比较牛的大小
if ((a - 2000) / 100 > (b - 2000) / 100)
{
return Abrand;
}
else if ((a - 2000) / 100 < (b - 2000) / 100)
{
return Bbrand;
}
else //牛相等 比较最大牌
{
a = a - 2000;
b = b - 2000;
while (a > 52 && b > 52)
{
a = a - 100;
b = b - 100;
}
if (a == BackCompareMax(a, b))
{
return Abrand;
}
else
{
return Bbrand;
}
}
}
a = Abrand;
b = Bbrand;
while (a > 52 && b > 52)
{
a = a - 1000;
b = b - 1000;
}
if (a == BackCompareMax(a, b))
{
return Abrand;
}
else
{
return Bbrand;
}
}
}
8.计算牌的倍数,不一样的牌型有不一样的倍数,可以根据自己玩的规则修改
/// <summary>
/// 返回牌的倍数
/// </summary>
/// <param name="brand"></param>
/// <returns></returns>
private static int BackTimes(int brand)
{
if (brand < 2000)
{
return 1; //无牛
}
else if (brand < 3000)//牛
{
//判断是牛7 牛8 还是 牛 9 还是普通牛
if (brand - 2900 > 0) return 3;
if (brand - 2800 > 0) return 2;
if (brand - 2700 > 0) return 2;
return 1;
}
else if (brand < 4000) //牛牛
{
return 4;
}
else if (brand < 5000) //顺子牛
{
return 5;
}
else if (brand < 6000)//同花牛
{
return 5;
}
else if (brand < 7000)//五花
{
return 5;
}
else if (brand < 8000)//葫芦
{
return 6;
}
else if (brand < 9000) //炸弹
{
return 6;
}
else //五小
{
return 8;
}
}
10.给用户模拟发牌
首先给用户模拟发一张牌,计算权值,然后判断他的类型,最后做一个类型的累加。
然后给模拟用户模拟发牌,计算权值之后与用户牌比较大小,留下大牌。
最后判断留下的大牌是否是用户的牌,如果不是则收益上减去最大牌的倍数,如果是就加最大牌的倍数
当然如果输赢都记录下次数,方便计算赢牌概率。
/// <summary>
/// 线程跑动
/// </summary>
/// <param name="num">发牌次数</param>
/// <param name="lis">剩余牌</param>
/// <param name="bran">用户手牌</param>
/// <param name="user">用户数</param>
/// <returns></returns>
private static void ThreadBackA(int num, List<int> list, List<int> brans, int user)
{
Random rd = new Random();
List<int> lis = new List<int>();
List<int> bran = new List<int>();
while (num > 0)
{
lis.Clear();
lis.AddRange(list);
bran.Clear();
bran.AddRange(brans);
int next = rd.Next(0, lis.Count);
bran.Add(lis[next]);//给用户的手牌
lis.RemoveAt(next);
int YH = BackMaxType(bran);
if (YH < 2000)
{
data[1]++; //无牛
}
else if (YH < 3000) //牛
{
data[2]++;
}
else if (YH < 4000) //牛牛
{
data[3]++;
}
else if (YH < 5000)//顺子牛
{
data[4]++;
}
else if (YH < 6000)//同花牛
{
data[5]++;
}
else if (YH < 7000)//五花牛
{
data[6]++;
}
else if (YH < 8000)//葫芦牛
{
data[7]++;
}
else if (YH < 9000)//炸弹
{
data[8]++;
}
else
{
data[9]++; //五小牛
}
int max = YH; //最大牌
//int cesji = BackMaxType(bran);
for (int i = 0; i < user; i++)
{
List<int> us = new List<int>();
for (int j = 0; j < 5; j++)
{
next = rd.Next(0, lis.Count);
us.Add(lis[next]);
lis.RemoveAt(next);
}
max = BackMaxBrand(max, BackMaxType(us));
}
if (max != YH)
{
data[0]++;
data[11] = data[11] - BackTimes(max);
}
else
{
data[10]++;
data[11] = data[11] + BackTimes(max);
}
num--;
}
}
11.异步task跑动
Tsak数组有多长就有几个异步task跑动,我这里是五个线程一共模拟发牌一百万次(个人觉得没必要这么多次,10万次就够了,最后结果相差不过零点几的误差,可有可无。)
最后返回的是一个数组,里面有输赢的次数,总收益,还有用户手牌出现类型的次数
public static int[] GetTransport(List<int> lis,int user)
{
data=new int[12];
HandAardA = lis;
GetAllHandAard(HandAardA);
//异步task
Task[] tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = new Task(() => { ThreadBackA(200000, AllHandAard, HandAardA, user); });
tasks[i].Start();
}
//Thread.Sleep(1000);
Task.WaitAll(tasks);
return data;
}
12.简陋界面
大概就是这么多了。
end