├── README-zh.md ├── README.md ├── fan-calculator-usage ├── ChineseOfficialMahjongHelper │ └── Classes │ │ └── mahjong-algorithm │ │ ├── CHANGELOG │ │ ├── LICENSE │ │ ├── README-zh.md │ │ ├── README.md │ │ ├── fan_calculator.cpp │ │ ├── fan_calculator.h │ │ ├── shanten.cpp │ │ ├── shanten.h │ │ ├── standard_tiles.h │ │ ├── stringify.cpp │ │ ├── stringify.h │ │ ├── tile.h │ │ └── unit_test.cpp ├── Mahjong-GB-CPP │ ├── MahjongGB │ │ ├── MahjongGB.cpp │ │ └── MahjongGB.h │ ├── README-zh.md │ ├── README.md │ └── test.cpp ├── Mahjong-GB-Python │ ├── README-zh.md │ ├── README.md │ ├── mahjong.cpp │ ├── setup.py │ └── test.py └── readme.md ├── judge └── main.cpp ├── mahjong-rules ├── A GUIDE TO MAHJONG Rules.pdf ├── IJCAI2020麻将比赛复式赛制介绍.pdf ├── README-zh.md ├── README.md ├── 《中国麻将竞赛规则试行》问答——作者:杜维忠.pdf ├── 中国麻将竞赛规则——1998年国家体育总局颁布.pdf ├── 国标麻将AI设计.pdf └── 国标麻将番种及相关术语简介.pdf └── sample-bot-Botzone ├── README.md └── sample.cpp /README-zh.md: -------------------------------------------------------------------------------- 1 | # Chinese-Standard-Mahjong 2 | 3 | README: [English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/README.md) | [中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/README-zh.md) 4 | 5 | 此项目提供的资料为北京大学网络所人工智能实验室与微智娱(北京)科技有限公司联合举办的国标麻将比赛。 6 | 7 | [点此查看](https://www.botzone.org.cn/static/gamecontest2020a.html)Botzone上的国标麻将比赛。 8 | 9 | [Botzone](https://www.botzone.org.cn/)是在线的AI程序对抗平台。用户根据游戏规则编写AI程序提交,平台使用天梯排名和小组比赛机制来评测AI的相对水平。在以下内容叙述中,我们将AI程序又称为bot。 10 | 11 | [点此查看](https://wiki.botzone.org.cn/index.php?title=%E9%A6%96%E9%A1%B5/en)维基来了解更多关于Botzone。 12 | 13 | - [比赛宗旨](#比赛宗旨) 14 | - [麻将历史](#麻将历史) 15 | - [项目使用](#项目使用) 16 | - [贡献者](#贡献者) 17 | 18 | ## 比赛宗旨 19 | 20 | - 基于Botzone平台开发国标麻将AI,使之具有能与麻将专业玩家匹配的水平。 21 | - 吸引更多研究团队使用Botzone,在此平台上做学术、教育等方面的研究。 22 | 23 | ## 麻将历史 24 | 25 | 麻将是一个非完全信息的四人游戏,起源于3000多年前的中国,而后传播至世界各地,延伸出不同的版本,深受人们喜爱,具有广泛的群众基础。1998年中国国家体育总局制订了国标麻将的规则,这也是我们在本次比赛中所使用的规则。在Botzone上的国标麻将,我们将原本的打4圈(共16局)简化为1局,但在实际比赛中我们将考虑座次公平性,交换座次在相同起手下进行比赛。 26 | 27 | 想打好麻将需要策略,也需要一定运气。这种不确定性增加了麻将的趣味性和挑战性。不同于日本立直麻将,国标麻将鼓励玩家采取进攻性策略,(有所表现为多吃多碰多杠),这使得游戏的可观赏性大大增加。 28 | 29 | ## 项目使用 30 | 31 | 我们在此项目中提供了以下资料: 32 | 33 | - [Fan calculator 算番器](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/fan-calculator-usage):这引自[ChineseOfficialMahjongHelper](https://github.com/summerinsects/ChineseOfficialMahjongHelper/tree/master/Classes/mahjong-algorithm)。在这一文件夹中,我们提供了C++和python版的使用样例代码。 34 | - [Judge Program 裁判程序](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/judge):[点此查看](https://wiki.botzone.org.cn/index.php?title=%E8%A3%81%E5%88%A4)Botzone裁判程序维基资料。 35 | - [Mahjong Rules 麻将规则](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules):我们提供了中英文的麻将番种说明。[点此查看](https://wiki.botzone.org.cn/index.php?title=Chinese-Standard-Mahjong)Botzone上的国标麻将游戏说明。 36 | - [Sample bot 样例bot程序](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/sample-bot-Botzone):这是Botzone上的样例bot程序。 37 | 38 | ## 微信公众号 39 | 40 | 欢迎关注Botzone官方公众号,接收最新比赛通知! 41 | 42 | ![avatar](https://www.botzone.org.cn/images/qrcode_for_gh_3a40a410124d_258.jpg) 43 | 44 | ## 贡献者 45 | 46 | @ailab-pku 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chinese-Standard-Mahjong 2 | 3 | README: [English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/README.md) | [中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/README-zh.md) 4 | 5 | This Chinese Standard Mahjong Competition is hosted by AILab in Peking University and WEIZHIYU (Beijing) Technology Co., Ltd. 6 | 7 | Click [here](https://www.botzone.org.cn/static/gamecontest2020a.html) to navigate to the Mahjong Competition on Botzone. 8 | 9 | [Botzone](https://www.botzone.org.cn/) is a universal online multi-agent game AI platform, designed to evaluate different implementations of game AI by applying them to agents and compete with each other, featuring an ELO ranking system and a contest system for users to evaluate their AI programs. 10 | 11 | Click [here](https://wiki.botzone.org.cn/index.php?title=%E9%A6%96%E9%A1%B5/en) to navigate to Botzone wiki for more details. 12 | 13 | - [Goal](#goal) 14 | - [Background](#background) 15 | - [Usage](#usage) 16 | - [Contributing](#contributing) 17 | 18 | ## Goal 19 | 20 | - Develop an intelligent Mahjong agent that can compete with other agents as well as human players based on the online AI platform, Botzone. 21 | - Attract more research societies to use and do searches on Botzone. 22 | 23 | ## Background 24 | 25 | Mahjong is an imperfect information four-player game that originated in China with a long history of more than 3000 years. In 1998 the All-China Sports Federation founded a universal competition standard called Chinese Standard Mahjong (Guóbiāo Májiàng in Chinese pronunciation), which we adopt in this competition. However, we reduce 16 games into 1 game on Botzone. 26 | 27 | Mahjong involves strategy and a certain degree of luck, and the uncertainty brings to the game a lot of fun and challenge. Different from Mahjong’s varieties in Asia, e.g., Riichi Mahjong (Japan), Chinese Standard Mahjong encourages the players to adopt aggressive strategies, which makes the competition watchable and attractive. 28 | 29 | ## Usage 30 | 31 | The codes are provided: 32 | 33 | - [Fan calculator](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/fan-calculator-usage): The is forked from [ChineseOfficialMahjongHelper](https://github.com/summerinsects/ChineseOfficialMahjongHelper/tree/master/Classes/mahjong-algorithm). We provide the usage of this tool in C++ and python. 34 | - [Judge Program](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/judge): Click to get more details about [Judge on Botzone](https://wiki.botzone.org.cn/index.php?title=%E8%A3%81%E5%88%A4/en). 35 | - [Mahjong Rules](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules): We provide the instructions of Mahjong in Chinese and English. Click to get more information about [Chinese Standard Mahjong on Botzone](https://wiki.botzone.org.cn/index.php?title=Chinese-Standard-Mahjong/en). 36 | - [Sample bot](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/sample-bot-Botzone): This is the sample bot program on Botzone. 37 | 38 | ## WeChat QRCode 39 | 40 | Follow our account on Wechat to keep updated on the latest competition news and trending topics. 41 | 42 | ![avatar](https://www.botzone.org.cn/images/qrcode_for_gh_3a40a410124d_258.jpg) 43 | 44 | ## Contributing 45 | 46 | @ailab-pku 47 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/CHANGELOG: -------------------------------------------------------------------------------- 1 | 2018-12-25 2 | 【新增】加杠与直杠的区分 3 | 【新增】花牌判断 4 | 5 | 2018-09-17 6 | 【修复】修正番种自相矛盾,比如不可能为绝张的,被传了绝张标记,算番时应予以去除 7 | 8 | 2018-08-15 9 | 【优化】鉴于圈风门风逻辑较复杂,将其调整步骤改到最后 10 | 11 | 2018-07-26 12 | 【修复】上听数计算保存路径时,重复保存,牌太复杂时,状态溢出 13 | 14 | 2018-04-18 15 | 【修复】1122233334444s2s算番错误 16 | 17 | 2018-03-29 18 | 【修复】组合龙算番bug 19 | 20 | 2017-11-10 21 | 【修复】当有一副顺子与其他3组无任何关系时,多算番的bug 22 | 23 | 2017-10-23 24 | 【修复】修改套算一次的逻辑,修复123m 789p 789s1299p3p漏算老少副的bug 25 | 26 | 2017-04-09 27 | 【修复】三杠少计双暗刻的bug 28 | 29 | 2017-04-08 30 | 【变更】暗杠的加计改为国际麻将联盟(MIL)的规则 31 | 32 | 2017-03-26 33 | 【修复】七星七对误判为连七对的bug 34 | 35 | 2016-12-18 36 | 【修复】特殊和型漏计附加条件的bug 37 | 38 | 2016-12-17 39 | 【修复】缺一门判断错误的bug 40 | 【修复】非门清状态下组合龙的bug 41 | 42 | 2016-12-15 43 | 【变更】清幺九按通行计法不计双同刻 44 | 【修复】小四喜多计幺九刻的bug 45 | 46 | 2016-12-14 47 | 【修复】暗刻数计算错误 48 | 49 | 2016-07-28 50 | 【修复】门清的一色双龙会漏计边嵌钓问题 51 | 52 | 2016-07-23 53 | 【修复】七对与常规形多面听,多计边嵌钓问题,如445566m2277779s,和8s不应该计嵌张 54 | 55 | 2016-04-18 56 | 【新增】不得相同原则 57 | 58 | 2016-04-10 59 | 【修复】三风刻重复计算19刻的bug 60 | 61 | 2016-04-04 62 | 【修复】当只有三组刻子刚好构成三节高(四组刻子构成四节高)时可能漏计幺九刻bug 63 | 64 | 2016-04-03 65 | 【新增】明暗杠 66 | 67 | 2016-04-01 68 | 【修复】九莲宝灯不计不求人,减计一个幺九刻 69 | 70 | 2016-03-18 71 | 【新增】严格98规则,绿一色 字一色 清混幺九 字一色 全大中小 大小于五不接受七对。用宏控制 72 | 73 | 2016-03-10 74 | 【修复】清幺九不计所有的同刻 75 | 76 | 2016-03-09 77 | 【变更】双暗杠改为6番 78 | 【新增】各种不计缺一门 79 | 80 | 2016-02-28 81 | 【新增】第一版 82 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2020 Jeff Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README-zh.md: -------------------------------------------------------------------------------- 1 | mahjong-algorithm 关于麻将的各种算法 2 | ========= 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README-zh.md) 5 | 6 | ## 说明 7 | - 这些算法虽然是用C++写的,但并未使用C++的容器,可以很方便改写为其他语言。 8 | - fan_calculator 为算番相关。 9 | - shanten 为判断听牌、听牌计算、上听数计算、有效牌计算。 10 | - stringify 为字符串转化相关。 11 | - 详见unit_test.cpp。 12 | 13 | ## 常见相关术语解释 14 | - 顺子:数牌中,花色相同序数相连的3张牌。 15 | - 刻子:三张相同的牌。碰出的为明刻,未碰出的为暗刻。俗称坎。杠也算刻子,明杠算明刻,暗杠算暗刻。 16 | - 面子:顺子和刻子的统称。俗称一句话、一坎牌。 17 | - 雀头:基本和牌形式中,单独组合的对子,也叫将、眼。 18 | - 基本和型:4面子1雀头的和牌形式。 19 | - 特殊和型:非4面子1雀头的和牌形式,在国标规则中,有七对、十三幺、全不靠等特殊和型。 20 | - 门清:也叫门前清,指不吃、不碰、不明杠的状态。特殊和型必然是门清状态。暗杠虽然不破门清,但会暴露出手牌不是特殊和型的信息。 21 | - 副露:吃牌、碰牌、杠牌的统称,即利用其他选手打出的牌完成自己手牌面子的行为,一般不包括暗杠,也叫鸣牌,俗称动牌。副露有时候也包括暗杠,此时将暗杠称为之暗副露,而吃、碰、明杠称为明副露。 22 | - 立牌:整个手牌除去吃、碰、杠之后的牌。 23 | - 手牌:包括立牌和吃、碰、杠的牌,有时仅指立牌。 24 | - 听牌:只差所需要的一张牌即能和牌的状态。俗称下叫、落叫、叫和(糊)。 25 | - 一上听:指差一张就能听牌的状态,也叫一向听、一入听。以此类推有二上听、三上听、N上听。 26 | - 上听数:达到听牌状态需要牌的张数。 27 | - 有效牌:能使上听数减少的牌,也称进张牌、上张牌。 28 | - 改良牌:能使有效牌增加的牌。通俗来说就是能使进张面变宽的牌。 29 | - 对子:两张相同的牌。雀头一定是对子,但对子不一定是雀头。 30 | - 两面:①数牌中,花色相同数字相邻的两张牌,如45m,与两侧的牌都构成顺子。也叫两头。②“两面搭子”的简称。 31 | - 嵌张:①数牌中,花色相同数字相隔1的两张牌,如57s,只能与中间的牌构成顺子,中间的这张牌称为嵌张。②“嵌张搭子”的简称。③国标麻将的“坎张”番种。 32 | - 边张:①也是数字相邻的两张牌,但由于处在数字的最小或最大边界位置,只能与一侧的牌构成顺子,如12m只能与3m构成顺子、89s只能与7s构成顺子,这张3m或者7s便称为边张。②“边张搭子”的简称。③国标麻将的“边张”番种。 33 | - 搭子:指差一张牌就能构成1组面子的两张牌。其形态有两面搭子、嵌张搭子、边张搭子,有时也可将对子看成是刻子搭子。 34 | - 复合搭子:3张或3张以上牌构成的搭子。常见的有:连嵌张、两面带对子、嵌张带对子、边张带对子等等形态。 35 | - 对倒:听牌时,其他牌都已经构成面子,剩余两对,只需任意一对成刻即可和牌,此时另一对充当雀头,这种听牌形态叫对倒,也叫双碰、对碰、对杵。 36 | 37 | ## 字符串化说明 38 | - 数牌:万=m 条=s 饼=p。后缀使用小写字母,一连串同花色的数牌可合并使用用一个后缀,如123m、678s等等。 39 | - 字牌:东南西北=ESWN,中发白=CFP。使用大写字母。亦兼容天凤风格的后缀z,但按中国习惯顺序567z为中发白。 40 | - 吃、碰、杠用英文\[\],可选用逗号+数字表示供牌来源。具体规则如下: 41 | - 吃:由于只能吃上家,逗号后的数字表示第几张牌是由上家打出,例如:\[567m,2\]表示57万吃6万(第2张),对于不指定数字的,默认第1张为上家打出。 42 | - 碰:逗号后的数字表示由哪家打出,根据从左到右的直观顺序,1为上家,2为对家,3为下家,例如:\[999s,3\]表示碰下家的9条。对于不指定数字的,默认为上家打出。 43 | - 杠:与碰类似,但对于不指定数字的,则认为是暗杠。例如:\[SSSS\]表示暗杠南;\[8888p,1\]表示大明杠上家的8饼。当数字为5、6、7时,表示加杠。例如:\[1111s,6\]表示碰对家的1条后,又摸到1条加杠。 44 | - 范例: 45 | - \[EEEE\]\[CCCC\]\[FFFF\]\[PPPP\]NN 46 | - 1112345678999s9s 47 | - \[WWWW,1\]\[444s\]45m678pFF6m 48 | - \[EEEE\]288s349pSCFF2p 49 | - \[123p,1\]\[345s,2\]\[999s,3\]6m6pEW1m 50 | - 356m18s1579pWNFF9p 51 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README.md: -------------------------------------------------------------------------------- 1 | Mahjong-algorithm 2 | ========= 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README-zh.md) 5 | 6 | ## Introduction 7 | Though written in C++, the code doesn't contain STL. It is easy to use in other languages. 8 | - fan_calculator 9 | - calculate the combination of tiles in hand. 10 | - shanten: 11 | - check whether the player is waiting to win. 12 | - calculate which tiles can make Mahjong. 13 | - calculate the distance to making Mahjong. 14 | - calculate which tiles in hand are effective. 15 | - stringify: 16 | - string convertion. 17 | - For more details, please read unit_test.cpp. 18 | 19 | ## Terminology 20 | - Claiming a tile 21 | - Sequence / Chow: 3 consecutive tiles of the same suit, like < Dot 2, Dot3, Dot 4 >. 22 | - Pung: The same 3 tiles of the same kind. 23 | - Kong: The same 4 tiles of the same kind. The exposed Kong uses the tile discarded by others. The concealed Kong means that one has the same 4 tiles, and it belongs to Concealed hand introduced below. 24 | - Concealed hand: No Chow, Pung or exposed Kong. 25 | - Meld: The collective name of Sequence, Pung and Kong. 26 | - Pair: The same 2 tiles of the same kind. 27 | - Basic combination: 4 Melds and 1 pair. 28 | - Special combination: Seven Pairs, Thirteen Orphans, Lesser Honours and Knitted Tiles, etc. 29 | - Tiles in hand: The tiles excluding declared chows, pungs, and kongs. 30 | - All hand tiles: All the tiles owned by the player, including declared chows, pungs, and kongs. 31 | - Ready hand / Waiting to win: The distance between all hand tiles and winning hands. 32 | - N shanten: Change N tiles, the hand tiles is waiting to win. 33 | - Effective tiles: The tiles which could decrease the number of shanten. 34 | - Improved tiles: The tiles which could increase the number of effective tiles. 35 | 36 | 37 | ## Stringify 38 | - Suits: Character=m, Bamboo=s, Dot=p. In this tool we use lowercase character as suffic. If a series of tiles are of the same suit, we can simplify 1m2m3m as 123m. 39 | - Direction/Wind and Cardinal Tiles: 40 | - East=E / 1z, South=S / 2z, West=W / 3z, North=N / 4z. 41 | - Red Dragon=C / 5z, Green Dragon=F / 6z, White Dragon=P / 7z. 42 | - Chow: The player can only chows the opponent in the left. We use \[567m,2\] to represent the player forming a sequence of 5m, 6m, and 7m. 2 represents that the 6m is the tile discarded in the last turn. If there is no 2, the default discard is the first tile, i.e., 5m. 43 | - Pung: We use \[999s,3\] to represent that the player forms a pung of 9s by the tile discarded by the opponent in the right (the number 3). Number 2 refers to the opponent in the opposite, and number 1 refers to the opponent in the left. The default number is 1. 44 | - Kong: It is similar as Pung.However, if there is no specific number, it is be considered as a concealed kong. E.g., \[SSSS\] represents a concealed kong of South. \[8888p,1\] represents forming a exposed kong by the 8p discarded by the opponent in the left. When the number is 5/6/7, it means pung to kong. E.g., \[1111s,6\] represents that the player formed a pung of 1s by the discard from the opponent in the opposite, and in the following turn, he draws the 4th 1s and decides to change the declared pung to kong. 45 | - Examples: 46 | - \[EEEE\]\[CCCC\]\[FFFF\]\[PPPP\]NN 47 | - 1112345678999s9s 48 | - \[WWWW,1\]\[444s\]45m678pFF6m 49 | - \[EEEE\]288s349pSCFF2p 50 | - \[123p,1\]\[345s,2\]\[999s,3\]6m6pEW1m 51 | - 356m18s1579pWNFF9p 52 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/fan_calculator.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #ifndef __MAHJONG_ALGORITHM__FAN_CALCULATOR_H__ 24 | #define __MAHJONG_ALGORITHM__FAN_CALCULATOR_H__ 25 | 26 | #include "tile.h" 27 | 28 | #define SUPPORT_CONCEALED_KONG_AND_MELDED_KONG 1 // 支持明暗杠 29 | 30 | namespace mahjong { 31 | 32 | /** 33 | * @addtogroup calculator 34 | * @{ 35 | */ 36 | 37 | /** 38 | * @brief 番种 39 | */ 40 | enum fan_t { 41 | FAN_NONE = 0, ///< 无效 42 | BIG_FOUR_WINDS, ///< 大四喜 43 | BIG_THREE_DRAGONS, ///< 大三元 44 | ALL_GREEN, ///< 绿一色 45 | NINE_GATES, ///< 九莲宝灯 46 | FOUR_KONGS, ///< 四杠 47 | SEVEN_SHIFTED_PAIRS, ///< 连七对 48 | THIRTEEN_ORPHANS, ///< 十三幺 49 | 50 | ALL_TERMINALS, ///< 清幺九 51 | LITTLE_FOUR_WINDS, ///< 小四喜 52 | LITTLE_THREE_DRAGONS, ///< 小三元 53 | ALL_HONORS, ///< 字一色 54 | FOUR_CONCEALED_PUNGS, ///< 四暗刻 55 | PURE_TERMINAL_CHOWS, ///< 一色双龙会 56 | 57 | QUADRUPLE_CHOW, ///< 一色四同顺 58 | FOUR_PURE_SHIFTED_PUNGS, ///< 一色四节高 59 | 60 | FOUR_PURE_SHIFTED_CHOWS, ///< 一色四步高 61 | THREE_KONGS, ///< 三杠 62 | ALL_TERMINALS_AND_HONORS, ///< 混幺九 63 | 64 | SEVEN_PAIRS, ///< 七对 65 | GREATER_HONORS_AND_KNITTED_TILES, ///< 七星不靠 66 | ALL_EVEN_PUNGS, ///< 全双刻 67 | FULL_FLUSH, ///< 清一色 68 | PURE_TRIPLE_CHOW, ///< 一色三同顺 69 | PURE_SHIFTED_PUNGS, ///< 一色三节高 70 | UPPER_TILES, ///< 全大 71 | MIDDLE_TILES, ///< 全中 72 | LOWER_TILES, ///< 全小 73 | 74 | PURE_STRAIGHT, ///< 清龙 75 | THREE_SUITED_TERMINAL_CHOWS, ///< 三色双龙会 76 | PURE_SHIFTED_CHOWS, ///< 一色三步高 77 | ALL_FIVE, ///< 全带五 78 | TRIPLE_PUNG, ///< 三同刻 79 | THREE_CONCEALED_PUNGS, ///< 三暗刻 80 | 81 | LESSER_HONORS_AND_KNITTED_TILES, ///< 全不靠 82 | KNITTED_STRAIGHT, ///< 组合龙 83 | UPPER_FOUR, ///< 大于五 84 | LOWER_FOUR, ///< 小于五 85 | BIG_THREE_WINDS, ///< 三风刻 86 | 87 | MIXED_STRAIGHT, ///< 花龙 88 | REVERSIBLE_TILES, ///< 推不倒 89 | MIXED_TRIPLE_CHOW, ///< 三色三同顺 90 | MIXED_SHIFTED_PUNGS, ///< 三色三节高 91 | CHICKEN_HAND, ///< 无番和 92 | LAST_TILE_DRAW, ///< 妙手回春 93 | LAST_TILE_CLAIM, ///< 海底捞月 94 | OUT_WITH_REPLACEMENT_TILE, ///< 杠上开花 95 | ROBBING_THE_KONG, ///< 抢杠和 96 | 97 | ALL_PUNGS, ///< 碰碰和 98 | HALF_FLUSH, ///< 混一色 99 | MIXED_SHIFTED_CHOWS, ///< 三色三步高 100 | ALL_TYPES, ///< 五门齐 101 | MELDED_HAND, ///< 全求人 102 | TWO_CONCEALED_KONGS, ///< 双暗杠 103 | TWO_DRAGONS_PUNGS, ///< 双箭刻 104 | 105 | OUTSIDE_HAND, ///< 全带幺 106 | FULLY_CONCEALED_HAND, ///< 不求人 107 | TWO_MELDED_KONGS, ///< 双明杠 108 | LAST_TILE, ///< 和绝张 109 | 110 | DRAGON_PUNG, ///< 箭刻 111 | PREVALENT_WIND, ///< 圈风刻 112 | SEAT_WIND, ///< 门风刻 113 | CONCEALED_HAND, ///< 门前清 114 | ALL_CHOWS, ///< 平和 115 | TILE_HOG, ///< 四归一 116 | DOUBLE_PUNG, ///< 双同刻 117 | TWO_CONCEALED_PUNGS, ///< 双暗刻 118 | CONCEALED_KONG, ///< 暗杠 119 | ALL_SIMPLES, ///< 断幺 120 | 121 | PURE_DOUBLE_CHOW, ///< 一般高 122 | MIXED_DOUBLE_CHOW, ///< 喜相逢 123 | SHORT_STRAIGHT, ///< 连六 124 | TWO_TERMINAL_CHOWS, ///< 老少副 125 | PUNG_OF_TERMINALS_OR_HONORS, ///< 幺九刻 126 | MELDED_KONG, ///< 明杠 127 | ONE_VOIDED_SUIT, ///< 缺一门 128 | NO_HONORS, ///< 无字 129 | EDGE_WAIT, ///< 边张 130 | CLOSED_WAIT, ///< 嵌张 131 | SINGLE_WAIT, ///< 单钓将 132 | SELF_DRAWN, ///< 自摸 133 | 134 | FLOWER_TILES, ///< 花牌 135 | 136 | #if SUPPORT_CONCEALED_KONG_AND_MELDED_KONG 137 | CONCEALED_KONG_AND_MELDED_KONG, ///< 明暗杠 138 | #endif 139 | 140 | FAN_TABLE_SIZE 141 | }; 142 | 143 | /** 144 | * @brief 风(用来表示圈风门风) 145 | */ 146 | enum class wind_t { 147 | EAST, SOUTH, WEST, NORTH 148 | }; 149 | 150 | /** 151 | * @brief 和牌标记 152 | */ 153 | typedef uint8_t win_flag_t; 154 | 155 | /** 156 | * @name win flag 157 | * @{ 158 | */ 159 | #define WIN_FLAG_DISCARD 0 ///< 点和 160 | #define WIN_FLAG_SELF_DRAWN 1 ///< 自摸 161 | #define WIN_FLAG_4TH_TILE 2 ///< 绝张 162 | #define WIN_FLAG_ABOUT_KONG 4 ///< 关于杠,复合点和时为枪杠和,复合自摸则为杠上开花 163 | #define WIN_FLAG_WALL_LAST 8 ///< 牌墙最后一张,复合点和时为海底捞月,复合自摸则为妙手回春 164 | #define WIN_FLAG_INIT 16 ///< 起手,复合点和时为地和,复合自摸则为天和 165 | 166 | /** 167 | * @} 168 | */ 169 | 170 | /** 171 | * @name error codes 172 | * @{ 173 | */ 174 | #define ERROR_WRONG_TILES_COUNT -1 ///< 错误的张数 175 | #define ERROR_TILE_COUNT_GREATER_THAN_4 -2 ///< 某张牌出现超过4枚 176 | #define ERROR_NOT_WIN -3 ///< 没和牌 177 | /** 178 | * @} 179 | */ 180 | 181 | /** 182 | * @brief 检查算番的输入是否合法 183 | * 184 | * @param [in] hand_tiles 手牌 185 | * @param [in] win_tile 和牌张 186 | * @retval 0 成功 187 | * @retval ERROR_WRONG_TILES_COUNT 错误的张数 188 | * @retval ERROR_TILE_COUNT_GREATER_THAN_4 某张牌出现超过4枚 189 | */ 190 | int check_calculator_input(const hand_tiles_t *hand_tiles, tile_t win_tile); 191 | 192 | /** 193 | * @brief 算番参数 194 | */ 195 | struct calculate_param_t { 196 | hand_tiles_t hand_tiles; ///< 手牌 197 | tile_t win_tile; ///< 和牌张 198 | uint8_t flower_count; ///< 花牌数 199 | win_flag_t win_flag; ///< 和牌标记 200 | wind_t prevalent_wind; ///< 圈风 201 | wind_t seat_wind; ///< 门风 202 | }; 203 | 204 | /** 205 | * @brief 番表 206 | */ 207 | typedef uint16_t fan_table_t[FAN_TABLE_SIZE]; 208 | 209 | /** 210 | * @brief 算番 211 | * 212 | * @param [in] calculate_param 算番参数 213 | * @param [out] fan_table 番表,当有某种番时,相应的会设置为这种番出现的次数 214 | * @retval >0 番数 215 | * @retval ERROR_WRONG_TILES_COUNT 错误的张数 216 | * @retval ERROR_TILE_COUNT_GREATER_THAN_4 某张牌出现超过4枚 217 | * @retval ERROR_NOT_WIN 没和牌 218 | */ 219 | int calculate_fan(const calculate_param_t *calculate_param, fan_table_t *fan_table); 220 | 221 | /** 222 | * @brief 番名(英文) 223 | */ 224 | static const char *fan_name_en[] = { 225 | "None", 226 | "Big Four Winds", "Big Three Dragons", "All Green", "Nine Gates", "Four Kongs", "Seven Shifted Pairs", "Thirteen Orphans", 227 | "All Terminals", "Little Four Winds", "Little Three Dragons", "All Honors", "Four Concealed Pungs", "Pure Terminal Chows", 228 | "Quadruple Chow", "Four Pure Shifted Pungs", 229 | "Four Pure Shifted Chows", "Three Kongs", "All Terminals and Honors", 230 | "Seven Pairs", "Greater Honors and Knitted Tiles", "All Even Pungs", "Full Flush", "Pure Triple Chow", "Pure Shifted Pungs", "Upper Tiles", "Middle Tiles", "Lower Tiles", 231 | "Pure Straight", "Three-Suited Terminal Chows", "Pure Shifted Chows", "All Five", "Triple Pung", "Three Concealed Pungs", 232 | "Lesser Honors and Knitted Tiles", "Knitted Straight", "Upper Four", "Lower Four", "Big Three Winds", 233 | "Mixed Straight", "Reversible Tiles", "Mixed Triple Chow", "Mixed Shifted Pungs", "Chicken Hand", "Last Tile Draw", "Last Tile Claim", "Out with Replacement Tile", "Robbing The Kong", 234 | "All Pungs", "Half Flush", "Mixed Shifted Chows", "All Types", "Melded Hand", "Two Concealed Kongs", "Two Dragons Pungs", 235 | "Outside Hand", "Fully Concealed Hand", "Two Melded Kongs", "Last Tile", 236 | "Dragon Pung", "Prevalent Wind", "Seat Wind", "Concealed Hand", "All Chows", "Tile Hog", "Double Pung", 237 | "Two Concealed Pungs", "Concealed Kong", "All Simples", 238 | "Pure Double Chow", "Mixed Double Chow", "Short Straight", "Two Terminal Chows", "Pung of Terminals or Honors", "Melded Kong", "One Voided Suit", "No Honors", "Edge Wait", "Closed Wait", "Single Wait", "Self-Drawn", 239 | "Flower Tiles" 240 | #if SUPPORT_CONCEALED_KONG_AND_MELDED_KONG 241 | , "Concealed Kong and Melded Kong" 242 | #endif 243 | }; 244 | 245 | #ifdef _MSC_VER 246 | #pragma execution_character_set("utf-8") 247 | #endif 248 | 249 | // u8 literal prefix 250 | #ifndef __UTF8_TEXT 251 | // VS2015 GCC4.7 Clang5.0 252 | #if (defined(_MSC_VER) && (_MSC_VER >= 1900)) || (defined(__GNUC__) && ((__GNUC__ << 8 | __GNUC_MINOR__) >= 0x407)) || (defined(__clang__) && (__clang_major__ >= 5)) 253 | #define __UTF8_TEXT(quote) u8 ## quote 254 | #else 255 | #define __UTF8_TEXT(quote) quote 256 | #endif 257 | #endif 258 | 259 | #ifndef __UTF8 260 | #define __UTF8(quote) __UTF8_TEXT(quote) 261 | #endif 262 | 263 | /** 264 | * @brief 番名(简体中文) 265 | */ 266 | static const char *fan_name[] = { 267 | __UTF8("无"), 268 | __UTF8("大四喜"), __UTF8("大三元"), __UTF8("绿一色"), __UTF8("九莲宝灯"), __UTF8("四杠"), __UTF8("连七对"), __UTF8("十三幺"), 269 | __UTF8("清幺九"), __UTF8("小四喜"), __UTF8("小三元"), __UTF8("字一色"), __UTF8("四暗刻"), __UTF8("一色双龙会"), 270 | __UTF8("一色四同顺"), __UTF8("一色四节高"), 271 | __UTF8("一色四步高"), __UTF8("三杠"), __UTF8("混幺九"), 272 | __UTF8("七对"), __UTF8("七星不靠"), __UTF8("全双刻"), __UTF8("清一色"), __UTF8("一色三同顺"), __UTF8("一色三节高"), __UTF8("全大"), __UTF8("全中"), __UTF8("全小"), 273 | __UTF8("清龙"), __UTF8("三色双龙会"), __UTF8("一色三步高"), __UTF8("全带五"), __UTF8("三同刻"), __UTF8("三暗刻"), 274 | __UTF8("全不靠"), __UTF8("组合龙"), __UTF8("大于五"), __UTF8("小于五"), __UTF8("三风刻"), 275 | __UTF8("花龙"), __UTF8("推不倒"), __UTF8("三色三同顺"), __UTF8("三色三节高"), __UTF8("无番和"), __UTF8("妙手回春"), __UTF8("海底捞月"), __UTF8("杠上开花"), __UTF8("抢杠和"), 276 | __UTF8("碰碰和"), __UTF8("混一色"), __UTF8("三色三步高"), __UTF8("五门齐"), __UTF8("全求人"), __UTF8("双暗杠"), __UTF8("双箭刻"), 277 | __UTF8("全带幺"), __UTF8("不求人"), __UTF8("双明杠"), __UTF8("和绝张"), 278 | __UTF8("箭刻"), __UTF8("圈风刻"), __UTF8("门风刻"), __UTF8("门前清"), __UTF8("平和"), __UTF8("四归一"), __UTF8("双同刻"), __UTF8("双暗刻"), __UTF8("暗杠"), __UTF8("断幺"), 279 | __UTF8("一般高"), __UTF8("喜相逢"), __UTF8("连六"), __UTF8("老少副"), __UTF8("幺九刻"), __UTF8("明杠"), __UTF8("缺一门"), __UTF8("无字"), __UTF8("边张"), __UTF8("嵌张"), __UTF8("单钓将"), __UTF8("自摸"), 280 | __UTF8("花牌") 281 | #if SUPPORT_CONCEALED_KONG_AND_MELDED_KONG 282 | , __UTF8("明暗杠") 283 | #endif 284 | }; 285 | 286 | /** 287 | * @brief 番值 288 | */ 289 | static const uint16_t fan_value_table[FAN_TABLE_SIZE] = { 290 | 0, 291 | 88, 88, 88, 88, 88, 88, 88, 292 | 64, 64, 64, 64, 64, 64, 293 | 48, 48, 294 | 32, 32, 32, 295 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 296 | 16, 16, 16, 16, 16, 16, 297 | 12, 12, 12, 12, 12, 298 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 299 | 6, 6, 6, 6, 6, 6, 6, 300 | 4, 4, 4, 4, 301 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 302 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 303 | 1 304 | #if SUPPORT_CONCEALED_KONG_AND_MELDED_KONG 305 | , 5 306 | #endif 307 | }; 308 | 309 | /** 310 | * @brief 判断立牌是否包含和牌 311 | * 如果是,则必然不是和绝张 312 | * 313 | * @param [in] standing_tiles 立牌 314 | * @param [in] standing_cnt 立牌数 315 | * @param [in] win_tile 和牌张 316 | * @return bool 317 | */ 318 | bool is_standing_tiles_contains_win_tile(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t win_tile); 319 | 320 | /** 321 | * @brief 统计和牌在副露牌组中出现的张数 322 | * 如果出现3张,则必然和绝张 323 | * 324 | * @param [in] fixed_packs 副露牌组 325 | * @param [in] fixed_cnt 副露牌组数 326 | * @param [in] win_tile 和牌张 327 | * @return size_t 328 | */ 329 | size_t count_win_tile_in_fixed_packs(const pack_t *fixed_packs, intptr_t fixed_cnt, tile_t win_tile); 330 | 331 | /** 332 | * @brief 判断副露牌组是否包含杠 333 | * 334 | * @param [in] fixed_packs 副露牌组 335 | * @param [in] fixed_cnt 副露牌组数 336 | * @return bool 337 | */ 338 | bool is_fixed_packs_contains_kong(const pack_t *fixed_packs, intptr_t fixed_cnt); 339 | 340 | /** 341 | * end group 342 | * @} 343 | */ 344 | 345 | } 346 | 347 | #endif 348 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/shanten.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #include "shanten.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "standard_tiles.h" 30 | 31 | namespace mahjong { 32 | 33 | // 牌组转换成牌 34 | intptr_t packs_to_tiles(const pack_t *packs, intptr_t pack_cnt, tile_t *tiles, intptr_t tile_cnt) { 35 | if (packs == nullptr || tiles == nullptr) { 36 | return 0; 37 | } 38 | 39 | intptr_t cnt = 0; 40 | for (int i = 0; i < pack_cnt && cnt < tile_cnt; ++i) { 41 | tile_t tile = pack_get_tile(packs[i]); 42 | switch (pack_get_type(packs[i])) { 43 | case PACK_TYPE_CHOW: 44 | if (cnt < tile_cnt) tiles[cnt++] = static_cast(tile - 1); 45 | if (cnt < tile_cnt) tiles[cnt++] = tile; 46 | if (cnt < tile_cnt) tiles[cnt++] = static_cast(tile + 1); 47 | break; 48 | case PACK_TYPE_PUNG: 49 | if (cnt < tile_cnt) tiles[cnt++] = tile; 50 | if (cnt < tile_cnt) tiles[cnt++] = tile; 51 | if (cnt < tile_cnt) tiles[cnt++] = tile; 52 | break; 53 | case PACK_TYPE_KONG: 54 | if (cnt < tile_cnt) tiles[cnt++] = tile; 55 | if (cnt < tile_cnt) tiles[cnt++] = tile; 56 | if (cnt < tile_cnt) tiles[cnt++] = tile; 57 | if (cnt < tile_cnt) tiles[cnt++] = tile; 58 | break; 59 | case PACK_TYPE_PAIR: 60 | if (cnt < tile_cnt) tiles[cnt++] = tile; 61 | if (cnt < tile_cnt) tiles[cnt++] = tile; 62 | break; 63 | default: 64 | UNREACHABLE(); 65 | break; 66 | } 67 | } 68 | return cnt; 69 | } 70 | 71 | // 将牌打表 72 | void map_tiles(const tile_t *tiles, intptr_t cnt, tile_table_t *cnt_table) { 73 | memset(*cnt_table, 0, sizeof(*cnt_table)); 74 | for (intptr_t i = 0; i < cnt; ++i) { 75 | ++(*cnt_table)[tiles[i]]; 76 | } 77 | } 78 | 79 | // 将手牌打表 80 | bool map_hand_tiles(const hand_tiles_t *hand_tiles, tile_table_t *cnt_table) { 81 | // 将每一组副露当作3张牌来算,那么总张数=13 82 | if (hand_tiles->tile_count <= 0 || hand_tiles->pack_count < 0 || hand_tiles->pack_count > 4 83 | || hand_tiles->pack_count * 3 + hand_tiles->tile_count != 13) { 84 | return false; 85 | } 86 | 87 | // 将副露恢复成牌 88 | tile_t tiles[18]; 89 | intptr_t tile_cnt = 0; 90 | if (hand_tiles->pack_count == 0) { 91 | memcpy(tiles, hand_tiles->standing_tiles, 13 * sizeof(tile_t)); 92 | tile_cnt = 13; 93 | } 94 | else { 95 | tile_cnt = packs_to_tiles(hand_tiles->fixed_packs, hand_tiles->pack_count, tiles, 18); 96 | memcpy(tiles + tile_cnt, hand_tiles->standing_tiles, hand_tiles->tile_count * sizeof(tile_t)); 97 | tile_cnt += hand_tiles->tile_count; 98 | } 99 | 100 | // 打表 101 | map_tiles(tiles, tile_cnt, cnt_table); 102 | return true; 103 | } 104 | 105 | // 将表转换成牌 106 | intptr_t table_to_tiles(const tile_table_t &cnt_table, tile_t *tiles, intptr_t max_cnt) { 107 | intptr_t cnt = 0; 108 | for (int i = 0; i < 34; ++i) { 109 | tile_t t = all_tiles[i]; 110 | for (int n = 0; n < cnt_table[t]; ++n) { 111 | *tiles++ = t; 112 | ++cnt; 113 | if (cnt == max_cnt) { 114 | return max_cnt; 115 | } 116 | } 117 | } 118 | return cnt; 119 | } 120 | 121 | namespace { 122 | 123 | // 路径单元,单元有面子、雀头、搭子等种类,见下面的宏 124 | // 高8位表示类型,低8位表示牌 125 | // 对于顺子和顺子搭子,牌指的是最小的一张牌, 126 | // 例如在顺子123万中,牌为1万,在两面搭子45条中,牌为4条等等 127 | typedef uint16_t path_unit_t; 128 | 129 | #define UNIT_TYPE_CHOW 1 // 顺子 130 | #define UNIT_TYPE_PUNG 2 // 刻子 131 | #define UNIT_TYPE_PAIR 4 // 雀头 132 | #define UNIT_TYPE_CHOW_OPEN_END 5 // 两面或者边张搭子 133 | #define UNIT_TYPE_CHOW_CLOSED 6 // 嵌张搭子 134 | #define UNIT_TYPE_INCOMPLETE_PUNG 7 // 刻子搭子 135 | 136 | #define MAKE_UNIT(type_, tile_) static_cast(((type_) << 8) | (tile_)) 137 | #define UNIT_TYPE(unit_) (((unit_) >> 8) & 0xFF) 138 | #define UNIT_TILE(unit_) ((unit_) & 0xFF) 139 | 140 | #define MAX_STATE 512 141 | #define UNIT_SIZE 7 142 | 143 | // 一条路径 144 | struct work_path_t { 145 | path_unit_t units[UNIT_SIZE]; // 14/2=7最多7个搭子 146 | uint16_t depth; // 当前路径深度 147 | }; 148 | 149 | // 当前工作状态 150 | struct work_state_t { 151 | work_path_t paths[MAX_STATE]; // 所有路径 152 | intptr_t count; // 路径数量 153 | }; 154 | } 155 | 156 | // 路径是否来过了 157 | static bool is_basic_form_branch_exist(const intptr_t fixed_cnt, const work_path_t *work_path, const work_state_t *work_state) { 158 | if (work_state->count <= 0 || work_path->depth == 0) { 159 | return false; 160 | } 161 | 162 | // depth处有信息,所以按stl风格的end应该要+1 163 | const uint16_t depth = static_cast(work_path->depth + 1); 164 | 165 | // std::includes要求有序,但又不能破坏当前数据 166 | work_path_t temp; 167 | std::copy(&work_path->units[fixed_cnt], &work_path->units[depth], &temp.units[fixed_cnt]); 168 | std::sort(&temp.units[fixed_cnt], &temp.units[depth]); 169 | 170 | return std::any_of(&work_state->paths[0], &work_state->paths[work_state->count], 171 | [&temp, fixed_cnt, depth](const work_path_t &path) { 172 | return std::includes(&path.units[fixed_cnt], &path.units[path.depth], &temp.units[fixed_cnt], &temp.units[depth]); 173 | }); 174 | } 175 | 176 | // 保存路径 177 | static void save_work_path(const intptr_t fixed_cnt, const work_path_t *work_path, work_state_t *work_state) { 178 | // 复制一份数据,不破坏当前数据 179 | work_path_t temp; 180 | temp.depth = work_path->depth; 181 | std::copy(&work_path->units[fixed_cnt], &work_path->units[temp.depth + 1], &temp.units[fixed_cnt]); 182 | std::sort(&temp.units[fixed_cnt], &temp.units[temp.depth + 1]); 183 | 184 | // 判断是否重复 185 | if (std::none_of(&work_state->paths[0], &work_state->paths[work_state->count], 186 | [&temp, fixed_cnt](const work_path_t &path) { 187 | return (path.depth == temp.depth && std::equal(&path.units[fixed_cnt], &path.units[path.depth + 1], &temp.units[fixed_cnt])); 188 | })) { 189 | if (work_state->count < MAX_STATE) { 190 | work_path_t &path = work_state->paths[work_state->count++]; 191 | path.depth = temp.depth; 192 | std::copy(&temp.units[fixed_cnt], &temp.units[temp.depth + 1], &path.units[fixed_cnt]); 193 | } 194 | else { 195 | assert(0 && "too many state!"); 196 | } 197 | } 198 | } 199 | 200 | // 递归计算基本和型上听数 201 | // 参数说明: 202 | // cnt_table牌表 203 | // has_pair是否有雀头 204 | // pack_cnt完成的面子数 205 | // incomplete_cnt搭子数 206 | // 最后三个参数为优化性能用的, 207 | // work_path保存当前正在计算的路径, 208 | // work_state保存了所有已经计算过的路径, 209 | // 从0到fixed_cnt的数据是不使用的,这些保留给了副露的面子 210 | static int basic_form_shanten_recursively(tile_table_t &cnt_table, const bool has_pair, const unsigned pack_cnt, const unsigned incomplete_cnt, 211 | const intptr_t fixed_cnt, work_path_t *work_path, work_state_t *work_state) { 212 | if (fixed_cnt == 4) { // 4副露 213 | for (int i = 0; i < 34; ++i) { 214 | tile_t t = all_tiles[i]; 215 | if (cnt_table[t] > 1) { 216 | return -1; 217 | } 218 | } 219 | return 0; 220 | } 221 | 222 | if (pack_cnt == 4) { // 已经有4组面子 223 | return has_pair ? -1 : 0; // 如果有雀头,则和了;如果无雀头,则是听牌 224 | } 225 | 226 | int max_ret; // 当前状态能返回的最大上听数 227 | 228 | // 算法说明: 229 | // 缺少的面子数=4-完成的面子数 230 | // 缺少的搭子数=缺少的面子数-已有的搭子数 231 | // 两式合并:缺少的搭子数=4-完成的面子数-已有的搭子数 232 | int incomplete_need = 4 - pack_cnt - incomplete_cnt; 233 | if (incomplete_need > 0) { // 还需要搭子的情况 234 | // 有雀头时,上听数=已有的搭子数+缺少的搭子数*2-1 235 | // 无雀头时,上听数=已有的搭子数+缺少的搭子数*2 236 | max_ret = incomplete_cnt + incomplete_need * 2 - (has_pair ? 1 : 0); 237 | } 238 | else { // 搭子齐了的情况 239 | // 有雀头时,上听数=3-完成的面子数 240 | // 无雀头时,上听数=4-完成的面子数 241 | max_ret = (has_pair ? 3 : 4) - pack_cnt; 242 | } 243 | 244 | // 当前路径深度 245 | const unsigned depth = pack_cnt + incomplete_cnt + has_pair; 246 | work_path->depth = static_cast(depth); 247 | 248 | int result = max_ret; 249 | 250 | if (pack_cnt + incomplete_cnt > 4) { // 搭子超载 251 | save_work_path(fixed_cnt, work_path, work_state); 252 | return max_ret; 253 | } 254 | 255 | for (int i = 0; i < 34; ++i) { 256 | tile_t t = all_tiles[i]; 257 | if (cnt_table[t] < 1) { 258 | continue; 259 | } 260 | 261 | // 雀头 262 | if (!has_pair && cnt_table[t] > 1) { 263 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_PAIR, t); // 记录雀头 264 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 265 | // 削减雀头,递归 266 | cnt_table[t] -= 2; 267 | int ret = basic_form_shanten_recursively(cnt_table, true, pack_cnt, incomplete_cnt, 268 | fixed_cnt, work_path, work_state); 269 | result = std::min(ret, result); 270 | // 还原 271 | cnt_table[t] += 2; 272 | } 273 | } 274 | 275 | // 刻子 276 | if (cnt_table[t] > 2) { 277 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_PUNG, t); // 记录刻子 278 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 279 | // 削减这组刻子,递归 280 | cnt_table[t] -= 3; 281 | int ret = basic_form_shanten_recursively(cnt_table, has_pair, pack_cnt + 1, incomplete_cnt, 282 | fixed_cnt, work_path, work_state); 283 | result = std::min(ret, result); 284 | // 还原 285 | cnt_table[t] += 3; 286 | } 287 | } 288 | 289 | // 顺子(只能是数牌) 290 | bool is_numbered = is_numbered_suit(t); 291 | // 顺子t t+1 t+2,显然t不能是8点以上的数牌 292 | if (is_numbered && tile_get_rank(t) < 8 && cnt_table[t + 1] && cnt_table[t + 2]) { 293 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_CHOW, t); // 记录顺子 294 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 295 | // 削减这组顺子,递归 296 | --cnt_table[t]; 297 | --cnt_table[t + 1]; 298 | --cnt_table[t + 2]; 299 | int ret = basic_form_shanten_recursively(cnt_table, has_pair, pack_cnt + 1, incomplete_cnt, 300 | fixed_cnt, work_path, work_state); 301 | result = std::min(ret, result); 302 | // 还原 303 | ++cnt_table[t]; 304 | ++cnt_table[t + 1]; 305 | ++cnt_table[t + 2]; 306 | } 307 | } 308 | 309 | // 如果已经通过削减雀头/面子降低了上听数,再按搭子计算的上听数肯定不会更少 310 | if (result < max_ret) { 311 | continue; 312 | } 313 | 314 | // 刻子搭子 315 | if (cnt_table[t] > 1) { 316 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_INCOMPLETE_PUNG, t); // 记录刻子搭子 317 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 318 | // 削减刻子搭子,递归 319 | cnt_table[t] -= 2; 320 | int ret = basic_form_shanten_recursively(cnt_table, has_pair, pack_cnt, incomplete_cnt + 1, 321 | fixed_cnt, work_path, work_state); 322 | result = std::min(ret, result); 323 | // 还原 324 | cnt_table[t] += 2; 325 | } 326 | } 327 | 328 | // 顺子搭子(只能是数牌) 329 | if (is_numbered) { 330 | // 两面或者边张搭子t t+1,显然t不能是9点以上的数牌 331 | if (tile_get_rank(t) < 9 && cnt_table[t + 1]) { // 两面或者边张 332 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_CHOW_OPEN_END, t); // 记录两面或者边张搭子 333 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 334 | // 削减搭子,递归 335 | --cnt_table[t]; 336 | --cnt_table[t + 1]; 337 | int ret = basic_form_shanten_recursively(cnt_table, has_pair, pack_cnt, incomplete_cnt + 1, 338 | fixed_cnt, work_path, work_state); 339 | result = std::min(ret, result); 340 | // 还原 341 | ++cnt_table[t]; 342 | ++cnt_table[t + 1]; 343 | } 344 | } 345 | // 嵌张搭子t t+2,显然t不能是8点以上的数牌 346 | if (tile_get_rank(t) < 8 && cnt_table[t + 2]) { // 嵌张 347 | work_path->units[depth] = MAKE_UNIT(UNIT_TYPE_CHOW_CLOSED, t); // 记录嵌张搭子 348 | if (!is_basic_form_branch_exist(fixed_cnt, work_path, work_state)) { 349 | // 削减搭子,递归 350 | --cnt_table[t]; 351 | --cnt_table[t + 2]; 352 | int ret = basic_form_shanten_recursively(cnt_table, has_pair, pack_cnt, incomplete_cnt + 1, 353 | fixed_cnt, work_path, work_state); 354 | result = std::min(ret, result); 355 | // 还原 356 | ++cnt_table[t]; 357 | ++cnt_table[t + 2]; 358 | } 359 | } 360 | } 361 | } 362 | 363 | if (result == max_ret) { 364 | save_work_path(fixed_cnt, work_path, work_state); 365 | } 366 | 367 | return result; 368 | } 369 | 370 | // 数牌是否有搭子 371 | static bool numbered_tile_has_neighbor(const tile_table_t &cnt_table, tile_t t) { 372 | rank_t r = tile_get_rank(t); 373 | if (r < 9) { if (cnt_table[t + 1]) return true; } 374 | if (r < 8) { if (cnt_table[t + 2]) return true; } 375 | if (r > 1) { if (cnt_table[t - 1]) return true; } 376 | if (r > 2) { if (cnt_table[t - 2]) return true; } 377 | return false; 378 | } 379 | 380 | // 以表格为参数计算基本和型上听数 381 | static int basic_form_shanten_from_table(tile_table_t &cnt_table, intptr_t fixed_cnt, useful_table_t *useful_table) { 382 | // 计算上听数 383 | work_path_t work_path; 384 | work_state_t work_state; 385 | work_state.count = 0; 386 | int result = basic_form_shanten_recursively(cnt_table, false, static_cast(fixed_cnt), 0, 387 | fixed_cnt, &work_path, &work_state); 388 | 389 | if (useful_table == nullptr) { 390 | return result; 391 | } 392 | 393 | // 穷举所有的牌,获取能减少上听数的牌 394 | for (int i = 0; i < 34; ++i) { 395 | tile_t t = all_tiles[i]; 396 | if (cnt_table[t] == 4 && result > 0) { 397 | continue; 398 | } 399 | 400 | if (cnt_table[t] == 0) { 401 | // 跳过孤张字牌和不靠张的数牌,这些牌都无法减少上听数 402 | if (is_honor(t) || !numbered_tile_has_neighbor(cnt_table, t)) { 403 | continue; 404 | } 405 | } 406 | 407 | ++cnt_table[t]; 408 | work_state.count = 0; 409 | int temp = basic_form_shanten_recursively(cnt_table, false, static_cast(fixed_cnt), 0, 410 | fixed_cnt, &work_path, &work_state); 411 | if (temp < result) { 412 | (*useful_table)[t] = true; // 标记为有效牌 413 | } 414 | --cnt_table[t]; 415 | } 416 | 417 | return result; 418 | } 419 | 420 | // 基本和型上听数 421 | int basic_form_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table) { 422 | if (standing_tiles == nullptr || (standing_cnt != 13 423 | && standing_cnt != 10 && standing_cnt != 7 && standing_cnt != 4 && standing_cnt != 1)) { 424 | return std::numeric_limits::max(); 425 | } 426 | 427 | // 对立牌的种类进行打表 428 | tile_table_t cnt_table; 429 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 430 | 431 | if (useful_table != nullptr) { 432 | memset(*useful_table, 0, sizeof(*useful_table)); 433 | } 434 | return basic_form_shanten_from_table(cnt_table, (13 - standing_cnt) / 3, useful_table); 435 | } 436 | 437 | // 基本和型判断1张是否听牌 438 | static bool is_basic_form_wait_1(tile_table_t &cnt_table, useful_table_t *waiting_table) { 439 | for (int i = 0; i < 34; ++i) { 440 | tile_t t = all_tiles[i]; 441 | if (cnt_table[t] != 1) { 442 | continue; 443 | } 444 | 445 | // 单钓将 446 | cnt_table[t] = 0; 447 | if (std::all_of(std::begin(cnt_table), std::end(cnt_table), [](int n) { return n == 0; })) { 448 | cnt_table[t] = 1; 449 | if (waiting_table != nullptr) { // 不需要获取听牌张,则可以直接返回 450 | (*waiting_table)[t] = true; 451 | } 452 | return true; 453 | } 454 | cnt_table[t] = 1; 455 | } 456 | 457 | return false; 458 | } 459 | 460 | // 基本和型判断2张是否听牌 461 | static bool is_basic_form_wait_2(const tile_table_t &cnt_table, useful_table_t *waiting_table) { 462 | bool ret = false; 463 | for (int i = 0; i < 34; ++i) { 464 | tile_t t = all_tiles[i]; 465 | if (cnt_table[t] < 1) { 466 | continue; 467 | } 468 | if (cnt_table[t] > 1) { 469 | if (waiting_table != nullptr) { // 获取听牌张 470 | (*waiting_table)[t] = true; // 对倒 471 | ret = true; 472 | continue; 473 | } 474 | else { // 不需要获取听牌张,则可以直接返回 475 | return true; 476 | } 477 | } 478 | if (is_numbered_suit_quick(t)) { // 数牌搭子 479 | rank_t r = tile_get_rank(t); 480 | if (r > 1 && cnt_table[t - 1]) { // 两面或者边张 481 | if (waiting_table != nullptr) { // 获取听牌张 482 | if (r < 9) (*waiting_table)[t + 1] = true; 483 | if (r > 2) (*waiting_table)[t - 2] = true; 484 | ret = true; 485 | continue; 486 | } 487 | else { // 不需要获取听牌张,则可以直接返回 488 | return true; 489 | } 490 | } 491 | if (r > 2 && cnt_table[t - 2]) { // 嵌张 492 | if (waiting_table != nullptr) { // 获取听牌张 493 | (*waiting_table)[t - 1] = true; 494 | ret = true; 495 | continue; 496 | } 497 | else { // 不需要获取听牌张,则可以直接返回 498 | return true; 499 | } 500 | } 501 | } 502 | } 503 | return ret; 504 | } 505 | 506 | // 基本和型判断4张是否听牌 507 | static bool is_basic_form_wait_4(tile_table_t &cnt_table, useful_table_t *waiting_table) { 508 | bool ret = false; 509 | // 削减雀头 510 | for (int i = 0; i < 34; ++i) { 511 | tile_t t = all_tiles[i]; 512 | if (cnt_table[t] < 2) { 513 | continue; 514 | } 515 | // 削减雀头,递归 516 | cnt_table[t] -= 2; 517 | if (is_basic_form_wait_2(cnt_table, waiting_table)) { 518 | ret = true; 519 | } 520 | // 还原 521 | cnt_table[t] += 2; 522 | if (ret && waiting_table == nullptr) { // 不需要获取听牌张,则可以直接结束递归 523 | return true; 524 | } 525 | } 526 | 527 | return ret; 528 | } 529 | 530 | // 递归计算基本和型是否听牌 531 | static bool is_basic_form_wait_recursively(tile_table_t &cnt_table, intptr_t left_cnt, useful_table_t *waiting_table) { 532 | if (left_cnt == 1) { 533 | return is_basic_form_wait_1(cnt_table, waiting_table); 534 | } 535 | 536 | bool ret = false; 537 | if (left_cnt == 4) { 538 | ret = is_basic_form_wait_4(cnt_table, waiting_table); 539 | if (ret && waiting_table == nullptr) { // 不需要获取听牌张,则可以直接结束递归 540 | return true; 541 | } 542 | } 543 | 544 | for (int i = 0; i < 34; ++i) { 545 | tile_t t = all_tiles[i]; 546 | if (cnt_table[t] < 1) { 547 | continue; 548 | } 549 | 550 | // 刻子 551 | if (cnt_table[t] > 2) { 552 | // 削减这组刻子,递归 553 | cnt_table[t] -= 3; 554 | if (is_basic_form_wait_recursively(cnt_table, left_cnt - 3, waiting_table)) { 555 | ret = true; 556 | } 557 | // 还原 558 | cnt_table[t] += 3; 559 | if (ret && waiting_table == nullptr) { // 不需要获取听牌张,则可以直接结束递归 560 | return true; 561 | } 562 | } 563 | 564 | // 顺子(只能是数牌) 565 | if (is_numbered_suit(t)) { 566 | // 顺子t t+1 t+2,显然t不能是8点以上的数牌 567 | if (tile_get_rank(t) < 8 && cnt_table[t + 1] && cnt_table[t + 2]) { 568 | // 削减这组顺子,递归 569 | --cnt_table[t]; 570 | --cnt_table[t + 1]; 571 | --cnt_table[t + 2]; 572 | if (is_basic_form_wait_recursively(cnt_table, left_cnt - 3, waiting_table)) { 573 | ret = true; 574 | } 575 | // 还原 576 | ++cnt_table[t]; 577 | ++cnt_table[t + 1]; 578 | ++cnt_table[t + 2]; 579 | if (ret && waiting_table == nullptr) { // 不需要获取听牌张,则可以直接结束递归 580 | return true; 581 | } 582 | } 583 | } 584 | } 585 | 586 | return ret; 587 | } 588 | 589 | // 基本和型是否听牌 590 | // 这里之所以不用直接调用上听数计算函数,判断其返回值为0的方式 591 | // 是因为前者会削减搭子,这个操作在和牌判断中是没必要的,所以单独写一套更快逻辑 592 | bool is_basic_form_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table) { 593 | // 对立牌的种类进行打表 594 | tile_table_t cnt_table; 595 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 596 | 597 | if (waiting_table != nullptr) { 598 | memset(*waiting_table, 0, sizeof(*waiting_table)); 599 | } 600 | return is_basic_form_wait_recursively(cnt_table, standing_cnt, waiting_table); 601 | } 602 | 603 | // 基本和型2张能否和牌 604 | static bool is_basic_form_win_2(const tile_table_t &cnt_table) { 605 | // 找到未使用的牌 606 | typedef std::remove_all_extents::type table_elem_t; 607 | const table_elem_t *it = std::find_if(std::begin(cnt_table), std::end(cnt_table), [](table_elem_t n) { return n > 0; }); 608 | // 存在且张数等于2 609 | if (it == std::end(cnt_table) || *it != 2) { 610 | return false; 611 | } 612 | // 还有其他未使用的牌 613 | return std::none_of(it + 1, std::end(cnt_table), [](int n) { return n > 0; }); 614 | } 615 | 616 | // 递归计算基本和型是否和牌 617 | // 这里之所以不用直接调用上听数计算函数,判断其返回值为-1的方式, 618 | // 是因为前者会削减搭子,这个操作在和牌判断中是没必要的,所以单独写一套更快逻辑 619 | static bool is_basic_form_win_recursively(tile_table_t &cnt_table, intptr_t left_cnt) { 620 | if (left_cnt == 2) { 621 | return is_basic_form_win_2(cnt_table); 622 | } 623 | 624 | for (int i = 0; i < 34; ++i) { 625 | tile_t t = all_tiles[i]; 626 | if (cnt_table[t] < 1) { 627 | continue; 628 | } 629 | 630 | // 刻子 631 | if (cnt_table[t] > 2) { 632 | // 削减这组刻子,递归 633 | cnt_table[t] -= 3; 634 | bool ret = is_basic_form_win_recursively(cnt_table, left_cnt - 3); 635 | // 还原 636 | cnt_table[t] += 3; 637 | if (ret) { 638 | return true; 639 | } 640 | } 641 | 642 | // 顺子(只能是数牌) 643 | if (is_numbered_suit(t)) { 644 | // 顺子t t+1 t+2,显然t不能是8点以上的数牌 645 | if (tile_get_rank(t) < 8 && cnt_table[t + 1] && cnt_table[t + 2]) { 646 | // 削减这组顺子,递归 647 | --cnt_table[t]; 648 | --cnt_table[t + 1]; 649 | --cnt_table[t + 2]; 650 | bool ret = is_basic_form_win_recursively(cnt_table, left_cnt - 3); 651 | // 还原 652 | ++cnt_table[t]; 653 | ++cnt_table[t + 1]; 654 | ++cnt_table[t + 2]; 655 | if (ret) { 656 | return true; 657 | } 658 | } 659 | } 660 | } 661 | 662 | return false; 663 | } 664 | 665 | // 基本和型是否和牌 666 | bool is_basic_form_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile) { 667 | // 对立牌的种类进行打表 668 | tile_table_t cnt_table; 669 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 670 | ++cnt_table[test_tile]; // 添加测试的牌 671 | return is_basic_form_win_recursively(cnt_table, standing_cnt + 1); 672 | } 673 | 674 | //-------------------------------- 七对 -------------------------------- 675 | 676 | // 七对上听数 677 | int seven_pairs_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table) { 678 | if (standing_tiles == nullptr || standing_cnt != 13) { 679 | return std::numeric_limits::max(); 680 | } 681 | 682 | // 对牌的种类进行打表,并统计对子数 683 | int pair_cnt = 0; 684 | tile_table_t cnt_table = { 0 }; 685 | for (intptr_t i = 0; i < standing_cnt; ++i) { 686 | tile_t tile = standing_tiles[i]; 687 | ++cnt_table[tile]; 688 | if (cnt_table[tile] == 2) { 689 | ++pair_cnt; 690 | cnt_table[tile] = 0; 691 | } 692 | } 693 | 694 | // 有效牌 695 | if (useful_table != nullptr) { 696 | std::transform(std::begin(cnt_table), std::end(cnt_table), std::begin(*useful_table), [](int n) { return n != 0; }); 697 | } 698 | return 6 - pair_cnt; 699 | } 700 | 701 | // 七对是否听牌 702 | bool is_seven_pairs_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table) { 703 | // 直接计算其上听数,上听数为0即为听牌 704 | if (waiting_table == nullptr) { 705 | return (0 == seven_pairs_shanten(standing_tiles, standing_cnt, nullptr)); 706 | } 707 | 708 | useful_table_t useful_table; 709 | if (0 == seven_pairs_shanten(standing_tiles, standing_cnt, &useful_table)) { 710 | memcpy(*waiting_table, useful_table, sizeof(*waiting_table)); 711 | return true; 712 | } 713 | return false; 714 | } 715 | 716 | // 七对是否和牌 717 | bool is_seven_pairs_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile) { 718 | useful_table_t useful_table; 719 | return (0 == seven_pairs_shanten(standing_tiles, standing_cnt, &useful_table) 720 | && useful_table[test_tile]); 721 | } 722 | 723 | //-------------------------------- 十三幺 -------------------------------- 724 | 725 | // 十三幺上听数 726 | int thirteen_orphans_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table) { 727 | if (standing_tiles == nullptr || standing_cnt != 13) { 728 | return std::numeric_limits::max(); 729 | } 730 | 731 | // 对牌的种类进行打表 732 | tile_table_t cnt_table; 733 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 734 | 735 | bool has_pair = false; 736 | int cnt = 0; 737 | for (int i = 0; i < 13; ++i) { 738 | int n = cnt_table[standard_thirteen_orphans[i]]; 739 | if (n > 0) { 740 | ++cnt; // 幺九牌的种类 741 | if (n > 1) { 742 | has_pair = true; // 幺九牌对子 743 | } 744 | } 745 | } 746 | 747 | // 当有对子时,上听数为:12-幺九牌的种类 748 | // 当没有对子时,上听数为:13-幺九牌的种类 749 | int ret = has_pair ? 12 - cnt : 13 - cnt; 750 | 751 | if (useful_table != nullptr) { 752 | // 先标记所有的幺九牌为有效牌 753 | memset(*useful_table, 0, sizeof(*useful_table)); 754 | std::for_each(std::begin(standard_thirteen_orphans), std::end(standard_thirteen_orphans), 755 | [useful_table](tile_t t) { 756 | (*useful_table)[t] = true; 757 | }); 758 | 759 | // 当有对子时,已有的幺九牌都不需要了 760 | if (has_pair) { 761 | for (int i = 0; i < 13; ++i) { 762 | tile_t t = standard_thirteen_orphans[i]; 763 | int n = cnt_table[t]; 764 | if (n > 0) { 765 | (*useful_table)[t] = false; 766 | } 767 | } 768 | } 769 | } 770 | 771 | return ret; 772 | } 773 | 774 | // 十三幺是否听牌 775 | bool is_thirteen_orphans_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table) { 776 | // 直接计算其上听数,上听数为0即为听牌 777 | if (waiting_table == nullptr) { 778 | return (0 == thirteen_orphans_shanten(standing_tiles, standing_cnt, nullptr)); 779 | } 780 | 781 | useful_table_t useful_table; 782 | if (0 == thirteen_orphans_shanten(standing_tiles, standing_cnt, &useful_table)) { 783 | memcpy(*waiting_table, useful_table, sizeof(*waiting_table)); 784 | return true; 785 | } 786 | return false; 787 | } 788 | 789 | // 十三幺是否和牌 790 | bool is_thirteen_orphans_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile) { 791 | useful_table_t useful_table; 792 | return (0 == thirteen_orphans_shanten(standing_tiles, standing_cnt, &useful_table) 793 | && useful_table[test_tile]); 794 | } 795 | 796 | //-------------------------------- “组合龙+面子+雀头”和型 -------------------------------- 797 | 798 | // 以表格为参数计算组合龙是否听牌 799 | static bool is_knitted_straight_wait_from_table(const tile_table_t &cnt_table, intptr_t left_cnt, useful_table_t *waiting_table) { 800 | // 匹配组合龙 801 | const tile_t (*matched_seq)[9] = nullptr; 802 | tile_t missing_tiles[9]; 803 | int missing_cnt = 0; 804 | for (int i = 0; i < 6; ++i) { // 逐个组合龙测试 805 | missing_cnt = 0; 806 | for (int k = 0; k < 9; ++k) { 807 | tile_t t = standard_knitted_straight[i][k]; 808 | if (cnt_table[t] == 0) { // 缺失的 809 | missing_tiles[missing_cnt++] = t; 810 | } 811 | } 812 | if (missing_cnt < 2) { // 缺2张或以上的肯定没听 813 | matched_seq = &standard_knitted_straight[i]; 814 | break; 815 | } 816 | } 817 | 818 | if (matched_seq == nullptr || missing_cnt > 2) { 819 | return false; 820 | } 821 | 822 | if (waiting_table != nullptr) { 823 | memset(*waiting_table, 0, sizeof(*waiting_table)); 824 | } 825 | 826 | // 剔除组合龙 827 | tile_table_t temp_table; 828 | memcpy(&temp_table, &cnt_table, sizeof(temp_table)); 829 | for (int i = 0; i < 9; ++i) { 830 | tile_t t = (*matched_seq)[i]; 831 | if (temp_table[t]) { 832 | --temp_table[t]; 833 | } 834 | } 835 | 836 | if (missing_cnt == 1) { // 如果缺一张,那么除去组合龙之后的牌应该是完成状态才能听牌 837 | if (left_cnt == 10) { 838 | if (is_basic_form_win_recursively(temp_table, 2)) { 839 | if (waiting_table != nullptr) { // 获取听牌张,听组合龙缺的一张 840 | (*waiting_table)[missing_tiles[0]] = true; 841 | } 842 | return true; 843 | } 844 | } 845 | else { 846 | if (is_basic_form_win_recursively(temp_table, 5)) { 847 | if (waiting_table != nullptr) { // 获取听牌张,听组合龙缺的一张 848 | (*waiting_table)[missing_tiles[0]] = true; 849 | } 850 | return true; 851 | } 852 | } 853 | } 854 | else if (missing_cnt == 0) { // 如果组合龙齐了,那么除去组合龙之后的牌要能听,整手牌才能听 855 | if (left_cnt == 10) { 856 | return is_basic_form_wait_1(temp_table, waiting_table); 857 | } 858 | else { 859 | return is_basic_form_wait_recursively(temp_table, 4, waiting_table); 860 | } 861 | } 862 | 863 | return false; 864 | } 865 | 866 | // 基本和型包含主番的上听数,可用于计算三步高 三同顺 龙等三组面子的番种整个立牌的上听数 867 | static int basic_form_shanten_specified(const tile_table_t &cnt_table, const tile_t *main_tiles, int main_cnt, 868 | intptr_t fixed_cnt, useful_table_t *useful_table) { 869 | 870 | tile_table_t temp_table; 871 | memcpy(&temp_table, &cnt_table, sizeof(temp_table)); 872 | int exist_cnt = 0; 873 | 874 | // 统计主番的牌 875 | for (int i = 0; i < main_cnt; ++i) { 876 | tile_t t = main_tiles[i]; 877 | int n = cnt_table[t]; 878 | if (n > 0) { // 有,削减之 879 | ++exist_cnt; 880 | --temp_table[t]; 881 | } 882 | } 883 | 884 | // 记录有效牌 885 | if (useful_table != nullptr) { 886 | memset(*useful_table, 0, sizeof(*useful_table)); 887 | 888 | // 统计主番缺失的牌 889 | for (int i = 0; i < main_cnt; ++i) { 890 | tile_t t = main_tiles[i]; 891 | int n = cnt_table[t]; 892 | if (n <= 0) { 893 | (*useful_table)[t] = true; 894 | } 895 | } 896 | } 897 | 898 | // 余下牌的上听数 899 | int result = basic_form_shanten_from_table(temp_table, fixed_cnt + main_cnt / 3, useful_table); 900 | 901 | // 上听数=主番缺少的张数+余下牌的上听数 902 | return (main_cnt - exist_cnt) + result; 903 | } 904 | 905 | // 组合龙上听数 906 | int knitted_straight_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table) { 907 | if (standing_tiles == nullptr || (standing_cnt != 13 && standing_cnt != 10)) { 908 | return std::numeric_limits::max(); 909 | } 910 | 911 | // 打表 912 | tile_table_t cnt_table; 913 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 914 | 915 | int ret = std::numeric_limits::max(); 916 | 917 | // 需要获取有效牌时,计算上听数的同时就获取有效牌了 918 | if (useful_table != nullptr) { 919 | memset(*useful_table, 0, sizeof(*useful_table)); 920 | 921 | useful_table_t temp_table; 922 | 923 | // 6种组合龙分别计算 924 | for (int i = 0; i < 6; ++i) { 925 | int fixed_cnt = (13 - static_cast(standing_cnt)) / 3; 926 | int st = basic_form_shanten_specified(cnt_table, standard_knitted_straight[i], 9, fixed_cnt, &temp_table); 927 | if (st < ret) { // 上听数小的,直接覆盖数据 928 | ret = st; 929 | memcpy(*useful_table, temp_table, sizeof(*useful_table)); // 直接覆盖原来的有效牌数据 930 | } 931 | else if (st == ret) { // 两种不同组合龙上听数如果相等的话,直接合并有效牌 932 | std::transform(std::begin(*useful_table), std::end(*useful_table), std::begin(temp_table), 933 | std::begin(*useful_table), [](bool u, bool t) { return u || t; }); 934 | } 935 | } 936 | } 937 | else { 938 | // 6种组合龙分别计算 939 | for (int i = 0; i < 6; ++i) { 940 | int fixed_cnt = (13 - static_cast(standing_cnt)) / 3; 941 | int st = basic_form_shanten_specified(cnt_table, standard_knitted_straight[i], 9, fixed_cnt, nullptr); 942 | if (st < ret) { 943 | ret = st; 944 | } 945 | } 946 | } 947 | 948 | return ret; 949 | } 950 | 951 | // 组合龙是否听牌 952 | bool is_knitted_straight_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table) { 953 | if (standing_tiles == nullptr || (standing_cnt != 13 && standing_cnt != 10)) { 954 | return false; 955 | } 956 | 957 | // 对立牌的种类进行打表 958 | tile_table_t cnt_table; 959 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 960 | 961 | return is_knitted_straight_wait_from_table(cnt_table, standing_cnt, waiting_table); 962 | } 963 | 964 | // 组合龙是否和牌 965 | bool is_knitted_straight_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile) { 966 | useful_table_t waiting_table; 967 | return (is_knitted_straight_wait(standing_tiles, standing_cnt, &waiting_table) 968 | && waiting_table[test_tile]); 969 | } 970 | 971 | //-------------------------------- 全不靠/七星不靠 -------------------------------- 972 | 973 | // 1种组合龙的全不靠上听数 974 | static int honors_and_knitted_tiles_shanten_1(const tile_t *standing_tiles, intptr_t standing_cnt, int which_seq, useful_table_t *useful_table) { 975 | if (standing_tiles == nullptr || standing_cnt != 13) { 976 | return std::numeric_limits::max(); 977 | } 978 | 979 | // 对牌的种类进行打表 980 | tile_table_t cnt_table; 981 | map_tiles(standing_tiles, standing_cnt, &cnt_table); 982 | 983 | int cnt = 0; 984 | 985 | // 统计组合龙部分的数牌 986 | for (int i = 0; i < 9; ++i) { 987 | tile_t t = standard_knitted_straight[which_seq][i]; 988 | int n = cnt_table[t]; 989 | if (n > 0) { // 有,增加计数 990 | ++cnt; 991 | } 992 | } 993 | 994 | // 统计字牌 995 | for (int i = 6; i < 13; ++i) { 996 | tile_t t = standard_thirteen_orphans[i]; 997 | int n = cnt_table[t]; 998 | if (n > 0) { // 有,增加计数 999 | ++cnt; 1000 | } 1001 | } 1002 | 1003 | // 记录有效牌 1004 | if (useful_table != nullptr) { 1005 | memset(*useful_table, 0, sizeof(*useful_table)); 1006 | 1007 | // 统计组合龙部分缺失的数牌 1008 | for (int i = 0; i < 9; ++i) { 1009 | tile_t t = standard_knitted_straight[which_seq][i]; 1010 | int n = cnt_table[t]; 1011 | if (n <= 0) { 1012 | (*useful_table)[t] = true; 1013 | } 1014 | } 1015 | 1016 | // 统计缺失的字牌 1017 | for (int i = 6; i < 13; ++i) { 1018 | tile_t t = standard_thirteen_orphans[i]; 1019 | int n = cnt_table[t]; 1020 | if (n <= 0) { 1021 | (*useful_table)[t] = true; 1022 | } 1023 | } 1024 | } 1025 | 1026 | // 上听数=13-符合牌型的计数 1027 | return 13 - cnt; 1028 | } 1029 | 1030 | // 全不靠上听数 1031 | int honors_and_knitted_tiles_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table) { 1032 | int ret = std::numeric_limits::max(); 1033 | 1034 | // 需要获取有效牌时,计算上听数的同时就获取有效牌了 1035 | if (useful_table != nullptr) { 1036 | memset(*useful_table, 0, sizeof(*useful_table)); 1037 | 1038 | useful_table_t temp_table; 1039 | 1040 | // 6种组合龙分别计算 1041 | for (int i = 0; i < 6; ++i) { 1042 | int st = honors_and_knitted_tiles_shanten_1(standing_tiles, standing_cnt, i, &temp_table); 1043 | if (st < ret) { // 上听数小的,直接覆盖数据 1044 | ret = st; 1045 | memcpy(*useful_table, temp_table, sizeof(*useful_table)); // 直接覆盖原来的有效牌数据 1046 | } 1047 | else if (st == ret) { // 两种不同组合龙上听数如果相等的话,直接合并有效牌 1048 | std::transform(std::begin(*useful_table), std::end(*useful_table), std::begin(temp_table), 1049 | std::begin(*useful_table), [](bool u, bool t) { return u || t; }); 1050 | } 1051 | } 1052 | } 1053 | else { 1054 | // 6种组合龙分别计算 1055 | for (int i = 0; i < 6; ++i) { 1056 | int st = honors_and_knitted_tiles_shanten_1(standing_tiles, standing_cnt, i, nullptr); 1057 | if (st < ret) { 1058 | ret = st; 1059 | } 1060 | } 1061 | } 1062 | return ret; 1063 | } 1064 | 1065 | // 全不靠是否听牌 1066 | bool is_honors_and_knitted_tiles_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table) { 1067 | // 直接计算其上听数,上听数为0即为听牌 1068 | if (waiting_table == nullptr) { 1069 | return (0 == honors_and_knitted_tiles_shanten(standing_tiles, standing_cnt, nullptr)); 1070 | } 1071 | 1072 | useful_table_t useful_table; 1073 | if (0 == honors_and_knitted_tiles_shanten(standing_tiles, standing_cnt, &useful_table)) { 1074 | memcpy(*waiting_table, useful_table, sizeof(*waiting_table)); 1075 | return true; 1076 | } 1077 | return false; 1078 | } 1079 | 1080 | // 全不靠是否和牌 1081 | bool is_honors_and_knitted_tiles_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile) { 1082 | useful_table_t useful_table; 1083 | if (0 == honors_and_knitted_tiles_shanten(standing_tiles, standing_cnt, &useful_table)) { 1084 | return useful_table[test_tile]; 1085 | } 1086 | return false; 1087 | } 1088 | 1089 | //-------------------------------- 所有情况综合 -------------------------------- 1090 | 1091 | bool is_waiting(const hand_tiles_t &hand_tiles, useful_table_t *useful_table) { 1092 | bool spcial_waiting = false, basic_waiting = false; 1093 | useful_table_t table_special, table_basic; 1094 | 1095 | if (hand_tiles.tile_count == 13) { 1096 | if (is_thirteen_orphans_wait(hand_tiles.standing_tiles, 13, &table_special)) { 1097 | spcial_waiting = true; 1098 | } 1099 | else if (is_honors_and_knitted_tiles_wait(hand_tiles.standing_tiles, 13, &table_special)) { 1100 | spcial_waiting = true; 1101 | } 1102 | else if (is_seven_pairs_wait(hand_tiles.standing_tiles, 13, &table_special)) { 1103 | spcial_waiting = true; 1104 | } 1105 | else if (is_knitted_straight_wait(hand_tiles.standing_tiles, 13, &table_special)) { 1106 | spcial_waiting = true; 1107 | } 1108 | } 1109 | else if (hand_tiles.tile_count == 10) { 1110 | if (is_knitted_straight_wait(hand_tiles.standing_tiles, 10, &table_special)) { 1111 | spcial_waiting = true; 1112 | } 1113 | } 1114 | 1115 | if (is_basic_form_wait(hand_tiles.standing_tiles, hand_tiles.tile_count, &table_basic)) { 1116 | basic_waiting = true; 1117 | } 1118 | 1119 | if (useful_table != nullptr) { 1120 | if (spcial_waiting && basic_waiting) { 1121 | std::transform(std::begin(table_special), std::end(table_special), std::begin(table_basic), std::begin(*useful_table), 1122 | [](bool a, bool b) { return a || b; }); 1123 | } 1124 | else if (basic_waiting) { 1125 | memcpy(*useful_table, table_basic, sizeof(table_basic)); 1126 | } 1127 | else if (spcial_waiting) { 1128 | memcpy(*useful_table, table_special, sizeof(table_special)); 1129 | } 1130 | } 1131 | 1132 | return (spcial_waiting || basic_waiting); 1133 | } 1134 | 1135 | //-------------------------------- 枚举打牌 -------------------------------- 1136 | 1137 | // 枚举打哪张牌1次 1138 | static bool enum_discard_tile_1(const hand_tiles_t *hand_tiles, tile_t discard_tile, uint8_t form_flag, 1139 | void *context, enum_callback_t enum_callback) { 1140 | enum_result_t result; 1141 | result.discard_tile = discard_tile; 1142 | result.form_flag = FORM_FLAG_BASIC_FORM; 1143 | result.shanten = basic_form_shanten(hand_tiles->standing_tiles, hand_tiles->tile_count, &result.useful_table); 1144 | if (result.shanten == 0 && result.useful_table[discard_tile]) { // 0上听,并且打出的牌是有效牌,则修正为和了 1145 | result.shanten = -1; 1146 | } 1147 | if (!enum_callback(context, &result)) { 1148 | return false; 1149 | } 1150 | 1151 | // 立牌有13张时,才需要计算特殊和型 1152 | if (hand_tiles->tile_count == 13) { 1153 | if (form_flag | FORM_FLAG_SEVEN_PAIRS) { 1154 | result.form_flag = FORM_FLAG_SEVEN_PAIRS; 1155 | result.shanten = seven_pairs_shanten(hand_tiles->standing_tiles, hand_tiles->tile_count, &result.useful_table); 1156 | if (result.shanten == 0 && result.useful_table[discard_tile]) { // 0上听,并且打出的牌是有效牌,则修正为和了 1157 | result.shanten = -1; 1158 | } 1159 | if (!enum_callback(context, &result)) { 1160 | return false; 1161 | } 1162 | } 1163 | 1164 | if (form_flag | FORM_FLAG_THIRTEEN_ORPHANS) { 1165 | result.form_flag = FORM_FLAG_THIRTEEN_ORPHANS; 1166 | result.shanten = thirteen_orphans_shanten(hand_tiles->standing_tiles, hand_tiles->tile_count, &result.useful_table); 1167 | if (result.shanten == 0 && result.useful_table[discard_tile]) { // 0上听,并且打出的牌是有效牌,则修正为和了 1168 | result.shanten = -1; 1169 | } 1170 | if (!enum_callback(context, &result)) { 1171 | return false; 1172 | } 1173 | } 1174 | 1175 | if (form_flag | FORM_FLAG_HONORS_AND_KNITTED_TILES) { 1176 | result.form_flag = FORM_FLAG_HONORS_AND_KNITTED_TILES; 1177 | result.shanten = honors_and_knitted_tiles_shanten(hand_tiles->standing_tiles, hand_tiles->tile_count, &result.useful_table); 1178 | if (result.shanten == 0 && result.useful_table[discard_tile]) { // 0上听,并且打出的牌是有效牌,则修正为和了 1179 | result.shanten = -1; 1180 | } 1181 | if (!enum_callback(context, &result)) { 1182 | return false; 1183 | } 1184 | } 1185 | } 1186 | 1187 | // 立牌有13张或者10张时,才需要计算组合龙 1188 | if (hand_tiles->tile_count == 13 || hand_tiles->tile_count == 10) { 1189 | if (form_flag | FORM_FLAG_KNITTED_STRAIGHT) { 1190 | result.form_flag = FORM_FLAG_KNITTED_STRAIGHT; 1191 | result.shanten = knitted_straight_shanten(hand_tiles->standing_tiles, hand_tiles->tile_count, &result.useful_table); 1192 | if (result.shanten == 0 && result.useful_table[discard_tile]) { // 0上听,并且打出的牌是有效牌,则修正为和了 1193 | result.shanten = -1; 1194 | } 1195 | if (!enum_callback(context, &result)) { 1196 | return false; 1197 | } 1198 | } 1199 | } 1200 | 1201 | return true; 1202 | } 1203 | 1204 | // 枚举打哪张牌 1205 | void enum_discard_tile(const hand_tiles_t *hand_tiles, tile_t serving_tile, uint8_t form_flag, 1206 | void *context, enum_callback_t enum_callback) { 1207 | // 先计算摸切的 1208 | if (!enum_discard_tile_1(hand_tiles, serving_tile, form_flag, context, enum_callback)) { 1209 | return; 1210 | } 1211 | 1212 | if (serving_tile == 0) { 1213 | return; 1214 | } 1215 | 1216 | // 将立牌打表 1217 | tile_table_t cnt_table; 1218 | map_tiles(hand_tiles->standing_tiles, hand_tiles->tile_count, &cnt_table); 1219 | 1220 | // 复制一份手牌 1221 | hand_tiles_t temp; 1222 | memcpy(&temp, hand_tiles, sizeof(temp)); 1223 | 1224 | // 依次尝试打手中的立牌 1225 | for (int i = 0; i < 34; ++i) { 1226 | tile_t t = all_tiles[i]; 1227 | if (cnt_table[t] && t != serving_tile && cnt_table[serving_tile] < 4) { 1228 | --cnt_table[t]; // 打这张牌 1229 | ++cnt_table[serving_tile]; // 上这张牌 1230 | 1231 | // 从table转成立牌 1232 | table_to_tiles(cnt_table, temp.standing_tiles, temp.tile_count); 1233 | 1234 | // 计算 1235 | if (!enum_discard_tile_1(&temp, t, form_flag, context, enum_callback)) { 1236 | return; 1237 | } 1238 | 1239 | // 复原 1240 | --cnt_table[serving_tile]; 1241 | ++cnt_table[t]; 1242 | } 1243 | } 1244 | } 1245 | 1246 | } 1247 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/shanten.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #ifndef __MAHJONG_ALGORITHM__SHANTEN_H__ 24 | #define __MAHJONG_ALGORITHM__SHANTEN_H__ 25 | 26 | #include "tile.h" 27 | 28 | namespace mahjong { 29 | 30 | /** 31 | * @brief 牌组转换成牌 32 | * 33 | * @param [in] packs 牌组 34 | * @param [in] pack_cnt 牌组的数量 35 | * @param [out] tiles 牌 36 | * @param [in] tile_cnt 牌的最大数量 37 | * @return intptr_t 牌的实际数量 38 | */ 39 | intptr_t packs_to_tiles(const pack_t *packs, intptr_t pack_cnt, tile_t *tiles, intptr_t tile_cnt); 40 | 41 | /** 42 | * @brief 将牌打表 43 | * 44 | * @param [in] tiles 牌 45 | * @param [in] cnt 牌的数量 46 | * @param [out] cnt_table 牌的数量表 47 | */ 48 | void map_tiles(const tile_t *tiles, intptr_t cnt, tile_table_t *cnt_table); 49 | 50 | /** 51 | * @brief 将手牌打表 52 | * 53 | * @param [in] hand_tiles 手牌 54 | * @param [out] cnt_table 牌的数量表 55 | * @return bool 手牌结构是否正确。即是否符合:副露组数*3+立牌数=13 56 | */ 57 | bool map_hand_tiles(const hand_tiles_t *hand_tiles, tile_table_t *cnt_table); 58 | 59 | /** 60 | * @brief 将表转换成牌 61 | * 62 | * @param [in] cnt_table 牌的数量表 63 | * @param [out] tiles 牌 64 | * @param [in] max_cnt 牌的最大数量 65 | * @return intptr_t 牌的实际数量 66 | */ 67 | intptr_t table_to_tiles(const tile_table_t &cnt_table, tile_t *tiles, intptr_t max_cnt); 68 | 69 | /** 70 | * @brief 有效牌标记表类型 71 | */ 72 | typedef bool useful_table_t[TILE_TABLE_SIZE]; 73 | 74 | /** 75 | * @addtogroup shanten 76 | * @{ 77 | */ 78 | 79 | /** 80 | * @addtogroup basic_form 81 | * @{ 82 | */ 83 | 84 | /** 85 | * @brief 基本和型上听数 86 | * 87 | * @param [in] standing_tiles 立牌 88 | * @param [in] standing_cnt 立牌数 89 | * @param [out] useful_table 有效牌标记表(可为null) 90 | * @return int 上听数 91 | */ 92 | int basic_form_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table); 93 | 94 | /** 95 | * @brief 基本和型是否听牌 96 | * 97 | * @param [in] standing_tiles 立牌 98 | * @param [in] standing_cnt 立牌数 99 | * @param [out] waiting_table 听牌标记表(可为null) 100 | * @return bool 是否听牌 101 | */ 102 | bool is_basic_form_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table); 103 | 104 | /** 105 | * @brief 基本和型是否和牌 106 | * 107 | * @param [in] standing_tiles 立牌 108 | * @param [in] standing_cnt 立牌数 109 | * @param [in] test_tile 测试的牌 110 | * @return bool 是否和牌 111 | */ 112 | bool is_basic_form_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile); 113 | 114 | /** 115 | * end group 116 | * @} 117 | */ 118 | 119 | /** 120 | * @addtogroup seven_pairs 121 | * @{ 122 | */ 123 | 124 | /** 125 | * @brief 七对上听数 126 | * 127 | * @param [in] standing_tiles 立牌 128 | * @param [in] standing_cnt 立牌数 129 | * @param [out] useful_table 有效牌标记表(可为null) 130 | * @return int 上听数 131 | */ 132 | int seven_pairs_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table); 133 | 134 | /** 135 | * @brief 七对是否听牌 136 | * 137 | * @param [in] standing_tiles 立牌 138 | * @param [in] standing_cnt 立牌数 139 | * @param [out] waiting_table 听牌标记表(可为null) 140 | * @return bool 是否听牌 141 | */ 142 | bool is_seven_pairs_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table); 143 | 144 | /** 145 | * @brief 七对是否和牌 146 | * 147 | * @param [in] standing_tiles 立牌 148 | * @param [in] standing_cnt 立牌数 149 | * @param [in] test_tile 测试的牌 150 | * @return bool 是否和牌 151 | */ 152 | bool is_seven_pairs_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile); 153 | 154 | /** 155 | * end group 156 | * @} 157 | */ 158 | 159 | /** 160 | * @addtogroup thirteen_orphans 161 | * @{ 162 | */ 163 | 164 | /** 165 | * @brief 十三幺上听数 166 | * 167 | * @param [in] standing_tiles 立牌 168 | * @param [in] standing_cnt 立牌数 169 | * @param [out] useful_table 有效牌标记表(可为null) 170 | * @return int 上听数 171 | */ 172 | int thirteen_orphans_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table); 173 | 174 | /** 175 | * @brief 十三幺是否听牌 176 | * 177 | * @param [in] standing_tiles 立牌 178 | * @param [in] standing_cnt 立牌数 179 | * @param [out] waiting_table 听牌标记表(可为null) 180 | * @return bool 是否听牌 181 | */ 182 | bool is_thirteen_orphans_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table); 183 | 184 | /** 185 | * @brief 十三幺是否和牌 186 | * 187 | * @param [in] standing_tiles 立牌 188 | * @param [in] standing_cnt 立牌数 189 | * @param [in] test_tile 测试的牌 190 | * @return bool 是否和牌 191 | */ 192 | bool is_thirteen_orphans_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile); 193 | 194 | /** 195 | * end group 196 | * @} 197 | */ 198 | 199 | /** 200 | * @addtogroup knitted_straight 201 | * @{ 202 | */ 203 | 204 | /** 205 | * @brief 组合龙上听数 206 | * 207 | * @param [in] standing_tiles 立牌 208 | * @param [in] standing_cnt 立牌数 209 | * @param [out] useful_table 有效牌标记表(可为null) 210 | * @return int 上听数 211 | */ 212 | int knitted_straight_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table); 213 | 214 | /** 215 | * @brief 组合龙是否听牌 216 | * 217 | * @param [in] standing_tiles 立牌 218 | * @param [in] standing_cnt 立牌数 219 | * @param [out] waiting_table 听牌标记表(可为null) 220 | * @return bool 是否听牌 221 | */ 222 | bool is_knitted_straight_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table); 223 | 224 | /** 225 | * @brief 组合龙是否和牌 226 | * 227 | * @param [in] standing_tiles 立牌 228 | * @param [in] standing_cnt 立牌数 229 | * @param [in] test_tile 测试的牌 230 | * @return bool 是否和牌 231 | */ 232 | bool is_knitted_straight_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile); 233 | 234 | /** 235 | * end group 236 | * @} 237 | */ 238 | 239 | /** 240 | * @addtogroup honors_and_knitted_tiles 241 | * @{ 242 | */ 243 | 244 | /** 245 | * @brief 全不靠上听数 246 | * 247 | * @param [in] standing_tiles 立牌 248 | * @param [in] standing_cnt 立牌数 249 | * @param [out] useful_table 有效牌标记表(可为null) 250 | * @return int 上听数 251 | */ 252 | int honors_and_knitted_tiles_shanten(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *useful_table); 253 | 254 | /** 255 | * @brief 全不靠是否听牌 256 | * 257 | * @param [in] standing_tiles 立牌 258 | * @param [in] standing_cnt 立牌数 259 | * @param [out] waiting_table 听牌标记表(可为null) 260 | * @return bool 是否听牌 261 | */ 262 | bool is_honors_and_knitted_tiles_wait(const tile_t *standing_tiles, intptr_t standing_cnt, useful_table_t *waiting_table); 263 | 264 | /** 265 | * @brief 全不靠是否和牌 266 | * 267 | * @param [in] standing_tiles 立牌 268 | * @param [in] standing_cnt 立牌数 269 | * @param [in] test_tile 测试的牌 270 | * @return bool 是否和牌 271 | */ 272 | bool is_honors_and_knitted_tiles_win(const tile_t *standing_tiles, intptr_t standing_cnt, tile_t test_tile); 273 | 274 | /** 275 | * end group 276 | * @} 277 | */ 278 | 279 | /** 280 | * @brief 是否听牌 281 | * 282 | * @param [in] hand_tiles 手牌结构 283 | * @param [out] useful_table 有效牌标记表(可为null) 284 | * @return bool 是否听牌 285 | */ 286 | bool is_waiting(const hand_tiles_t &hand_tiles, useful_table_t *useful_table); 287 | 288 | /** 289 | * end group 290 | * @} 291 | */ 292 | 293 | /** 294 | * @name form flags 295 | * @{ 296 | * 和型 297 | */ 298 | #define FORM_FLAG_BASIC_FORM 0x01 ///< 基本和型 299 | #define FORM_FLAG_SEVEN_PAIRS 0x02 ///< 七对 300 | #define FORM_FLAG_THIRTEEN_ORPHANS 0x04 ///< 十三幺 301 | #define FORM_FLAG_HONORS_AND_KNITTED_TILES 0x08 ///< 全不靠 302 | #define FORM_FLAG_KNITTED_STRAIGHT 0x10 ///< 组合龙 303 | #define FORM_FLAG_ALL 0xFF ///< 全部和型 304 | /** 305 | * @} 306 | */ 307 | 308 | /** 309 | * @brief 枚举打哪张牌的计算结果信息 310 | */ 311 | struct enum_result_t { 312 | tile_t discard_tile; ///< 打这张牌 313 | uint8_t form_flag; ///< 和牌形式 314 | int shanten; ///< 上听数 315 | useful_table_t useful_table; ///< 有效牌标记表 316 | }; 317 | 318 | /** 319 | * @brief 枚举打哪张牌的计算回调函数 320 | * 321 | * @param [in] context 从enum_discard_tile传过来的context原样传回 322 | * @param [in] result 计算结果 323 | * @retval true 继续枚举 324 | * @retval false 结束枚举 325 | */ 326 | typedef bool (*enum_callback_t)(void *context, const enum_result_t *result); 327 | 328 | /** 329 | * @brief 枚举打哪张牌 330 | * 331 | * @param [in] hand_tiles 手牌结构 332 | * @param [in] serving_tile 上牌(可为0,此时仅计算手牌的信息) 333 | * @param [in] form_flag 计算哪些和型 334 | * @param [in] context 用户自定义参数,将原样从回调函数传回 335 | * @param [in] enum_callback 回调函数 336 | */ 337 | void enum_discard_tile(const hand_tiles_t *hand_tiles, tile_t serving_tile, uint8_t form_flag, 338 | void *context, enum_callback_t enum_callback); 339 | 340 | } 341 | 342 | /** 343 | * end group 344 | * @} 345 | */ 346 | 347 | #endif 348 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/standard_tiles.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #ifndef __MAHJONG_ALGORITHM__STANDARD_TILES_H__ 24 | #define __MAHJONG_ALGORITHM__STANDARD_TILES_H__ 25 | 26 | #include "tile.h" 27 | 28 | namespace mahjong { 29 | 30 | // 十三幺13面听 31 | static const tile_t standard_thirteen_orphans[13] = { 32 | TILE_1m, TILE_9m, TILE_1s, TILE_9s, TILE_1p, TILE_9p, TILE_E, TILE_S, TILE_W, TILE_N, TILE_C, TILE_F, TILE_P 33 | }; 34 | 35 | // 组合龙只有如下6种 36 | // 147m 258s 369p 37 | // 147m 369s 258p 38 | // 258m 147s 369p 39 | // 258m 369s 147p 40 | // 369m 147s 258p 41 | // 369m 258s 147p 42 | static const tile_t standard_knitted_straight[6][9] = { 43 | { TILE_1m, TILE_4m, TILE_7m, TILE_2s, TILE_5s, TILE_8s, TILE_3p, TILE_6p, TILE_9p }, 44 | { TILE_1m, TILE_4m, TILE_7m, TILE_3s, TILE_6s, TILE_9s, TILE_2p, TILE_5p, TILE_8p }, 45 | { TILE_2m, TILE_5m, TILE_8m, TILE_1s, TILE_4s, TILE_7s, TILE_3p, TILE_6p, TILE_9p }, 46 | { TILE_2m, TILE_5m, TILE_8m, TILE_3s, TILE_6s, TILE_9s, TILE_1p, TILE_4p, TILE_7p }, 47 | { TILE_3m, TILE_6m, TILE_9m, TILE_1s, TILE_4s, TILE_7s, TILE_2p, TILE_5p, TILE_8p }, 48 | { TILE_3m, TILE_6m, TILE_9m, TILE_2s, TILE_5s, TILE_8s, TILE_1p, TILE_4p, TILE_7p }, 49 | }; 50 | 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/stringify.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #include "stringify.h" 24 | #include 25 | #include 26 | #include 27 | 28 | namespace mahjong { 29 | 30 | // 解析牌实现函数 31 | static intptr_t parse_tiles_impl(const char *str, tile_t *tiles, intptr_t max_cnt, intptr_t *out_tile_cnt) { 32 | //if (strspn(str, "123456789mpsESWNCFP") != strlen(str)) { 33 | // return PARSE_ERROR_ILLEGAL_CHARACTER; 34 | //} 35 | 36 | intptr_t tile_cnt = 0; 37 | 38 | #define SET_SUIT_FOR_NUMBERED(value_) \ 39 | for (intptr_t i = tile_cnt; i > 0;) { \ 40 | if (tiles[--i] & 0xF0) break; \ 41 | tiles[i] |= value_; \ 42 | } (void)0 43 | 44 | #define SET_SUIT_FOR_CHARACTERS() SET_SUIT_FOR_NUMBERED(0x10) 45 | #define SET_SUIT_FOR_BAMBOO() SET_SUIT_FOR_NUMBERED(0x20) 46 | #define SET_SUIT_FOR_DOTS() SET_SUIT_FOR_NUMBERED(0x30) 47 | 48 | #define SET_SUIT_FOR_HONOR() \ 49 | for (intptr_t i = tile_cnt; i > 0;) { \ 50 | if (tiles[--i] & 0xF0) break; \ 51 | if (tiles[i] > 7) return PARSE_ERROR_ILLEGAL_CHARACTER; \ 52 | tiles[i] |= 0x40; \ 53 | } (void)0 54 | 55 | #define NO_SUFFIX_AFTER_DIGIT() (tile_cnt > 0 && !(tiles[tile_cnt - 1] & 0xF0)) 56 | #define CHECK_SUFFIX() if (NO_SUFFIX_AFTER_DIGIT()) return PARSE_ERROR_NO_SUFFIX_AFTER_DIGIT 57 | 58 | const char *p = str; 59 | for (; tile_cnt < max_cnt && *p != '\0'; ++p) { 60 | char c = *p; 61 | switch (c) { 62 | case '0': tiles[tile_cnt++] = 5; break; 63 | case '1': tiles[tile_cnt++] = 1; break; 64 | case '2': tiles[tile_cnt++] = 2; break; 65 | case '3': tiles[tile_cnt++] = 3; break; 66 | case '4': tiles[tile_cnt++] = 4; break; 67 | case '5': tiles[tile_cnt++] = 5; break; 68 | case '6': tiles[tile_cnt++] = 6; break; 69 | case '7': tiles[tile_cnt++] = 7; break; 70 | case '8': tiles[tile_cnt++] = 8; break; 71 | case '9': tiles[tile_cnt++] = 9; break; 72 | case 'm': SET_SUIT_FOR_CHARACTERS(); break; 73 | case 's': SET_SUIT_FOR_BAMBOO(); break; 74 | case 'p': SET_SUIT_FOR_DOTS(); break; 75 | case 'z': SET_SUIT_FOR_HONOR(); break; 76 | case 'E': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_E; break; 77 | case 'S': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_S; break; 78 | case 'W': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_W; break; 79 | case 'N': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_N; break; 80 | case 'C': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_C; break; 81 | case 'F': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_F; break; 82 | case 'P': CHECK_SUFFIX(); tiles[tile_cnt++] = TILE_P; break; 83 | default: goto finish_parse; 84 | } 85 | } 86 | 87 | finish_parse: 88 | // 一连串数字+后缀,但已经超过容量,说明牌过多 89 | if (NO_SUFFIX_AFTER_DIGIT()) { 90 | // 这里的逻辑为:放弃中间一部分数字,直接解析最近的后缀 91 | const char *p1 = strpbrk(p, "mspz"); 92 | if (p1 == nullptr) { 93 | return PARSE_ERROR_NO_SUFFIX_AFTER_DIGIT; 94 | } 95 | 96 | switch (*p1) { 97 | case 'm': SET_SUIT_FOR_CHARACTERS(); break; 98 | case 's': SET_SUIT_FOR_BAMBOO(); break; 99 | case 'p': SET_SUIT_FOR_DOTS(); break; 100 | case 'z': SET_SUIT_FOR_HONOR(); break; 101 | default: return PARSE_ERROR_NO_SUFFIX_AFTER_DIGIT; 102 | } 103 | 104 | if (p1 != p) { // 放弃过中间的数字 105 | return PARSE_ERROR_TOO_MANY_TILES; 106 | } 107 | 108 | p = p1 + 1; 109 | } 110 | 111 | #undef SET_SUIT_FOR_NUMBERED 112 | #undef SET_SUIT_FOR_CHARACTERS 113 | #undef SET_SUIT_FOR_BAMBOO 114 | #undef SET_SUIT_FOR_DOTS 115 | #undef SET_SUIT_FOR_HONOR 116 | #undef NO_SUFFIX_AFTER_DIGIT 117 | #undef CHECK_SUFFIX 118 | 119 | *out_tile_cnt = tile_cnt; 120 | return static_cast(p - str); 121 | } 122 | 123 | // 解析牌 124 | intptr_t parse_tiles(const char *str, tile_t *tiles, intptr_t max_cnt) { 125 | intptr_t tile_cnt; 126 | if (parse_tiles_impl(str, tiles, max_cnt, &tile_cnt) > 0) { 127 | return tile_cnt; 128 | } 129 | return 0; 130 | } 131 | 132 | // 生成副露 133 | static intptr_t make_fixed_pack(const tile_t *tiles, intptr_t tile_cnt, pack_t *pack, uint8_t offer) { 134 | if (tile_cnt > 0) { 135 | if (tile_cnt != 3 && tile_cnt != 4) { 136 | return PARSE_ERROR_WRONG_TILES_COUNT_FOR_FIXED_PACK; 137 | } 138 | if (tile_cnt == 3) { 139 | if (offer == 0) { 140 | offer = 1; 141 | } 142 | if (tiles[0] == tiles[1] && tiles[1] == tiles[2]) { 143 | *pack = make_pack(offer, PACK_TYPE_PUNG, tiles[0]); 144 | } 145 | else { 146 | if (tiles[0] + 1 == tiles[1] && tiles[1] + 1 == tiles[2]) { 147 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[1]); 148 | } 149 | else if (tiles[0] + 1 == tiles[2] && tiles[2] + 1 == tiles[1]) { 150 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[2]); 151 | } 152 | else if (tiles[1] + 1 == tiles[0] && tiles[0] + 1 == tiles[2]) { 153 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[0]); 154 | } 155 | else if (tiles[1] + 1 == tiles[2] && tiles[2] + 1 == tiles[0]) { 156 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[2]); 157 | } 158 | else if (tiles[2] + 1 == tiles[0] && tiles[0] + 1 == tiles[1]) { 159 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[0]); 160 | } 161 | else if (tiles[2] + 1 == tiles[1] && tiles[1] + 1 == tiles[0]) { 162 | *pack = make_pack(offer, PACK_TYPE_CHOW, tiles[1]); 163 | } 164 | else { 165 | return PARSE_ERROR_CANNOT_MAKE_FIXED_PACK; 166 | } 167 | } 168 | } 169 | else { 170 | if (tiles[0] != tiles[1] || tiles[1] != tiles[2] || tiles[2] != tiles[3]) { 171 | return PARSE_ERROR_CANNOT_MAKE_FIXED_PACK; 172 | } 173 | *pack = make_pack(offer, PACK_TYPE_KONG, tiles[0]); 174 | } 175 | return 1; 176 | } 177 | return 0; 178 | } 179 | 180 | // 字符串转换为手牌结构和上牌 181 | intptr_t string_to_tiles(const char *str, hand_tiles_t *hand_tiles, tile_t *serving_tile) { 182 | size_t len = strlen(str); 183 | if (strspn(str, "0123456789mpszESWNCFP,[]") != len) { 184 | return PARSE_ERROR_ILLEGAL_CHARACTER; 185 | } 186 | 187 | pack_t packs[4]; 188 | intptr_t pack_cnt = 0; 189 | tile_t standing_tiles[14]; 190 | intptr_t standing_cnt = 0; 191 | 192 | bool in_brackets = false; 193 | tile_t temp_tiles[14]; 194 | intptr_t temp_cnt = 0; 195 | intptr_t max_cnt = 14; 196 | uint8_t offer = 0; 197 | 198 | tile_table_t cnt_table = { 0 }; 199 | 200 | const char *p = str; 201 | while (char c = *p) { 202 | const char *q; 203 | switch (c) { 204 | case ',': { // 副露来源 205 | if (!in_brackets) { 206 | return PARSE_ERROR_ILLEGAL_CHARACTER; 207 | } 208 | offer = static_cast(*++p - '0'); 209 | q = ++p; 210 | if (*p != ']') { 211 | return PARSE_ERROR_ILLEGAL_CHARACTER; 212 | } 213 | break; 214 | } 215 | case '[': { // 开始一组副露 216 | if (in_brackets) { 217 | return PARSE_ERROR_ILLEGAL_CHARACTER; 218 | } 219 | if (pack_cnt > 4) { 220 | return PARSE_ERROR_TOO_MANY_FIXED_PACKS; 221 | } 222 | if (temp_cnt > 0) { // 处理[]符号外面的牌 223 | if (standing_cnt + temp_cnt >= max_cnt) { 224 | return PARSE_ERROR_TOO_MANY_TILES; 225 | } 226 | // 放到立牌中 227 | memcpy(&standing_tiles[standing_cnt], temp_tiles, temp_cnt * sizeof(tile_t)); 228 | standing_cnt += temp_cnt; 229 | temp_cnt = 0; 230 | } 231 | 232 | q = ++p; 233 | in_brackets = true; 234 | offer = 0; 235 | max_cnt = 4; // 副露的牌组最多包含4张牌 236 | break; 237 | } 238 | case ']': { // 结束一副副露 239 | if (!in_brackets) { 240 | return PARSE_ERROR_ILLEGAL_CHARACTER; 241 | } 242 | // 生成副露 243 | intptr_t ret = make_fixed_pack(temp_tiles, temp_cnt, &packs[pack_cnt], offer); 244 | if (ret < 0) { 245 | return ret; 246 | } 247 | 248 | q = ++p; 249 | temp_cnt = 0; 250 | in_brackets = false; 251 | ++pack_cnt; 252 | max_cnt = 14 - standing_cnt - pack_cnt * 3; // 余下立牌数的最大值 253 | break; 254 | } 255 | default: { // 牌 256 | if (temp_cnt != 0) { // 重复进入 257 | return PARSE_ERROR_TOO_MANY_TILES; 258 | } 259 | // 解析max_cnt张牌 260 | intptr_t ret = parse_tiles_impl(p, temp_tiles, max_cnt, &temp_cnt); 261 | if (ret < 0) { // 出错 262 | return ret; 263 | } 264 | if (ret == 0) { 265 | return PARSE_ERROR_ILLEGAL_CHARACTER; 266 | } 267 | // 对牌打表 268 | for (intptr_t i = 0; i < temp_cnt; ++i) { 269 | ++cnt_table[temp_tiles[i]]; 270 | } 271 | q = p + ret; 272 | break; 273 | } 274 | } 275 | p = q; 276 | } 277 | 278 | max_cnt = 14 - pack_cnt * 3; 279 | if (temp_cnt > 0) { // 处理[]符号外面的牌 280 | if (standing_cnt + temp_cnt > max_cnt) { 281 | return PARSE_ERROR_TOO_MANY_TILES; 282 | } 283 | // 放到立牌中 284 | memcpy(&standing_tiles[standing_cnt], temp_tiles, temp_cnt * sizeof(tile_t)); 285 | standing_cnt += temp_cnt; 286 | } 287 | 288 | if (standing_cnt > max_cnt) { 289 | return PARSE_ERROR_TOO_MANY_TILES; 290 | } 291 | 292 | // 如果某张牌超过4 293 | if (std::any_of(std::begin(cnt_table), std::end(cnt_table), [](int cnt) { return cnt > 4; })) { 294 | return PARSE_ERROR_TILE_COUNT_GREATER_THAN_4; 295 | } 296 | 297 | // 无错误时再写回数据 298 | tile_t last_tile = 0; 299 | if (standing_cnt == max_cnt) { 300 | memcpy(hand_tiles->standing_tiles, standing_tiles, (max_cnt - 1) * sizeof(tile_t)); 301 | hand_tiles->tile_count = max_cnt - 1; 302 | last_tile = standing_tiles[max_cnt - 1]; 303 | } 304 | else { 305 | memcpy(hand_tiles->standing_tiles, standing_tiles, standing_cnt * sizeof(tile_t)); 306 | hand_tiles->tile_count = standing_cnt; 307 | } 308 | 309 | memcpy(hand_tiles->fixed_packs, packs, pack_cnt * sizeof(pack_t)); 310 | hand_tiles->pack_count = pack_cnt; 311 | *serving_tile = last_tile; 312 | 313 | return PARSE_NO_ERROR; 314 | } 315 | 316 | // 牌转换为字符串 317 | intptr_t tiles_to_string(const tile_t *tiles, intptr_t tile_cnt, char *str, intptr_t max_size) { 318 | bool tenhon = false; 319 | char *p = str, *end = str + max_size; 320 | 321 | static const char suffix[] = "mspz"; 322 | static const char honor_text[] = "ESWNCFP"; 323 | suit_t last_suit = 0; 324 | for (intptr_t i = 0; i < tile_cnt && p < end; ++i) { 325 | tile_t t = tiles[i]; 326 | suit_t s = tile_get_suit(t); 327 | rank_t r = tile_get_rank(t); 328 | if (s == 1 || s == 2 || s == 3) { // 数牌 329 | if (r >= 1 && r <= 9) { // 有效范围1-9 330 | if (last_suit != s && last_suit != 0) { // 花色变了,加后缀 331 | if (last_suit != 4 || tenhon) { 332 | *p++ = suffix[last_suit - 1]; 333 | } 334 | } 335 | if (p < end) { 336 | *p++ = '0' + r; // 写入一个数字字符 337 | } 338 | last_suit = s; // 记录花色 339 | } 340 | } 341 | else if (s == 4) { // 字牌 342 | if (r >= 1 && r <= 7) { // 有效范围1-7 343 | if (last_suit != s && last_suit != 0) { // 花色变了,加后缀 344 | if (last_suit != 4) { 345 | *p++ = suffix[last_suit - 1]; 346 | } 347 | } 348 | if (p < end) { 349 | if (tenhon) { // 天凤式后缀 350 | *p++ = '0' + r; // 写入一个数字字符 351 | } 352 | else { 353 | *p++ = honor_text[r - 1]; // 直接写入字牌相应字母 354 | } 355 | last_suit = s; 356 | } 357 | } 358 | } 359 | } 360 | 361 | // 写入过且还有空间,补充后缀 362 | if (p != str && p < end && (last_suit != 4 || tenhon)) { 363 | *p++ = suffix[last_suit - 1]; 364 | } 365 | 366 | if (p < end) { 367 | *p = '\0'; 368 | } 369 | return static_cast(p - str); 370 | } 371 | 372 | // 牌组转换为字符串 373 | intptr_t packs_to_string(const pack_t *packs, intptr_t pack_cnt, char *str, intptr_t max_size) { 374 | char *p = str, *end = str + max_size; 375 | tile_t temp[4]; 376 | for (intptr_t i = 0; i < pack_cnt && p < end; ++i) { 377 | pack_t pack = packs[i]; 378 | uint8_t o = pack_get_offer(pack); 379 | tile_t t = pack_get_tile(pack); 380 | uint8_t pt = pack_get_type(pack); 381 | switch (pt) { 382 | case PACK_TYPE_CHOW: 383 | if (p >= end) break; 384 | *p++ = '['; 385 | temp[0] = static_cast(t - 1); temp[1] = t; temp[2] = static_cast(t + 1); 386 | p += tiles_to_string(temp, 3, p, static_cast(end - p)); 387 | if (p >= end) break; 388 | *p++ = ','; 389 | if (p >= end) break; 390 | *p++ = '0' + o; 391 | if (p >= end) break; 392 | *p++ = ']'; 393 | break; 394 | case PACK_TYPE_PUNG: 395 | if (p >= end) break; 396 | *p++ = '['; 397 | temp[0] = t; temp[1] = t; temp[2] = t; 398 | p += tiles_to_string(temp, 3, p, static_cast(end - p)); 399 | if (p >= end) break; 400 | *p++ = ','; 401 | if (p >= end) break; 402 | *p++ = '0' + o; 403 | if (p >= end) break; 404 | *p++ = ']'; 405 | break; 406 | case PACK_TYPE_KONG: 407 | if (p >= end) break; 408 | *p++ = '['; 409 | temp[0] = t; temp[1] = t; temp[2] = t; temp[3] = t; 410 | p += tiles_to_string(temp, 4, p, static_cast(end - p)); 411 | if (p >= end) break; 412 | *p++ = ','; 413 | if (p >= end) break; 414 | *p++ = '0' + (is_promoted_kong(pack) ? o | 0x4 : o); 415 | if (p >= end) break; 416 | *p++ = ']'; 417 | break; 418 | case PACK_TYPE_PAIR: 419 | temp[0] = t; temp[1] = t; 420 | p += tiles_to_string(temp, 2, p, static_cast(end - p)); 421 | break; 422 | default: break; 423 | } 424 | } 425 | 426 | if (p < end) { 427 | *p = '\0'; 428 | } 429 | return static_cast(p - str); 430 | } 431 | 432 | // 手牌结构转换为字符串 433 | intptr_t hand_tiles_to_string(const hand_tiles_t *hand_tiles, char *str, intptr_t max_size) { 434 | char *p = str, *end = str + max_size; 435 | p += packs_to_string(hand_tiles->fixed_packs, hand_tiles->pack_count, str, max_size); 436 | if (p < end) p += tiles_to_string(hand_tiles->standing_tiles, hand_tiles->tile_count, p, static_cast(end - p)); 437 | return static_cast(p - str); 438 | } 439 | 440 | } 441 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/stringify.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #ifndef __MAHJONG_ALGORITHM__STRINGIFY_H__ 24 | #define __MAHJONG_ALGORITHM__STRINGIFY_H__ 25 | 26 | #include "tile.h" 27 | 28 | namespace mahjong { 29 | 30 | /** 31 | * @brief 字符串格式: 32 | * - 数牌:万=m 条=s 饼=p。后缀使用小写字母,一连串同花色的数牌可合并使用用一个后缀,如123m、678s等等。 33 | * - 字牌:东南西北=ESWN,中发白=CFP。使用大写字母。亦兼容天凤风格的后缀z,但按中国习惯顺序567z为中发白。 34 | * - 吃、碰、杠用英文[],可选用逗号+数字表示供牌来源。数字的具体规则如下: 35 | * - 吃:表示第几张牌是由上家打出,如[567m,2]表示57万吃6万(第2张)。对于不指定数字的,默认为吃第1张。 36 | * - 碰:表示由哪家打出,1为上家,2为对家,3为下家,如[999s,3]表示碰下家的9条。对于不指定数字的,默认为碰上家。 37 | * - 杠:与碰类似,但对于不指定数字的,则认为是暗杠。例如:[SSSS]表示暗杠南;[8888p,1]表示大明杠上家的8饼。当数字为5、6、7时,表示加杠。例如:[1111s,6]表示碰对家的1条后,又摸到1条加杠。 38 | * - 范例 39 | * - [EEEE][CCCC][FFFF][PPPP]NN 40 | * - 1112345678999s9s 41 | * - [WWWW,1][444s]45m678pFF6m 42 | * - [EEEE]288s349pSCFF2p 43 | * - [123p,1][345s,2][999s,3]6m6pEW1m 44 | * - 356m18s1579pWNFF9p 45 | */ 46 | 47 | /** 48 | * @addtogroup stringify 49 | * @{ 50 | */ 51 | 52 | /** 53 | * @name error codes 54 | * @{ 55 | * 解析牌的错误码 56 | */ 57 | #define PARSE_NO_ERROR 0 ///< 无错误 58 | #define PARSE_ERROR_ILLEGAL_CHARACTER -1 ///< 非法字符 59 | #define PARSE_ERROR_NO_SUFFIX_AFTER_DIGIT -2 ///< 数字后面缺少后缀 60 | #define PARSE_ERROR_WRONG_TILES_COUNT_FOR_FIXED_PACK -3 ///< 副露包含错误的牌数目 61 | #define PARSE_ERROR_CANNOT_MAKE_FIXED_PACK -4 ///< 无法正确解析副露 62 | #define PARSE_ERROR_TOO_MANY_FIXED_PACKS -5 ///< 过多组副露(一副合法手牌最多4副露) 63 | #define PARSE_ERROR_TOO_MANY_TILES -6 ///< 过多牌 64 | #define PARSE_ERROR_TILE_COUNT_GREATER_THAN_4 -7 ///< 某张牌出现超过4枚 65 | 66 | /** 67 | * @} 68 | */ 69 | 70 | /** 71 | * @brief 解析牌 72 | * @param [in] str 字符串 73 | * @param [out] tiles 牌 74 | * @param [in] max_cnt 牌的最大数量 75 | * @retval > 0 实际牌的数量 76 | * @retval == 0 失败 77 | */ 78 | intptr_t parse_tiles(const char *str, tile_t *tiles, intptr_t max_cnt); 79 | 80 | /** 81 | * @brief 字符串转换为手牌结构和上牌 82 | * @param [in] str 字符串 83 | * @param [out] hand_tiles 手牌结构 84 | * @param [out] serving_tile 上的牌 85 | * @retval PARSE_NO_ERROR 无错误 86 | * @retval PARSE_ERROR_ILLEGAL_CHARACTER 非法字符 87 | * @retval PARSE_ERROR_NO_SUFFIX_AFTER_DIGIT 数字后面缺少后缀 88 | * @retval PARSE_ERROR_WRONG_TILES_COUNT_FOR_FIXED_PACK 副露包含错误的牌数目 89 | * @retval PARSE_ERROR_CANNOT_MAKE_FIXED_PACK 无法正确解析副露 90 | * @retval PARSE_ERROR_TOO_MANY_FIXED_PACKS 过多组副露(一副合法手牌最多4副露) 91 | * @retval PARSE_ERROR_TOO_MANY_TILES 过多牌 92 | * @retval PARSE_ERROR_TILE_COUNT_GREATER_THAN_4 某张牌出现超过4枚 93 | */ 94 | intptr_t string_to_tiles(const char *str, hand_tiles_t *hand_tiles, tile_t *serving_tile); 95 | 96 | /** 97 | * @brief 牌转换为字符串 98 | * @param [in] tiles 牌 99 | * @param [in] tile_cnt 牌的数量 100 | * @param [out] str 字符串 101 | * @param [in] max_size 字符串最大长度 102 | * @return intptr_t 写入的字符串数 103 | */ 104 | intptr_t tiles_to_string(const tile_t *tiles, intptr_t tile_cnt, char *str, intptr_t max_size); 105 | 106 | /** 107 | * @brief 牌组转换为字符串 108 | * @param [in] packs 牌组 109 | * @param [in] pack_cnt 牌组的数量 110 | * @param [out] str 字符串 111 | * @param [in] max_size 字符串最大长度 112 | * @return intptr_t 写入的字符串数 113 | */ 114 | intptr_t packs_to_string(const pack_t *packs, intptr_t pack_cnt, char *str, intptr_t max_size); 115 | 116 | /** 117 | * @brief 手牌结构转换为字符串 118 | * @param [in] hand_tiles 手牌结构 119 | * @param [out] str 字符串 120 | * @param [in] max_size 字符串最大长度 121 | * @return intptr_t 写入的字符串数 122 | */ 123 | intptr_t hand_tiles_to_string(const hand_tiles_t *hand_tiles, char *str, intptr_t max_size); 124 | 125 | /** 126 | * end group 127 | * @} 128 | */ 129 | 130 | } 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/tile.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | Copyright (c) 2016-2020 Jeff Wang 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ****************************************************************************/ 22 | 23 | #ifndef __MAHJONG_ALGORITHM__TILE_H__ 24 | #define __MAHJONG_ALGORITHM__TILE_H__ 25 | 26 | #include 27 | #include 28 | 29 | // force inline 30 | #ifndef FORCE_INLINE 31 | #if defined(_MSC_VER) && (_MSC_VER >= 1200) 32 | #define FORCE_INLINE __forceinline 33 | #elif defined(__GNUC__) && ((__GNUC__ << 8 | __GNUC_MINOR__) >= 0x301) 34 | #define FORCE_INLINE __inline__ __attribute__((__always_inline__)) 35 | #else 36 | #define FORCE_INLINE inline 37 | #endif 38 | #endif 39 | 40 | // unreachable 41 | #ifndef UNREACHABLE 42 | #if defined(_MSC_VER) && (_MSC_VER >= 1300) 43 | #define UNREACHABLE() __assume(0) 44 | #elif defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ << 8 | __GNUC_MINOR__) >= 0x405)) 45 | #define UNREACHABLE() __builtin_unreachable() 46 | #else 47 | #define UNREACHABLE() assert(0) 48 | #endif 49 | #endif 50 | 51 | namespace mahjong { 52 | 53 | /** 54 | * @brief 代码注释中用到的术语简介 55 | * - 顺子:数牌中,花色相同序数相连的3张牌。 56 | * - 刻子:三张相同的牌。碰出的为明刻,未碰出的为暗刻。俗称坎。杠也算刻子,明杠算明刻,暗杠算暗刻。 57 | * - 面子:顺子和刻子的统称。俗称一句话、一坎牌。 58 | * - 雀头:基本和牌形式中,单独组合的对子,也叫将、眼。 59 | * - 基本和型:4面子1雀头的和牌形式。 60 | * - 特殊和型:非4面子1雀头的和牌形式,在国标规则中,有七对、十三幺、全不靠等特殊和型。 61 | * - 门清:也叫门前清,指不吃、不碰、不明杠的状态。特殊和型必然是门清状态。暗杠虽然不破门清,但会暴露出手牌不是特殊和型的信息。 62 | * - 副露:吃牌、碰牌、杠牌的统称,即利用其他选手打出的牌完成自己手牌面子的行为,一般不包括暗杠,也叫鸣牌,俗称动牌。 63 | * 副露有时候也包括暗杠,此时将暗杠称为之暗副露,而吃、碰、明杠称为明副露。 64 | * - 立牌:整个手牌除去吃、碰、杠之后的牌。 65 | * - 手牌:包括立牌和吃、碰、杠的牌,有时仅指立牌。 66 | * - 听牌:只差所需要的一张牌即能和牌的状态。俗称下叫、落叫、叫和(糊)。 67 | * - 一上听:指差一张就能听牌的状态,也叫一向听、一入听。以此类推有二上听、三上听、N上听。 68 | * - 上听数:达到听牌状态需要牌的张数。 69 | * - 有效牌:能使上听数减少的牌,也称进张牌、上张牌。 70 | * - 改良牌:能使有效牌增加的牌。通俗来说就是能使进张面变宽的牌。 71 | * - 对子:两张相同的牌。雀头一定是对子,但对子不一定是雀头。 72 | * - 两面:数牌中,花色相同数字相邻的两张牌,如45m,与两侧的牌都构成顺子。也叫两头。 73 | * - 嵌张:数牌中,花色相同数字相隔1的两张牌,如57s,只能与中间的牌构成顺子,中间的这张牌称为嵌张。 74 | * - 边张:也是数字相邻的两张牌,但由于处在边界位置,只能与一侧的牌能构成顺子,如12只能与3构成顺子、89只能与7构成顺子,这张3或者7便称为边张。 75 | * - 搭子:指差一张牌就能构成1组面子的两张牌。其形态有刻子搭子(即对子)、两面搭子、嵌张搭子、边张搭子。 76 | * - 复合搭子:多张牌构成的搭子。常见的有:连嵌张、两面带对子、嵌张带对子、边张带对子等等形态。 77 | * - 对倒:听牌时,其他牌都已经构成面子,剩余两对,只需任意一对成刻即可和牌,此时另一对充当雀头,这种听牌形态叫对倒,也叫双碰、对碰、对杵。 78 | */ 79 | 80 | 81 | /** 82 | * @addtogroup tile 83 | * @{ 84 | */ 85 | 86 | /** 87 | * @brief 花色 88 | */ 89 | typedef uint8_t suit_t; 90 | 91 | /** 92 | * @brief 点数 93 | */ 94 | typedef uint8_t rank_t; 95 | 96 | #define TILE_SUIT_NONE 0 ///< 无效 97 | #define TILE_SUIT_CHARACTERS 1 ///< 万子(CHARACTERS) 98 | #define TILE_SUIT_BAMBOO 2 ///< 条子(BAMBOO) 99 | #define TILE_SUIT_DOTS 3 ///< 饼子(DOTS) 100 | #define TILE_SUIT_HONORS 4 ///< 字牌(HONORS) 101 | 102 | /** 103 | * @brief 牌\n 104 | * 内存结构: 105 | * - 0-3 4bit 牌的点数 106 | * - 4-7 4bit 牌的花色 107 | * 合法的牌为: 108 | * - 0x11 - 0x19 万子(CHARACTERS) 109 | * - 0x21 - 0x29 条子(BAMBOO) 110 | * - 0x31 - 0x39 饼子(DOTS) 111 | * - 0x41 - 0x47 字牌(HONORS) 112 | * - 0x51 - 0x58 花牌(FLOWER) 113 | */ 114 | typedef uint8_t tile_t; 115 | 116 | /** 117 | * @brief 生成一张牌 118 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 119 | * @param [in] suit 花色 120 | * @param [in] rank 点数 121 | * @return tile_t 牌 122 | */ 123 | static FORCE_INLINE tile_t make_tile(suit_t suit, rank_t rank) { 124 | return (((suit & 0xF) << 4) | (rank & 0xF)); 125 | } 126 | 127 | /** 128 | * @brief 获取牌的花色 129 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 130 | * @param [in] tile 牌 131 | * @return suit_t 花色 132 | */ 133 | static FORCE_INLINE suit_t tile_get_suit(tile_t tile) { 134 | return ((tile >> 4) & 0xF); 135 | } 136 | 137 | /** 138 | * @brief 判断是否为花牌 139 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 140 | * @param [in] tile 牌 141 | * @return bool 142 | */ 143 | static FORCE_INLINE bool is_flower(tile_t tile) { 144 | return ((tile >> 4) & 0xF) == 5; 145 | } 146 | 147 | /** 148 | * @brief 获取牌的点数 149 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 150 | * @param [in] tile 牌 151 | * @return rank_t 点数 152 | */ 153 | static FORCE_INLINE rank_t tile_get_rank(tile_t tile) { 154 | return (tile & 0xF); 155 | } 156 | 157 | /** 158 | * @brief 所有牌的值,不包括花牌 159 | */ 160 | enum tile_value_t { 161 | TILE_1m = 0x11, TILE_2m, TILE_3m, TILE_4m, TILE_5m, TILE_6m, TILE_7m, TILE_8m, TILE_9m, 162 | TILE_1s = 0x21, TILE_2s, TILE_3s, TILE_4s, TILE_5s, TILE_6s, TILE_7s, TILE_8s, TILE_9s, 163 | TILE_1p = 0x31, TILE_2p, TILE_3p, TILE_4p, TILE_5p, TILE_6p, TILE_7p, TILE_8p, TILE_9p, 164 | TILE_E = 0x41, TILE_S , TILE_W , TILE_N , TILE_C , TILE_F , TILE_P , 165 | TILE_TABLE_SIZE 166 | }; 167 | 168 | /** 169 | * @brief 所有合法的牌,不包括花牌 170 | */ 171 | static const tile_t all_tiles[] = { 172 | TILE_1m, TILE_2m, TILE_3m, TILE_4m, TILE_5m, TILE_6m, TILE_7m, TILE_8m, TILE_9m, 173 | TILE_1s, TILE_2s, TILE_3s, TILE_4s, TILE_5s, TILE_6s, TILE_7s, TILE_8s, TILE_9s, 174 | TILE_1p, TILE_2p, TILE_3p, TILE_4p, TILE_5p, TILE_6p, TILE_7p, TILE_8p, TILE_9p, 175 | TILE_E , TILE_S , TILE_W , TILE_N , TILE_C , TILE_F , TILE_P 176 | }; 177 | 178 | /** 179 | * @brief 牌表类型 180 | * 181 | * 说明:在判断听牌、计算上听数等算法中,主流的对于牌有两种存储方式: 182 | * - 一种是用牌表,各索引表示各种牌拥有的枚数,这种存储方式的优点是在递归计算时削减面子只需要修改表中相应下标的值,缺点是一手牌的总数不方便确定 183 | * - 另一种是直接用牌的数组,这种存储方式的优点是很容易确定一手牌的总数,缺点是在递归计算时削减面子不方便,需要进行数组删除元素操作 184 | */ 185 | typedef uint16_t tile_table_t[TILE_TABLE_SIZE]; 186 | 187 | #define PACK_TYPE_NONE 0 ///< 无效 188 | #define PACK_TYPE_CHOW 1 ///< 顺子 189 | #define PACK_TYPE_PUNG 2 ///< 刻子 190 | #define PACK_TYPE_KONG 3 ///< 杠 191 | #define PACK_TYPE_PAIR 4 ///< 雀头 192 | 193 | /** 194 | * @brief 牌组 195 | * 用于表示一组面子或者雀头 196 | * 197 | * 内存结构: 198 | * - 0-7 8bit tile 牌(对于顺子,则表示中间那张牌,比如234p,那么牌为3p) 199 | * - 8-11 4bit type 牌组类型,使用PACK_TYPE_xxx宏 200 | * - 12-13 2bit offer 供牌信息,取值范围为0123\n 201 | * - 14 1bit promoted 是否为加杠 202 | * 0表示暗手(暗顺、暗刻、暗杠),非0表示明手(明顺、明刻、明杠) 203 | * 204 | * 对于牌组是刻子和杠时,123分别来表示是上家/对家/下家供的\n 205 | * 对于牌组为顺子时,由于吃牌只能是上家供,这里用123分别来表示第几张是上家供的 206 | */ 207 | typedef uint16_t pack_t; 208 | 209 | /** 210 | * @brief 生成一个牌组 211 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 212 | * @param [in] offer 供牌信息 213 | * @param [in] type 牌组类型 214 | * @param [in] tile 牌(对于顺子,为中间那张牌) 215 | */ 216 | static FORCE_INLINE pack_t make_pack(uint8_t offer, uint8_t type, tile_t tile) { 217 | return (offer << 12 | (type << 8) | tile); 218 | } 219 | 220 | /** 221 | * @brief 牌组是否为明的 222 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 223 | * @param [in] pack 牌组 224 | * @return bool 225 | */ 226 | static FORCE_INLINE bool is_pack_melded(pack_t pack) { 227 | return !!(pack & 0x3000); 228 | } 229 | 230 | /** 231 | * @brief 牌组是否为加杠 232 | * 当牌组不是PACK_TYPE_KONG时,结果是无意义的 233 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 234 | * @param [in] pack 牌组 235 | * @return bool 236 | */ 237 | static FORCE_INLINE bool is_promoted_kong(pack_t pack) { 238 | return !!(pack & 0x4000); 239 | } 240 | 241 | /** 242 | * @brief 碰的牌组转换为加杠 243 | * 当牌组不是PACK_TYPE_PUNG时,结果是无意义的 244 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 245 | * @param [in] pack 碰的牌组 246 | * @return pack_t 加杠的牌组 247 | */ 248 | static FORCE_INLINE pack_t promote_pung_to_kong(pack_t pack) { 249 | return pack | 0x4300; 250 | } 251 | 252 | /** 253 | * @brief 牌组的供牌信息 254 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 255 | * @param [in] pack 牌组 256 | * @return uint8_t 257 | */ 258 | static FORCE_INLINE uint8_t pack_get_offer(pack_t pack) { 259 | return ((pack >> 12) & 0x3); 260 | } 261 | 262 | /** 263 | * @brief 获取牌组的类型 264 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 265 | * @param [in] pack 牌组 266 | * @return uint8_t 牌组类型 267 | */ 268 | static FORCE_INLINE uint8_t pack_get_type(pack_t pack) { 269 | return ((pack >> 8) & 0xF); 270 | } 271 | 272 | /** 273 | * @brief 获取牌的点数 274 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 275 | * @param [in] pack 牌组 276 | * @return tile_t 牌(对于顺子,为中间那张牌) 277 | */ 278 | static FORCE_INLINE tile_t pack_get_tile(pack_t pack) { 279 | return (pack & 0xFF); 280 | } 281 | 282 | /** 283 | * @brief 手牌结构 284 | * 手牌结构一定满足等式:3*副露的牌组数+立牌数=13 285 | */ 286 | struct hand_tiles_t { 287 | pack_t fixed_packs[5]; ///< 副露的牌组(面子),包括暗杠 288 | intptr_t pack_count; ///< 副露的牌组(面子)数,包括暗杠 289 | tile_t standing_tiles[13]; ///< 立牌 290 | intptr_t tile_count; ///< 立牌数 291 | }; 292 | 293 | 294 | /** 295 | * @brief 判断是否为绿一色构成牌 296 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 297 | * @param [in] tile 牌 298 | * @return bool 299 | */ 300 | static FORCE_INLINE bool is_green(tile_t tile) { 301 | // 最基本的逐个判断,23468s及发财为绿一色构成牌 302 | //return (tile == TILE_2s || tile == TILE_3s || tile == TILE_4s || tile == TILE_6s || tile == TILE_8s || tile == TILE_F); 303 | 304 | // 算法原理: 305 | // 0x48-0x11=0x37=55刚好在一个64位整型的范围内, 306 | // 用uint64_t的每一位表示一张牌的标记,事先得到一个魔数, 307 | // 然后每次测试相应位即可 308 | return !!(0x0020000000AE0000ULL & (1ULL << (tile - TILE_1m))); 309 | } 310 | 311 | /** 312 | * @brief 判断是否为推不倒构成牌 313 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 314 | * @param [in] tile 牌 315 | * @return bool 316 | */ 317 | static FORCE_INLINE bool is_reversible(tile_t tile) { 318 | // 最基本的逐个判断:245689s、1234589p及白板为推不倒构成牌 319 | //return (tile == TILE_2s || tile == TILE_4s || tile == TILE_5s || tile == TILE_6s || tile == TILE_8s || tile == TILE_9s || 320 | // tile == TILE_1p || tile == TILE_2p || tile == TILE_3p || tile == TILE_4p || tile == TILE_5p || tile == TILE_8p || tile == TILE_9p || 321 | // tile == TILE_P); 322 | 323 | // 算法原理同绿一色构成牌判断函数 324 | return !!(0x0040019F01BA0000ULL & (1ULL << (tile - TILE_1m))); 325 | } 326 | 327 | /** 328 | * @brief 判断是否为数牌幺九(老头牌) 329 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 330 | * @param [in] tile 牌 331 | * @return bool 332 | */ 333 | static FORCE_INLINE bool is_terminal(tile_t tile) { 334 | // 最基本的逐个判断 335 | //return (tile == TILE_1m || tile == TILE_9m || tile == TILE_1s || tile == TILE_9s || tile == TILE_1p || tile == TILE_9p); 336 | 337 | // 算法原理:观察数牌幺九的二进制位: 338 | // 0x11:0001 0001 339 | // 0x19:0001 1001 340 | // 0x21:0010 0001 341 | // 0x29:0010 1001 342 | // 0x31:0011 0001 343 | // 0x39:0011 1001 344 | // 所有牌的低4bit只会出现在0001到1001之间,跟0111位与,只有0001和1001的结果为1 345 | // 所有数牌的高4bit只会出现在0001到0011之间,跟1100位与,必然为0 346 | // 于是构造魔数0xC7(1100 0111)跟牌位与,结果为1的,就为数牌幺九 347 | // 缺陷:低4bit的操作会对0xB、0xD、0xF产生误判,高4bit的操作会对0x01和0x09产生误判 348 | return ((tile & 0xC7) == 1); 349 | } 350 | 351 | /** 352 | * @brief 判断是否为风牌 353 | * @param [in] tile 牌 354 | * @return bool 355 | */ 356 | static FORCE_INLINE bool is_winds(tile_t tile) { 357 | return (tile > 0x40 && tile < 0x45); 358 | } 359 | 360 | /** 361 | * @brief 判断是否为箭牌(三元牌) 362 | * @param [in] tile 牌 363 | * @return bool 364 | */ 365 | static FORCE_INLINE bool is_dragons(tile_t tile) { 366 | return (tile > 0x44 && tile < 0x48); 367 | } 368 | 369 | /** 370 | * @brief 判断是否为字牌 371 | * @param [in] tile 牌 372 | * @return bool 373 | */ 374 | static FORCE_INLINE bool is_honor(tile_t tile) { 375 | return (tile > 0x40 && tile < 0x48); 376 | } 377 | 378 | /** 379 | * @brief 判断是否为数牌 380 | * @param [in] tile 牌 381 | * @return bool 382 | */ 383 | static FORCE_INLINE bool is_numbered_suit(tile_t tile) { 384 | if (tile < 0x1A) return (tile > 0x10); 385 | if (tile < 0x2A) return (tile > 0x20); 386 | if (tile < 0x3A) return (tile > 0x30); 387 | return false; 388 | } 389 | 390 | /** 391 | * @brief 判断是否为数牌(更快) 392 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 393 | * @see is_numbered_suit 394 | * @param [in] tile 牌 395 | * @return bool 396 | */ 397 | static FORCE_INLINE bool is_numbered_suit_quick(tile_t tile) { 398 | // 算法原理:数牌为0x11-0x19,0x21-0x29,0x31-0x39,跟0xC0位与,结果为0 399 | return !(tile & 0xC0); 400 | } 401 | 402 | /** 403 | * @brief 判断是否为幺九牌(包括数牌幺九和字牌) 404 | * @param [in] tile 牌 405 | * @return bool 406 | */ 407 | static FORCE_INLINE bool is_terminal_or_honor(tile_t tile) { 408 | return is_terminal(tile) || is_honor(tile); 409 | } 410 | 411 | /** 412 | * @brief 判断两张牌花色是否相同(更快) 413 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 414 | * @param [in] tile0 牌0 415 | * @param [in] tile1 牌1 416 | * @return bool 417 | */ 418 | static FORCE_INLINE bool is_suit_equal_quick(tile_t tile0, tile_t tile1) { 419 | // 算法原理:高4bit表示花色 420 | return ((tile0 & 0xF0) == (tile1 & 0xF0)); 421 | } 422 | 423 | /** 424 | * @brief 判断两张牌点数是否相同(更快) 425 | * 函数不检查输入的合法性。如果输入不合法的值,将无法保证合法返回值的合法性 426 | * @param [in] tile0 牌0 427 | * @param [in] tile1 牌1 428 | * @return bool 429 | */ 430 | static FORCE_INLINE bool is_rank_equal_quick(tile_t tile0, tile_t tile1) { 431 | // 算法原理:低4bit表示花色。高4bit设置为C是为了过滤掉字牌 432 | return ((tile0 & 0xCF) == (tile1 & 0xCF)); 433 | } 434 | 435 | /** 436 | * end group 437 | * @} 438 | */ 439 | 440 | } 441 | 442 | #endif 443 | -------------------------------------------------------------------------------- /fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/unit_test.cpp: -------------------------------------------------------------------------------- 1 | #include "tile.h" 2 | #include "shanten.h" 3 | #include "stringify.h" 4 | #include "fan_calculator.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace mahjong; 13 | 14 | static int count_useful_tile(const tile_table_t &used_table, const useful_table_t &useful_table) { 15 | int cnt = 0; 16 | for (int i = 0; i < 34; ++i) { 17 | tile_t t = all_tiles[i]; 18 | if (useful_table[t]) { 19 | cnt += 4 - used_table[t]; 20 | } 21 | } 22 | return cnt; 23 | } 24 | 25 | void test_wait(const char *str) { 26 | hand_tiles_t hand_tiles; 27 | tile_t serving_tile; 28 | string_to_tiles(str, &hand_tiles, &serving_tile); 29 | 30 | std::cout << "----------------" << std::endl; 31 | puts(str); 32 | useful_table_t useful_table; 33 | bool is_wait = mahjong::is_waiting(hand_tiles, &useful_table); 34 | if (is_wait) { 35 | puts(" waiting:"); 36 | char buf[64]; 37 | for (tile_t t = TILE_1m; t < TILE_TABLE_SIZE; ++t) { 38 | if (useful_table[t]) { 39 | tiles_to_string(&t, 1, buf, sizeof(buf)); 40 | printf("%s ", buf); 41 | } 42 | } 43 | } 44 | else { 45 | puts("not wait!"); 46 | } 47 | puts(""); 48 | } 49 | 50 | void test_points(const char *str, win_flag_t win_flag, wind_t prevalent_wind, wind_t seat_wind) { 51 | calculate_param_t param; 52 | 53 | long ret = string_to_tiles(str, ¶m.hand_tiles, ¶m.win_tile); 54 | if (ret != PARSE_NO_ERROR) { 55 | printf("error at line %d error = %ld\n", __LINE__, ret); 56 | return; 57 | } 58 | 59 | fan_table_t fan_table/* = { 0 }*/; 60 | puts("----------------"); 61 | puts(str); 62 | 63 | param.flower_count = 0; 64 | 65 | param.win_flag = win_flag; 66 | param.prevalent_wind = prevalent_wind; 67 | param.seat_wind = seat_wind; 68 | int points = calculate_fan(¶m, &fan_table); 69 | 70 | printf("max points = %d\n\n", points); 71 | if (points < 0) { 72 | return; 73 | } 74 | 75 | for (int i = 1; i < FLOWER_TILES; ++i) { 76 | if (fan_table[i] == 0) { 77 | continue; 78 | } 79 | if (fan_table[i] == 1) { 80 | printf("%s %d\n", fan_name[i], fan_value_table[i]); 81 | } 82 | else { 83 | printf("%s %d*%hd\n", fan_name[i], fan_value_table[i], fan_table[i]); 84 | } 85 | } 86 | } 87 | 88 | void test_shanten(const char *str) { 89 | hand_tiles_t hand_tiles; 90 | tile_t serving_tile; 91 | long ret = string_to_tiles(str, &hand_tiles, &serving_tile); 92 | if (ret != 0) { 93 | printf("error at line %d error = %ld\n", __LINE__, ret); 94 | return; 95 | } 96 | 97 | char buf[20]; 98 | ret = hand_tiles_to_string(&hand_tiles, buf, sizeof(buf)); 99 | puts(buf); 100 | 101 | auto display = [](const hand_tiles_t *hand_tiles, useful_table_t &useful_table) { 102 | char buf[64]; 103 | for (tile_t t = TILE_1m; t < TILE_TABLE_SIZE; ++t) { 104 | if (useful_table[t]) { 105 | tiles_to_string(&t, 1, buf, sizeof(buf)); 106 | printf("%s ", buf); 107 | } 108 | } 109 | 110 | tile_table_t cnt_table; 111 | map_hand_tiles(hand_tiles, &cnt_table); 112 | 113 | printf("%d枚", count_useful_tile(cnt_table, useful_table)); 114 | }; 115 | 116 | puts(str); 117 | useful_table_t useful_table/* = {false}*/; 118 | int ret0; 119 | ret0 = thirteen_orphans_shanten(hand_tiles.standing_tiles, hand_tiles.tile_count, &useful_table); 120 | printf("131=== %d shanten\n", ret0); 121 | if (ret0 != std::numeric_limits::max()) display(&hand_tiles, useful_table); 122 | puts("\n"); 123 | 124 | ret0 = seven_pairs_shanten(hand_tiles.standing_tiles, hand_tiles.tile_count, &useful_table); 125 | printf("7d=== %d shanten\n", ret0); 126 | if (ret0 != std::numeric_limits::max()) display(&hand_tiles, useful_table); 127 | puts("\n"); 128 | 129 | ret0 = honors_and_knitted_tiles_shanten(hand_tiles.standing_tiles, hand_tiles.tile_count, &useful_table); 130 | printf("honors and knitted tiles %d shanten\n", ret0); 131 | if (ret0 != std::numeric_limits::max()) display(&hand_tiles, useful_table); 132 | puts("\n"); 133 | 134 | ret0 = knitted_straight_shanten(hand_tiles.standing_tiles, hand_tiles.tile_count, &useful_table); 135 | printf("knitted straight in basic form %d shanten\n", ret0); 136 | if (ret0 != std::numeric_limits::max()) display(&hand_tiles, useful_table); 137 | puts("\n"); 138 | 139 | ret0 = basic_form_shanten(hand_tiles.standing_tiles, hand_tiles.tile_count, &useful_table); 140 | printf("basic form %d shanten\n", ret0); 141 | if (ret0 != std::numeric_limits::max()) display(&hand_tiles, useful_table); 142 | puts("\n"); 143 | } 144 | 145 | int main(int argc, const char *argv[]) { 146 | #ifdef _MSC_VER 147 | system("chcp 65001"); 148 | #endif 149 | 150 | //test_shanten("19m19s22pESWCFPP"); 151 | //test_shanten("278m3378s3779pEC"); 152 | test_shanten("111m 5m12p1569sSWP"); 153 | test_shanten("[111m]5m12p1569sSWP"); 154 | //return 0; 155 | 156 | #if 1 157 | test_points("1112345678999p9p", WIN_FLAG_INIT | WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 158 | test_points("1112345678999p9p", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 159 | test_points("1112345678999p9p", WIN_FLAG_INIT, wind_t::EAST, wind_t::EAST); 160 | test_points("123456m45679p66s8p", WIN_FLAG_INIT | WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 161 | test_points("123456m45679p66s8p", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 162 | test_points("123456m45679p66s8p", WIN_FLAG_INIT, wind_t::EAST, wind_t::EAST); 163 | 164 | // BUG测试 165 | test_points("[234s][234s][234s][234s]6s6s", WIN_FLAG_4TH_TILE, wind_t::EAST, wind_t::EAST); 166 | 167 | test_points("1122233334444s2s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); // 剪枝BUG 2018.4.18 168 | 169 | // 组合龙莫名其妙的边张Bug 2018.3.29 170 | test_points("33469m258s147pWW2m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 171 | 172 | // 套算一次原则相关bug 173 | test_points("234s2233445678p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); // 喜相逢、一般高、连六 2017.11.10 174 | test_points("[123m][789p]789s1299p3p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); // 漏算老少副BUG 2017.10.23 175 | 176 | // 清龙+同色龙顺,统一改为一般高 2017.5.22 177 | test_points("112233456789mEE", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 178 | test_points("123445566789sSS", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 179 | test_points("123456778899pWW", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 180 | 181 | // 三杠少计双暗刻 2017.4.9 182 | test_points("[2222s][3333s][5555p,1]67mEE8m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 183 | // 七星七对误判为连七对 2017.3.26 184 | test_points("EESSWWNNCCFFPP", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 185 | // 小四喜的牌型中不会有幺九刻(这样是混幺九)2016.12.15 186 | test_points("[EEE][WWW][NNN]11sSS1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 187 | // 门清一的色双龙漏计边嵌钓 2016.7.28 188 | test_points("1122355778899m3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 189 | test_points("1123355778899s2s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 190 | test_points("1122335778899p5p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 191 | // 七对与基本和型多面听,多计了边嵌钓2016.7.23 192 | test_points("445566m2277779s8s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 193 | 194 | // 基本测试 195 | test_wait("19m19s199pESWNCF"); // 十三幺听白 196 | test_wait("19m19s19pESWNCFP"); // 十三幺13面听 197 | 198 | test_wait("2229999mSSWWFF"); // 七对听2m 199 | 200 | test_wait("369s147pESWNCFP"); // 全不靠听258m 201 | test_wait("58m369s17pEWNCFP"); // 全不靠听 2m 4p 南 202 | test_wait("258m369s147pECFP"); // 全不靠听 南 西 北 203 | 204 | test_wait("1112345678999s"); // 九莲宝灯 205 | test_wait("1112223456777m"); 206 | test_wait("2223334445678m"); 207 | test_wait("25558m369s46778p"); // 组合龙听龙身,1p 208 | test_wait("25558m369s14677p"); // 组合龙听第四组,58p 209 | test_wait("25568m369s14777p"); // 组合龙听第四组,47m 210 | test_wait("258m369s1445677p"); // 组合龙听两面钓将,47p 211 | test_wait("2233445566778s"); 212 | test_wait("2458m369s147p"); // 组合龙听单钓将,4m 213 | test_wait("22334455p77779s"); // 基本形听8s、七对听9s 214 | 215 | test_points("445566m445566s5p5p", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 216 | 217 | test_points("[EEEE]22233344m44s4m", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 218 | test_points("[1111p,1]23477m23457p6p", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 219 | 220 | test_points("[222p][123m]456s78pFF9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 221 | test_points("[222p][123m]456s78pFF6p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 222 | 223 | test_points("1112345678999p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 224 | test_points("1122335578899s7s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 225 | test_points("1112223335589s7s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 226 | test_points("12389m123789s55p7m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 227 | test_points("78899m123789s55p7m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 228 | 229 | test_points("24m22s223344567p3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 230 | 231 | test_points("1223334m445566p3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 232 | test_points("1122344556677s3s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 233 | 234 | test_points("1112223344455p3p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 235 | test_points("69m258s17pEWNCFP3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 236 | test_points("69m258s1pESWNCFP3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 237 | test_points("69m258s147pWNCFP3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 238 | test_points("2358m369s145677p3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 239 | test_points("12789m123789s77p3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 240 | #endif 241 | test_points("2223344555667m4m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 242 | test_points("2223344555667m4m", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 243 | // 理论最高番 244 | test_points("[EEEE][CCCC][FFFF][PPPP]NN", WIN_FLAG_SELF_DRAWN | WIN_FLAG_ABOUT_KONG | WIN_FLAG_WALL_LAST, wind_t::EAST, wind_t::EAST); 245 | test_points("[1111p][2222p][3333p]111s1m1m", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 246 | test_points("445566m5566p556s6s", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 247 | test_points("1111222233334s4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 248 | test_points("12378m123pCCPPP9m", WIN_FLAG_DISCARD | WIN_FLAG_4TH_TILE, wind_t::EAST, wind_t::EAST); 249 | //return 0; 250 | 251 | // 以下测试用例来自于规则书上 252 | puts("==== test big four winds ===="); 253 | test_points("[EEE][WWW]SSSNNCCN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 254 | test_points("[EEE][WWW]99mSSSNNN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 255 | test_points("[EEE][WWW]33sSSSNNN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 256 | 257 | puts("==== test big three dragons ===="); 258 | test_points("[CCC][PPP]11m99pFFF1m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 259 | test_points("[CCC][PPP]EEWWFFFE", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 260 | test_points("[CCC][PPP]5556sFFF4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 261 | 262 | puts("==== test all green ===="); 263 | test_points("[234s]23466888sFF6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 264 | test_points("[234s]22334666sFF4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 265 | test_points("[222s][444s]3366688s3s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 266 | test_points("223344668888sFF", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 267 | 268 | puts("==== test nine gates ===="); 269 | test_points("1112345678999m9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 270 | 271 | puts("==== test four kongs ===="); 272 | test_points("[2222s,1][5555m,2][7777p,3][EEEE]CC", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 273 | test_points("[1111m,1][2222s,2][3333p,3][1111s,1]4m4m", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 274 | test_points("[7777p,1][NNNN,2][CCCC,3][3333p,1]5p5p", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 275 | 276 | puts("==== test seven shifted pairs ===="); 277 | test_points("1122334455667m7m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 278 | test_points("2233445566778p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 279 | 280 | puts("==== test thirteen orphans ===="); 281 | test_points("19m19s19pESWNCFPN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 282 | 283 | puts("==== test all terminals ===="); 284 | test_points("[111m][111s][999m]99s1p1p9s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 285 | 286 | puts("==== test little four winds ===="); 287 | test_points("[EEE][WWW][NNN]23sSS1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 288 | test_points("[WWW][SSS][NNN]EEPPP", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 289 | 290 | puts("==== test little three dragons ===="); 291 | test_points("[CCC][FFF]11199pPP9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 292 | test_points("[CCC][FFF]23s111pPP1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 293 | test_points("[CCC][FFF]EEENNPPN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 294 | 295 | puts("==== test all honors ===="); 296 | test_points("[CCC][PPP]EEESSNNS", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 297 | 298 | puts("==== test four concealed pungs ===="); 299 | test_points("3444m222s222333p3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 300 | 301 | puts("==== test pure terminal chows ===="); 302 | test_points("1223355778899s1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 303 | 304 | puts("==== test quadruple chow ===="); 305 | test_points("[123m][123m]1122334m4m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 306 | 307 | puts("==== test four pure shifted pungs ===="); 308 | test_points("[111p][222p][333p]22s44p4p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 309 | 310 | puts("==== test four pure shifted chows ===="); 311 | test_points("[123m][234m][345m]1145m6m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 312 | test_points("[123s][345s][567s]78s55p9s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 313 | 314 | puts("==== test three kongs ===="); 315 | test_points("[2222m,1][3333m,2][4444m,3]2233s2s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 316 | 317 | puts("==== test all terminals and honors ===="); 318 | test_points("[EEE][111m][999s]99pCC9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 319 | 320 | puts("==== test seven pairs ===="); 321 | test_points("33m22s77pEENCCPPN", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 322 | test_points("33336688m22557s7s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 323 | test_points("EESSWWNNCCFFPP", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 324 | test_points("1199m1199s11999p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 325 | 326 | puts("==== test greater honors and knitted tiles ===="); 327 | test_points("17m36s25pESWNCFP9s", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 328 | 329 | puts("==== test all even pungs ===="); 330 | test_points("[222m][444s][666p]4488p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 331 | test_points("[222m][222s][222p]44m44s4m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 332 | test_points("[666m][666s][666p]88m22s8m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 333 | 334 | puts("==== test full flush ===="); 335 | test_points("[111m]2223334449m9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 336 | test_points("[123s]1112223334s4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 337 | test_points("[789p]1234567899p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 338 | 339 | puts("==== test pure triple chow ===="); 340 | test_points("[456m][456m][456m]4556p5p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 341 | 342 | puts("==== test pure shifted pungs ===="); 343 | test_points("[222s][333s][444s]2233p3p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 344 | 345 | puts("==== test upper tiles ===="); 346 | test_points("[789m][789s][789p]7899p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 347 | test_points("[777s][888m][777p]99m88s9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 348 | test_points("[789m][789s][888s]88m88p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 349 | 350 | puts("==== test middle tiles ===="); 351 | test_points("[456s][444s][555s]66s66p6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 352 | 353 | puts("==== test lower tiles ===="); 354 | test_points("[123p][123m][123s]2333s1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 355 | 356 | puts("==== test pure straight ===="); 357 | test_points("[123m][456m][789m]2377m1m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 358 | test_points("[123s][456s][789s]6688p6p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 359 | 360 | puts("==== test three suited terminal chows ===="); 361 | test_points("[123p][789p]12378m55s9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 362 | 363 | puts("==== test pure shifted chows ===="); 364 | test_points("[123p][234p][345p]2234s2s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 365 | test_points("[123s][345s][567s]2345s2s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 366 | test_points("[123m]345567m77s88p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 367 | 368 | puts("==== test all five ===="); 369 | test_points("[456p][456s][456m]4555m6m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 370 | test_points("[345m][456m][555p]55m55s5s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 371 | 372 | puts("==== test triple pung ===="); 373 | test_points("[333p][333m]44m23333s4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 374 | test_points("[111m][111p][111s]99s99p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 375 | 376 | puts("==== test three concealed pungs ===="); 377 | test_points("999m11s99pEEECCC1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 378 | test_points("[123s]4445777888s5s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 379 | 380 | puts("==== test lesser honors and knitted tiles ===="); 381 | test_points("258m147s36pESWFPC", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 382 | test_points("147m39s258pEWCFPN", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 383 | test_points("147m258s369pSWNCF", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 384 | 385 | puts("==== test knitted straight ===="); 386 | test_points("23358m14447s369p4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 387 | test_points("147m3669s122358p6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 388 | test_points("369m258s147pEEPPE", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 389 | 390 | puts("==== test upper four ===="); 391 | test_points("[789s][678p][777p]78m99s9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 392 | test_points("[789m][789s][789p]77s78p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 393 | test_points("[666s][666p][666m]7788p7p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 394 | 395 | puts("==== test lower four ===="); 396 | test_points("[123s][123m][123p]2333m1m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 397 | test_points("[111s][222s]22m33344s4s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 398 | 399 | puts("==== test big three winds ===="); 400 | test_points("[EEE][SSS][WWW]99m99s9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 401 | test_points("[SSS][WWW][NNN]2345m5m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 402 | test_points("[SSS][WWW]NNNCCFFC", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 403 | 404 | puts("==== test mixed straight ===="); 405 | test_points("[123s][456p]789m23s88p1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 406 | test_points("[123m][456s][789p]77m45p6p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 407 | 408 | puts("==== test reversible tiles ===="); 409 | test_points("[123p][234p][345p]8899p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 410 | test_points("[234p][234p][234p]1123p4p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 411 | test_points("[345p][345p][456s]4555s6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 412 | test_points("[234p][456s][888p]88sPP8s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 413 | test_points("[111p][222p][333p]4455p4p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 414 | test_points("[222s][456s]4555888s6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 415 | test_points("[888p][999p][999s]88sPPP", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 416 | test_points("1122334455889p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 417 | 418 | puts("==== test mixed triple chow ===="); 419 | test_points("[345s][345p][345m]4456m4m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 420 | test_points("[678m][678s][678p]99s67p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 421 | 422 | puts("==== test mixed shifted pungs ===="); 423 | test_points("[222p][333s][444m]22m33p3p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 424 | test_points("[777m][888s][999p]99m78p9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 425 | 426 | puts("==== test chicken hand ===="); 427 | test_points("[123p][444s][789m]34pCC2p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 428 | 429 | puts("==== test last tile draw ===="); 430 | puts("==== test last tile claim ===="); 431 | puts("==== test out with replacement tile ===="); 432 | puts("==== test robbing the kong ===="); 433 | 434 | puts("==== test two concealed kongs ===="); 435 | test_points("[1111s][EEEE,1][SSS][789m]8m8m", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 436 | 437 | puts("==== test all pungs ===="); 438 | test_points("[888m][888p]888sEEPPP", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 439 | 440 | puts("==== test half flush ===="); 441 | test_points("[123m][234m]34578mCC9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 442 | 443 | puts("==== test mixed shifted chows ===="); 444 | test_points("[123s][234m][345p]55m45s6s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 445 | 446 | puts("==== test all types ===="); 447 | test_points("[123m][456p]789sNNFFF", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 448 | 449 | puts("==== test melded hand ===="); 450 | test_points("[2222m,1][456p][678p][888s]6m6m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 451 | 452 | puts("==== test two dragons pungs ===="); 453 | test_points("[CCC][FFF]12378m88s9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 454 | 455 | puts("==== test outside hand ===="); 456 | test_points("[123m][123m][111p]11s11m1s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 457 | test_points("[789p][789m]7788999s9s", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 458 | test_points("[123m][123m][789m]78mCC9m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 459 | test_points("[123m][123p]999m78pEE9p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 460 | 461 | puts("==== test fully concealed hand ===="); 462 | test_points("234m4468s345678p7s", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 463 | 464 | puts("==== test two melded kongs ===="); 465 | test_points("[4444p,1][4444m,1][CCC]1133m1m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 466 | 467 | puts("==== test last tile ===="); 468 | puts("==== test dragon pung ===="); 469 | puts("==== test prevalent wind ===="); 470 | puts("==== test seat wind ===="); 471 | puts("==== test concealed hand ===="); 472 | test_points("234567m66s34567p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 473 | 474 | puts("==== test all chows ===="); 475 | test_points("234m456789s3477p5p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 476 | 477 | puts("==== test tile hog ===="); 478 | test_points("[789p][789s][789m]77m33p7m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 479 | 480 | puts("==== test double pung ===="); 481 | test_points("[222m][555m][555s]4488p8p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 482 | 483 | puts("==== test two concealed pungs ===="); 484 | test_points("[9999p]1255789m999s3m", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 485 | 486 | puts("==== test concealed kong ===="); 487 | puts("==== test all simples ===="); 488 | test_points("234m456777s3444p5p", WIN_FLAG_DISCARD, wind_t::EAST, wind_t::EAST); 489 | 490 | puts("==== test pure double chow ===="); 491 | puts("==== test mixed double chow ===="); 492 | puts("==== test short straight ===="); 493 | puts("==== test two terminal chows ===="); 494 | puts("==== test pung of terminals or honors ===="); 495 | puts("==== test melded kong ===="); 496 | puts("==== test one voided suit ===="); 497 | puts("==== test no honors ===="); 498 | puts("==== test edge wait ===="); 499 | puts("==== test closed wait ===="); 500 | puts("==== test single wait ===="); 501 | puts("==== test self drawn ===="); 502 | test_points("[1111p,1][456s]2789s456p2s", WIN_FLAG_SELF_DRAWN, wind_t::EAST, wind_t::EAST); 503 | 504 | return 0; 505 | } 506 | 507 | #include "stringify.cpp" 508 | #include "shanten.cpp" 509 | #include "fan_calculator.cpp" 510 | -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-CPP/MahjongGB/MahjongGB.cpp: -------------------------------------------------------------------------------- 1 | #include "MahjongGB.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/fan_calculator.h" 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | static unordered_map str2tile; 14 | 15 | vector > MahjongFanCalculator( 16 | vector > > pack, 17 | vector hand, 18 | string winTile, 19 | int flowerCount, 20 | bool isZIMO, 21 | bool isJUEZHANG, 22 | bool isGANG, 23 | bool isLAST, 24 | int menFeng, 25 | int quanFeng) 26 | { 27 | vector> ans; 28 | mahjong::calculate_param_t calculate_param; 29 | mahjong::fan_table_t fan_table; 30 | memset(&calculate_param, 0, sizeof(mahjong::calculate_param_t)); 31 | memset(&fan_table, 0, sizeof(mahjong::fan_table_t)); 32 | calculate_param.hand_tiles.tile_count = hand.size(); 33 | for(unsigned int i = 0; i < hand.size(); i++) { 34 | if(str2tile.find(hand[i]) == str2tile.end()){ 35 | throw string("ERROE_WRONG_TILE_CODE"); 36 | } 37 | calculate_param.hand_tiles.standing_tiles[i] = str2tile[hand[i]]; 38 | } 39 | calculate_param.hand_tiles.pack_count = pack.size(); 40 | for(unsigned int i = 0; i < pack.size(); i++) { 41 | pair> &sPack = pack[i]; 42 | mahjong::pack_t &dPack = calculate_param.hand_tiles.fixed_packs[i]; 43 | if(sPack.first == "PENG") { 44 | dPack = mahjong::make_pack(sPack.second.second, PACK_TYPE_PUNG, str2tile[sPack.second.first]); 45 | } else if(sPack.first == "GANG") { 46 | dPack = mahjong::make_pack(sPack.second.second, PACK_TYPE_KONG, str2tile[sPack.second.first]); 47 | } else if(sPack.first == "CHI"){ 48 | dPack = mahjong::make_pack(sPack.second.second, PACK_TYPE_CHOW, str2tile[sPack.second.first]); 49 | } else { 50 | throw string("ERROE_WRONG_PACK_CODE"); 51 | } 52 | } 53 | calculate_param.win_tile = str2tile[winTile]; 54 | calculate_param.flower_count = flowerCount; 55 | if(isZIMO) { 56 | calculate_param.win_flag |= WIN_FLAG_SELF_DRAWN; 57 | } 58 | if(isLAST) { 59 | calculate_param.win_flag |= WIN_FLAG_WALL_LAST; 60 | } 61 | if(isJUEZHANG) { 62 | calculate_param.win_flag |= WIN_FLAG_4TH_TILE; 63 | } 64 | if(isGANG) { 65 | calculate_param.win_flag |= WIN_FLAG_ABOUT_KONG; 66 | } 67 | calculate_param.prevalent_wind = (mahjong::wind_t)quanFeng; 68 | calculate_param.seat_wind = (mahjong::wind_t)menFeng; 69 | int re = mahjong::calculate_fan(&calculate_param, &fan_table); 70 | if(re == -1) { 71 | throw string("ERROR_WRONG_TILES_COUNT"); 72 | }else if(re == -2) { 73 | throw string("ERROR_TILE_COUNT_GREATER_THAN_4"); 74 | }else if(re == -3) { 75 | throw string("ERROR_NOT_WIN"); 76 | } 77 | for(int i = 0; i < mahjong::FAN_TABLE_SIZE; i++) { 78 | if(fan_table[i] > 0) { 79 | ans.push_back(make_pair(fan_table[i]*mahjong::fan_value_table[i],mahjong::fan_name[i])); 80 | } 81 | } 82 | return ans; 83 | } 84 | 85 | void MahjongInit() 86 | { 87 | for(int i = 1; i <= 9; i++) { 88 | str2tile["W" + to_string(i)] = mahjong::make_tile(TILE_SUIT_CHARACTERS, i); 89 | str2tile["B" + to_string(i)] = mahjong::make_tile(TILE_SUIT_DOTS, i); 90 | str2tile["T" + to_string(i)] = mahjong::make_tile(TILE_SUIT_BAMBOO, i); 91 | } 92 | for(int i = 1; i <= 4; i++) { 93 | str2tile["F" + to_string((i))] = mahjong::make_tile(TILE_SUIT_HONORS, i); 94 | } 95 | for(int i = 1; i <= 3; i++) { 96 | str2tile["J" + to_string((i))] = mahjong::make_tile(TILE_SUIT_HONORS, i + 4); 97 | } 98 | } -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-CPP/MahjongGB/MahjongGB.h: -------------------------------------------------------------------------------- 1 | #ifndef MAHJONG_H 2 | #define MAHJONG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | //CPP 9 | #include "MahjongGB.cpp" 10 | #include "../../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/fan_calculator.cpp" 11 | #include "../../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/shanten.cpp" 12 | //Dangerous 13 | 14 | using namespace std; 15 | 16 | void MahjongInit(); 17 | 18 | vector > MahjongFanCalculator( 19 | vector > > pack, 20 | vector hand, 21 | string winTile, 22 | int flowerCount, 23 | bool isZIMO, 24 | bool isJUEZHANG, 25 | bool isGANG, 26 | bool isLAST, 27 | int menFeng, 28 | int quanFeng); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-CPP/README-zh.md: -------------------------------------------------------------------------------- 1 | Mahjong Fan Calculator CPP 2 | ===== 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-CPP/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-CPP/README-zh.md) 5 | 6 | 7 | ***提交Bot的时候请选择 "G++ 7.2.0 with many lib"。*** 8 | 9 | ```cpp 10 | // 参考test.cpp 11 | #include "MahjongGB/MahjongGB.h" 12 | 13 | // 使用前初始化 14 | void MahjongInit(); 15 | 16 | // 算番函数 17 | vector > MahjongFanCalculator( 18 | vector > > pack, 19 | vector hand, 20 | string winTile, 21 | int flowerCount, 22 | bool isZIMO, 23 | bool isJUEZHANG, 24 | bool isGANG, 25 | bool isLAST, 26 | int menFeng, 27 | int quanFeng); 28 | ``` 29 | 30 | - pack:玩家的明牌,每组第一个string为"PENG" "GANG" "CHI" 三者之一,第二个- string为牌代码(吃牌表示中间牌代码),第三个int碰、杠时表示上家、对家、下家供牌,吃时123表示第几张是上家供牌。 31 | - hand:玩家的暗牌,string为牌代码 32 | - winTile:和的那张牌代码 33 | - flowerCount:补花数 34 | - isZIMO:是否为自摸和牌 35 | - isJUEZHANG:是否为和绝张 36 | - isGANG:关于杠,复合点和时为枪杠和,复合自摸则为杠上开花 37 | - isLast:是否为牌墙最后一张,复合自摸为妙手回春,否则为海底捞月 38 | - menFeng:门风,0123表示东南西北 39 | - quanFeng:圈风,0123表示东南西北 40 | - 返回值:函数返回vector,每组int表示番数,求和为总番数,string是每个番形的描述 41 | -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-CPP/README.md: -------------------------------------------------------------------------------- 1 | Mahjong Fan Calculator CPP 2 | ===== 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-CPP/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-CPP/README-zh.md) 5 | 6 | The usage is shown in test.cpp 7 | 8 | ***Please choose "G++ 7.2.0 with many lib" as compiler on Botzone.*** 9 | 10 | ```cpp 11 | #include "MahjongGB/MahjongGB.h" 12 | 13 | // Init 14 | void MahjongInit(); 15 | 16 | // Fan calculator 17 | vector > MahjongFanCalculator( 18 | vector > > pack, 19 | vector hand, 20 | string winTile, 21 | int flowerCount, 22 | bool isZIMO, 23 | bool isJUEZHANG, 24 | bool isGANG, 25 | bool isLAST, 26 | int menFeng, 27 | int quanFeng 28 | ); 29 | ``` 30 | 31 | - pack: The declared tiles. Each pair in the vector consists of a string ("PENG", "GANG", or "CHI") and another pair which contains the information of the declared tiles. Click [here](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README.md) for details. 32 | - hand: The concealed tiles in hand. Each string is a tile. 33 | - winTile: This tile could make Mahjong. 34 | - flowerCount: The number of the Flower and Season tiles. 35 | - isZIMO: Whether self-draw winning or not. 36 | - isJUEZHANG: Whether the winning tile is the 4th one. 37 | - isGANG: Whether the win is because of GANG. If the player wins by other's discard, it is Robbing the Kong. Otherwise, if the player wins by self-drawn, it is Out with Replacement Tile. 38 | - isLast: Whether the winning tile is the last one in tile wall. If self-drawn, it is Last Tile Draw. Otherwise, it is Last Tile Claim. 39 | - menFeng: Seat wind. The number 0, 1, 2, 3 represent East, South, West, and North respectively. 40 | - quanFeng: Round Wind. The number 0, 1, 2, 3 represent East, South, West, and North respectively. 41 | - return: This function returns a vector of pair. Each pair is a fan, with the int as the point and the string as the description. 42 | -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-CPP/test.cpp: -------------------------------------------------------------------------------- 1 | #include "MahjongGB/MahjongGB.h" 2 | #include 3 | using namespace std; 4 | int main() { 5 | MahjongInit(); 6 | try{ 7 | auto re = MahjongFanCalculator({{"GANG",{"W1",1}}},{"W2","W2","W2","W3","W3","W3","W4","W4","W4","W5"},"W5",1,0,0,0,0,0,0); 8 | for(auto i : re){ 9 | cout << i.first << " " << i.second << endl; 10 | } 11 | }catch(const string &error){ 12 | cout << error << endl; 13 | } 14 | cout << "----------" << endl; 15 | 16 | try{ 17 | auto re = MahjongFanCalculator({{"GANG",{"W1",1}},{"CHI",{"T2",2}}},{"W3","W3","W3","W4","W4","W4","W5"},"W5",1,0,0,0,0,0,0); 18 | for(auto i : re){ 19 | cout << i.first << " " << i.second << endl; 20 | } 21 | }catch(const string &error){ 22 | cout << error << endl; 23 | } 24 | cout << "----------" << endl; 25 | 26 | //No HU 27 | try{ 28 | auto re = MahjongFanCalculator({{"GANG",{"W1",1}},{"CHI",{"T2",2}}},{"W3","W3","W3","W4","W4","W4","W5"},"W7",1,0,0,0,0,0,0); 29 | for(auto i : re){ 30 | cout << i.first << " " << i.second << endl; 31 | } 32 | }catch(const string &error){ 33 | cout << error << endl; 34 | } 35 | return 0; 36 | } -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-Python/README-zh.md: -------------------------------------------------------------------------------- 1 | Mahjong Fan Calculator Python 2 | ===== 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-Python/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-Python/README-zh.md) 5 | 6 | ```Python 7 | from MahjongGB import MahjongFanCalculator 8 | 9 | # 算番函数 10 | ((fanCount, fanName), ...) MahjongFanCalculator( 11 | pack = ((packType, tileCode, offer), ...), 12 | hand = (tileCode, ...), 13 | winTile = tileCode, 14 | flowerCount = int 0..8, 15 | isSelfDrawn = bool, 16 | is4thTile = bool, 17 | isAboutKong = bool, 18 | isWallLast = bool, 19 | seatWind = int 0..3, 20 | prevalentWind = int 0..3, 21 | [optional, default = False]verbose = bool) 22 | ``` 23 | 24 | - pack(tuple套tuple):玩家的明牌,每组packType(string)为"PENG" "GANG" "CHI" 三者之一,tileCode(string)为牌代码(吃牌表示中间牌代码),offer(int)碰、杠时表示上家、对家、下家供牌,吃时123表示第几张是上家供牌。 25 | - hand(tuple):玩家的暗牌,tileCode(string)为牌代码 26 | - winTile(string):和的那张牌代码 27 | - flowerCount(int):补花数 28 | - isSelfDrawnbool):是否为自摸和牌 29 | - is4thTile(bool):是否为和绝张 30 | - isAboutKong(bool):关于杠,复合点和时为枪杠和,复合自摸则为杠上开花 31 | - isWallLast(bool):是否为牌墙最后一张,复合自摸为妙手回春,否则为海底捞月 32 | - seatWind(int):门风,0123表示东南西北 33 | - prevalentWind(int):圈风,0123表示东南西北 34 | - verbose(bool,默认值为False):用来控制返回格式,如果设置为True,返回形式为每个番型的(点数、次数、中文名、英文名) 35 | - 返回值(tuple套tuple):每组int表示番数,求和为总番数,string是每个番形的描述 -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-Python/README.md: -------------------------------------------------------------------------------- 1 | Mahjong Fan Calculator Python 2 | ===== 3 | 4 | README:[English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-Python/README.md)|[中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/Mahjong-GB-Python/README-zh.md) 5 | 6 | ```Python 7 | from MahjongGB import MahjongFanCalculator 8 | 9 | # Fan calculator 10 | ((fanCount, fanName), ...) MahjongFanCalculator( 11 | pack = ((packType, tileCode, offer), ...), 12 | hand = (tileCode, ...), 13 | winTile = tileCode, 14 | flowerCount = int 0..8, 15 | isSelfDrawn = bool, 16 | is4thTile = bool, 17 | isAboutKong = bool, 18 | isWallLast = bool, 19 | seatWind = int 0..3, 20 | prevalentWind = int 0..3, 21 | [optional, default = False]verbose = bool) 22 | ``` 23 | 24 | - pack: The declared tiles. A tuple of tuples of three elements each: packType of "PENG"/"GANG"/"CHI", tileCode and offer. Click [here](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/fan-calculator-usage/ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/README.md) for details. 25 | - hand: The concealed tiles in hand. A tuple of tileCodes. 26 | - winTile: The winning tile to make Mahjong. 27 | - flowerCount: The number of the Flower and Season tiles. 28 | - isSelfDrawn: Whether the winning tile is self drawn. 29 | - is4thTile: Whether the winning tile is the 4th tile. 30 | - isAboutKong: Whether the winning is about Kong. If the player wins by other's discard, it is Robbing the Kong. Otherwise, if the player wins by self-drawn, it is Out with Replacement Tile. 31 | - isWallLast: Whether the winning tile is the last one in tile wall. If self-drawn, it is Last Tile Draw. Otherwise, it is Last Tile Claim. 32 | - seatWind: Seat wind. The number 0, 1, 2, 3 represent East, South, West, and North respectively. 33 | - prevalentWind: Prevalent wind. The number 0, 1, 2, 3 represent East, South, West, and North respectively. 34 | - verbose: Default to False. If set to True, return format is (fan_point, cnt, fan_name, fan_name_en) instead of (fan_count, fan_name). 35 | - return: This function returns a tuple of tuples of two elements each: the fan count and fan name of each fan. -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-Python/mahjong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/fan_calculator.h" 5 | 6 | using namespace std; 7 | 8 | static unordered_map str2tile; 9 | 10 | static void MahjongInit() { 11 | for(int i = 1; i <= 9; ++i) { 12 | str2tile["W" + to_string(i)] = mahjong::make_tile(TILE_SUIT_CHARACTERS, i); 13 | str2tile["B" + to_string(i)] = mahjong::make_tile(TILE_SUIT_DOTS, i); 14 | str2tile["T" + to_string(i)] = mahjong::make_tile(TILE_SUIT_BAMBOO, i); 15 | } 16 | for(int i = 1; i <= 4; ++i) 17 | str2tile["F" + to_string(i)] = mahjong::make_tile(TILE_SUIT_HONORS, i); 18 | for(int i = 1; i <= 3; ++i) 19 | str2tile["J" + to_string(i)] = mahjong::make_tile(TILE_SUIT_HONORS, i + 4); 20 | } 21 | 22 | static const char *doc = "Calculate Mahjong Fans.\n" 23 | "Parameters:\n" 24 | "\tpack - A tuple of fixed packs, each of which is a tuple of form (\"CHI\"/\"PENG\"/\"GANG\", tile, offer:0..3);\n" 25 | "\thand - A tuple of standing tiles;\n" 26 | "\twinTile - Winning Tile;\n" 27 | "\tflowerCount - Number of flower tiles;\n" 28 | "\tisSelfDrawn - bool indicate self drawn;\n" 29 | "\tis4thTileDrawn - bool indicate 4th tile;\n" 30 | "\tisAboutKong - bool indicate about kong;\n" 31 | "\tisWallLast - bool indicate wall last;\n" 32 | "\tseatWind - seat wind of 0..3 indicate east/south/west/north;\n" 33 | "\tprevalentWind - prevalent wind of 0..3 indicate east/south/west/north.\n" 34 | "\tverbose - (Optional) bool control return format, default to be False.\n" 35 | "Returns:\n" 36 | "\tA tuple of fans, each of which is a tuple of form (fan_count, fan_name).\n" 37 | "\tIf verbose is set to be True, form (fan_point, cnt, fan_name, fan_name_en) is used instead.\n" 38 | "Raises:" 39 | "\tTypeError - If any invalid input is encountered.\n"; 40 | 41 | static PyObject *MahjongFanCalculator(PyObject *self, PyObject *args, PyObject *kwargs) { 42 | try { 43 | // Parse arguments 44 | static char *kwlist[] = {"pack", "hand", "winTile", "flowerCount" 45 | , "isSelfDrawn", "is4thTile", "isAboutKong", "isWallLast" 46 | , "seatWind", "prevalentWind", "verbose", nullptr}; 47 | PyObject *packs = nullptr, *hands = nullptr; 48 | const char *winTile = nullptr; 49 | int flowerCount, isSelfDrawn, is4thTile, isAboutKong, isWallLast, seatWind, prevalentWind, verbose = 0; 50 | if(!PyArg_ParseTupleAndKeywords(args, kwargs, "OOsippppii|p", kwlist 51 | , &packs, &hands, &winTile, &flowerCount 52 | , &isSelfDrawn, &is4thTile, &isAboutKong, &isWallLast, &seatWind, &prevalentWind, &verbose)) 53 | return nullptr; 54 | // Prepare params 55 | mahjong::calculate_param_t calculate_param = {}; 56 | // Parse pack tuple 57 | if(!PyTuple_Check(packs)) throw "Param `pack` must be a tuple!"; 58 | int packSize = PyTuple_Size(packs); 59 | calculate_param.hand_tiles.pack_count = packSize; 60 | for(int i = 0; i < packSize; ++i) { 61 | // Parse pack info 62 | PyObject *pack = PyTuple_GET_ITEM(packs, i); 63 | if(!PyTuple_Check(pack)) throw "Param `pack` must be a tuple of tuples!"; 64 | const char *type = nullptr, *tile = nullptr; 65 | int offer, packCode; 66 | if(!PyArg_ParseTuple(pack, "ssi", &type, &tile, &offer)) return nullptr; 67 | if(str2tile.find(tile) == str2tile.end()) throw "ERROE_WRONG_TILE_CODE"; 68 | if(offer < 0 || offer >= 4) throw "ERROE_WRONG_OFFER_CODE"; 69 | if(!strcmp(type, "PENG")) packCode = PACK_TYPE_PUNG; 70 | else if(!strcmp(type, "GANG")) packCode = PACK_TYPE_KONG; 71 | else if(!strcmp(type, "CHI")) packCode = PACK_TYPE_CHOW; 72 | else throw "ERROE_WRONG_PACK_CODE"; 73 | calculate_param.hand_tiles.fixed_packs[i] = mahjong::make_pack(offer, packCode, str2tile[tile]); 74 | } 75 | // Parse hand tuple 76 | if(!PyTuple_Check(hands)) throw "Param `hand` must be a tuple!"; 77 | int handSize = PyTuple_Size(hands); 78 | calculate_param.hand_tiles.tile_count = handSize; 79 | for(int i = 0; i < handSize; ++i) { 80 | // Parse hand tile 81 | PyObject *hand = PyTuple_GET_ITEM(hands, i); 82 | if(!PyUnicode_Check(hand)) throw "Param `hand` must be a tuple of strs!"; 83 | const char *tile = PyUnicode_AsUTF8(hand); 84 | if(str2tile.find(tile) == str2tile.end()) throw "ERROE_WRONG_TILE_CODE"; 85 | calculate_param.hand_tiles.standing_tiles[i] = str2tile[tile]; 86 | } 87 | // Other params 88 | if(str2tile.find(winTile) == str2tile.end()) throw "ERROE_WRONG_TILE_CODE"; 89 | calculate_param.win_tile = str2tile[winTile]; 90 | calculate_param.flower_count = flowerCount; 91 | calculate_param.win_flag = 92 | isSelfDrawn * WIN_FLAG_SELF_DRAWN + 93 | is4thTile * WIN_FLAG_4TH_TILE + 94 | isAboutKong * WIN_FLAG_ABOUT_KONG + 95 | isWallLast * WIN_FLAG_WALL_LAST; 96 | calculate_param.seat_wind = (mahjong::wind_t)seatWind; 97 | calculate_param.prevalent_wind = (mahjong::wind_t)prevalentWind; 98 | // Prepare results 99 | mahjong::fan_table_t fan_table = {}; 100 | int re = mahjong::calculate_fan(&calculate_param, &fan_table); 101 | switch(re) { 102 | case -1: throw "ERROR_WRONG_TILES_COUNT"; 103 | case -2: throw "ERROR_TILE_COUNT_GREATER_THAN_4"; 104 | case -3: throw "ERROR_NOT_WIN"; 105 | } 106 | int l = 0; 107 | for(int i = 0; i < mahjong::FAN_TABLE_SIZE; i++) 108 | if(fan_table[i]) ++l; 109 | PyObject *ans = PyTuple_New(l); 110 | l = 0; 111 | for(int i = 0; i < mahjong::FAN_TABLE_SIZE; i++) 112 | if(fan_table[i]) { 113 | PyObject *item; 114 | if(!verbose) item = Py_BuildValue("is", mahjong::fan_value_table[i] * fan_table[i], mahjong::fan_name[i]); 115 | else item = Py_BuildValue("iiss", mahjong::fan_value_table[i], fan_table[i], mahjong::fan_name[i], mahjong::fan_name_en[i]); 116 | PyTuple_SetItem(ans, l++, item); 117 | } 118 | return ans; 119 | } catch (const char *msg) { 120 | PyErr_SetString(PyExc_TypeError, msg); 121 | return nullptr; 122 | } 123 | } 124 | 125 | static PyMethodDef methods[] = { 126 | {"MahjongFanCalculator", (PyCFunction)(void(*)(void))MahjongFanCalculator, METH_VARARGS | METH_KEYWORDS, doc}, 127 | {NULL, NULL, 0, NULL}, 128 | }; 129 | 130 | static PyModuleDef module = { 131 | PyModuleDef_HEAD_INIT, 132 | "MahjongGB", 133 | "Library to calculate fans in Chinese Standard Mahjong.", 134 | -1, 135 | methods, 136 | }; 137 | PyMODINIT_FUNC 138 | PyInit_MahjongGB(void) { 139 | MahjongInit(); 140 | return PyModule_Create(&module); 141 | } -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-Python/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, Extension 3 | 4 | module = Extension('MahjongGB', sources=[ 5 | 'mahjong.cpp', 6 | '../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/fan_calculator.cpp', 7 | '../ChineseOfficialMahjongHelper/Classes/mahjong-algorithm/shanten.cpp' 8 | ], language='c++', extra_compile_args = ["-std=c++11"]) 9 | 10 | setup(name='MahjongGB', ext_modules = [module]) -------------------------------------------------------------------------------- /fan-calculator-usage/Mahjong-GB-Python/test.py: -------------------------------------------------------------------------------- 1 | from MahjongGB import MahjongFanCalculator 2 | 3 | # Non-positional arguments 4 | print(MahjongFanCalculator((),("W1","W1","W1","W2","W2","W2","W3","W3","W3","W4","W4","W4","W5"),"W5",1,True,False,False,True,0,0)) 5 | 6 | # Support keyword arguments 7 | print(MahjongFanCalculator( 8 | pack = (("GANG","W1",2),) 9 | , hand = ("W2","W2","W2","W3","W3","W3","W4","W4","W4","W5") 10 | , winTile = "W5" 11 | , flowerCount = 2 12 | , isSelfDrawn = False 13 | , is4thTile = False 14 | , isAboutKong = False 15 | , isWallLast = False 16 | , seatWind = 0 17 | , prevalentWind = 0 18 | )) 19 | 20 | # Support mixed arguments with unarranged keyword arguments, as well as verbose mode 21 | print(MahjongFanCalculator( 22 | (("GANG","W1",2),) 23 | , ("W2","W2","W2","W3","W3","W3","W4","W4","W4","W5") 24 | , "W5" 25 | , prevalentWind = 0 26 | , seatWind = 0 27 | , is4thTile = False 28 | , isAboutKong = False 29 | , isSelfDrawn = False 30 | , isWallLast = False 31 | , flowerCount = 2 32 | , verbose = True 33 | )) 34 | 35 | # Wrong tile count 36 | try: 37 | ans = MahjongFanCalculator((),("W1","W1","W1","W2","W2","W2","W3","W3","W3","W4","W4","W4"),"W5",1,True,False,False,True,0,0) 38 | except Exception as err: 39 | print(err) 40 | else: 41 | print(ans) 42 | 43 | # Not win 44 | try: 45 | ans = MahjongFanCalculator((("CHI","W1",0),),("W2","W2","W2","W3","W3","W3","W4","W4","W4","W5"),"W7",1,False,False,False,False,0,0) 46 | except Exception as err: 47 | print(err) 48 | else: 49 | print(ans) -------------------------------------------------------------------------------- /fan-calculator-usage/readme.md: -------------------------------------------------------------------------------- 1 | # English 2 | ChineseOfficialMahjongHelper provides function to calculate the combination of tiles in hand. 3 | It is forked from https://github.com/summerinsects/ChineseOfficialMahjongHelper.git 4 | 5 | If you write AI in C++, please refer Mahjong-GB-CPP. 6 | 7 | If you write AI in Python, please refer Mahjong-GB-Python. 8 | 9 | Note: We have already provided these libraries (fan calculators) in Botzone runtime, and you may just `from MahjongGB import MahjongFanCalculator` or `#include "MahjongGB/MahjongGB.h"` directly in your code to submit to Botzone. 10 | 11 | ***For C++ fan calculator to work, please choose "G++ 7.2.0 with many lib" as compiler on Botzone.*** 12 | 13 | OR 14 | 15 | If you'd rather use interfaces provided by https://github.com/summerinsects/ChineseOfficialMahjongHelper/tree/master/Classes/mahjong-algorithm , you may include cpp files as shown below (Please choose the default compiler on Botzone if this is the case): 16 | ``` 17 | #include "MahjongGB/fan_calculator.cpp" 18 | #include "MahjongGB/shanten.cpp" 19 | ``` 20 | 21 | # 中文 22 | ChineseOfficialMahjongHelper文件夹为国标麻将算番器。此算番器接口调用开源项目: 23 | https://github.com/summerinsects/ChineseOfficialMahjongHelper.git 24 | 25 | C++请参阅Mahjong-GB-CPP 26 | 27 | Python请参阅Mahjong-GB-Python 28 | 29 | 注:Botzone内置算番库,直接from MahjongGB import MahjongFanCalculator或者#include "MahjongGB/MahjongGB.h"就可以使用算番器。 30 | 31 | ***如使用 C++ 的算番库,请在创建 Bot 时选择 "G++ 7.2.0 with many lib" 作为编译器。*** 32 | 33 | 或者 34 | 35 | 如果你希望直接使用 https://github.com/summerinsects/ChineseOfficialMahjongHelper/tree/master/Classes/mahjong-algorithm 的接口,可以通过如下方式引入对应的文件(此时在 Botzone 上请使用默认编译器): 36 | ``` 37 | #include "MahjongGB/fan_calculator.cpp" 38 | #include "MahjongGB/shanten.cpp" 39 | ``` 40 | -------------------------------------------------------------------------------- /judge/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "jsoncpp/json.h" 11 | #include 12 | #include 13 | #include "MahjongGB/fan_calculator.cpp" 14 | #include "MahjongGB/shanten.cpp" 15 | 16 | using namespace std; 17 | 18 | Json::Value inputValue, outputValue; 19 | 20 | struct PlayerData { 21 | struct Pack { 22 | string type; 23 | //"PENG" "GANG" "CHI" 24 | string tile; 25 | int offer; 26 | //PENG GANG 提供者 27 | //CHI 第几张牌是上家的 28 | Pack(string type, string tile, int offer) : 29 | type(type), tile(tile), offer(offer) 30 | { 31 | } 32 | }; 33 | vector pack; 34 | vector tile; 35 | vector flower; 36 | vector pTileWall; 37 | }; 38 | PlayerData playerData[4]; 39 | string lastTile; 40 | string lastOp; 41 | //"DRAW" 42 | //"FLOWER" 43 | //"PLAY" 44 | //"PENG" 45 | //"GANG" 46 | //"CHI" 47 | string tileCHI; 48 | bool lastBUGANG = false; 49 | //上一回合是否为补杠 50 | bool currBUGANG = false; 51 | //当前回合是否为补杠 52 | //与抢杠和 杠上开花有关 53 | bool lastGANG = false; 54 | bool currGANG = false; 55 | bool lastANGANG = false; 56 | bool currANGANG = false; 57 | 58 | int roundStage = -2, lastRoundStage; 59 | //-2:通知位置 60 | //-1:发牌 61 | //0-3:玩家摸牌 62 | //4-7:玩家打出牌后,通知所有玩家 63 | //8-12:玩家杠牌,通知所有玩家 64 | int quan; 65 | vector tileWall; 66 | 67 | unordered_map str2tile; 68 | 69 | unordered_map shownTile; 70 | //记录已经明示的牌,用于和绝张 71 | 72 | void playerError(int player, const string code) 73 | { 74 | outputValue["display"]["action"] = code; 75 | outputValue["display"]["player"] = player; 76 | outputValue["command"] = "finish"; 77 | for(int i = 0; i < 4; i++) { 78 | if(i == player) { 79 | outputValue["display"]["score"][i] = -30; 80 | outputValue["content"][to_string(i)] = -30; 81 | } else { 82 | outputValue["display"]["score"][i] = 10; 83 | outputValue["content"][to_string(i)] = 10; 84 | } 85 | } 86 | cout << outputValue; 87 | exit(0); 88 | } 89 | 90 | //若finish=true,表示bot选择和牌,进入finish阶段 91 | int checkHu(int player, bool finish) { 92 | mahjong::calculate_param_t calculate_param; 93 | mahjong::fan_table_t fan_table; 94 | memset(&calculate_param, 0, sizeof(mahjong::calculate_param_t)); 95 | memset(&fan_table, 0, sizeof(mahjong::fan_table_t)); 96 | calculate_param.hand_tiles.tile_count = (int) playerData[player].tile.size(); 97 | for(unsigned int i = 0; i < playerData[player].tile.size(); i++) { 98 | calculate_param.hand_tiles.standing_tiles[i] = str2tile[playerData[player].tile[i]]; 99 | } 100 | calculate_param.hand_tiles.pack_count = (int) playerData[player].pack.size(); 101 | for(unsigned int i = 0; i < playerData[player].pack.size(); i++) { 102 | PlayerData::Pack &sPack = playerData[player].pack[i]; 103 | mahjong::pack_t &dPack = calculate_param.hand_tiles.fixed_packs[i]; 104 | if(sPack.type == "PENG") { 105 | dPack = mahjong::make_pack((sPack.offer - player + 4) % 4, PACK_TYPE_PUNG, str2tile[sPack.tile]); 106 | } else if(sPack.type == "GANG") { 107 | dPack = mahjong::make_pack((sPack.offer - player + 4) % 4, PACK_TYPE_KONG, str2tile[sPack.tile]); 108 | } else { 109 | dPack = mahjong::make_pack(sPack.offer + 1, PACK_TYPE_CHOW, str2tile[sPack.tile]); 110 | } 111 | } 112 | calculate_param.win_tile = str2tile[lastTile]; 113 | calculate_param.flower_count = (int) playerData[player].flower.size(); 114 | if(roundStage == player) { 115 | calculate_param.win_flag |= WIN_FLAG_SELF_DRAWN; 116 | } 117 | if(playerData[(roundStage + 1) % 4].pTileWall.size() == 0) { 118 | calculate_param.win_flag |= WIN_FLAG_WALL_LAST; 119 | } 120 | if(shownTile[lastTile] == 3) { 121 | calculate_param.win_flag |= WIN_FLAG_4TH_TILE; 122 | } 123 | if(lastBUGANG || lastANGANG || currGANG) { 124 | calculate_param.win_flag |= WIN_FLAG_ABOUT_KONG; 125 | } 126 | calculate_param.prevalent_wind = (mahjong::wind_t)quan; 127 | calculate_param.seat_wind = (mahjong::wind_t)player; 128 | int re = mahjong::calculate_fan(&calculate_param, &fan_table); 129 | //cerr << re << endl; 130 | if(finish) { 131 | outputValue["display"]["action"] = "HU"; 132 | outputValue["display"]["player"] = player; 133 | outputValue["display"]["fanCnt"] = re; 134 | for(int i = 0; i < mahjong::FAN_TABLE_SIZE; i++) { 135 | if(fan_table[i] > 0) { 136 | Json::Value cFan; 137 | cFan["name"] = mahjong::fan_name[i]; 138 | cFan["cnt"] = fan_table[i]; 139 | cFan["value"] = mahjong::fan_value_table[i]; 140 | outputValue["display"]["fan"].append(cFan); 141 | } 142 | } 143 | if(re < (8 + (int)playerData[player].flower.size())) { 144 | playerError(player, "WH"); 145 | } 146 | for(int i = 0; i < 4; i++) { 147 | if(roundStage < 4) { 148 | if(i == player) { 149 | outputValue["display"]["score"][i] = 3 * (8 + re); 150 | outputValue["content"][to_string(i)] = 3 * (8 + re); 151 | } else { 152 | outputValue["display"]["score"][i] = -(8 + re); 153 | outputValue["content"][to_string(i)] = -(8 + re); 154 | } 155 | } else { 156 | if(i == player) { 157 | outputValue["display"]["score"][i] = (3 * 8 + re); 158 | outputValue["content"][to_string(i)] = (3 * 8 + re); 159 | } else if(roundStage == i + 4) { 160 | outputValue["display"]["score"][i] = -(8 + re); 161 | outputValue["content"][to_string(i)] = -(8 + re); 162 | } else if(roundStage == i + 8 && (lastBUGANG || lastANGANG)) { // 自杠 杠上开花 163 | outputValue["display"]["score"][i] = -(8 + re); 164 | outputValue["content"][to_string(i)] = -(8 + re); 165 | } else { 166 | outputValue["display"]["score"][i] = -8; 167 | outputValue["content"][to_string(i)] = -8; 168 | } 169 | } 170 | } 171 | outputValue["command"] = "finish"; 172 | cout << outputValue; 173 | exit(0); 174 | } 175 | return re; 176 | } 177 | 178 | //检查玩家输出是否为PASS 179 | void checkInputPASS(const Json::Value &playerOutput, int player) 180 | { 181 | if(playerOutput["verdict"] != "OK") { 182 | playerError(player, playerOutput["verdict"].asString()); 183 | } 184 | if(playerOutput["response"] != "PASS") { 185 | playerError(player, "WA"); 186 | } 187 | } 188 | 189 | //检查玩家摸牌后的输出 190 | 191 | void checkInputDRAW(const Json::Value &playerOutput, int player) 192 | { 193 | if(playerOutput["verdict"] != "OK") { 194 | playerError(player, playerOutput["verdict"].asString()); 195 | } 196 | vector outputList; 197 | string outputString = playerOutput["response"].asString(); 198 | boost::split(outputList, outputString, boost::is_any_of(" ")); 199 | if(outputList.size() == 1) { 200 | if(outputList[0] == "HU") { 201 | checkHu(player, true); 202 | } 203 | } else if(outputList.size() == 2) { 204 | playerData[player].tile.push_back(lastTile); 205 | lastTile = outputList[1]; 206 | if(outputList[0] == "PLAY") { 207 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 208 | if(curr != playerData[player].tile.end()) { 209 | playerData[player].tile.erase(curr); 210 | lastOp = "PLAY"; 211 | roundStage += 4; 212 | return; 213 | } 214 | } else if(outputList[0] == "GANG") { 215 | if (playerData[player].pTileWall.empty() || playerData[(player + 1) % 4].pTileWall.empty()) { 216 | playerError(player, "WA"); 217 | } 218 | for(int i = 0; i < 4; i++) { 219 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 220 | if(curr == playerData[player].tile.end()) { 221 | playerError(player, "WA"); 222 | } 223 | playerData[player].tile.erase(curr); 224 | } 225 | playerData[player].pack.push_back(PlayerData::Pack("GANG", lastTile, player)); 226 | lastOp = "GANG"; 227 | currANGANG = true; 228 | currGANG = lastGANG = currBUGANG = lastBUGANG = false; 229 | roundStage = player + 8; 230 | return; 231 | } else if(outputList[0] == "BUGANG") { 232 | if (playerData[player].pTileWall.empty() || playerData[(player + 1) % 4].pTileWall.empty()) { 233 | playerError(player, "WA"); 234 | } 235 | for(unsigned int i = 0; i < playerData[player].pack.size(); i++) { 236 | if(playerData[player].pack[i].type == "PENG" && 237 | playerData[player].pack[i].tile == lastTile) { 238 | playerData[player].pack[i].type = "GANG"; 239 | auto it = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 240 | if(it == playerData[player].tile.end()) { 241 | playerError(player, "WA"); 242 | } 243 | playerData[player].tile.erase(it); 244 | shownTile[lastTile] = 4; 245 | lastOp = "BUGANG"; 246 | currBUGANG = true; 247 | currANGANG = lastANGANG = currGANG = lastGANG = false; 248 | roundStage = player + 8; 249 | return; 250 | } 251 | } 252 | } 253 | } 254 | playerError(player, "WA"); 255 | return; 256 | } 257 | 258 | //检查其他玩家打出牌后,玩家的输出 259 | //1检查和牌操作 260 | void checkInputPLAY1(const Json::Value &playerOutput, int player) 261 | { 262 | if(playerOutput["verdict"] != "OK") { 263 | playerError(player, playerOutput["verdict"].asString()); 264 | } 265 | string outputString = playerOutput["response"].asString(); 266 | if(outputString == "HU") { 267 | checkHu(player, true); 268 | } 269 | } 270 | //2检查碰牌、杠牌 271 | bool checkInputPLAY2(const Json::Value &playerOutput, int player) 272 | { 273 | vector outputList; 274 | string outputString = playerOutput["response"].asString(); 275 | boost::split(outputList, outputString, boost::is_any_of(" ")); 276 | if(outputList.size() == 1) { 277 | if(outputList[0] == "PASS") { 278 | return false; 279 | }else if(outputList[0] == "GANG") { 280 | for(int i = 0; i < 3; i++) { 281 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 282 | if(curr == playerData[player].tile.end()) { 283 | playerError(player, "WA"); 284 | } 285 | playerData[player].tile.erase(curr); 286 | } 287 | shownTile[lastTile] = 4; 288 | lastOp = "GANG"; 289 | currGANG = true; 290 | lastBUGANG = currBUGANG = lastANGANG = currANGANG = false; 291 | playerData[player].pack.push_back(PlayerData::Pack("GANG", lastTile, roundStage % 4)); 292 | roundStage = player + 8; 293 | return true; 294 | } 295 | playerError(player, "WA"); 296 | } else if(outputList.size() == 2) { 297 | if(outputList[0] == "PENG") { 298 | for(int i = 0; i < 2; i++) { 299 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 300 | if(curr == playerData[player].tile.end()) { 301 | playerError(player, "WA"); 302 | } 303 | playerData[player].tile.erase(curr); 304 | } 305 | shownTile[lastTile] += 3; 306 | lastOp = "PENG"; 307 | playerData[player].pack.push_back(PlayerData::Pack("PENG", lastTile, roundStage % 4)); 308 | lastTile = outputList[1]; 309 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 310 | if(curr == playerData[player].tile.end()) { 311 | playerError(player, "WA"); 312 | } 313 | playerData[player].tile.erase(curr); 314 | roundStage = 4 + player; 315 | return true; 316 | } 317 | playerError(player, "WA"); 318 | } 319 | if(outputList.size() != 3) { 320 | playerError(player, "WA"); 321 | } 322 | return false; 323 | } 324 | //3检查吃牌 325 | bool checkInputPLAY3(const Json::Value &playerOutput, int player) 326 | { 327 | vector outputList; 328 | string outputString = playerOutput["response"].asString(); 329 | boost::split(outputList, outputString, boost::is_any_of(" ")); 330 | if(outputList.size() == 3) { 331 | if(outputList[0] != "CHI" || (roundStage - player) % 4 != 3) { 332 | playerError(player, "WA"); 333 | } 334 | playerData[player].tile.push_back(lastTile); 335 | string c = outputList[1]; 336 | if(str2tile.find(c) == str2tile.end() || 337 | (c[0] != 'W' && c[0] != 'B' && c[0] != 'T') || 338 | c[0] != lastTile[0] || abs(c[1] - lastTile[1]) > 1) { 339 | playerError(player, "WA"); 340 | } 341 | c[1]--; 342 | for(int i = -1; i <= 1; i++) { 343 | shownTile[c]++; 344 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), c); 345 | if(curr == playerData[player].tile.end()) { 346 | playerError(player, "WA"); 347 | } 348 | playerData[player].tile.erase(curr); 349 | c[1]++; 350 | } 351 | lastOp = "CHI"; 352 | tileCHI = outputList[1]; 353 | playerData[player].pack.push_back(PlayerData::Pack("CHI", tileCHI, lastTile[1] - outputList[1][1] + 1)); 354 | lastTile = outputList[2]; 355 | auto curr = find(playerData[player].tile.begin(), playerData[player].tile.end(), lastTile); 356 | if(curr == playerData[player].tile.end()) { 357 | playerError(player, "WA"); 358 | } 359 | playerData[player].tile.erase(curr); 360 | roundStage = 4 + player; 361 | return true; 362 | } 363 | return false; 364 | } 365 | 366 | //检查玩家杠牌后的回应 367 | //用于抢杠和 368 | void checkInputGANG(const Json::Value &playerOutput, int player) 369 | { 370 | if(playerOutput["verdict"] != "OK") { 371 | playerError(player, playerOutput["verdict"].asString()); 372 | } 373 | if(playerOutput["response"] == "PASS") { 374 | return; 375 | } 376 | if(lastBUGANG && roundStage % 4 != player && playerOutput["response"] == "HU") { 377 | checkHu(player, true); 378 | } 379 | playerError(player, "WA"); 380 | } 381 | 382 | void roundOutput(Json::Value &outputValue) 383 | { 384 | if(roundStage == -1) { 385 | outputValue["display"]["action"] = "DEAL"; 386 | for(int i = 0; i < 4; i++) { 387 | while(playerData[i].tile.size() < 13) { 388 | auto & tw = playerData[i].pTileWall; 389 | string nextTile = *tw.rbegin(); 390 | tw.pop_back(); 391 | outputValue["display"]["hand"][i].append(nextTile); 392 | if(nextTile[0] == 'H') { 393 | playerData[i].flower.push_back(nextTile); 394 | } else { 395 | playerData[i].tile.push_back(nextTile); 396 | } 397 | } 398 | } 399 | for(int i = 0; i < 4; i++) { 400 | string outputString = "1"; 401 | for(int j = 0; j < 4; j++) { 402 | outputString += " " + to_string(playerData[j].flower.size()); 403 | } 404 | for(int j = 0; j < 13; j++) { 405 | outputString += " " + playerData[i].tile[j]; 406 | } 407 | for(int j = 0; j < 4; j++) { 408 | for(string k : playerData[j].flower) 409 | outputString += " " + k; 410 | } 411 | outputValue["content"][to_string(i)] = outputString; 412 | } 413 | } else if(roundStage >= 0 && roundStage < 4) { 414 | auto & tw = playerData[roundStage % 4].pTileWall; 415 | if(tw.empty()) { 416 | outputValue["display"]["action"] = "HUANG"; 417 | outputValue["command"] = "finish"; 418 | for(int i = 0; i < 4; i++) { 419 | outputValue["content"][to_string(i)] = 0; 420 | } 421 | return; 422 | } 423 | lastTile = *tw.rbegin(); 424 | tw.pop_back(); 425 | if(lastTile[0] == 'H') { 426 | lastOp = "BUHUA"; 427 | outputValue["display"]["action"] = "BUHUA"; 428 | outputValue["display"]["player"] = roundStage; 429 | outputValue["display"]["tile"] = lastTile; 430 | playerData[roundStage % 4].flower.push_back(lastTile); 431 | for(int i = 0; i < 4; i++) { 432 | outputValue["content"][to_string(i)] = "3 " + to_string(roundStage) + " BUHUA " + lastTile; 433 | } 434 | } else { 435 | lastOp = "DRAW"; 436 | outputValue["display"]["action"] = "DRAW"; 437 | outputValue["display"]["player"] = roundStage; 438 | outputValue["display"]["tile"] = lastTile; 439 | for(int i = 0; i < 4; i++) { 440 | if(roundStage % 4 == i) { 441 | outputValue["content"][to_string(i)] = "2 " + lastTile; 442 | } else { 443 | outputValue["content"][to_string(i)] = "3 " + to_string(roundStage % 4) + " DRAW"; 444 | } 445 | } 446 | outputValue["display"]["canHu"][roundStage] = checkHu(roundStage, false); 447 | } 448 | } else if(roundStage >= 4 && roundStage < 8) { 449 | auto & tw = playerData[(lastRoundStage + 1) % 4]; 450 | if (tw.pTileWall.empty() && (lastOp == "CHI" || lastOp == "PENG")) { // 当牌墙为空时,不能进行吃碰行为 451 | playerError(roundStage % 4, "WA"); 452 | } 453 | outputValue["display"]["action"] = lastOp; 454 | outputValue["display"]["player"] = roundStage % 4; 455 | outputValue["display"]["tile"] = lastTile; 456 | if(lastOp == "CHI") { 457 | outputValue["display"]["tileCHI"] = tileCHI; 458 | } 459 | for(int i = 0; i < 4; i++) { 460 | if(lastOp == "CHI") { 461 | outputValue["content"][to_string(i)] = "3 " + to_string(roundStage % 4) + " CHI " + tileCHI + " " + lastTile; 462 | } else { 463 | outputValue["content"][to_string(i)] = "3 " + to_string(roundStage % 4) + " " + lastOp + " " + lastTile; 464 | } 465 | outputValue["display"]["canHu"][i] = checkHu(i, false); 466 | } 467 | } else { 468 | auto & tw = playerData[(lastRoundStage + 1) % 4]; 469 | if (tw.pTileWall.empty() && (lastOp == "GANG" || lastOp == "BUGANG")) { // 当牌墙为空时,不能进行吃碰行为 470 | playerError(roundStage % 4, "WA"); 471 | } 472 | string cOp = "GANG"; 473 | if(lastOp != "GANG" && lastBUGANG) { 474 | cOp = "BUGANG " + lastTile; 475 | for(int i = 0; i < 4; i++) { 476 | if(roundStage % 4 != i) { 477 | outputValue["display"]["canHu"][i] = checkHu(i, false); 478 | } 479 | } 480 | } 481 | outputValue["display"]["action"] = lastOp == "GANG" ? "GANG" : "BUGANG"; 482 | outputValue["display"]["player"] = roundStage % 4; 483 | outputValue["display"]["tile"] = lastTile; 484 | for(int i = 0; i < 4; i++) { 485 | outputValue["content"][to_string(i)] = "3 " + to_string(roundStage % 4) + " " + cOp; 486 | } 487 | } 488 | } 489 | void roundInput(Json::Value &inputValue) 490 | { 491 | lastRoundStage = roundStage; 492 | if(roundStage < 0) { 493 | for(int i = 0; i < 4; i++) { 494 | checkInputPASS(inputValue[to_string(i)], i); 495 | } 496 | roundStage++; 497 | } else if(roundStage >= 0 && roundStage < 4) { 498 | for(int i = 0; i < 4; i++) { 499 | if(lastOp == "BUHUA" || roundStage != i) { 500 | checkInputPASS(inputValue[to_string(i)], i); 501 | } else { 502 | checkInputDRAW(inputValue[to_string(i)], i); 503 | } 504 | } 505 | lastBUGANG = currBUGANG; 506 | lastGANG = currGANG; 507 | lastANGANG = currANGANG; 508 | currBUGANG = currGANG = currANGANG = false; 509 | } else if(roundStage >= 4 && roundStage < 8) { 510 | for (int i = 0; i < 4; i++) { 511 | if (i == 0) { 512 | checkInputPASS(inputValue[to_string(roundStage % 4)], roundStage % 4); 513 | } 514 | else { 515 | checkInputPLAY1(inputValue[to_string((roundStage + i) % 4)], (roundStage + i) % 4); 516 | } 517 | } 518 | bool pass = true; 519 | for(int i = 0; i < 4; i++) { 520 | if(pass && roundStage != i + 4) { 521 | pass = !checkInputPLAY2(inputValue[to_string(i)], i); 522 | } 523 | } 524 | for(int i = 0; i < 4; i++) { 525 | if(pass && roundStage != i + 4) { 526 | pass = !checkInputPLAY3(inputValue[to_string(i)], i); 527 | } 528 | } 529 | if(pass) { 530 | roundStage = (roundStage + 1) % 4; 531 | shownTile[lastTile]++; 532 | } 533 | } else { 534 | for(int i = 0; i < 4; i++) { 535 | checkInputGANG(inputValue[to_string((roundStage + i) % 4)], (roundStage + i) % 4); 536 | } 537 | roundStage -= 8; 538 | } 539 | } 540 | 541 | int main() 542 | { 543 | #ifdef _BOTZONE_ONLINE 544 | // 在botzone环境中 545 | #else 546 | // 不在botzone环境中 547 | freopen("data.json", "r", stdin); 548 | #endif 549 | cin >> inputValue; 550 | for(int i = 1; i <= 9; i++) { 551 | str2tile["W" + to_string(i)] = mahjong::make_tile(TILE_SUIT_CHARACTERS, i); 552 | str2tile["B" + to_string(i)] = mahjong::make_tile(TILE_SUIT_DOTS, i); 553 | str2tile["T" + to_string(i)] = mahjong::make_tile(TILE_SUIT_BAMBOO, i); 554 | } 555 | for(int i = 1; i <= 4; i++) { 556 | str2tile["F" + to_string((i))] = mahjong::make_tile(TILE_SUIT_HONORS, i); 557 | } 558 | for(int i = 1; i <= 3; i++) { 559 | str2tile["J" + to_string((i))] = mahjong::make_tile(TILE_SUIT_HONORS, i + 4); 560 | } 561 | for(int i = 0; i < 4; i++) { 562 | outputValue["display"]["canHu"][i] = -4; 563 | } 564 | for(int i = 1; i <= 9; i++) { 565 | for(int j = 0; j < 4; j++) { 566 | tileWall.push_back("W" + to_string(i)); 567 | tileWall.push_back("B" + to_string(i)); 568 | tileWall.push_back("T" + to_string(i)); 569 | } 570 | } 571 | for(int i = 1; i <= 4; i++) { 572 | for(int j = 0; j < 4; j++) { 573 | tileWall.push_back("F" + to_string(i)); 574 | } 575 | } 576 | for(int i = 1; i <= 3; i++) { 577 | for(int j = 0; j < 4; j++) { 578 | tileWall.push_back("J" + to_string(i)); 579 | } 580 | } 581 | unsigned int randSeed = time(nullptr); 582 | outputValue["command"] = "request"; 583 | 584 | // "content": { 585 | // "0": "1 1 0 1 2 W3 T5 T1 T7 T7 J1 T2 F2 T4 W6 F2 B6 T5 H3 H1 H7 H5", 586 | // "1": "1 1 0 1 2 T9 W9 J1 B3 W7 W7 T3 B3 T9 W1 T7 W8 T5 H3 H1 H7 H5", 587 | // "2": "1 1 0 1 2 W3 T2 B5 F1 W2 W7 F3 B7 F3 W4 B5 T3 W8 H3 H1 H7 H5", 588 | // "3": "1 1 0 1 2 B4 W3 B7 T4 W5 B1 W5 B3 J2 T3 F1 F4 W9 H3 H1 H7 H5" 589 | // } 590 | 591 | string str = ""; 592 | if (inputValue["initdata"].isObject()) { 593 | if(inputValue["initdata"]["srand"].isInt()) { 594 | randSeed = inputValue["initdata"]["srand"].asInt(); 595 | } 596 | srand(randSeed); 597 | 598 | if(inputValue["initdata"]["quan"].isInt()) { 599 | quan = inputValue["initdata"]["quan"].asInt(); 600 | if (quan < 0 || quan > 3) 601 | quan = rand() % 4; 602 | } 603 | else { 604 | quan=rand()%4; 605 | } 606 | 607 | if (inputValue["initdata"]["walltiles"].isString()) 608 | { 609 | str = inputValue["initdata"]["walltiles"].asString(); 610 | if (str.length() != 0) { 611 | tileWall.clear(); 612 | boost::split(tileWall, str, boost::is_any_of(" ")); 613 | } 614 | } 615 | } 616 | else { 617 | srand(randSeed); 618 | quan=rand()%4; 619 | } 620 | 621 | outputValue["display"]["strlen"] = (int) str.length(); 622 | if (str.length() == 0) { 623 | random_shuffle(tileWall.begin(), tileWall.end()); 624 | str = ""; 625 | for (auto & x : tileWall) { 626 | str = str + x + " "; 627 | } 628 | str.pop_back(); 629 | } 630 | if(inputValue["log"].size() == 0) { 631 | outputValue["display"]["action"] = "INIT"; 632 | outputValue["display"]["srand"] = randSeed; 633 | outputValue["display"]["quan"] = quan; 634 | // outputValue["display"]["walltiles"] = str; 635 | for(int i = 0; i < 4; i++) { 636 | outputValue["content"][to_string(i)] = "0 " + to_string(i) + " " + to_string(quan); 637 | } 638 | outputValue["initdata"]["walltiles"] = str; 639 | outputValue["initdata"]["quan"] = quan; 640 | outputValue["initdata"]["srand"] = randSeed; 641 | cout << outputValue; 642 | return 0; 643 | } 644 | 645 | int partLength = tileWall.size() / 4; 646 | for (int i = 0; i < 4; ++i) { 647 | for (int j = 0; j < partLength; ++j) { 648 | playerData[i].pTileWall.push_back(tileWall[partLength * i + j]); 649 | } 650 | } 651 | 652 | roundInput(inputValue["log"][1]); 653 | for(unsigned int i = 2; i < inputValue["log"].size(); i += 2) { 654 | //carr << i / 2 << endl; 655 | Json::Value tmp = outputValue; 656 | outputValue["display"].clear(); 657 | for(int i = 0; i < 4; i++) { 658 | outputValue["display"]["canHu"][i] = -4; 659 | } 660 | roundOutput(tmp); 661 | //carr << inputValue["log"][i + 1] << endl; 662 | roundInput(inputValue["log"][i + 1]); 663 | } 664 | outputValue["display"].clear(); 665 | for(int i = 0; i < 4; i++) { 666 | outputValue["display"]["canHu"][i] = -4; 667 | } 668 | roundOutput(outputValue); 669 | for (int i = 0; i < 4; ++i) 670 | outputValue["display"]["tileCnt"][i] = (int)playerData[i].pTileWall.size(); 671 | // outputValue["display"]["tileCnt"] = (int) tileWall.size(); 672 | cout << outputValue; 673 | return 0; 674 | } 675 | -------------------------------------------------------------------------------- /mahjong-rules/A GUIDE TO MAHJONG Rules.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/A GUIDE TO MAHJONG Rules.pdf -------------------------------------------------------------------------------- /mahjong-rules/IJCAI2020麻将比赛复式赛制介绍.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/IJCAI2020麻将比赛复式赛制介绍.pdf -------------------------------------------------------------------------------- /mahjong-rules/README-zh.md: -------------------------------------------------------------------------------- 1 | # Chinese-Standard-Mahjong 2 | 3 | README: [English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules/README.md) | [中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules/README-zh.md) 4 | 5 | ## 规则文档 6 | 7 | 我们提供了4个说明文档及1个分析性文档 8 | 9 | 1. [国标麻将番种及相关术语简介.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E5%9B%BD%E6%A0%87%E9%BA%BB%E5%B0%86%E7%95%AA%E7%A7%8D%E5%8F%8A%E7%9B%B8%E5%85%B3%E6%9C%AF%E8%AF%AD%E7%AE%80%E4%BB%8B.pdf):仅供快速入门使用。对于麻将番种及规则的深入理解,请查看最下方两份官方中国麻将竞赛规则文档[3](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E4%B8%AD%E5%9B%BD%E9%BA%BB%E5%B0%86%E7%AB%9E%E8%B5%9B%E8%A7%84%E5%88%99%E2%80%94%E2%80%941998%E5%B9%B4%E5%9B%BD%E5%AE%B6%E4%BD%93%E8%82%B2%E6%80%BB%E5%B1%80%E9%A2%81%E5%B8%83.pdf)和[4](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E3%80%8A%E4%B8%AD%E5%9B%BD%E9%BA%BB%E5%B0%86%E7%AB%9E%E8%B5%9B%E8%A7%84%E5%88%99%E8%AF%95%E8%A1%8C%E3%80%8B%E9%97%AE%E7%AD%94%E2%80%94%E2%80%94%E4%BD%9C%E8%80%85%EF%BC%9A%E6%9D%9C%E7%BB%B4%E5%BF%A0.pdf)。 10 | 11 | 2. [A GUIDE TO MAHJONG Rules.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/A%20GUIDE%20TO%20MAHJONG%20Rules.pdf):文档[1](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E5%9B%BD%E6%A0%87%E9%BA%BB%E5%B0%86%E7%95%AA%E7%A7%8D%E5%8F%8A%E7%9B%B8%E5%85%B3%E6%9C%AF%E8%AF%AD%E7%AE%80%E4%BB%8B.pdf)的英文版本 12 | 13 | 3. [中国麻将竞赛规则——1998年国家体育总局颁布.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E4%B8%AD%E5%9B%BD%E9%BA%BB%E5%B0%86%E7%AB%9E%E8%B5%9B%E8%A7%84%E5%88%99%E2%80%94%E2%80%941998%E5%B9%B4%E5%9B%BD%E5%AE%B6%E4%BD%93%E8%82%B2%E6%80%BB%E5%B1%80%E9%A2%81%E5%B8%83.pdf):如其名,国家体育总局颁布的中国麻将竞赛规则官方文档。 14 | 15 | 4. [《中国麻将竞赛规则试行》问答——作者:杜维忠.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E3%80%8A%E4%B8%AD%E5%9B%BD%E9%BA%BB%E5%B0%86%E7%AB%9E%E8%B5%9B%E8%A7%84%E5%88%99%E8%AF%95%E8%A1%8C%E3%80%8B%E9%97%AE%E7%AD%94%E2%80%94%E2%80%94%E4%BD%9C%E8%80%85%EF%BC%9A%E6%9D%9C%E7%BB%B4%E5%BF%A0.pdf):对中国麻将竞赛规则试行的官方补充说明。 16 | 17 | 5. [国标麻将AI设计.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/%E5%9B%BD%E6%A0%87%E9%BA%BB%E5%B0%86AI%E8%AE%BE%E8%AE%A1.pdf):课程习题课课件,主要为解释数据集,以及建模分析 18 | 19 | 6. [IJCAI2020麻将比赛复式赛制介绍.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/IJCAI2020%E9%BA%BB%E5%B0%86%E6%AF%94%E8%B5%9B%E5%A4%8D%E5%BC%8F%E8%B5%9B%E5%88%B6%E4%BB%8B%E7%BB%8D.pdf):本次比赛所用赛制规则介绍 -------------------------------------------------------------------------------- /mahjong-rules/README.md: -------------------------------------------------------------------------------- 1 | # Mahjong Rules 2 | 3 | README: [English](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules/README.md) | [中文](https://github.com/ailab-pku/Chinese-Standard-Mahjong/tree/master/mahjong-rules/README-zh.md) 4 | 5 | ## PDF 6 | 7 | We provide a version of mahjong rules in English: [A GUIDE TO MAHJONG Rules.pdf](https://github.com/ailab-pku/Chinese-Standard-Mahjong/blob/master/mahjong-rules/A%20GUIDE%20TO%20MAHJONG%20Rules.pdf) 8 | 9 | ## Mahjong International League 10 | 11 | Click [here](http://mahjong-mil.org/rules_inter.html) to navigate to Mahjong International League for more information. -------------------------------------------------------------------------------- /mahjong-rules/《中国麻将竞赛规则试行》问答——作者:杜维忠.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/《中国麻将竞赛规则试行》问答——作者:杜维忠.pdf -------------------------------------------------------------------------------- /mahjong-rules/中国麻将竞赛规则——1998年国家体育总局颁布.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/中国麻将竞赛规则——1998年国家体育总局颁布.pdf -------------------------------------------------------------------------------- /mahjong-rules/国标麻将AI设计.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/国标麻将AI设计.pdf -------------------------------------------------------------------------------- /mahjong-rules/国标麻将番种及相关术语简介.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/Chinese-Standard-Mahjong/5e818212d833a74f0ed1f4e6fb24c5b533a48ef9/mahjong-rules/国标麻将番种及相关术语简介.pdf -------------------------------------------------------------------------------- /sample-bot-Botzone/README.md: -------------------------------------------------------------------------------- 1 | Mahjong Sample Bot 2 | ======== 3 | 4 | 国标样例AI程序/Bot 5 | 6 | - If you want to run the code in local environment, you should put sample.cpp, jsoncpp folder, and jsoncpp.cpp in the same path. 7 | 8 | 本地运行需要将sample.cpp,jsoncpp文件夹和jsoncpp.cpp放在同一个路径里。 9 | 10 | - Please click [here](https://wiki.botzone.org.cn/index.php?title=JSONCPP) to download jsoncpp. 11 | 12 | jsoncpp请到 https://wiki.botzone.org.cn/index.php?title=JSONCPP 最下方的下载链接中选择一个进行下载。 13 | 14 | - If you intend to use the fan calculator we provide, please see the fan-calculator-usage folder. 15 | 16 | 如需要用到算番器,请查看上一级的fan-calculator-usage文件夹。 -------------------------------------------------------------------------------- /sample-bot-Botzone/sample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _BOTZONE_ONLINE 8 | #include "jsoncpp/json.h" 9 | #else 10 | #include 11 | #endif 12 | 13 | #define SIMPLEIO 0 14 | //由玩家自己定义,0表示JSON交互,1表示简单交互。 15 | 16 | using namespace std; 17 | 18 | vector request, response; 19 | vector hand; 20 | 21 | int main() 22 | { 23 | int turnID; 24 | string stmp; 25 | #if SIMPLEIO 26 | cin >> turnID; 27 | turnID--; 28 | getline(cin, stmp); 29 | for(int i = 0; i < turnID; i++) { 30 | getline(cin, stmp); 31 | request.push_back(stmp); 32 | getline(cin, stmp); 33 | response.push_back(stmp); 34 | } 35 | getline(cin, stmp); 36 | request.push_back(stmp); 37 | #else 38 | Json::Value inputJSON; 39 | cin >> inputJSON; 40 | turnID = inputJSON["responses"].size(); 41 | for(int i = 0; i < turnID; i++) { 42 | request.push_back(inputJSON["requests"][i].asString()); 43 | response.push_back(inputJSON["responses"][i].asString()); 44 | } 45 | request.push_back(inputJSON["requests"][turnID].asString()); 46 | #endif 47 | 48 | if(turnID < 2) { 49 | response.push_back("PASS"); 50 | } else { 51 | int itmp, myPlayerID, quan; 52 | ostringstream sout; 53 | istringstream sin; 54 | sin.str(request[0]); 55 | sin >> itmp >> myPlayerID >> quan; 56 | sin.clear(); 57 | sin.str(request[1]); 58 | for(int j = 0; j < 5; j++) sin >> itmp; 59 | for(int j = 0; j < 13; j++) { 60 | sin >> stmp; 61 | hand.push_back(stmp); 62 | } 63 | for(int i = 2; i < turnID; i++) { 64 | sin.clear(); 65 | sin.str(request[i]); 66 | sin >> itmp; 67 | if(itmp == 2) { 68 | sin >> stmp; 69 | hand.push_back(stmp); 70 | sin.clear(); 71 | sin.str(response[i]); 72 | sin >> stmp >> stmp; 73 | hand.erase(find(hand.begin(), hand.end(), stmp)); 74 | } 75 | } 76 | sin.clear(); 77 | sin.str(request[turnID]); 78 | sin >> itmp; 79 | if(itmp == 2) { 80 | random_shuffle(hand.begin(), hand.end()); 81 | sout << "PLAY " << *hand.rbegin(); 82 | hand.pop_back(); 83 | } else { 84 | sout << "PASS"; 85 | } 86 | response.push_back(sout.str()); 87 | } 88 | 89 | #if SIMPLEIO 90 | cout << response[turnID] << endl; 91 | #else 92 | Json::Value outputJSON; 93 | outputJSON["response"] = response[turnID]; 94 | cout << outputJSON << endl; 95 | #endif 96 | return 0; 97 | } 98 | --------------------------------------------------------------------------------