癩子麻將胡牌演算法實現,癩子麻將牌演算法
最先實現的就是演算法的實現。
需求:碰杠胡 ,不能吃 ,不能聽 ,只能自摸胡,其中癩子可以做任意牌但是不能碰和杠。
寫的時候還不會玩麻將,還是老闆教的。^_^
最麻煩的是胡牌演算法。之前搜到的都是不包含癩子正常的胡牌,用的是%3餘2,其中餘數2就是餘的將的意思。
但是有癩子就不能這麼用了。只好自己寫一個了。
一個有136張牌,萬,餅,條,東西南北中發白34種牌。
有四個癩子是直接就胡牌的,最壞的情況是有3個癩子,但是如果遍曆一遍不用邏輯判斷就有34X34X34接近4萬次.
想一下如果能胡牌,最壞的情況下是在最後一次判斷能胡牌,那之前的近4萬次的判斷都是浪費的。
這裡轉變一下思維,就是有目的的按需所取成胡牌所需要的癩子個數,而不是盲目遍曆再判斷胡牌。
演算法的正確性:如果想胡牌必然是三撲一將(正常胡牌)。其中撲指的是順子或者三重牌(比如 一餅二餅三餅 或者東風東風東風)。將指的是兩個重牌。
四種情況:
1.假如將在【萬】裡面那麼【餅】【條】【風】(包含中發白)必然是整撲。
2.假如將在【餅】裡面那麼【萬】【條】【風】(包含中發白)必然是整撲。
3.假如將在【條】裡面那麼【萬】【餅】【風】(包含中發白)必然是整撲。
4.假如將在【風】裡面(包含中發白)那麼【萬】【餅】【條】必然是整撲。
假如當前癩子的數目是curHunNum。
現在先擷取【萬】【餅】【條】【風】各自成為整撲所需要癩子的個數,如果是情況一。
needHunNum= 【餅】成為整撲需要癩子的個數+【條】成為整撲需要癩子的個數+【風】成為整撲需要癩子的個數;
如果hadHunNum = needHunNum - curHunNum; 如果hadHunNum<0 需求的比擁有的多 就不做判斷。
否則就判斷【萬】中成為整撲一將需要的數目。
情況二三四依次類推。
主要有三個函數:
1.測試胡牌
//發的牌bool Player::testHuPai(Pai* pai){for (int i = MJPAI_HUN; i <= MJPAI_FENGZFB; i++){myPaiCopy[i].clear();}vector<Pai*>::iterator iter;for (int i = MJPAI_HUN; i <= MJPAI_FENGZFB; i++){for (iter = m_MyPAIVec[i].begin(); iter != m_MyPAIVec[i].end(); iter++){myPaiCopy[i].push_back(*iter);}}//插入insertPai(pai, myPaiCopy);int curHunNum = myPaiCopy[MJPAI_HUN].size();if (curHunNum>3){CCLOG("Four Hun!!");return true;}addIDEveryPai(myPaiCopy);int needHunNum = 0;int jiangNeedNum = 0;needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(myPaiCopy[MJPAI_WAN], 0);int wanToPuNeedNum = needMinHunNum;needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(myPaiCopy[MJPAI_BING], 0);int bingToPuNeedNum = needMinHunNum;needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(myPaiCopy[MJPAI_TIAO], 0);int tiaoToPuNeedNum = needMinHunNum;needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(myPaiCopy[MJPAI_FENGZFB], 0);int fengToPuNeedNum = needMinHunNum;CCLOG("wanToPuNeedNum:%d bingToPuNeedNum:%d tiaoToPuNeedNum:%d fengToPuNeedNum:%d",wanToPuNeedNum, bingToPuNeedNum, tiaoToPuNeedNum, fengToPuNeedNum);//將在萬中//如果需要的混小於等於當前的則計算將在將在萬中需要的混的個數needHunNum = bingToPuNeedNum + tiaoToPuNeedNum + fengToPuNeedNum;if (needHunNum <= curHunNum){CCLOG("jiang in wan:%d %d", needHunNum, curHunNum);int vecSize = myPaiCopy[MJPAI_WAN].size();int hasNum = curHunNum - needHunNum;m_HuPaiVec.clear();//不裝混牌for (iter = myPaiCopy[MJPAI_WAN].begin(); iter != myPaiCopy[MJPAI_WAN].end(); iter++){m_HuPaiVec.push_back(*iter);}bool isHu = vecCanHu(hasNum);if (isHu) return true;}//將在餅中needHunNum = wanToPuNeedNum + tiaoToPuNeedNum + fengToPuNeedNum;if (needHunNum <= curHunNum){CCLOG("jiang in bing:%d %d", needHunNum, curHunNum);int vecSize = myPaiCopy[MJPAI_BING].size();int hasNum = curHunNum - needHunNum;m_HuPaiVec.clear();//不裝混牌for (iter = myPaiCopy[MJPAI_BING].begin(); iter != myPaiCopy[MJPAI_BING].end(); iter++){m_HuPaiVec.push_back(*iter);}bool isHu = vecCanHu(hasNum);if (isHu) return true;}//將在條中needHunNum = wanToPuNeedNum + bingToPuNeedNum + fengToPuNeedNum;if (needHunNum <= curHunNum){CCLOG("jiang in tiao:%d %d", needHunNum, curHunNum);int vecSize = myPaiCopy[MJPAI_TIAO].size();int hasNum = curHunNum - needHunNum;m_HuPaiVec.clear();//不裝混牌for (iter = myPaiCopy[MJPAI_TIAO].begin(); iter != myPaiCopy[MJPAI_TIAO].end(); iter++){m_HuPaiVec.push_back(*iter);}bool isHu = vecCanHu(hasNum);if (isHu) return true;}//將在風中needHunNum = wanToPuNeedNum + bingToPuNeedNum + tiaoToPuNeedNum;if (needHunNum <= curHunNum){CCLOG("jiang in feng:%d %d", needHunNum, curHunNum);int vecSize = myPaiCopy[MJPAI_FENGZFB].size();int hasNum = curHunNum - needHunNum;m_HuPaiVec.clear();//不裝混(癩子)牌for (iter = myPaiCopy[MJPAI_FENGZFB].begin(); iter != myPaiCopy[MJPAI_FENGZFB].end(); iter++){m_HuPaiVec.push_back(*iter);}bool isHu = vecCanHu(hasNum);if (isHu) return true;}return false;}
2.成為整撲需要的癩子個數:
void Player::getNeedHunNumToBePu(vector<Pai*>typeVec, int needNum){if (needMinHunNum == 0) return;if (needNum >= needMinHunNum)return;int vSize = typeVec.size();if (vSize == 0){needMinHunNum = MIN(needNum, needMinHunNum);return;}else if (vSize == 1){needMinHunNum = MIN(needNum + 2, needMinHunNum);return;}else if (vSize == 2){Pai* p1 = typeVec.at(0);Pai* p2 = typeVec.at(1);//如果後一個是東西南北中發白 不可能是出現順牌if (p2->getType() == MJPAI_FENGZFB){if (p1->getValue() == p2->getValue()) needMinHunNum = MIN(needMinHunNum, needNum + 1);vector<Pai*>::iterator iter;for (iter = typeVec.begin(); iter != typeVec.end(); iter++){CCLOG("3Pai:%d:Type:%d Value:%d", needMinHunNum, (*iter)->getType(), (*iter)->getValue());}return;}else if (p2->getValue() - p1->getValue() < 3){needMinHunNum = MIN(needMinHunNum, needNum + 1);}return;}//大於等於3張牌Pai* p1 = typeVec.at(0);Pai* p2 = typeVec.at(1);Pai* p3 = typeVec.at(2);//第一個自己一撲if (needNum + 2<needMinHunNum){typeVec = removeVector(p1, typeVec);getNeedHunNumToBePu(typeVec, needNum + 2);typeVec = insertVector(p1, typeVec);}//第一個跟其它的一個一撲if (needNum + 1<needMinHunNum){//p1是風if (p1->getType() == MJPAI_FENGZFB){if (p1->getValue() == p2->getValue()){typeVec = removeVector(p1, typeVec);typeVec = removeVector(p2, typeVec);getNeedHunNumToBePu(typeVec, needNum + 1);typeVec = insertVector(p2, typeVec);typeVec = insertVector(p1, typeVec);}}else{for (unsigned int i = 1; i < typeVec.size(); i++){if (needNum + 1 >= needMinHunNum) break;p2 = typeVec[i];//455567這裡可結合的可能為 45 46 否則是45 45 45 46//如果當前的value不等於下一個value則和下一個結合避免重複if (i + 1 != typeVec.size()){p3 = typeVec[i + 1];if (p3->getValue() == p2->getValue()) continue;;}if (p2->getValue() - p1->getValue() < 3){typeVec = removeVector(p1, typeVec);typeVec = removeVector(p2, typeVec);getNeedHunNumToBePu(typeVec, needNum + 1);typeVec = insertVector(p2, typeVec);typeVec = insertVector(p1, typeVec);}else break;}}}//第一個和其它兩個一撲//後面間隔兩張張不跟前面一張相同222234 //可能性為222 234for (unsigned int i = 1; i < typeVec.size(); i++){if (needNum >= needMinHunNum) break;p2 = typeVec[i];if (i + 2 < typeVec.size()){if (typeVec[i + 2]->getValue() == p2->getValue()) continue;}for (unsigned int j = i + 1; j < typeVec.size(); j++){if (needNum >= needMinHunNum) break;p3 = typeVec[j];if (p1 == p3){CCLOG("cao!!");}if (j + 1 < typeVec.size()){if (p3->getValue() == typeVec[j + 1]->getValue()) continue;}if (test3Combine(p1, p2, p3)){typeVec = removeVector(p1, typeVec);typeVec = removeVector(p2, typeVec);typeVec = removeVector(p3, typeVec);getNeedHunNumToBePu(typeVec, needNum);typeVec = insertVector(p3, typeVec);typeVec = insertVector(p2, typeVec);typeVec = insertVector(p1, typeVec);}//4556}}}
3.判斷某一類型能否成為整撲一將:
bool Player::vecCanHu(int hunNum){CCLOG("vecCanHu");int huSize = m_HuPaiVec.size();DebugPlayer(HUVEC);if (huSize <= 0){if (hunNum >= 2)return true;return false;}Pai* firstPai = m_HuPaiVec.front();int kType = firstPai->getType();vector<Pai*>::iterator iter;vector<Pai*> huPaiCopy;for (iter = m_HuPaiVec.begin(); iter != m_HuPaiVec.end(); iter++) huPaiCopy.push_back(*iter);for (iter = huPaiCopy.begin(); iter != huPaiCopy.end(); iter++){if (iter == huPaiCopy.end() - 1){CCLOG("last iterator!!");if (hunNum > 0){hunNum = hunNum - 1;removeHuPai(*iter);needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(m_HuPaiVec, 0);if (needMinHunNum <= hunNum){CCLOG("T:%d V:%d", (*iter)->getType(), (*iter)->getValue());return true;}hunNum = hunNum + 1;insertHuPai(*iter);}}else{if ((iter + 2 == huPaiCopy.end()) || ((*iter)->getValue() != (*(iter + 2))->getValue())){CCLOG("other iterator!!");if (test2Combine(*iter, *(iter + 1))){CCLOG("%d %d", (*iter)->getType(), (*iter)->getValue());removeHuPai(*iter);removeHuPai(*(iter + 1));needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(m_HuPaiVec, 0);if (needMinHunNum <= hunNum){CCLOG("T:%d V:%d", (*iter)->getType(), (*iter)->getValue());return true;}insertHuPai(*iter);insertHuPai(*(iter + 1));}}if (hunNum>0 && (*iter)->getValue() != (*(iter + 1))->getValue()){CCLOG("%d %d", (*iter)->getType(), (*iter)->getValue());hunNum = hunNum - 1;removeHuPai(*iter);needMinHunNum = MJPAI_HUNMAX;getNeedHunNumToBePu(m_HuPaiVec, 0);if (needMinHunNum <= hunNum){CCLOG("T:%d V:%d", (*iter)->getType(), (*iter)->getValue());return true;}hunNum = hunNum + 1;insertHuPai(*iter);}}}return false;}
其中vecCanHu函數是可以再加強剪枝的比如改變癩子時候加上%3餘2的思想,但是測試的時候已經很快了就沒有繼續最佳化。
麻將是快寫完了,學到不少東西。加油吧,騷年!!
麻將胡牌的演算法C/C++
萬筒條好辦,那幾個字只能將和刻的。。。
然後能胡還要輸出所有可能。。。
說個簡單點的
1112345678999
隨便抓個同花色的就胡了
代碼不寫了,大致思路說一下
首先先歸類,萬、筒、條、字
然後萬、筒、條,理順了,看看有沒有出將,如果出將了
那麼後面就判斷只能刻
如果沒出,後面的字可以出將
中間的痛點在於,有的時候同樣的三張牌,出刻了會成為斷牌,所以順必須優先判斷。當然你這裡不涉及牌的大小計算又相對好一點
麻將胡牌演算法
國標麻將規則的番種
88番
1 大四喜 由4副風刻(杠)組成的和牌。不計圈風刻、門風刻、三風刻、碰碰和
2 大三元 和牌中,有中發白3副刻子。不計箭刻
3 綠一色 由23468條及發字中的任何牌組成的順子、刻五、將的和牌。不計混一色。如無“發”字組成的各牌,可計清一色
4 九蓮寶燈 由一種花色序數牌子按1112345678999組成的特定牌型,見同花色任何1張序數牌即成和牌。不計清一色
5 四杠 4個杠
6 連七對 由一種花色序數牌組成序數相連的7個對子的和牌。不計清一色、不求人、單釣
7 十三么 由3種序數牌的一、九牌,7種字牌及其中一對作將組成的和牌。不計五門齊、不求人、單釣
64番
8 清么九 由序數牌一、九刻子組成的和牌。不計碰碰和、同刻、無字
9 小四喜 和牌時有風牌的3副刻子及將牌。不計三風刻
10 小三元 和牌時有箭牌的兩副刻子及將牌。不計箭刻
11 字一色 由字牌的刻子(杠)、將組成的和牌。不計碰碰和
12 四暗刻 4個暗刻(暗杠)。不計門前清、碰碰和
13 一色雙龍會 一種花色的兩個老少副,5為將牌。不計平各、七對、清一色
48番
14 一色四同順 一種花色4副序數相同的順子,不計一色三節高、一般高、四歸一
15 一色四節高 一種花色4副依次遞增一位元的刻子不計一色三同順、碰碰和
32番
16 一色四步高 一種花色4副依次遞增一位元或依次遞增二位元的順子
17 三杠 3個杠
18 混么九 由字牌和序數牌一、九的刻子用將牌組成的和牌。不計碰碰和
24番
19 七對 由7個對子組成和牌。不計不求人、單釣
20 七星不靠 必須有7個單張的東西南北中發白,加上3種花色,數位按147、258、369中的7張序數牌組成沒有將牌的和牌。不計五門齊、不求人、單釣
21 全雙刻 由2、4、6、8序數牌的刻了、將牌組成的和牌。不計碰碰和、斷么
22 清一色 由一種花色的序數牌組成和各牌。不無字
23 一色三同順 和牌時有一種花色3副序數相同的順了。不計一色三節高
24 一色三節高 和牌時有一種花色3副依次遞增一位元字的刻了。不計一色三同順
25 全大 由序數牌789組成的順了、刻子(杠)、將牌的和牌。不計無字
26 全中 由序數牌456組成的順子、刻子(杠)、將牌的和牌。不計斷么
27 全小 由序數牌123組成的順子、刻子(杠)將牌的的和牌。不計無字
16番
28 清龍 和牌時,有一種花色1-9相串連的序數牌
29 三色雙龍會 2種花色2個老少副、另一種花色5作將的和牌。不計喜相逢、老少副、無字、平和
30 一色三步高 和牌時,有一種花色3副依次遞增一位或依次遞增二位元字的順子
31 全帶五 每副牌及將牌必須有5的序數牌。不計斷么
32 三同刻 3個序數相同的刻子(杠)
33 三暗刻 3個暗刻
12番
34 全不靠 由單張3種花色147、258、369不能錯位的序數牌及東南西北中發白中的任何14張牌組成的和牌。不計五門齊、不求人、單釣
35 組合龍 3種花色的147、258、369不能錯位的序數牌
36 大於五 由序數牌6-9的順子、刻子、將牌組成的和牌。不計無字
37 小於五 由序數牌1-4的順子、刻子、將牌組成的和牌。不計無字
38 三風刻 3個風刻
8 番
39 花龍 3種花色的3副順子串連成1-9的序數牌
40 推不倒 由牌面圖形沒有上下區別的牌組成的和牌,包括1234589餅、245689條、白板。不計缺一門
41 三色三同順 和牌時,有3種花色3副序數相同的順子
42 三色三節高 和牌時,有3種花色3副依次......餘下全文>>