纵向广告1
人生倒计时
- 今日已经过去小时
- 这周已经过去天
- 本月已经过去天
- 今年已经过去个月
纵向广告2
大家好,近期很多朋友对于麻将公式abc产不是很理解。然后还有一些网友想弄清楚四川麻将公式ABC,已经为你找到了相关问题的答案,接下来和我们一起看看吧,希望对大家有所帮助!
此算法基本可以通用于所有麻将的平胡规则,即满足m*ABC+n*AAA+AA(其中m、n可为0)的胡牌公式,红黑字牌也可由此算法演变。
首先,我们要约定每张麻将都可以由一个数字表示,比如11表示一万,12表示二万,21表示一条,22表示二条,31表示一筒,32表示二筒……
即所有牌用两位数表示,表示万条筒的两位数个位为牌点,十位为牌类型,其它表示非字牌的两位数与牌类型相同,以下用一个枚举类定义:
importjava.util.HashMap;importjava.util.Map;/***麻将类型枚举**@authorzkpursuit*/publicenumCardType{wan(1,“万”),tiao(2,“条”),tong(3,“筒”),dong(40,“东风”),nan(41,“南风”),xi(42,“西风”),bei(43,“北风”),zhong(44,“中”),fa(45,“发”),ban(46,“白板”);//类型privatefinalintvalue;//牌名privatefinalStringname;privateCardType(intvalue,Stringname){this.value=value;this.name=name;}publicintgetValue(){returnvalue;}publicStringgetName(){returnname;}privatestaticfinalMap<Integer,String>numMap=newHashMap<>();privatestaticfinalMap<Integer,CardType>types=newHashMap<>();privatestaticfinalMap<Integer,String>typeNames=newHashMap<>();static{numMap.put(1,“一”);numMap.put(2,“二”);numMap.put(3,“三”);numMap.put(4,“四”);numMap.put(5,“五”);numMap.put(6,“六”);numMap.put(7,“七”);numMap.put(8,“八”);numMap.put(9,“九”);CardType[]enums=CardType.values();for(CardTypecardType:enums){types.put(cardType.getValue(),cardType);typeNames.put(cardType.getValue(),cardType.getName());}}/***获取牌类型枚举**@paramtypeValue牌类型值*@return牌类型枚举*/publicstaticfinalCardTypegetCardType(inttypeValue){returntypes.get(typeValue);}/***获取牌的类型名**@paramtypeValue牌类型*@return牌类型名*/publicstaticfinalStringgetCardTypeName(inttypeValue){returntypeNames.get(typeValue);}/***获取牌类型数值表示**@paramcard牌号*@return牌类型数值表示*/publicstaticfinalintgetCardTypeValue(intcard){if(card<40){returnHandCards.getCardLeftValue(card);}returncard;}/***将牌数据转换为现实中可读的牌**@paramcard牌数据*@return现实中可读的牌*/publicstaticfinalStringgetCardName(intcard){if(card<40){inttype=HandCards.getCardLeftValue(card);intpoint=HandCards.getCardRightValue(card);StringBuildersb=newStringBuilder();sb.append(numMap.get(point));sb.append(getCardTypeName(type));returnsb.toString();}returngetCardTypeName(card);}}
以上定义了各张牌的数字表示,接下来我们分析手牌的存储结构,手牌可以用一个数组表示,数组下标号能除尽10的数组元素为保留位,不用于存储任何数据。举例解释此数组存储牌的数据结构:
0号下标保留位
1~9号下标为万字牌牌点,其对应的数组元素为牌的张数
10号下标保留位
11~19号下标为条字牌牌点,其对应的数组元素为牌的张数
20号下标为保留位
21~29号下标为筒字牌牌点,其对应的数组元素为牌的张数
40~46号下标分别表示东、南、西、北、中、发、白的存储位。
根据以上的定义,则可以根据数组下标获得万条筒字牌的类型和牌点,(下标/10+1)则为字牌类型,(下标%10)则为字牌点数。
具体定义一个手牌类,里面定义了各种静态的换算函数,可参看注释。
/***手牌**@authorzkpursuit*/publicclassHandCards{/***获取牌号最左边的一位数,如果牌为筒、条、万,则返回值为牌类型数值**@paramcard牌号*@return牌号从左至右第一位数(十位数)*/publicfinalstaticintgetCardLeftValue(intcard){returncard/10;}/***获取牌号最右边的一位数,如果牌为筒、条、万,则返回值为牌点数**@paramcard牌号*@return牌号从右至左第一位数(个位数)*/publicfinalstaticintgetCardRightValue(intcard){returncard%10;}/***获取牌号最左边的一位数,如果牌为筒、条、万,则返回值为牌类型数值**@paramidx牌在归类数组中的索引位置*@return牌号从左至右第一位数(十位数)*/publicfinalstaticintgetCardLeftValueByClusterIndex(intidx){returnidx/10+1;}/***获取牌号最右边的一位数,如果牌为筒、条、万,则返回值为牌点数**@paramidx牌在归类数组中的索引位置*@return牌号从右至左第一位数(个位数)*/publicfinalstaticintgetCardRightValueByClusterIndex(intidx){returnidx%10;}/***根据牌号取得其所在的牌归类数组中的索引**@paramcard牌号*@return牌归类数组中的索引*/publicfinalstaticintgetClusterIndexByCard(intcard){intleft=getCardLeftValue(card);intright=getCardRightValue(card);intidx=(left–1)*10+right;returnidx;}/***根据十位数和个位数确定牌在聚合数组中的索引位置**@paramleftValue十位数*@paramrightValue个位数*@return聚合数组中的索引位置*/publicfinalstaticintgetClusterIndex(intleftValue,intrightValue){return(leftValue–1)*10+rightValue;}/***归类牌<br>*数组索引/10+1表示牌类型<br>*数组索引%10表示牌点数<br>*数组索引位置的值表示牌数量*/privateint[]cardClusterArray;/***起始有效的索引位置<br>*第一个值不为0的索引位置<br>*/privateintstartIndex;/***归类牌数组的有效索引位置,因为有可能后面的位置全是0<br>*此索引的后续索引位置的值全部为0,即最后一个值不为0的索引位置<br>*/privateintlastIndex;/***所有的牌数量*/privateintcardTotals;/***构造方法*/publicHandCards(){cardClusterArray=newint[40];startIndex=1000;lastIndex=-1;cardTotals=0;}/***构造方法**@paramcards未归类的牌数组*/publicHandCards(int[]cards){this();if(cards!=null){setCards(cards);}}/***重置数据*/publicvoidreset(){if(cardTotals!=0){intlen=getClusterValidLength();for(inti=0;i<len;i++){cardClusterArray[i]=0;}}startIndex=1000;lastIndex=-1;cardTotals=0;}/***清除数据*/publicvoidclear(){reset();}/***重置数据并以传入的牌数据再次初始化数据**@paramcards牌数据*/publicfinalvoidsetCards(int[]cards){reset();for(intcard:cards){addCard(card);}}/***添加num张牌**@paramcard添加的牌号*@paramnum添加的数量*@returntrue添加成功;false添加失败*/publicbooleanaddCard(intcard,intnum){intidx=getClusterIndexByCard(card);intlastNum=cardClusterArray[idx]+num;if(lastNum>4){returnfalse;}cardClusterArray[idx]=lastNum;if(idx>lastIndex){lastIndex=idx;}if(idx<startIndex){startIndex=idx;}cardTotals+=num;returntrue;}/***添加一张牌**@paramcard牌号*@returntrue添加成功;false添加失败*/publicbooleanaddCard(intcard){returnaddCard(card,1);}/***添加牌集合**@paramcards牌集合,比如[11,23,33,33,33,34]*@returntrue添加成功,只要有一张添加失败则全部失败*/publicbooleanaddCards(int…cards){for(intcard:cards){intidx=getClusterIndexByCard(card);intlastNum=cardClusterArray[idx]+1;if(lastNum>4){returnfalse;}}for(intcard:cards){addCard(card);}returntrue;}/***移除num张牌**@paramcard移除的牌号*@paramnum移除的数量*@returntrue移除成功;false移除失败*/publicbooleanremoveCard(intcard,intnum){intidx=getClusterIndexByCard(card);if(cardClusterArray[idx]<num){returnfalse;}cardClusterArray[idx]-=num;if(cardClusterArray[idx]==0){if(idx==startIndex){startIndex=1000;for(inti=idx;i<cardClusterArray.length;i++){if(cardClusterArray[i]>0){startIndex=i;break;}}}if(lastIndex==idx){intstart=startIndex;if(start>=cardClusterArray.length){start=0;}lastIndex=-1;for(inti=idx;i>=start;i–){if(cardClusterArray[i]>0){lastIndex=i;break;}}}}cardTotals-=num;returntrue;}/***移除一张牌**@paramcard牌号*@returntrue移除成功;false移除失败*/publicbooleanremoveCard(intcard){returnremoveCard(card,1);}/***移除牌号对应的所有牌**@paramcard牌号*@returntrue移除成功;false移除失败*/publicbooleanremoveCardOfAll(intcard){intnum=getCardNum(card);if(num>=0){returnremoveCard(card,num);}returntrue;}/***移除牌**@paramcards需要移除的牌*@returntrue表示移除成功,只要有一张牌移除失败则整个失败*/publicbooleanremoveCards(int…cards){for(intcard:cards){intidx=getClusterIndexByCard(card);if(cardClusterArray[idx]<1){returnfalse;}}for(intcard:cards){removeCard(card);}returntrue;}/***是否有指定的牌**@paramcard牌号*@returntrue表示存在*/publicbooleanhasCard(intcard){returngetCardNum(card)>0;}/***获取牌号对应的数量**@paramcard牌号*@return牌号对应的数量*/publicintgetCardNum(intcard){intidx=getClusterIndexByCard(card);returncardClusterArray[idx];}/***获取归类的牌数据,整除10的索引位置为保留位,不参与任何实际运算<br>*数组索引从0开始,有效长度(后面全部为0)结束<br>*此数组为数据副本,其中的任何数据变动都不会改变原数组<br>*数组索引/10+1表示牌类型<br>*数组索引%10表示牌点数<br>**@return归类的牌数据*/publicint[]getCardClusterArray(){int[]array=newint[getClusterValidLength()];System.arraycopy(cardClusterArray,0,array,0,array.length);returnarray;}/***根据提供的索引位置获取牌数量**@paramidx牌归类数组中的索引位置*@return牌数量*/publicintgetCardNumByClusterIndex(intidx){returncardClusterArray[idx];}/***根据索引位置定位对应的牌**@paramidx归类牌数组中的索引位置*@return-1表示找不到对应的牌,否则返回牌号*/publicintgetCardByClusterIndex(intidx){if(cardClusterArray[idx]<=0){return-1;}intleft=getCardLeftValueByClusterIndex(idx);intright=getCardRightValueByClusterIndex(idx);returnleft*10+right;}/***归类牌数组中起始有效索引**@return起始有效索引,第一个值不为0的索引位置*/publicintgetClusterValidStartIndex(){if(cardTotals==0){return1;}returnstartIndex;}/***归类牌数组中最终的有效索引**@return最终有效索引,其后的值全为0*/publicintgetClusterValidEndIndex(){returnlastIndex;}/***归类牌数组的有效长度<br>*有效的起始索引到有效的最后索引之前的长度<br>**@return有效长度,因为归类数组中后面可能有很多无效的0*/publicintgetClusterValidLength(){returnlastIndex+1;}/***所有牌的张数**@return总张数*/publicintgetCardTotals(){returncardTotals;}/***获取所有的牌数据,未归类**@return未归类的牌数据,两位数的牌号数组*/publicint[]getCards(){if(cardTotals<=0){returnnull;}intlen=getClusterValidLength();int[]cards=newint[cardTotals];intidx=0;for(inti=getClusterValidStartIndex();i<len;i++){intleft=getCardLeftValueByClusterIndex(i);intright=getCardRightValueByClusterIndex(i);intcount=cardClusterArray[i];intcard=left*10+right;for(intj=0;j<count;j++){cards[idx]=card;idx++;}}returncards;}@OverridepublicHandCardsclone(){HandCardscopy=newHandCards();copy.cardTotals=this.cardTotals;copy.lastIndex=this.lastIndex;copy.startIndex=this.startIndex;if(cardClusterArray!=null){int[]copyCardClusterArray=newint[cardClusterArray.length];System.arraycopy(cardClusterArray,0,copyCardClusterArray,0,cardClusterArray.length);copy.cardClusterArray=copyCardClusterArray;}returncopy;}}
准备工作都做好了,怎么使用上面定义的数据结构实现平胡算法呢?平胡满足m*ABC+n*AAA+AA(其中m、n可为0)的胡牌公式,分析此公式,AA表示一对牌,则算法必然需要分析手牌中是否含有一对牌,ABC表示三张相同类型且连续的牌,AAA表示三张相同类型且牌点也相同的牌。
依据公式,我们用递归思路编写一个平胡胡牌算法(其中包含简单的测试用例):
importjava.util.Arrays;/****@authorzkpursuit*/publicfinalclassAI{/***递归方式判断平胡**@paramcardClusterArray牌号和牌数量的簇集对象集合*@paramlen所有牌数量*@returntrue表示可以胡牌*/privatestaticbooleanisPingHu(int[]cardClusterArray,intstartIndex,intlen){if(len==0){returntrue;}inti;if(len%3==2){//移除一对(两张牌),胡牌中必须包含一对for(i=startIndex;i<cardClusterArray.length;i++){if(cardClusterArray[i]>=2){cardClusterArray[i]-=2;if(AI.isPingHu(cardClusterArray,startIndex,len–2)){returntrue;}cardClusterArray[i]+=2;}}}else{//是否是顺子intloopCount=cardClusterArray.length–2;for(i=startIndex;i<loopCount;i++){intidx1=i+1;intidx2=i+2;inttype1=HandCards.getCardLeftValueByClusterIndex(i);inttype2=HandCards.getCardLeftValueByClusterIndex(idx1);inttype3=HandCards.getCardLeftValueByClusterIndex(idx2);if(cardClusterArray[i]>0&&cardClusterArray[idx1]>0&&cardClusterArray[idx2]>0&&type1<4&&type2<4&&type3<4){cardClusterArray[i]-=1;cardClusterArray[idx1]-=1;cardClusterArray[idx2]-=1;if(AI.isPingHu(cardClusterArray,startIndex,len–3)){returntrue;}cardClusterArray[i]+=1;cardClusterArray[idx1]+=1;cardClusterArray[idx2]+=1;}}//三个一样的牌(暗刻)for(i=startIndex;i<cardClusterArray.length;i++){if(cardClusterArray[i]>=3){cardClusterArray[i]-=3;if(AI.isPingHu(cardClusterArray,startIndex,len–3)){returntrue;}cardClusterArray[i]+=3;}}}returnfalse;}/***递归方式判断平胡**@parammycards手牌*@returntrue表示可以胡牌*/publicstaticbooleanisPingHu(HandCardsmycards){int[]cardClusterArray=mycards.getCardClusterArray();inttotals=mycards.getCardTotals();if(totals%3!=2){returnfalse;}returnAI.isPingHu(cardClusterArray,mycards.getClusterValidStartIndex(),totals);}publicstaticvoidmain(String[]args){HandCardshandCards=newHandCards(newint[]{11,12,13,22,23,24,33,33,33,36,36});System.out.println(Arrays.toString(handCards.getCardClusterArray()));System.out.println(Arrays.toString(handCards.getCards()));for(inti=handCards.getClusterValidStartIndex();i<=handCards.getClusterValidEndIndex();i++){intcard=handCards.getCardByClusterIndex(i);if(card>0){intnum=handCards.getCardNum(card);System.out.println(num+“张”+CardType.getCardName(card));}}booleanbool=isPingHu(handCards);System.out.println(“是否胡牌:”+bool);}}
作者:kakai
原文链接:https://my.oschina.net/zkpursuit/blog/3046187





