From a936908fdcaaf4d1bb6a6d0dbb2e5b8e09a9cb9e Mon Sep 17 00:00:00 2001 From: yungege <273461474@qq.com> Date: Mon, 29 Oct 2018 23:03:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BD=91=E5=8F=8B24431188?= =?UTF-8?q?=EF=BC=8C=E9=87=8D=E6=9E=84=E7=9A=84js=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mjlib_js2/Algorithm.txt | 62 ++ mjlib_js2/App/Mocha_Test/testHu.js | 57 ++ mjlib_js2/App/Models/Card.js | 559 ++++++++++++++++++ mjlib_js2/App/Models/HuAlgorithm/Algorithm.rd | 61 ++ mjlib_js2/App/Models/HuAlgorithm/GenTable.js | 263 ++++++++ mjlib_js2/App/Models/HuAlgorithm/HuLogic.js | 118 ++++ mjlib_js2/App/Models/Mahjong.js | 370 ++++++++++++ mjlib_js2/App/Models/ModelUtil.js | 25 + mjlib_js2/App/app.js | 51 ++ mjlib_js2/App/package-lock.json | 241 ++++++++ mjlib_js2/App/package.json | 7 + 11 files changed, 1814 insertions(+) create mode 100644 mjlib_js2/Algorithm.txt create mode 100644 mjlib_js2/App/Mocha_Test/testHu.js create mode 100644 mjlib_js2/App/Models/Card.js create mode 100644 mjlib_js2/App/Models/HuAlgorithm/Algorithm.rd create mode 100644 mjlib_js2/App/Models/HuAlgorithm/GenTable.js create mode 100644 mjlib_js2/App/Models/HuAlgorithm/HuLogic.js create mode 100644 mjlib_js2/App/Models/Mahjong.js create mode 100644 mjlib_js2/App/Models/ModelUtil.js create mode 100644 mjlib_js2/App/app.js create mode 100644 mjlib_js2/App/package-lock.json create mode 100644 mjlib_js2/App/package.json diff --git a/mjlib_js2/Algorithm.txt b/mjlib_js2/Algorithm.txt new file mode 100644 index 0000000..a20fca5 --- /dev/null +++ b/mjlib_js2/Algorithm.txt @@ -0,0 +1,62 @@ +由网友QQ 24431188 重写版本 + +一)查表法核心思想: + 1、名词解释:eye(将),字牌(feng、东南西北中发白),花色(万、筒、条、字牌) + 2、分而治之:检查手牌是否能胡是依次检查万、筒、条、字牌四种花色是否能组成胡牌的一部分。 + 3、单一花色要能满足胡牌的部分,则要么是3*n(不带将),要么是3*n+2(带将)。3*n中的3带表三张牌一样的刻子,或三张连续的牌如1筒2筒3筒。 + 4、判断是否满足胡牌的单一花色部分,需要根据是否有将,有几个赖子,查询不同的表。表内容表示表里的元素加上对应的赖子数量能组成3*n 或3*n+2。 + 赖子数是表名最后的数字,带有eye的表表示满足3*n+2,没有的表示满足3*n。 + 5、查表的key值,是直接根据1-9有几张牌就填几(赖子不算),如1-9万各一张,则key为111111111。如1万3张,9万2张,则key为300000002。 + 6、组合多种花色,判断是否能胡牌。将赖子分配给不同的花色,有若干种分配方式,只要有一种分配能让所有花色满足单一花色胡牌部分,则手牌能胡。 + 如:手上有3个赖子,可以分配万、筒、条各一张,也可以万、同、字牌各一张 + 7、根据是否有将、是否字牌分为4种表,每种表又根据赖子个数0-8分别建表,共36张表,具体如下: + + 赖子个数 筒子万子索子表 字牌表 + 0 Table0 Table_Feng0 + 1 Table1 Table_Feng1 + 2 Table2 Table_Feng2 + 3 Table3 Table_Feng3 + 4 Table4 Table_Feng4 + 5 Table5 Table_Feng5 + 6 Table6 Table_Feng6 + 7 Table7 Table_Feng7 + 8 Table8 Table_Feng8 + + 要单独分开字牌表是因为字牌是不能形成顺子的,东南西不是顺子。(但是有些地方的玩法,例如南昌,它是可是东南西组成顺子的。) + + 步骤: + 1、统计手牌中鬼牌个数 ghostCount,将鬼牌从牌数据中去除. + 2、把手上的牌编码成一串数字比如:1筒2筒3筒3筒3筒3筒6筒7筒8筒2万3万3万3万4万 + 筒: 1,1,4,0,0,1,1,1,0 得出的数字为114001110 + 万: 0,1,3,1,0,0,0,0,0 得出的数字为13100000 + 3、查表检查单一种花色能否胡牌先查Table0; 如果不能胡,再根据手上鬼牌的个数查其它表。 + 4、当所有牌型都能胡,并且有且仅有一种牌型是3*n+2, 其它牌型为3*n. + + +二) 表的产生 + + 非字牌表的产生: + 1、穷举万字牌所有满足胡牌胡可能,将对应的牌型记录为数字,放入Table0中。 + 具体是每次加入一个刻子,顺子或是将(将只能加入一对),最多加入四组外带将牌。 + 2、将Table0中牌去掉一张,放入Table1中,表示去掉的牌用1张赖子代替。 + 3、将Table1中牌去掉一张,放入Table2中,表示去掉的牌用1张赖子代替。 + 4、将Table2中牌去掉一张,放入Table3中,表示去掉的牌用1张赖子代替。 + 5、将Table3中牌去掉一张,放入Table4中,表示去掉的牌用1张赖子代替。 + 6、将Table4中牌去掉一张,放入Table5中,表示去掉的牌用1张赖子代替。 + 7、将Table5中牌去掉一张,放入Table6中,表示去掉的牌用1张赖子代替。 + 8、将Table6中牌去掉一张,放入Table7中,表示去掉的牌用1张赖子代替。 + 9、将Table7中牌去掉一张,放入Table8中,表示去掉的牌用1张赖子代替。 + + 只计算到8张牌,如果手上有超过8张牌,无论是什么牌都是胡牌的。 + + 字牌表的产生: + 与非字牌表的产生方法相同,只是第一步中,不能加入顺子(除非麻将玩法字牌是能组成顺子的)。 + +三) 代码实现 + 1) 只有在没有表的情况下,才会生成每一张表。 + 2) 每张表生成以后,将会加载下来,并存放在内存中。 + 3) GenTable是个单例,这样可以节约内存。 + + 使用方法: + 1) let hulogic = new HuLogic(); + 2) hulogic.checkHu(cardList,ongoingCard); diff --git a/mjlib_js2/App/Mocha_Test/testHu.js b/mjlib_js2/App/Mocha_Test/testHu.js new file mode 100644 index 0000000..e817fc7 --- /dev/null +++ b/mjlib_js2/App/Mocha_Test/testHu.js @@ -0,0 +1,57 @@ + + +const Card = require('../Models/Card'); +const HuLogic = require('../Models/HuAlgorithm/HuLogic') +const assert = require('assert'); + + +describe('胡牌测试', function () { + + it('检查胡牌', function () { + var testPaiList = + [ + ["1万", "3万", "5万", "6万", "7万", "8筒", "8筒", "6筒", "6筒", "4筒", "4筒", "中", "中", "5筒"], + ["5万", "6万", "4万", "9筒", "9筒", "8筒", "1条", "6筒", "2条", "3条", "南", "南", "中", "7筒",],//7筒 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "南", "中", "中", "南"],//南 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "中", "中", "中", "8万"],//8万 + ["2筒", "4筒", "5筒", "6筒", "7筒", "2万", "4万", "5万", "7万", "8万", "8万", "中", "白", "3万"],//3万 + + ["6筒", "6筒", "1条", "3条", "4条", "5条", "6条", "7条", "9条", "7万", "9万", "中", "白", "中"],//中 + ["3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "3条", "4条", "8条", "8条", "3万", "5万", "白", "4万"],//4万 + ["9筒", "9筒", "6条", "7条", "8条", "3万", "3万", "4万", "6万", "7万", "7万", "7万", "白", "3万"],//3万 + ["1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "2万", "5万", "7万", "8万", "中", "中", "白", "4万"],//4万 + ["2筒", "3筒", "4筒", "7筒", "8筒", "5条", "6条", "7条", "9条", "9条", "7万", "7万", "白", "中"],//中 + + ["5筒", "1条", "2条", "3条", "6条", "7条", "8条", "6万", "7万", "7万", "8万", "9万", "白", "5万"],//5万 + + ["白", "中", "中", "8条", "7条", "7条", "5条", "4条", "3条", "2条", "5万", "3万", "2万", "1万"],//1万 + ["中", "1条", "3条", "4条", "5条", "6条", "1万", "1万", "2万", "3万", "4万", "5万", "7万", "白"],//白 + ["白", "9条", "8条", "8条", "7条", "7条", "4条", "3条", "5筒", "4筒", "3筒", "2筒", "2筒", "4条"],//4条 + + ["7万", "8万", "9万", "9条", "9条", "8条", "8条", "4筒", "3筒", "2筒", "1筒", "白", "白", "中"],//"中" + ["9万", "8万", "7万", "6万", "4万", "3万", "2万", "1万", "3条", "1条", "1条", "白", "白", "1条"],// + + ["7万", "7万", "3万", "3万", "9条", "8条", "7条", "8筒", "7筒", "6筒", "5筒", "4筒", "中", "7万"],//"7万" + ["8万", "7万", "4万", "4万", "9条", "8条", "7条", "7筒", "5筒", "2筒", "2筒", "白", "中", "2筒"],//"2筒" + + ["8万", "7万", "6万", "4万", "3万", "2万", "1万", "7筒", "6筒", "5筒", "3条", "3条", "中", "9万"],//"9万" + + + ["中", "中", "9条", "8条", "8条", "7条", "6条", "5条", "4条", "3条", "9筒", "7筒", "2筒", "8筒"],//"8筒" + ["白", "中", "9条", "8条", "7条", "6条", "9筒", "9筒", "7筒", "5筒", "4筒", "2筒", "3筒", "4条"]//"4条" + ]; + + var testHuResult = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1]; + for (let i = 0; i < testPaiList.length; i++) { + let cardDisplayNameList = testPaiList[i]; + + let cardList = Card.CreateCardWithNameList(cardDisplayNameList, [31, 32, 33]); + let huLogic = new HuLogic(); + let isHu = huLogic.checkHu(cardList); + assert(isHu == testHuResult[i]); + + } + + + }) +}) \ No newline at end of file diff --git a/mjlib_js2/App/Models/Card.js b/mjlib_js2/App/Models/Card.js new file mode 100644 index 0000000..dd85f59 --- /dev/null +++ b/mjlib_js2/App/Models/Card.js @@ -0,0 +1,559 @@ +/** + * Created by mac on 2016/11/22. + */ +//suit:0 = 筒,1=索, 2=万,3=字牌, +//rank:筒索万:0〜8:表示1~9,字牌0:东,1:南,2:西,3:北,4:中,5:发,6:白, +//privatePoint:0〜8:表示1~9筒;9〜17:表示1〜9索;18〜26:1〜9万;27〜33:东南西北发中白 +/** + * 一张麻将牌 + * suit:0 = 筒,1=索, 2=万,3=字牌, + * rank:筒索万:0〜8:表示1~9,字牌0:东,1:南,2:西,3:北,4:中,5:发,6:白, + * privatePoint:0〜8:表示1~9筒;9〜17:表示1〜9索;18〜26:1〜9万;27〜33:东南西北中发白 + */ +const ModelUtil = require('./ModelUtil'); +const SAFE = ModelUtil.SAFE; +CARDTYPE = { + CARDTYPE_TONGZI: 0, //筒子 + CARDTYPE_TIAOZI: 1, //条子 + CARDTYPE_SUOZI: 1, //索子 + CARDTYPE_WANZI: 2, //万子 + CARDTYPE_ZIPAI: 3 //字牌 +} + +CARDID = { + CARDID_TONG1: 0, //1筒 + CARDID_TONG2: 1, //2筒 + CARDID_TONG3: 2, //3筒 + CARDID_TONG4: 3, //4筒 + CARDID_TONG5: 4, //5筒 + CARDID_TONG6: 5, //6筒 + CARDID_TONG7: 6, //7筒 + CARDID_TONG8: 7, //8筒 + CARDID_TONG9: 8, //9筒 + + CARDID_TIAO1: 9, //1条 + CARDID_TIAO2: 10, //2条 + CARDID_TIAO3: 11, //3条 + CARDID_TIAO4: 12, //4条 + CARDID_TIAO5: 13, //5条 + CARDID_TIAO6: 14, //6条 + CARDID_TIAO7: 15, //7条 + CARDID_TIAO8: 16, //8条 + CARDID_TIAO9: 17, //9条 + + CARDID_WAN1: 18, //1万 + CARDID_WAN2: 19, //2万 + CARDID_WAN3: 20, //3万 + CARDID_WAN4: 21, //4万 + CARDID_WAN5: 22, //5万 + CARDID_WAN6: 23, //6万 + CARDID_WAN7: 24, //7万 + CARDID_WAN8: 25, //8万 + CARDID_WAN9: 26, //9万 + + CARDID_DONG: 27, //东风 + CARDID_NAN: 28, //南风 + CARDID_XI: 29, //西风 + CARDID_BEI: 30, //北万 + + + CARDID_ZHONG: 31, //中 + CARDID_FA: 32, //发 + CARDID_BAI: 33 //白 +} + +/** + * @class Card + * 表示一张牌,使用int privatePoint唯一标识某张牌。 + * + */ +class Card { + constructor(opts = {}) { + this.privatePoint = SAFE(opts.privatePoint, 0);//实际大小 + this.suit = SAFE(opts.suit, 0);//花色,指筒子:0,万子:2,条子:1或者字牌:3 + this.isGhost = SAFE(opts.isGhost, 0);//是否鬼牌,鬼牌可变成任意的牌 + this.rank = Math.floor(this.privatePoint / 9);//牌面大小 + this._setRank(); + this._setSuit(); + this._initDisplayName(); + } + + + // + /** + * initWithPrivatePoint + * 使用PrivatePoint初始化, 0~33分别代表34种不同的牌,0:1筒, 9:1条, 18:1万,27〜33:东南西北发中白 + * 这里并没有指定是不是鬼牌 + * @param {Int} point privatePoint + * @param {ArrayInt} ghostPointList 鬼牌点数数组 ex:[31,32,33] + * @return {void} 没有返回值 + */ + initWithPrivatePoint(point, ghostPointList) { + if (point < 0 || point > 34) { + if (point < 0) { + point = 0; + } + + if (point > 34) { + point = 34; + } + } + + this.privatePoint = point; + this._setRank(); + this._setSuit(); + this.setIsGhost(ghostPointList); + this._initDisplayName(); + + } + + /** + * 设定该牌是否鬼牌,如果该牌的点数在鬼牌列表中,那么就设定为true; + * @param {*} ghostPointList 鬼牌的点数列表 ex:[31,32,33] + */ + setIsGhost(ghostPointList) { + for (let i in ghostPointList) { + if (this.privatePoint == ghostPointList[i]) { + this.isGhost = 1; + } + } + } + /** + * setIsGhost + * 指定这张牌是否为鬼牌 + * @param{int} isGhost 是否为鬼牌 + * @return {void} 没有返回值 + */ + // Card.prototype.setIsGhost = function(isGhost) { + // this.isGhost = isGhost; + // } + + + /** + * initDisplayName + * 初始化displayName + * @return {void} 没有返回值 + * @api private + */ + _initDisplayName() { + let displayNameList = ['1筒', '2筒', '3筒', '4筒', '5筒', '6筒', '7筒', '8筒', '9筒', '1条', '2条', '3条', '4条', '5条', '6条', '7条', '8条', '9条', '1万', '2万', '3万', '4万', '5万', '6万', '7万', '8万', '9万', '东', '南', '西', '北', '中', '发', '白']; + this.displayName = displayNameList[this.privatePoint]; + } + + /** + * setSuit + * 点数 + * @return {void} 没有返回值 + * @api private + */ + _setSuit() { + this.suit = Math.floor(this.privatePoint / 9); + } + + + /** + * setRank + * 花色 + * @return {void} 没有返回值 + * @api private + */ + _setRank() { + this.rank = Math.floor(this.privatePoint % 9); + } + + + /** + * 获取这张牌的下一张牌的privatePoint,比如9万,下一张牌为1万; 比如东,下一张牌为南 + * @returns {int} privatePoint + */ + getNextPoint() { + if (this.suit <= CARDTYPE.CARDTYPE_WANZI) { + return this.suit * 9 + (this.rank + 1) % 9; + } else if (this.suit === CARDTYPE.CARDTYPE_ZIPAI) { + return this.suit * 9 + (this.rank + 1) % 7; + } + return 0; + } + + /** + * 获取这张牌的下一张牌的Card实例 + * @returns {Card} card对象 + */ + getNextCard() { + let nextPoint = this.getNextPoint(); + return Card.CreateCardWithPoint(nextPoint); + } + + /** + * int数组转card对象列表 + * @param {IntArray} cardPoints + * @param {IntArray} ghostCards 鬼牌的privatePoint 列表 + * @returns {Array} + * + */ + static CreateCardsWithPointList(cardPoints, ghostCards = []) { + let cardsList = []; + if (cardPoints && cardPoints.length > 0) { + for (let i in cardPoints) { + let card = new Card(); + card.initWithPrivatePoint(parseInt(cardPoints[i]), ghostCards); + cardsList.push(card); + } + cardsList = ModelUtil.sort(cardsList); + } + return cardsList; + }; + + /** + * 根据展示的牌名列表,来创建牌 + * @param {ArrayString} otherNameList 需要创建的牌名数组 + * @param {ArrayInt} ghostPointList 鬼牌数组 + */ + static CreateCardWithNameList(otherNameList,ghostPointList = []){ + //根据牌名获取点数,然后根据点数创建牌 + let displayNameList = ['1筒', '2筒', '3筒', '4筒', '5筒', '6筒', '7筒', '8筒', '9筒', '1条', '2条', '3条', '4条', '5条', '6条', '7条', '8条', '9条', '1万', '2万', '3万', '4万', '5万', '6万', '7万', '8万', '9万', '东', '南', '西', '北', '中', '发', '白']; + + let indexList = otherNameList.map( (displayName) =>{ + return displayNameList.indexOf(displayName); + }) + //去掉无效的牌名,比如十筒 + indexList = indexList.filter( (index) =>{ + return index != -1; + }) + return Card.CreateCardsWithPointList(indexList,ghostPointList) + } + + /** + * 根据牌名创建一张牌 + * @param {String} displayName + * @param {ArrayInt} ghostPointList + */ + static CreateOneCardWithName(displayName,ghostPointList = []){ + let list = Card.CreateCardWithNameList([displayName],ghostPointList); + return list[0]; + } + + /** + * + * @param {int} privatePoint point {0~33} + * @param {BOOL} isGhost 是否标志为鬼牌 + * @returns {Card} card对象 + */ + static CreateCardWithPoint(privatePoint,isGhost = false) { + let currentCard = new Card(); + let ghostList = []; + if(isGhost){ + ghostList.push(privatePoint); + } + currentCard.initWithPrivatePoint(parseInt(privatePoint),ghostList); + return currentCard; + } + +} +module.exports = Card; + + +//胡牌测试用例 +function testSpecialCardEx(i, mahjong) { + + let testPaiList = [ + ["1万", "3万", "5万", "6万", "7万", "8筒", "8筒", "6筒", "6筒", "4筒", "4筒", "中", "中"], + ["5万", "6万", "4万", "9筒", "9筒", "8筒", "1条", "6筒", "2条", "3条", "南", "南", "中"], //7筒 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "南", "中", "中"], //南 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "中", "中", "中"], //8万 + ["2筒", "4筒", "5筒", "6筒", "7筒", "2万", "4万", "5万", "7万", "8万", "8万", "中", "白"], //3万 + + ["6筒", "6筒", "1条", "3条", "4条", "5条", "6条", "7条", "9条", "7万", "9万", "中", "白"], //中 + ["3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "3条", "4条", "8条", "8条", "3万", "5万", "白"], //4万 + ["9筒", "9筒", "6条", "7条", "8条", "3万", "3万", "4万", "6万", "7万", "7万", "7万", "白"], //3万 + ["1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "2万", "5万", "7万", "8万", "中", "中", "白"], //4万 + ["2筒", "3筒", "4筒", "7筒", "8筒", "5条", "6条", "7条", "9条", "9条", "7万", "7万", "白"], //中 + + ["5筒", "1条", "2条", "3条", "6条", "7条", "8条", "6万", "7万", "7万", "8万", "9万", "白"], //5万 + ["1筒", "2筒", "3筒", "7万", "8万", "1条", "2条", "3条", "4条", "5条", "6条", "中", "白"], + ["3筒", "4筒", "5筒", "2条", "3条", "4条", "5万", "6万", "7万", "8万", "7条", "8条", "9条"], + ["1条", "2条", "3条", "6条", "7条", "8条", "5万", "6万", "7万", "8万", "中", "白", "9万"], + ["1万", "2万", "3万", "9万", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "7筒", "中"], + + ["1万", "2万", "3万", "3条", "4条", "5条", "6条", "7条", "8条", "5筒", "6筒", "7筒", "白"], + ["1筒", "2筒", "3筒", "3万", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "7筒", "中"], + ["7条", "8条", "9条", "3万", "4万", "5万", "6万", "7万", "8万", "9万", "6筒", "7筒", "白"], + ["6万", "6万", "6万", "3万", "4万", "5万", "9万", "7万", "8万", "1筒", "2筒", "3筒", "中"], + ["5万", "5万", "1筒", "1筒", "4条", "5条", "6条", "2筒", "2筒", "3筒", "3筒", "7筒", "白"], + + ["9万", "9万", "9万", "8万", "4万", "5万", "6万", "7万", "南", "1万", "1万", "白", "中"], + ["1万", "2万", "3万", "1筒", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "7筒", "中"], + ["1万", "2万", "3万", "1筒", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "7筒", "中"], + ["2条", "2条", "3条", "3条", "4条", "2条", "3条", "5条", "6条", "1万", "1万", "6筒", "中"], + ["2条", "2条", "3条", "3条", "4条", "2条", "3条", "5条", "6条", "1万", "1万", "6筒", "中"], + + ["5万", "6万", "7万", "2筒", "2筒", "5条", "4条", "3条", "8万", "7万", "9万", "6万", "中"], + ["3万", "3万", "3万", "1条", "4万", "5万", "6万", "7万", "9万", "5筒", "6筒", "1条", "中"], + ["南", "南", "西", "西", "4万", "5万", "6万", "7万", "8万", "9万", "6筒", "4筒", "中"], + ["2万", "3万", "4万", "8筒", "8筒", "3筒", "3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "中"], + ["8筒", "8筒", "8筒", "南", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "7筒", "中"], + + ["4筒", "5筒", "6筒", "6条", "7条", "8条", "4万", "4万", "6万", "7万", "东", "白", "中"], + ["2筒", "4筒", "5筒", "6筒", "7筒", "8筒", "9筒", "6万", "7万", "8筒", "发", "发", "中"], + ["5筒", "6筒", "3万", "5万", "7万", "8万", "北", "北", "北", "发", "中", "白", "白"], + ["6筒", "5筒", "5万", "5万", "4万", "3万", "2万", "8条", "7条", "6条", "4条", "2条", "中"], + ["中", "中", "南", "南", "5万", "6万", "8万", "9万", "4条", "3条", "发", "发", "发"], + + ["北", "北", "6筒", "5筒", "4筒", "1万", "2万", "3万", "5条", "5条", "6条", "7条", "白"], + ["2筒", "4筒", "5筒", "6筒", "7筒", "2万", "4万", "5万", "8万", "7万", "8万", "白", "中"], + ["1筒", "1筒", "1筒", "2筒", "3筒", "4筒", "3条", "3条", "5条", "6条", "6万", "7万", "中"], + ["8筒", "9筒", "7筒", "3万", "4万", "5万", "6万", "7万", "8万", "5筒", "6筒", "1条", "中"], + ["1条", "3条", "3万", "5万", "7万", "8万", "南", "南", "南", "2条", "6万", "白", "白"], + + ["7条", "8条", "9条", "7万", "8万", "9万", "6筒", "7筒", "8筒", "9筒", "2条", "2条", "白"], + ["3条", "3条", "3万", "3万", "4万", "4万", "5万", "2万", "1筒", "2筒", "3筒", "4筒", "中"], + ["6条", "7条", "8条", "9条", "5条", "4条", "3条", "2条", "1条", "1条", "6筒", "7万", "白"], + ["1条", "3条", "3万", "5万", "7万", "8万", "7万", "8万", "6万", "2条", "6万", "白", "中"], + ["4筒", "7筒", "8筒", "9筒", "7万", "8万", "6万", "5万", "9万", "4万", "东", "白", "中"], + + ["西", "西", "1万", "2万", "3万", "8条", "8条", "6条", "6条", "5筒", "4筒", "西", "白"], + ["东", "东", "4条", "4条", "5条", "5条", "6条", "7条", "8条", "9条", "9条", "1万", "中"], + ["2条", "3条", "4万", "5万", "6万", "5筒", "8筒", "7筒", "发", "发", "9万", "中", "白"], + ["8条", "8条", "3筒", "4筒", "5筒", "8万", "7万", "6万", "1万", "3万", "3万", "白", "白"], + ["7筒", "7筒", "3条", "7条", "8条", "9条", "1万", "3万", "5万", "6万", "7万", "白", "中"], + + ["白", "白", "白", "1筒", "2筒", "3筒", "6筒", "6筒", "4万", "6万", "7万", "8万", "9万"], + ["中", "1筒", "1筒", "2筒", "2筒", "3筒", "3筒", "5筒", "6筒", "7筒", "8筒", "8万", "8万"], + ["中", "白", "白", "3筒", "3筒", "6筒", "7筒", "5条", "6条", "7条", "7万", "8万", "9万"], + ["白", "4筒", "5筒", "6筒", "7筒", "1条", "1条", "1条", "3条", "4条", "5条", "7条", "7条"], + ["白", "4筒", "4筒", "6筒", "6筒", "1条", "1条", "1条", "2条", "2条", "6万", "7万", "8万"], + + + ["6筒", "6筒", "7筒", "7筒", "8筒", "8筒", "6条", "7条", "7条", "7条", "2万", "3万", "4万"], + ["7筒", "8筒", "8筒", "9筒", "9筒", "2条", "3条", "4条", "6万", "7万", "8万", "北", "北"], + ["中", "1条", "5筒", "4筒", "6筒", "9筒", "9筒", "2条", "3条", "4条", "2万", "3万", "4万"], + ["3条", "3条", "5条", "5条", "7条", "8条", "8条", "9条", "9条", "6万", "7万", "8万", "白"], + ["白", "1条", "2条", "3条", "5条", "1万", "2万", "3万", "4万", "4万", "5万", "6万", "7万"], + + ["2筒", "3筒", "4筒", "5筒", "6筒", "7筒", "9筒", "白", "2条", "3条", "4条", "2万", "2万"], + ["中", "白", "1筒", "2筒", "2筒", "3筒", "3筒", "4筒", "2万", "2条", "7万", "8万", "9万"], + ["中", "3筒", "1筒", "1筒", "4条", "5条", "6条", "7条", "8条", "9条", "3万", "4万", "5万"], + ["中", "5筒", "3筒", "4筒", "6筒", "4条", "5条", "5条", "6条", "6条", "7条", "8条", "8条"], + ["中", "9条", "5筒", "6筒", "7筒", "1条", "1条", "6条", "7条", "8条", "7万", "8万", "9万"], + + ["白", "1筒", "1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "7筒", "8筒", "9筒", "2万", "3万"], + ["中", "4筒", "2筒", "2条", "3条", "4条", "4条", "6条", "7条", "8条", "3筒", "发", "发"], + ["中", "2筒", "3筒", "4筒", "7筒", "7筒", "8筒", "8筒", "9筒", "9筒", "3条", "6条", "6条"], + ["中", "白", "5筒", "5筒", "7筒", "7筒", "8筒", "8筒", "8筒", "4条", "4条", "5条", "6条"], + ["中", "2筒", "2筒", "4筒", "5筒", "6筒", "4条", "4条", "5条", "6条", "6条", "北", "北"], + + ["中", "中", "6筒", "6筒", "7筒", "8筒", "3条", "3条", "3条", "5条", "6条", "9条", "9条"], + ["白", "白", "2筒", "2筒", "3筒", "3筒", "5筒", "6筒", "7筒", "7筒", "8筒", "2万", "2万"], + ["中", "5条", "6条", "6条", "7条", "7条", "8条", "2万", "2万", "2万", "4万", "4万", "7万"], + ["4筒", "4筒", "7筒", "8筒", "8筒", "9筒", "2万", "3万", "3万", "4万", "5万", "6万", "6万"], + ["白", "2筒", "3筒", "3筒", "4筒", "4筒", "5筒", "1条", "2条", "3条", "3条", "4条", "5条"], + + ["5筒", "5筒", "1条", "2条", "3条", "3条", "4条", "1万", "2万", "2万", "3万", "3万", "7万"], + ["中", "白", "4筒", "5筒", "6筒", "9筒", "9筒", "1条", "2条", "3条", "3条", "5条", "7条"], + ["中", "中", "1筒", "1筒", "7筒", "8筒", "9筒", "5条", "6条", "7条", "4万", "5万", "6万"], + ["中", "6筒", "7筒", "8筒", "1条", "1条", "1条", "3条", "3条", "3条", "4条", "5条", "6条"], + ["4筒", "4筒", "5筒", "5筒", "5条", "5条", "5条", "7条", "7条", "1万", "1万", "1万", "2万"], + + ["中", "3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "4条", "5条", "6条", "7条", "8条", "8条"], + ["2筒", "2筒", "2筒", "3筒", "4筒", "4筒", "7筒", "7筒", "8筒", "8筒", "9筒", "9万", "9万"], + ["中", "1筒", "1筒", "2筒", "3筒", "4筒", "5筒", "7筒", "7筒", "6条", "6条", "7条", "8条"], + ["5筒", "6筒", "7筒", "1条", "1条", "2条", "2条", "3条", "3条", "2万", "3万", "4万", "4万"], + ["中", "中", "1筒", "1筒", "9筒", "9筒", "5条", "5条", "2万", "2万", "5万", "6万", "7万"], + + ["中", "6筒", "6筒", "8筒", "8筒", "2条", "2条", "3条", "3条", "5万", "7万", "7万", "7万"], + ["2筒", "3筒", "4筒", "6筒", "6筒", "6筒", "7筒", "8筒", "9筒", "4条", "5条", "6条", "6条"], + ["中", "白", "7条", "7条", "1万", "2万", "3万", "4万", "5万", "6万", "7万", "发", "发"], + ["中", "5筒", "6筒", "7筒", "8筒", "9筒", "9筒", "9筒", "5条", "6条", "7条", "发", "发"], + ["2筒", "2筒", "2筒", "3筒", "4筒", "7条", "7条", "7条", "8条", "9条", "1万", "2万", "3万"], + + ["白", "7筒", "8筒", "9筒", "1条", "1条", "3条", "4条", "5条", "3万", "4万", "5万", "6万"], + ["2筒", "3筒", "3筒", "4筒", "4筒", "5筒", "6筒", "7筒", "3条", "4条", "5条", "发", "发"], + ["中", "中", "白", "3筒", "5筒", "5筒", "1条", "1条", "3条", "4条", "5条", "西", "西"], + ["1条", "1条", "2条", "3条", "4条", "5条", "6条", "7条", "8条", "9条", "东", "东", "东"], + ["中", "白", "3筒", "4筒", "5筒", "3条", "4条", "5条", "4万", "4万", "5万", "5万", "5万"], + + ["中", "中", "3筒", "4筒", "5筒", "6筒", "3条", "4条", "4条", "4条", "5条", "7万", "8万"], + ["中", "5筒", "5筒", "2条", "3条", "4条", "1万", "2万", "3万", "3万", "4万", "8万", "8万"], + ["白", "1筒", "1筒", "1筒", "3筒", "5筒", "6筒", "7筒", "4条", "5条", "6条", "6条", "6条"], + ["2筒", "3筒", "3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "8筒", "4万", "5万", "6万", "6万"], + ["白", "6筒", "6筒", "4万", "9筒", "7筒", "8筒", "1万", "2万", "3万", "5万", "8万", "8万"], + + ["白", "3筒", "4筒", "5筒", "2条", "3条", "3条", "6条", "6条", "7条", "8条", "8条", "9条"], + ["中", "2筒", "2筒", "5筒", "6筒", "7筒", "8筒", "8筒", "9筒", "9筒", "2万", "2万", "3万"], + ["中", "白", "3筒", "3筒", "5筒", "6筒", "6条", "7条", "8条", "9条", "1万", "2万", "3万"], + ["3筒", "3筒", "5筒", "6筒", "6筒", "6筒", "7筒", "7筒", "8筒", "8筒", "8条", "发", "发"], + ["白", "5筒", "5筒", "5筒", "6筒", "6筒", "8筒", "8筒", "9筒", "3条", "3条", "东", "东"], + + ["中", "白", "6筒", "7筒", "3条", "4条", "5条", "6条", "6条", "7条", "7条", "8条", "8条"], + ["中", "7筒", "8筒", "9筒", "2万", "3万", "4万", "5万", "5万", "6万", "7万", "8万", "8万"], + ["中", "4条", "5条", "5条", "6条", "7条", "8条", "8条", "8条", "9条", "1万", "2万", "2万"], + ["中", "4筒", "5筒", "7筒", "8筒", "8筒", "8筒", "9筒", "4万", "5万", "西", "西", "西"], + ["白", "1筒", "1筒", "2筒", "2筒", "3筒", "4筒", "6筒", "7筒", "7筒", "8筒", "8筒", "9筒"], + + ["中", "白", "8筒", "8筒", "1条", "2条", "3条", "4条", "5条", "6条", "5万", "7万", "8万"], + ["中", "3筒", "4筒", "5筒", "7筒", "7筒", "7筒", "5条", "6条", "7条", "5万", "6万", "7万"], + ["中", "中", "1筒", "2筒", "5筒", "5筒", "5筒", "6万", "6万", "7万", "7万", "9万", "9万"], + ["1筒", "1筒", "2筒", "4筒", "5筒", "6筒", "6筒", "7筒", "8筒", "9筒", "9筒", "2条", "2条"], + ["中", "2筒", "3筒", "4筒", "6筒", "6筒", "5条", "5条", "5万", "6万", "7万", "7万", "7万"], + + ["白", "白", "6筒", "6筒", "6筒", "5条", "5条", "5条", "4万", "8万", "8万", "发", "发"], + ["3筒", "4筒", "4筒", "4筒", "4筒", "6筒", "7筒", "7筒", "8筒", "9筒", "1万", "2万", "2万"], + ["白", "1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "4条", "5条", "6条", "7条", "8条", "9条"], + ["3筒", "3筒", "4筒", "5筒", "5筒", "6筒", "5条", "5条", "5条", "7条", "7条", "8条", "8条"], + ["1筒", "1筒", "2筒", "2筒", "1条", "2条", "2条", "4条", "6条", "6条", "2万", "2万", "4万"], + + ["中", "2筒", "3筒", "4筒", "5条", "6条", "7条", "8条", "8条", "8万", "8万", "9万", "9万"], + ["2筒", "3筒", "4筒", "5筒", "5筒", "1条", "2条", "3条", "3条", "4条", "6条", "7条", "8条"], + ["中", "2筒", "3筒", "4筒", "4筒", "6筒", "7筒", "3万", "3万", "4万", "6万", "6万", "8万"], + ["白", "3筒", "3筒", "3筒", "5筒", "5筒", "5筒", "2条", "2条", "4条", "5条", "6条", "7条"], + ["中", "4筒", "4筒", "5筒", "6筒", "7筒", "7筒", "8筒", "8筒", "6条", "6条", "8条", "8条"], + + ["中", "6筒", "6筒", "7筒", "8筒", "9筒", "1条", "1条", "2条", "1万", "2万", "3万", "3万"], + ["白", "3筒", "3筒", "4筒", "5筒", "7筒", "7筒", "7筒", "8筒", "9筒", "6条", "7条", "8条"], + ["3筒", "3筒", "6筒", "7筒", "8筒", "4条", "5条", "6条", "6条", "7条", "7条", "8条", "9条"], + ["白", "5筒", "6筒", "7筒", "1条", "1条", "1条", "2条", "2条", "6条", "6条", "8条", "8条"], + ["中", "白", "3筒", "3筒", "3条", "3条", "4条", "4条", "南", "西", "北", "发", "发"], + + ["9筒", "9筒", "9筒", "1条", "1条", "2条", "2条", "5万", "5万", "6万", "6万", "7万", "7万"], + ["中", "中", "白", "2条", "3条", "4条", "6条", "6条", "1万", "2万", "3万", "6万", "7万"], + ["中", "3筒", "3筒", "3筒", "2条", "3条", "4条", "5条", "6条", "6条", "6条", "西", "西"], + ["中", "白", "3筒", "4筒", "5筒", "4条", "4条", "4条", "5条", "5条", "5条", "5万", "5万"], + ["中", "1筒", "1筒", "6筒", "7筒", "8筒", "9筒", "9筒", "9筒", "4万", "5万", "6万", "7万"], + + ["中", "中", "5筒", "5筒", "6筒", "6筒", "8筒", "8筒", "8筒", "5万", "6万", "7万", "7万"], + ["中", "白", "7筒", "8筒", "9筒", "3条", "3条", "4条", "5条", "5条", "5条", "2万", "2万"], + ["中", "6筒", "6筒", "3条", "4条", "5条", "4万", "4万", "6万", "6万", "6万", "7万", "7万"], + ["中", "4筒", "5筒", "5筒", "6筒", "6条", "6条", "4万", "5万", "5万", "7万", "西", "西"], + ["白", "2筒", "3筒", "3筒", "4筒", "7筒", "8筒", "9筒", "4条", "5条", "6条", "9条", "9条"], + + ["中", "3条", "4条", "6条", "7条", "7条", "8条", "8条", "9条", "南", "南", "发", "发"], + ["白", "白", "4筒", "5筒", "6筒", "1条", "2条", "3条", "4条", "5条", "6条", "6万", "7万"], + ["白", "白", "3筒", "4筒", "2条", "3条", "4条", "5条", "6条", "7条", "8条", "8万", "8万"], + ["白", "白", "2筒", "3筒", "4筒", "5筒", "2条", "2条", "3条", "3条", "北", "北", "北"], + ["中", "3条", "4条", "5条", "7条", "7条", "1万", "1万", "2万", "2万", "3万", "4万", "4万"], + + ["中", "3筒", "3筒", "4筒", "2万", "3万", "3万", "4万", "6万", "7万", "7万", "7万", "8万"], + ["白", "3筒", "3筒", "5筒", "5筒", "5筒", "7筒", "5条", "5条", "6条", "6条", "7条", "7条"], + ["中", "中", "白", "2筒", "3筒", "4筒", "6筒", "7筒", "8筒", "9筒", "5条", "5条", "5条"], + ["中", "1筒", "2筒", "3筒", "4筒", "4筒", "5筒", "5筒", "6筒", "6条", "6条", "5万", "6万"], + ["中", "5筒", "5筒", "5筒", "6筒", "7筒", "8筒", "6条", "7条", "8条", "9条", "9条", "9条"], + + ["1筒", "2筒", "3筒", "3条", "5条", "5条", "6条", "7条", "2万", "2万", "5万", "6万", "7万"], + ["白", "1筒", "3筒", "1筒", "2筒", "5筒", "5筒", "6筒", "7筒", "7筒", "1万", "2万", "3万"], + ["3筒", "4筒", "5筒", "6筒", "6筒", "5条", "5条", "5条", "6条", "7条", "8条", "9条", "9条"], + ["中", "白", "1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "1条", "2条", "3条", "6万", "6万"], + ["中", "1筒", "2筒", "3筒", "5筒", "6筒", "7筒", "6条", "7条", "7条", "7条", "8条", "9条"], + + ["中", "8筒", "8筒", "6条", "7条", "8条", "9条", "6万", "6万", "6万", "6万", "7万", "8万"], + ["中", "白", "6筒", "7筒", "1条", "1条", "1条", "3条", "4条", "5条", "6条", "6万", "6万"], + ["白", "2条", "3条", "4条", "5条", "2万", "3万", "4万", "5万", "5万", "7万", "8万", "9万"], + ["1筒", "1筒", "白", "2筒", "2筒", "3筒", "4筒", "4筒", "5筒", "6筒", "2万", "3万", "4万"], + ["中", "5筒", "6筒", "7筒", "8筒", "6条", "7条", "8条", "8条", "9条", "9条", "北", "北"], + + ["中", "中", "白", "1筒", "1筒", "9筒", "9筒", "9筒", "1条", "1条", "1条", "1万", "1万"], + ["2筒", "2筒", "2筒", "3筒", "4筒", "6筒", "6筒", "5条", "6条", "7条", "5万", "5万", "5万"], + ["3筒", "4筒", "5筒", "3条", "4条", "5条", "3万", "4万", "4万", "4万", "6万", "7万", "8万"], + ["7筒", "8筒", "8筒", "9筒", "9筒", "1万", "1万", "2万", "2万", "4万", "4万", "4万", "5万"], + ["中", "白", "3筒", "4筒", "5筒", "6筒", "4条", "4条", "2万", "3万", "4万", "4万", "5万"], + + ["白", "2筒", "3筒", "4筒", "4筒", "5筒", "5筒", "5条", "5条", "7条", "7条", "8条", "9条"], + ["白", "5筒", "5筒", "5筒", "6筒", "7筒", "8筒", "6条", "7条", "7条", "7条", "7条", "8条"], + ["中", "白", "2筒", "3筒", "5筒", "5筒", "5筒", "7筒", "7筒", "6万", "7万", "8万", "9万"], + ["中", "白", "5筒", "6筒", "7筒", "9筒", "9筒", "3条", "4条", "5条", "东", "东", "东"], + ["白", "4筒", "4筒", "5筒", "8筒", "8筒", "9筒", "9筒", "1条", "1条", "7条", "8条", "8条"], + + ["中", "白", "6条", "7条", "8条", "2万", "2万", "3万", "3万", "4万", "4万", "8万", "8万"], + ["白", "3筒", "4筒", "5筒", "6筒", "7筒", "7筒", "8筒", "8筒", "9筒", "9筒", "3条", "3条"], + ["中", "4条", "6筒", "3筒", "3筒", "3筒", "4筒", "5筒", "4条", "5条", "南", "南", "南"], + ["1筒", "2筒", "3筒", "3筒", "3筒", "6筒", "7筒", "7筒", "8筒", "8筒", "6条", "7条", "8条"], + ["中", "白", "5筒", "6筒", "7筒", "2条", "2条", "3万", "4万", "5万", "6万", "6万", "6万"], + + ["3筒", "4筒", "5筒", "1条", "2条", "3条", "7条", "8条", "3万", "4万", "5万", "5万", "5万"], + ["白", "6筒", "6筒", "8筒", "9筒", "9筒", "9筒", "3万", "3万", "5万", "6万", "6万", "南"], + ["中", "白", "3筒", "4筒", "5筒", "6筒", "7筒", "7筒", "2条", "2条", "2条", "7条", "7条"], + ["白", "4筒", "5筒", "7筒", "1条", "2条", "3条", "1万", "2万", "3万", "7万", "8万", "9万"], + ["白", "1筒", "2筒", "3筒", "5条", "6条", "6条", "7条", "7条", "8条", "8条", "发", "发"], + + ["4筒", "5筒", "5筒", "5筒", "6筒", "9筒", "9筒", "1条", "2条", "3条", "4万", "4万", "4万"], + ["3筒", "3筒", "9筒", "2条", "2条", "5万", "6万", "7万", "7万", "8万", "8万", "9万", "9万"], + ["中", "白", "6筒", "7筒", "8筒", "1条", "2条", "3条", "3万", "5万", "7万", "西", "西"], + ["中", "6筒", "7筒", "7筒", "9筒", "9筒", "1万", "1万", "3万", "3万", "7万", "9万", "9万"], + ["中", "1筒", "1筒", "1筒", "9筒", "9筒", "1条", "2条", "3条", "3万", "4万", "西", "西"], + + ["白", "白", "2条", "4条", "5条", "5条", "5条", "1万", "2万", "3万", "4万", "5万", "6万"], + ["中", "白", "1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "6条", "1万", "1万", "2万", "2万"], + ["白", "2筒", "3筒", "3筒", "4筒", "4筒", "5筒", "5筒", "1万", "2万", "3万", "7万", "7万"], + ["白", "9筒", "9筒", "9筒", "6条", "6条", "7条", "8条", "2万", "2万", "4万", "4万", "4万"], + ["1筒", "2筒", "3筒", "3筒", "4筒", "4筒", "5筒", "5筒", "7筒", "7筒", "7筒", "西", "西"], + + ["白", "2筒", "3筒", "4筒", "5筒", "6筒", "4条", "4条", "3万", "4万", "5万", "6万", "7万"], + ["中", "中", "3条", "3条", "4条", "4条", "5条", "7条", "8条", "9条", "2万", "3万", "4万"], + ["1筒", "2筒", "3筒", "3筒", "4筒", "6筒", "6筒", "1条", "1条", "2条", "2条", "3条", "4条"], + ["中", "3筒", "4筒", "5筒", "6筒", "1条", "1条", "6条", "7条", "8条", "2万", "3万", "4万"], + ["白", "1条", "1条", "1条", "5条", "5条", "3万", "4万", "4万", "5万", "6万", "发", "发"], + + ["中", "白", "6筒", "6筒", "7筒", "7筒", "8筒", "8筒", "1条", "1条", "1条", "5万", "5万"], + ["中", "白", "3筒", "3筒", "4筒", "4筒", "4筒", "6筒", "8筒", "8筒", "4条", "5条", "6条"], + ["中", "6筒", "7筒", "8筒", "2条", "2条", "2条", "4条", "4条", "4条", "1万", "2万", "3万"], + ["中", "中", "5筒", "6筒", "7筒", "1条", "2条", "3条", "6条", "7条", "8条", "6万", "6万"], + ["6筒", "7筒", "7筒", "8筒", "1条", "1条", "1条", "2条", "3条", "4条", "4条", "8条", "8条"], + + ["6条", "6条", "7条", "8条", "9条", "1万", "2万", "3万", "4万", "5万", "5万", "6万", "7万"], + ["白", "3筒", "4筒", "5筒", "6筒", "6筒", "6筒", "1条", "2条", "2条", "2条", "西", "西"], + ["8筒", "8筒", "9筒", "9筒", "4条", "4条", "5条", "6条", "7条", "8条", "8条", "9条", "9条"], + ["白", "2筒", "2筒", "2筒", "3筒", "4筒", "5条", "5条", "6条", "6条", "7条", "5万", "5万"], + ["白", "1筒", "2筒", "3筒", "3筒", "5筒", "6筒", "7筒", "1条", "1条", "3条", "1万", "1万"], + + ["白", "白", "1筒", "2筒", "3筒", "3筒", "3筒", "4筒", "4筒", "5筒", "4条", "北", "北"], + ["中", "2筒", "3筒", "4筒", "3条", "4条", "5条", "6条", "4万", "5万", "6万", "西", "西"], + ["中", "白", "2筒", "3筒", "4筒", "6筒", "7筒", "8筒", "5条", "5条", "7条", "8条", "9条"], + ["中", "3筒", "4筒", "5筒", "1条", "1条", "5万", "6万", "6万", "7万", "7万", "8万", "8万"], + ["中", "8筒", "9筒", "3条", "4条", "5条", "5条", "6条", "7条", "7条", "7条", "8条", "9条"], + + ["中", "白", "4筒", "5筒", "6筒", "5条", "5条", "2万", "3万", "3万", "4万", "9万", "9万"], + ["中", "1条", "1条", "3条", "4条", "8条", "8条", "9条", "1万", "1万", "2万", "4万", "5万"], + ["中", "3条", "3条", "7条", "8条", "9条", "1万", "1万", "1万", "3万", "4万", "5万", "7万"], + ["白", "1筒", "2筒", "3筒", "6筒", "7筒", "8筒", "9筒", "9筒", "1条", "2条", "8万", "9万"], + ["中", "白", "3筒", "3筒", "4筒", "4筒", "5筒", "5筒", "6筒", "7筒", "8筒", "7万", "7万"], + + ["3筒", "3筒", "4筒", "4筒", "5筒", "1条", "1条", "3条", "3条", "5条", "5条", "南", "南"], + ["1筒", "1筒", "3筒", "4筒", "5筒", "8筒", "8筒", "7条", "7条", "8条", "8条", "9条", "9条"], + ["白", "6筒", "6筒", "7筒", "7筒", "7筒", "5万", "6万", "7万", "7万", "8万", "8万", "9万"], + ["白", "2筒", "3筒", "4筒", "9筒", "9筒", "5条", "6条", "1万", "2万", "3万", "3万", "4万"], + ["中", "4筒", "5筒", "6筒", "7筒", "7条", "7条", "7条", "1万", "2万", "3万", "3万", "3万"], + + ["中", "3万", "3筒", "4筒", "5筒", "1条", "1条", "1条", "5条", "6条", "7条", "8万", "8万"], + ["白", "5筒", "5筒", "8筒", "8筒", "8筒", "4万", "6万", "7万", "7万", "8万", "8万", "9万"], + ["中", "5筒", "5筒", "6筒", "6筒", "7筒", "7筒", "8筒", "8筒", "4万", "5万", "6万", "7万"], + ["中", "1筒", "1筒", "2筒", "2筒", "3筒", "3筒", "5筒", "6筒", "7筒", "8筒", "8万", "8万"], + ["白", "4筒", "5筒", "6筒", "7筒", "1条", "1条", "1条", "3条", "4条", "5条", "7条", "7条"], + + ["白", "4筒", "4筒", "6筒", "6筒", "1条", "1条", "1条", "2条", "2条", "6万", "7万", "8万"], + ["白", "2条", "3条", "4条", "5条", "6条", "6条", "7条", "9条", "9条", "7万", "8万", "9万"], + ["中", "白", "4筒", "5筒", "5筒", "6筒", "6筒", "7筒", "4条", "4条", "6条", "8条", "8条"], + ["中", "中", "3筒", "4筒", "6筒", "7筒", "8筒", "9筒", "9筒", "4条", "5条", "6条", "7条"], + ["中", "1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "7筒", "4条", "4条", "3万", "3万", "3万"], + ["白", "6筒", "6筒", "7筒", "7筒", "7筒", "5万", "6万", "7万", "7万", "8万", "8万", "9万"] + + ]; + + + let randomInt = Math.floor(Math.random() * 10000) % testPaiList.length; + let list = []; + list = mahjong.getMysetCards(testPaiList[randomInt]); + + if (list.length == 13) { + return list; + } else { + + let loopCount = 13 - list.length; + for (; loopCount > 0; loopCount--) { + let card = mahjong.assigningOneCard(); + list.push(card); + } + return list; + } + +} +module.exports = Card; +module.exports.testSpecialCardEx = testSpecialCardEx \ No newline at end of file diff --git a/mjlib_js2/App/Models/HuAlgorithm/Algorithm.rd b/mjlib_js2/App/Models/HuAlgorithm/Algorithm.rd new file mode 100644 index 0000000..c4e8faa --- /dev/null +++ b/mjlib_js2/App/Models/HuAlgorithm/Algorithm.rd @@ -0,0 +1,61 @@ + +一)查表法核心思想: + 1、名词解释:eye(将),字牌(feng、东南西北中发白),花色(万、筒、条、字牌) + 2、分而治之:检查手牌是否能胡是依次检查万、筒、条、字牌四种花色是否能组成胡牌的一部分。 + 3、单一花色要能满足胡牌的部分,则要么是3*n(不带将),要么是3*n+2(带将)。3*n中的3带表三张牌一样的刻子,或三张连续的牌如1筒2筒3筒。 + 4、判断是否满足胡牌的单一花色部分,需要根据是否有将,有几个赖子,查询不同的表。表内容表示表里的元素加上对应的赖子数量能组成3*n 或3*n+2。 + 赖子数是表名最后的数字,带有eye的表表示满足3*n+2,没有的表示满足3*n。 + 5、查表的key值,是直接根据1-9有几张牌就填几(赖子不算),如1-9万各一张,则key为111111111。如1万3张,9万2张,则key为300000002。 + 6、组合多种花色,判断是否能胡牌。将赖子分配给不同的花色,有若干种分配方式,只要有一种分配能让所有花色满足单一花色胡牌部分,则手牌能胡。 + 如:手上有3个赖子,可以分配万、筒、条各一张,也可以万、同、字牌各一张 + 7、根据是否有将、是否字牌分为4种表,每种表又根据赖子个数0-8分别建表,共36张表,具体如下: + + 赖子个数 筒子万子索子表 字牌表 + 0 Table0 Table_Feng0 + 1 Table1 Table_Feng1 + 2 Table2 Table_Feng2 + 3 Table3 Table_Feng3 + 4 Table4 Table_Feng4 + 5 Table5 Table_Feng5 + 6 Table6 Table_Feng6 + 7 Table7 Table_Feng7 + 8 Table8 Table_Feng8 + + 要单独分开字牌表是因为字牌是不能形成顺子的,东南西不是顺子。(但是有些地方的玩法,例如南昌,它是可是东南西组成顺子的。) + + 步骤: + 1、统计手牌中鬼牌个数 ghostCount,将鬼牌从牌数据中去除. + 2、把手上的牌编码成一串数字比如:1筒2筒3筒3筒3筒3筒6筒7筒8筒2万3万3万3万4万 + 筒: 1,1,4,0,0,1,1,1,0 得出的数字为114001110 + 万: 0,1,3,1,0,0,0,0,0 得出的数字为13100000 + 3、查表检查单一种花色能否胡牌先查Table0; 如果不能胡,再根据手上鬼牌的个数查其它表。 + 4、当所有牌型都能胡,并且有且仅有一种牌型是3*n+2, 其它牌型为3*n. + + +二) 表的产生 + + 非字牌表的产生: + 1、穷举万字牌所有满足胡牌胡可能,将对应的牌型记录为数字,放入Table0中。 + 具体是每次加入一个刻子,顺子或是将(将只能加入一对),最多加入四组外带将牌。 + 2、将Table0中牌去掉一张,放入Table1中,表示去掉的牌用1张赖子代替。 + 3、将Table1中牌去掉一张,放入Table2中,表示去掉的牌用1张赖子代替。 + 4、将Table2中牌去掉一张,放入Table3中,表示去掉的牌用1张赖子代替。 + 5、将Table3中牌去掉一张,放入Table4中,表示去掉的牌用1张赖子代替。 + 6、将Table4中牌去掉一张,放入Table5中,表示去掉的牌用1张赖子代替。 + 7、将Table5中牌去掉一张,放入Table6中,表示去掉的牌用1张赖子代替。 + 8、将Table6中牌去掉一张,放入Table7中,表示去掉的牌用1张赖子代替。 + 9、将Table7中牌去掉一张,放入Table8中,表示去掉的牌用1张赖子代替。 + + 只计算到8张牌,如果手上有超过8张牌,无论是什么牌都是胡牌的。 + + 字牌表的产生: + 与非字牌表的产生方法相同,只是第一步中,不能加入顺子(除非麻将玩法字牌是能组成顺子的)。 + +三) 代码实现 + 1) 只有在没有表的情况下,才会生成每一张表。 + 2) 每张表生成以后,将会加载下来,并存放在内存中。 + 3) GenTable是个单例,这样可以节约内存。 + + 使用方法: + 1) let hulogic = new HuLogic(); + 2) hulogic.checkHu(cardList,ongoingCard); diff --git a/mjlib_js2/App/Models/HuAlgorithm/GenTable.js b/mjlib_js2/App/Models/HuAlgorithm/GenTable.js new file mode 100644 index 0000000..b796c0e --- /dev/null +++ b/mjlib_js2/App/Models/HuAlgorithm/GenTable.js @@ -0,0 +1,263 @@ + +const fs = require('fs'); + +class GenTable { + + constructor(opts) { + + + // 普通牌:万,筒,条 + this.KeyDic = {}; + this.KeyDicWithGhost = {}; + for (let i = 1; i < 9; i++) { + this.KeyDicWithGhost[i] = {}; + } + // 字牌 + this.KeyDicFeng = {}; + this.KeyDicFengWithGhost = {}; + for (let i = 1; i < 9; i++) { + this.KeyDicFengWithGhost[i] = {}; + } + } + + static Instance() { + if (!GenTable.instance) { + let instance = new GenTable(); + instance.init(); + GenTable.instance = instance; + } + return GenTable.instance; + } + + init() { + if (!fs.existsSync(__dirname + '/tbl/' + 'Table0')) { + this.start(); + } else { + this.load(); + } + } + + findCards(cards, ghostCount, isFeng) { + let key = this.generateKey(cards); + if (ghostCount > 0) { + if (isFeng && this.KeyDicFengWithGhost[ghostCount][key]) { + return true; + } + if (!isFeng && this.KeyDicWithGhost[ghostCount][key]) { + return true; + } + } else if (isFeng && this.KeyDicFeng[key]) { + return true; + } else if (!isFeng && this.KeyDic[key]) { + return true; + } + return false; + } + + start() { + let cards = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + let level = 0; + let eye = false; + // 非字牌 + let feng = false; + // 生成不带鬼穷举表 + this.genTable(cards, level, eye, feng); + // 生成带鬼穷举表 + this.genTableWithGhost(feng); + // 字牌 + feng = true; + // 生成不带鬼穷举表 + this.genTable(cards, level, eye, feng); + // 生成带鬼穷举表 + this.genTableWithGhost(feng); + // 保存 + this.dump(); + } + + dump() { + // 不带鬼 + this._dump('Table0', this.KeyDic); + // 带鬼 + for (let i in this.KeyDicWithGhost) { + this._dump('Table' + i, this.KeyDicWithGhost[i]); + } + // 字牌不带鬼 + this._dump('Table_Feng0', this.KeyDicFeng); + // 字牌带鬼 + for (let i in this.KeyDicFengWithGhost) { + this._dump('Table_Feng' + i, this.KeyDicFengWithGhost[i]); + } + } + + _dump(fileName, keyDic) { + var fWrite = fs.createWriteStream(__dirname + '/tbl/' + fileName); + for (let key in keyDic) { + fWrite.write(key, 'utf8'); + fWrite.write('\n', 'utf8'); + } + fWrite.end(); + + fWrite.on('finish', function () { + console.log('写入完成'); + }) + } + + load() { + // 不带鬼 + this._load('Table0', this.KeyDic); + // 带鬼 + for (let i = 1; i < 9; i++) { + this._load('Table' + i, this.KeyDicWithGhost[i]); + } + // 字牌不带鬼 + this._load('Table_Feng0', this.KeyDicFeng); + // 字牌带鬼 + for (let i = 1; i < 9; i++) { + this._load('Table_Feng' + i, this.KeyDicFengWithGhost[i]); + } + } + + _load(fileName, keyDic) { + if (!fs.existsSync(__dirname + '/tbl/' + fileName)) { + console.log(fileName, "文件不存在"); + return; + } + + const fileDoc = fs.readFileSync(__dirname + '/tbl/' + fileName); + const keyArray = String(fileDoc).split('\n'); + for (let i = 0; i < keyArray.length; i++) { + let key = keyArray[i]; + if (key) { + keyDic[key] = 1; + } + } + } + + genTableWithGhost(feng) { + let dic = this.KeyDic; + if (feng) { + dic = this.KeyDicFeng; + } + for (let key in dic) { + let cards = this.toNumberArray(key); + this.generateWithGhost(cards, 1, feng); + } + } + + generateWithGhost(cards, ghostCount, feng) { + for (let i = 0; i < 9; i++) { + if (cards[i] == 0) continue; + cards[i]--; + if (!this.tryAdd(cards, ghostCount, feng)) { + cards[i]++; + continue; + } + if (ghostCount < 8) { + this.generateWithGhost(cards, ghostCount + 1, feng); + } + cards[i]++; + } + } + // 判断是否符合规则 + tryAdd(cards, ghostCount, feng) { + for (let i = 0; i < 9; i++) { + if (cards[i] < 0 || cards[i] > 4) { + return false; + } + } + let key = this.generateKey(cards); + if (feng && this.KeyDicFengWithGhost[ghostCount][key]) { + return false; + } + if (!feng && this.KeyDicWithGhost[ghostCount][key]) { + return false; + } + if (feng) { + this.KeyDicFengWithGhost[ghostCount][key] = 1; + } else { + this.KeyDicWithGhost[ghostCount][key] = 1; + } + return true; + } + // 生成不带鬼的穷举表 + genTable(cards, level, eye, feng) { + for (let i = 0; i < 9; i++) { + if (feng && i > 6) continue; + let totalCardCount = this.calTotalCardCount(cards); + // +AAA,递归 + if (totalCardCount <= 11 && cards[i] <= 1) { + cards[i] += 3; + let key = this.generateKey(cards); + if (feng && this.KeyDicFeng[key] == null) { + this.KeyDicFeng[key] = 4; + } + if (!feng && this.KeyDic[key] == null) { + this.KeyDic[key] = 4; + } + if (level < 5) { + this.genTable(cards, level + 1, eye, feng); + } + cards[i] -= 3; + } + // +ABC,递归 + if (!feng && totalCardCount <= 11 && i < 7 && cards[i] <= 3 && cards[i + 1] <= 3 && cards[i + 2] <= 3) { + cards[i] += 1; + cards[i + 1] += 1; + cards[i + 2] += 1; + let key = this.generateKey(cards); + if (this.KeyDic[key] == null) { + this.KeyDic[key] = 4; + } + if (level < 5) { + this.genTable(cards, level + 1, eye, feng); + } + cards[i] -= 1; + cards[i + 1] -= 1; + cards[i + 2] -= 1; + } + // +DD,递归 + if (totalCardCount <= 12 && cards[i] <= 2 && !eye) { + cards[i] += 2; + let key = this.generateKey(cards); + if (feng && this.KeyDicFeng[key] == null) { + this.KeyDicFeng[key] = 4; + } + if (!feng && this.KeyDic[key] == null) { + this.KeyDic[key] = 4; + } + if (level < 5) { + this.genTable(cards, level + 1, true, feng); + } + cards[i] -= 2; + } + } + } + + toNumberArray(strKey) { + let result = []; + for (let i = 0; i < strKey.length; i++) { + let temp = strKey.slice(i, i + 1); + result.push(parseInt(temp)); + } + return result; + } + + generateKey(cards) { + let key = ''; + let dic = ['0', '1', '2', '3', '4'] + for (let i = 0; i < cards.length; i++) { + key = key + dic[cards[i]]; + } + return key; + } + + calTotalCardCount(cards) { + let count = 0; + for (let i = 0; i < 9; i++) { + count += cards[i]; + } + return count; + } +} + +module.exports = GenTable; \ No newline at end of file diff --git a/mjlib_js2/App/Models/HuAlgorithm/HuLogic.js b/mjlib_js2/App/Models/HuAlgorithm/HuLogic.js new file mode 100644 index 0000000..844d206 --- /dev/null +++ b/mjlib_js2/App/Models/HuAlgorithm/HuLogic.js @@ -0,0 +1,118 @@ + +let GenTable = require('./GenTable') +/** + * 查表法检查胡牌 + */ +class HuLogic { + constructor(opts) { + this.genTable = GenTable.Instance(); + } + + init(){ + return this; + } + + + checkHu(cardInHandList, cardOngoing){ + + let cardList = cardInHandList.slice(0); + if( cardOngoing != undefined){ + cardList.push(cardOngoing); + } + + let privatePointList = []; + let ghostList = []; + for( let i in cardList){ + let cardPoint = cardList[i].privatePoint; + privatePointList.push(cardPoint); + if(cardList[i].isGhost){ + ghostList.push(cardPoint); + } + } + return this.isHu(privatePointList,ghostList); + } + + isHu(cardList, ghostList) { + // 初始计数 + let cards = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0],// 筒 + [0, 0, 0, 0, 0, 0, 0, 0, 0],// 条 + [0, 0, 0, 0, 0, 0, 0, 0, 0],// 万 + [0, 0, 0, 0, 0, 0, 0, 0, 0] // 字,后两位为0不变,为了保持数组长度一致 + ]; + // 鬼牌总数 + let ghostCount = 0; + // 计数:统计各牌张数,鬼牌另计在ghostCount上 + for (let card of cardList) { + if (ghostList.indexOf(card) >= 0) { + // 是鬼牌 + ghostCount++; + } else { + let rank = card % 9; + let type = Math.floor(card / 9); + cards[type][rank]++; + } + } + // 开始匹配 + //tmpData是一个3维数组,tmpData[0]是手牌中除去鬼牌后的牌,它可能是筒子,万子,条子,或者字牌) + //tmpData[1] 是鬼牌的个数 + //tmpData[2] 是否有将 + let tmpData = {}; + tmpData[1] = ghostCount; + tmpData[2] = false; + //检查每一个牌型是否胡牌; + for (let i = 0; i < 4; i++) { + tmpData[0] = cards[i]; + if (!this.checkCards(tmpData, 0, i==3)) { + // console.log(array[i], cards[i], false); + return false; + } + // console.log(array[i], cards[i], true); + } + if (!tmpData[2] && tmpData[1]%3==2) { + // 目前匹配没有DD,但剩余鬼牌可匹配到DD + return true; + } + if (tmpData[2] && tmpData[1]%3 == 0) { + // 匹配到DD了,且剩余鬼牌符合AAA和ABC + return true; + } + return false; + } + + checkCards(tmpData, ghostCount, feng) { + let totalCardCount = this.genTable.calTotalCardCount(tmpData[0]); + if (totalCardCount == 0) { + return true; + } + //查表,如果表里没有,加一个鬼进去再试试 + if (!this.genTable.findCards(tmpData[0], ghostCount, feng)) { + //递归,每次鬼牌+1,直到鬼牌用尽为止,如果某个牌型把鬼牌用尽都不能胡,那么手牌就不能胡了。 + if (ghostCount < tmpData[1]) { + return this.checkCards(tmpData, ghostCount+1, feng); + } else { + return false; + } + } else {//查表找到了,看看牌型是否使用了将。 + //如果该牌型有将 + if ((totalCardCount + ghostCount)%3==2) { + //之前没用将,直接标记为已使用将了。 + if (!tmpData[2]) { + tmpData[2] = true; + } + //如果用将,那么尝试一下再加一个鬼牌进去,看能否胡牌。因为胡牌只能一对将。 + else if (ghostCount < tmpData[1]) { + return this.checkCards(tmpData, ghostCount + 1, feng); + } + else{//如果找不到就不能胡 + return false; + } + } + tmpData[1] = tmpData[1] - ghostCount; + // console.log('判断', tmpData[0], true, ghostCount, tmpData[1]); + } + return true; + } +} + +module.exports = HuLogic; \ No newline at end of file diff --git a/mjlib_js2/App/Models/Mahjong.js b/mjlib_js2/App/Models/Mahjong.js new file mode 100644 index 0000000..9afc62b --- /dev/null +++ b/mjlib_js2/App/Models/Mahjong.js @@ -0,0 +1,370 @@ +/** + * Created by mac on 2016/11/22. + */ +const Card = require('./Card'); +const ModelUtil = require('./ModelUtil'); + +/** + * @class Mahjong + * @mixes + * 麻将,构造一副麻将牌;筒子,索子,万子,字牌,共34 * 4张. + * + */ +class Mahjong { + constructor() { + this.assignedIndex = 0; //取牌的位置 + this.retainCardCount = 0; //剩余牌数 + this.cardArray = []; //牌组 + this.hadGetCard = []; //已经取出的牌 + this.ghostCards = []; //红中,白板 点数 + this.middleCard = 0; //翻鬼时,翻开一张牌,这张牌的上一张与下一张为鬼牌,这里保存的是中间翻开的牌. + } + + + /** + * init + * 初始化一副牌,共34种牌;筒子,索子,万子以及字牌,这里没有花牌 + */ + init() { + this.assignedIndex = 0; + this.cardArray = []; + this.retainCardCount = 34 * 4; + for (let i = 0; i < 34; i++) { + for (let j = 0; j < 4; j++) { + let card = new Card(); + card.initWithPrivatePoint(i); + this.cardArray.push(card); + } + } + + } + + /** + * 初始化有鬼牌的麻将。 + * @param {*} ghostCard 标示有哪些鬼牌 + * ghostCard = 0:无鬼 + * ghostCard = 1:白板鬼 + * ghostCard = 2: 红中鬼 + * ghostCard = 4:发财鬼 + * + * ghostCard = 3 = 1&2 白板红中鬼 + * ghostCard = 5 = 1&4 白板发财鬼 + * ghostCard = 6 = 2&4 红中发财鬼 + * ghostCard = 7 = 1&2&4 红中白板发财鬼 + * + * ghostCard = -1:双鬼番鬼 + * + */ + initByghostCard(ghostCard) { + return this.initByGhostType(ghostCard); + } + + + /** + * 初始化有鬼牌的麻将。 + * 参考initByghostCard + * @param {*} ghostType 鬼牌类型 + */ + initByGhostType(ghostType) { + this.assignedIndex = 0; + this.cardArray = []; + this.ghostCards = []; + this.retainCardCount = 34 * 4; + + let bitBai = 1; + let bitZhong = 1 << 1; + let bitFa = 1 << 2; + + let ghostCardBaiPrivatePoint = CARDID.CARDID_BAI; //白板 + let ghostCardZhongPrivatePoint = CARDID.CARDID_ZHONG; //红中 + let ghostCardFaPrivatePoint = CARDID.CARDID_FA; + + //翻鬼 + if (ghostType == -1) { + let tempCards = Mahjong.RandomGhostPoint(3); + + this.ghostCards.push(tempCards[0]); //鬼牌 + this.ghostCards.push(tempCards[2]); //鬼牌 + this.middleCard = tempCards[1]; //中间牌 + } else { + if ((bitBai & ghostType) == bitBai) { + this.ghostCards.push(ghostCardBaiPrivatePoint) + } + + if ((bitZhong & ghostType) == bitZhong) { + this.ghostCards.push(ghostCardZhongPrivatePoint) + } + + if ((bitFa & ghostType) == bitFa) { + this.ghostCards.push(ghostCardFaPrivatePoint); + } + } + + for (let i = 0; i < 34; i++) { + for (let j = 0; j < 4; j++) { + let card = new Card(); + card.initWithPrivatePoint(i, this.ghostCards); + this.cardArray.push(card); + } + } + } + + + /** + * initWithGhostBaiORZhong + * 初始化一副牌,指定白板是否鬼牌,红中是否鬼牌 + * @param {bool} isGhostBai true:白板为鬼牌 false:白板不是鬼牌 + * @param {bool} isGhostZhong true:红中为鬼牌 false:红中不是鬼牌 + */ + initWithGhostBaiORZhong(isGhostBai, isGhostZhong, isGhostFa) { + this.assignedIndex = 0; + this.cardArray = []; + this.ghostCards = []; + this.retainCardCount = 34 * 4; + + let ghostCardBaiPrivatePoint = CARDID.CARDID_BAI; //白板 + let ghostCardZhongPrivatePoint = CARDID.CARDID_ZHONG; //红中 + let ghostCardFaPrivatePoint = CARDID.CARDID_FA; + + if (isGhostBai) { + this.ghostCards.push(ghostCardBaiPrivatePoint) + } + + if (isGhostZhong) { + this.ghostCards.push(ghostCardZhongPrivatePoint) + } + + if (isGhostFa) { + this.ghostCards.push(ghostCardFaPrivatePoint); + } + + for (let i = 0; i < 34; i++) { + for (let j = 0; j < 4; j++) { + let card = new Card(); + card.initWithPrivatePoint(i, this.ghostCards); + this.cardArray.push(card); + } + } + + }; + + //洗牌 + /** + * shuffle + * 洗牌 + * 把遍历cardArray中的每一张牌与任意位置中的一张牌交换 + */ + shuffle() { + + for (let i = 0; i < this.cardArray.length; i++) { + let card = this.cardArray[i]; + + let randomPosition = getRandom() % this.cardArray.length; + let pcard = this.cardArray[randomPosition]; + this.cardArray[i] = pcard; + this.cardArray[randomPosition] = card; + } + // let pointList = this.cardArray.map((card) => { + // return card.privatePoint; + // }) + } + + shuffleSpecial(pointList) { + + //1条, 9筒,4万,2条,1万,9筒,1条,东,9万,6条,7筒,4条,8筒,2条, + // 西,2万,1筒,8筒,8万,白,白,6条,3条,1筒,7万,南,北, 8条,2筒, + //1筒,3条,5万,5条,4条,白,9万,4条,南,5万,2万, 9筒,1条,4筒,发,5条 + //,7万,发,8条,南,7万,7万,8条,1万, 东,6万,8筒,3筒,6筒,2条,4筒, + //8万,5条,8条,4万,8万,4万, 1万,9条,4筒,3万,6万,8万,东,9万,西,2筒, + //中,2万,2万, 4万,中,白,7条,7条,东,5万,北,6筒,4条,6条,7筒,9万, + // 2筒,西,7筒,5筒,9条,6万,3筒,9条,7条,6万,中,3筒,北, 西,1万,6筒, + //9条,5筒,3筒,5筒,3万,8筒,5筒,9筒,2筒,3万, 3万,3条,3条,7筒,6条, + //中,2条,4筒,5条,7条,南,发,5万, 北,1条,1筒,发,6筒, + let tempList = [9, 8, 21, 10, 18, 8, 9, 27, 26, 14, 6, 12, 7, 10, + 29, 19, 0, 7, 25, 33, 33, 14, 11, 0, 24, 28, 30, 16, 1, 0, + 11, 22, 13, 12, 33, 26, 12, 28, 22, 19, 8, 9, 3, 32, 13, + 24, 32, 16, 28, 24, 24, 16, 18, 27, 23, 7, 2, 5, 10, 3, + 25, 13, 16, 21, 25, 21, 18, 17, 3, 20, 23, 25, 27, 26, + 29, 1, 31, 19, 19, 21, 31, 33, 15, 15, 27, 22, 30, 5, + 12, 14, 6, 26, 1, 29, 6, 4, 17, 23, 2, 17, 15, 23, 31, 2, + 30, 29, 18, 5, 17, 4, 2, 4, 20, 7, 4, 8, 1, 20, 20, 11, 11, + 6, 14, 31, 10, 3, 13, 15, 28, 32, 22, 30, 9, 0, 32, 5]; + if (pointList != undefined) { + tempList = pointList; + } + + this.cardArray = []; + for (let i = 0; i < tempList.length; i++) { + let card = Card.CreateCardWithPoint(tempList[i], this.ghostCards.indexOf(i) >= 0); + this.cardArray.push(card); + } + + console.log("*****正在使用测试用的特殊牌型*****") + } + + + /** + * 获取指定的牌数据,这个函数一般用于测试 + * @param {*} displayNameList 牌名数组["1筒","2筒"..] + * @param {*} fillCount 如果找不到够的这些牌,也必须填补到这个张数,让返回的牌数个数与fillcount一致 + */ + getMysetCards(displayNameList, fillCount = 13) { + + let list = [] + for (let i in displayNameList) { + for (let j = this.assignedIndex; j < this.cardArray.length; j++) { + if (displayNameList[i] == this.cardArray[j].displayName) { + list.push(this.cardArray[j]) + this.cardArray.splice(j, 1); + break + } + } + } + this.retainCardCount -= list.length; + + if (list.length < fillCount) { + + let loopCount = fillCount - list.length; + for (; loopCount > 0; loopCount--) { + let card = this.assigningOneCard(); + list.push(card); + } + } + list = ModelUtil.sort(list); + return list + } + + /** + * assigningCards 取多张牌,一般为1手牌,13张 + * 发出多张牌 + * @param {Int} requestCardCount 请求发牌的张数 + * 当一副牌中不够发的时候,取剩余牌 + * @return {Array} 返回发出的牌队列,对象为Card + */ + assigningRemainCard(requestCardCount) { + if (requestCardCount < 1) { + return []; + } + if ((this.assignedIndex + requestCardCount) > this.cardArray.length) { + requestCardCount = this.cardArray.length - this.assignedIndex; + } + + let result = this.cardArray.slice(this.assignedIndex, this.assignedIndex + requestCardCount); + + //记录发出的牌 + this.hadGetCard = this.hadGetCard.concat(result); + + this.assignedIndex += requestCardCount; + this.retainCardCount -= requestCardCount; + if (this.retainCardCount <= 0) { + this.retainCardCount = 0; + } + result = ModelUtil.sort(result); + return result; + } + + + //取多张牌,一般为1手牌,13张 + /** + * assigningCards + * 发出多张牌 + * @param {Int} requestCardCount 请求发牌的张数 + * 当一副牌中不够发的时候,返回值为空队列 + * @return {Array} 返回发出的牌队列,对象为Card + */ + assigningCards(requestCardCount) { + if (requestCardCount < 1) { + return []; + } + if ((this.assignedIndex + requestCardCount) > this.cardArray.length) { + return []; + } + + let result = this.cardArray.slice(this.assignedIndex, this.assignedIndex + requestCardCount); + + //记录发出的牌 + this.hadGetCard = this.hadGetCard.concat(result); + + this.assignedIndex += requestCardCount; + this.retainCardCount -= requestCardCount; + result = ModelUtil.sort(result); + return result; + } + + //获取1张牌 + /** + * assigningOneCard + * 发出一张牌 + * @return {Object} 返回一个Card对象 + */ + assigningOneCard() { + let cardList = this.assigningCards(1); + if (cardList.length === 0) { + return null; + } + + let card = cardList[0]; + return card; + } + + + /** + * getRemindCardCount + * 获取剩余多少张牌还没有发出 + * @return {Int} 剩余多少张牌没有发出 + */ + getRemindCardCount() { + return this.cardArray.length - this.assignedIndex; + } + + /** + * getAssignedCardList + * 获取已发出牌的列表 + * @return {Array} 已发出牌的列表, 列表中为Card对象 + */ + getAssignedCardList() { + this.hadGetCard = ModelUtil.sort(this.hadGetCard); + return this.hadGetCard; + } + + /** + * 随机返回3张连续的牌,并且保证3张牌花色是一样的。 + */ + static RandomGhostPoint(count) { + //如果产生鬼的个数少于1个,或者大于7个,都视为不合理 + if (count <= 0 || count > 7) { + return []; + } + + let result = []; + //0〜8:表示1~9筒;9〜17:表示1〜9索;18〜26:1〜9万;27,28,29,30:东南西北 31~33:中发白 + let nRandom = Math.floor(Math.random() * 10000); + let otherRandom2 = Math.floor(Math.random() * 100); + let otherRandom3 = Math.floor(Math.random() * 100); + + let firstCard = (nRandom * otherRandom2 * otherRandom3) % 34; + + let card = new Card(); + card.initWithPrivatePoint(firstCard); + result.push(card.privatePoint); + + for (let i = 1; i < count; i++) { + let cardNext = card.getNextCard(); + result.push(cardNext.privatePoint); + card = cardNext; + } + + return result; + } +} + +module.exports = Mahjong; + + + +/** + * @return {Int} 返回随机数 + */ +function getRandom() { + return Math.floor(Math.random() * 10000); +} \ No newline at end of file diff --git a/mjlib_js2/App/Models/ModelUtil.js b/mjlib_js2/App/Models/ModelUtil.js new file mode 100644 index 0000000..0223ce7 --- /dev/null +++ b/mjlib_js2/App/Models/ModelUtil.js @@ -0,0 +1,25 @@ +var ModelUtil = module.exports; + +ModelUtil.deepCopy = function (list) { + if(list){ + var result = list.slice(0); + return result; + + } + return null; + +} + +ModelUtil.sort = function (list) { + list.sort(function (a, b) { + return (a.privatePoint - a.isGhost * 35) - (b.privatePoint - b.isGhost * 35); + }); + return list; +} + +ModelUtil.SAFE = function(otherValue,defVal){ + let result = otherValue !== undefined ? otherValue :defVal; + return result; +} + + diff --git a/mjlib_js2/App/app.js b/mjlib_js2/App/app.js new file mode 100644 index 0000000..eeaa2c0 --- /dev/null +++ b/mjlib_js2/App/app.js @@ -0,0 +1,51 @@ +const Card = require('./Models/Card'); +const HuLogic = require('./Models/HuAlgorithm/HuLogic') +const assert = require('assert'); + +function startTest(){ + var testPaiList = + [ + ["1万", "3万", "5万", "6万", "7万", "8筒", "8筒", "6筒", "6筒", "4筒", "4筒", "中", "中", "5筒"], + ["5万", "6万", "4万", "9筒", "9筒", "8筒", "1条", "6筒", "2条", "3条", "南", "南", "中", "7筒",],//7筒 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "南", "中", "中", "南"],//南 + ["1万", "3万", "4万", "5万", "6万", "2筒", "3筒", "4筒", "5筒", "7筒", "中", "中", "中", "8万"],//8万 + ["2筒", "4筒", "5筒", "6筒", "7筒", "2万", "4万", "5万", "7万", "8万", "8万", "中", "白", "3万"],//3万 + + ["6筒", "6筒", "1条", "3条", "4条", "5条", "6条", "7条", "9条", "7万", "9万", "中", "白", "中"],//中 + ["3筒", "4筒", "5筒", "5筒", "6筒", "7筒", "3条", "4条", "8条", "8条", "3万", "5万", "白", "4万"],//4万 + ["9筒", "9筒", "6条", "7条", "8条", "3万", "3万", "4万", "6万", "7万", "7万", "7万", "白", "3万"],//3万 + ["1筒", "2筒", "3筒", "4筒", "5筒", "6筒", "2万", "5万", "7万", "8万", "中", "中", "白", "4万"],//4万 + ["2筒", "3筒", "4筒", "7筒", "8筒", "5条", "6条", "7条", "9条", "9条", "7万", "7万", "白", "中"],//中 + + ["5筒", "1条", "2条", "3条", "6条", "7条", "8条", "6万", "7万", "7万", "8万", "9万", "白", "5万"],//5万 + + ["白", "中", "中", "8条", "7条", "7条", "5条", "4条", "3条", "2条", "5万", "3万", "2万", "1万"],//1万 + ["中", "1条", "3条", "4条", "5条", "6条", "1万", "1万", "2万", "3万", "4万", "5万", "7万", "白"],//白 + ["白", "9条", "8条", "8条", "7条", "7条", "4条", "3条", "5筒", "4筒", "3筒", "2筒", "2筒", "4条"],//4条 + + ["7万", "8万", "9万", "9条", "9条", "8条", "8条", "4筒", "3筒", "2筒", "1筒", "白", "白", "中"],//"中" + ["9万", "8万", "7万", "6万", "4万", "3万", "2万", "1万", "3条", "1条", "1条", "白", "白", "1条"],// + + ["7万", "7万", "3万", "3万", "9条", "8条", "7条", "8筒", "7筒", "6筒", "5筒", "4筒", "中", "7万"],//"7万" + ["8万", "7万", "4万", "4万", "9条", "8条", "7条", "7筒", "5筒", "2筒", "2筒", "白", "中", "2筒"],//"2筒" + + ["8万", "7万", "6万", "4万", "3万", "2万", "1万", "7筒", "6筒", "5筒", "3条", "3条", "中", "9万"],//"9万" + + + ["中", "中", "9条", "8条", "8条", "7条", "6条", "5条", "4条", "3条", "9筒", "7筒", "2筒", "8筒"],//"8筒" + ["白", "中", "9条", "8条", "7条", "6条", "9筒", "9筒", "7筒", "5筒", "4筒", "2筒", "3筒", "4条"]//"4条" + ]; + + var testHuResult = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1]; + for (let i = 0; i < testPaiList.length; i++) { + let cardDisplayNameList = testPaiList[i]; + + let cardList = Card.CreateCardWithNameList(cardDisplayNameList, [31, 32, 33]); + let huLogic = new HuLogic(); + let isHu = huLogic.checkHu(cardList); + assert(isHu == testHuResult[i]); + + } +} + +startTest(); \ No newline at end of file diff --git a/mjlib_js2/App/package-lock.json b/mjlib_js2/App/package-lock.json new file mode 100644 index 0000000..ea58fec --- /dev/null +++ b/mjlib_js2/App/package-lock.json @@ -0,0 +1,241 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "requires": { + "util": "0.10.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "requires": { + "should-equal": "2.0.0", + "should-format": "3.0.3", + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0", + "should-util": "1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "requires": { + "should-type": "1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "requires": { + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=" + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "requires": { + "should-type": "1.4.0", + "should-util": "1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=" + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "requires": { + "has-flag": "3.0.0" + } + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/mjlib_js2/App/package.json b/mjlib_js2/App/package.json new file mode 100644 index 0000000..9b2ace7 --- /dev/null +++ b/mjlib_js2/App/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "assert": "^1.4.1", + "mocha": "^5.2.0", + "should": "^13.2.3" + } +}