游戏源码
点击放大



#### 麻将 is a "Constraint satisfaction optimisation" multiplayer strategic board 游戏
This is a project for my master's thesis. Anyone who is interested in making powerful 麻将 AI is welcome to extend my agent. For more details about the applied algorithms and the reasons why such algorithms are used, please contact me with E-Mail.
In the case that you want to develop your own 麻将 agent, this Repo can also be used as a 框架 for realtime testing (*with real human players*). Then you can save all your time for finding the best strategy for your agent. Furthermore a de library (crawling and pre-processing of 游戏 logs, calculation of shantin and winning score etc.) is now a in:
Next Update coming soon:
1. *Better feature engineering needed* Decide whether to call a meld/call Riichi or not using Random Forest (Instead of condition-rules).
2. *Ongoing*: Training waiting tiles prediction model with LSTM.
Attention: If you test your bot parallelly with more than 4 accounts under the same IP adresse, your IP adress will be banned by tenhou.net for 24 hours. (I don't know exactly about the rules of banning players, but that's all inferred from my obser.)
|Author|Jianyang Tang (Thomas)|
|---|---
|E-mail|
***
The implemented 客户端 allows one to run a 麻将 agent directly through the programm, instead of doing this in the web browser. The site for online playing of Japanese Riichi 麻将 is
***
* [MahjongAI]
* [agents] Agent
* [utils]
* [clfs]
* multiple multilayer perceptrons classifiers <sub>several MLP classifiers for predicting dangerous tiles</sub>
* ......
* *wait_calc.py* <sub>contains functions that calculate whether the hand tiles are in waiting state, return the corresponding winning score, the partition, the to be discarded tile</sub>
* *win_calc.py* <sub>contains functions that calculate the winning score for a specific partition of hand tiles</sub>
* *ai_interface.py* <sub>the class that should be inherited while de your own 麻将 agent</sub>
* *experiment_ai.py* <sub>the old agent which was used in the experiments</sub>
* *jianyang_ai.py* <sub>the up-to-date agent</sub>
* *random_ai_example.py* <sub>the example 麻将 agent class for giving a simple explanation on how to develop your own 麻将 agent</sub>
* [client] For realtime testing with human beings
* [tilespng] <sub>Pics used for GUI</sub>
* *mahjong_meld.py* <sub>class of 麻将 meld</sub>
* *mahjong_tile.py* <sub>class of 麻将 tile</sub>
* *mahjong_table.py* <sub>class of 麻将 游戏 table, for storing 游戏 state</sub>
* *mahjong_player.py* <sub>class of 麻将 player, for storing players' tiles and 游戏 status</sub>
* *tenhou_client.py* <sub>class of 客户端 for tenhou.net. This class implements the 游戏 just as the browser </sub>
* *tenhou_parser.py* <sub>class of parser for tenhou.net 服务端. It provides decoding methods for messages re from the tenhou.net 服务端</sub>
* [logger] Recording the playing history of the agent while testing
* *logger_handler.py* <sub>class of logging, for storing 游戏 run and 游戏 results</sub>
* *main.py* <sub>contains example of how to run the 麻将 agent</sub>
* *gui_main.py* <sub>contains example of how to run the 麻将 agent with 界面</sub>
***
The proposed 麻将 agent was tested on tenhou.net. The test was carried out in two , i.e. one with defence model and another one without. The raw 游戏 logs and intermediate 游戏 results can be found in my another repository: The experiments were carried out with the agent in *experiment_ai.py*.
For the with defence model, 526 games were played, and for the without defence model, 532 games were played. This is not as many as two related works, but as shown in the figure of the con behavior of agent's performance, 526 games are sufficient.
#### Con behavior of agent's performance *with* defence model
<img src="./figs/stats_rank_distribution.png" width="550">
<img src="./figs/stats_wl_rate.png" width="550">
#### Con behavior of agent's performance *without* defence model
<img src="./figs/stats_rank_distribution_wo.png" width="550">
<img src="./figs/stats_wl_rate_wo.png" width="550">
Mizukami's extended work can be seen as currently the best and the most reliable 麻将 agent in English literatures. Here a comparison between the performance of my 麻将 agent and that of Mizukami's is presented:
| | [1] | [2] | [3] | [4] |
| ---------- | ----------- | ----------- | ----------- | ----------- |
| Games played | 526 | 532 | 2634 | 1441 |
| 1st place rate | 23.95% | 22.65% | 24.10% | 25.30% |
| 2nd place rate | 26.62% | 25.92% | 28.10% | 24.80% |
| 3rd place rate | 31.75% | 25.71% | 24.80% | 25.10% |
| 4th place rate | *17.68%* | 25.71% | 23.00% | 24.80% |
| win rate | 24.68% | 26.50% | 24.50% | 25.60% |
| lose rate | 13.92% | 20.21% | 13.10% | 14.80% |
| fixed level | 2.21 Dan | 0.77 Dan | 1.14 Dan | 1.04 Dan |
* *[1]* My Mahjong agent *with* defence model
* *[2]* My Mahjong agent *without* defence model
* *[3]* Mizukami's extended work: Mizukami N., Tsuruoka Y.. *Building a computer 麻将 player based on monte carlo
simulation and opponent models.* In: 2015 IEEE Conference on Computational Intelligence
and Games (CIG), pp. 275–283. IEEE (2015)
* *[4]* Mizukami et. al.: N. Mizukami, R. Nakahari, A. Ura, M. Miwa, Y. Tsuruoka, and T. Chikayama. *Realizing
a four-player computer 麻将 program by supervised learning with isolated multi-player
aspects.* Transactions of Information Processing Society of Japan, vol. 55, no. 11, pp. 1–11,
2014, (in Japanese).
Note that while playing 麻将, falling into 4th place is definitely a taboo, since one would get le reduced. As a result, the rate of falling into 4th place of a player is critical to its o performance. My bot has a better fixed le due to the low 4th place rate.
***
1. AI instance: A class instance of the 麻将 agent. In this repository three of 麻将 agent are provided. The first one is in *agents.random_ai_example.py*, this is a demo class for showing potential de how to implement his/her own agents. The second one is in *agents.experiment_ai.py* and the experiment results given in part 4 is generated by this AI. The third one is the up-to-date AI and is in *agents.jianyang_ai.py*.
2. Opponent player class: The class of Opponent player. One can use the default class OpponentPlayer in *client.mahjong_player*. If one has extended the OpponentPlayer class due to extra needs, this should be set to your corresponding class.
3. User ID: A token in the form as shown in the example that one got after registration on tenhou.net. *ATTENTION: Please use your own user ID. If the same ID is used under different IP address too often, the account will be temperorily blocked by tenhou.net.*
4. User name: The corresponding user name you ha while registrating on tenhou.net. This is only for identifying your test logs.
5. Game type: The 游戏 type is encoded as a 8-bit integer. Followings are the description for each bit.
* 0-th: 1 play with online players, 0 play with robots
* 1-th: 1 no red bonus tiles, 0 with red bonus tiles
* 2-th: 1 no open tanyao, 0 with open tanyao
* 3-th: 1 only east+south round (regurarily 8 rounds), 0 only east round (4 rounds)
* 4-th: 1 three players (two opponents), 0 four players (three opponents)
* 6-th: 1 fast 游戏 (7s for decision making), 0 slow 游戏 (15s)
* 5-th & 7-th: 游戏 room 00-starter, 01-upper, 10-mega upper, 11-phoenix
For examples:
* "1" = "00000001": with online real human players, with red bonus tiles, with open tanyao, 4 rounds, 4 players, slow 游戏, starter 游戏 room
* "137" = "10001001": with online real human players, with red bonus tiles, with open tanyao, 8 rounds, 4 players, slow 游戏, upper 游戏 room
* "193" = "11000001": with online real human players, with red bonus tiles, with open tanyao, 4 rounds, 4 players, fast 游戏, upper 游戏 room
6. Logger: Two parameters are required for initialising the logger. The first one is the user-defined logger's ID, such that de can freely name his/her test history.
After specifying all these configurations, just throw all these parameters to connect_and_play(). Then it's time watch the show of your 麻将 agent!!!
***
Four functions must be implemented for the 麻将 bot, as shown in the "interface" class in *agents.ai_interface*. It is recommended that your agent is an inheritance of the AIInterface. For a deeper explanation and a simple example of these functions, please see documentation in *agents.random_ai_example.py*.
* *to_discard_tile*: Based on all the accessible information about the 游戏 state, this function returns a tile to discard. The return is an integer in the range 0-135. There are toally 136 tiles in the 麻将 游戏, i.e. 34 kinds of tiles and 4 copies for each kind. In different occasions we use either the 34-form (each number corresponds to one kind of tile) or the 136-form (each number corresponds to a tile). Note that here the return should be in the 136-form.
* *should_call_kan*: This function should decide whether the agent should call a kan(Quad) meld. *tile136* stands for the tile that some opponent has discarded, which can be used for the agent to form the kan meld. *from_opponent* indicates whether the agent forms the kan meld by opponent's discard (three tiles in hand and the opponent discards the fourth one) or own tiles(all four tiles in hand).
* *try_to_call_meld*: This function decides whether the agent should call a Pon(Triplet)/Chi(Sequence) meld. *tile136* stands for the tile in 136-form that some opponents has discarded. *might_call_chi* indicates whether the agent could call a Chi meld, since a Chi meld can only be called with discard of opponent in the left seat.
* *can_call_reach*: This function decides whether the agent should claim Riichi.
When the 麻将 agent class is a subclass of the *AIInterface* class, information listed as follows can be accessed inside the agent class as specified.
#### (1) Agent's own game state
| Access | Data type | Mutable | Desription |
| ---------- | ----------- | ----------- | ----------- |
| <sub>self.tiles136</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>hand tiles in 136-form</sub> |
| <sub>self.hand34</sub> | <sub>list of integers</sub> | <sub>N</sub> | <sub>hand tiles in 34-form (tile34 = tile136//4)</sub> |
| <sub>self.discard136</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>the discards of the agent in 136-from</sub> |
| <sub>self.discard34</sub> | <sub>list of integers</sub> | <sub>N</sub> | <sub>the discards of the agent in 34-form</sub> |
| <sub>self.meld136</sub> | <sub>list of Meld instances</sub> | <sub>Y</sub> | <sub>the called melds of the agent, instances of class Meld in *client.mahjong_meld.py*</sub> |
| <sub>self.total_melds34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called melds of the agent in 34-form</sub> |
| <sub>self.meld34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called pon/chow melds of the agent in 34-form</sub> |
| <sub>self.pon34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called pon melds of the agent in 34-form</sub> |
| <sub>self.chow34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called chow melds of the agent in 34-form</sub> |
| <sub>self.minkan34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called minkan melds of the agent in 34-form</sub> |
| <sub>self.ankan34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called ankan melds of the agent in 34-form</sub> |
| <sub>self.name</sub> | <sub>string</sub> | <sub>Y</sub> | <sub>name of the account</sub> |
| <sub>self.level</sub> | <sub>string</sub> | <sub>Y</sub> | <sub>level of the account</sub> |
| <sub>self.seat</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>seat ID, the agent always has 0</sub> |
| <sub>self.dealer_seat</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>the seat ID of the dealer</sub> |
| <sub>self.is_dealer</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the agent is dealer or not</sub> |
| <sub>self.reach_status</sub> | <sub>boolean</sub> | <sub>Y</sub> | <sub>indicates whether the agent has claimed Riichi</sub> |
| <sub>self.just_reach()</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the agent just claimed Riichi</sub> |
| <sub>self.tmp_rank</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>rank of the agent in the current 游戏</sub> |
| <sub>self.score</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>score of the agent in the current 游戏</sub> |
| <sub>self.is_open_hand</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the agent has already called open melds</sub> |
| <sub>self.turn_num</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>the number of the current turn</sub> |
| <sub>self.player_wind</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>player wind is one kind of yaku</sub> |
| <sub>self.round_wind</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>round wind is one kind of yaku</sub> |
| <sub>self.bonus_honors</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>all the character tiles which have yaku</sub> |
#### (2) Opponents' game state
One can access to the instance of opponent class by calling *self.game_table.get_player(i)* with i equals 1,2,3, which indicates the corresponding id of the opponent.
| Access | Data type | Mutable | Desription |
| ---------- | ----------- | ----------- | ----------- |
| <sub>.discard136</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>the discards of the obser in 136-from</sub> |
| <sub>.discard34</sub> | <sub>list of integers</sub> | <sub>N</sub> | <sub>the discards of the obser in 34-form</sub> |
| <sub>.meld136</sub> | <sub>list of *Meld* instances</sub> | <sub>Y</sub> | <sub>the called melds of the obser
| <sub>.total_melds34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called melds of the obser in 34-form</sub> |
| <sub>.meld34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called pon/chow melds of the obser in 34-form</sub> |
| <sub>.pon34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called pon melds of the obser in 34-form</sub> |
| <sub>.chow34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called chow melds of the obser in 34-form</sub> |
| <sub>.minkan34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called minkan melds of the obser in 34-form</sub> |
| <sub>.ankan34</sub> | <sub>list of list of integers</sub> | <sub>N</sub> | <sub>the called ankan melds of the obser in 34-form</sub> |
| <sub>.safe_tiles</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>tiles in 34-form which are absolutely safe for the agent, i.e. the obser cannot win with these tiles</sub> |
| <sub>.name</sub> | <sub>string</sub> | <sub>Y</sub> | <sub>name of the opponent</sub> |
| <sub>.level</sub> | <sub>string</sub> | <sub>Y</sub> | <sub>level of the opponent</sub> |
| <sub>.seat</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>seat ID of the obser</sub> |
| <sub>.dealer_seat</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>the seat ID of the dealer</sub> |
| <sub>.is_dealer</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the obser is dealer or not</sub> |
| <sub>.reach_status</sub> | <sub>boolean</sub> | <sub>Y</sub> | <sub>indicates whether the obser has claimed Riichi</sub> |
| <sub>.just_reach()</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the obser just claimed Riichi</sub> |
| <sub>.tmp_rank</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>rank of the obser in the current 游戏</sub> |
| <sub>.score</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>score of the obser in the current 游戏</sub> |
| <sub>.is_open_hand</sub> | <sub>boolean</sub> | <sub>N</sub> | <sub>whether the obser has already called open melds</sub> |
| <sub>.turn_num</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>the number of the current turn</sub> |
| <sub>.player_wind</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>player wind is one kind of yaku</sub> |
| <sub>.round_wind</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>round wind is one kind of yaku</sub> |
| <sub>.bonus_honors</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>all the character tiles which have yaku</sub> |
#### (3) Information about the game table
To access information about the 游戏 table, one can call *self.game_table*
| Access | Data type | Mutable | Desription |
| ---------- | ----------- | ----------- | ----------- |
| <sub>.bot</sub> | <sub>instance of agent class</sub> | <sub>Y</sub> | <sub>class instance of the agent</sub> |
| <sub>.get_player(i)</sub> | <sub>instance of opponent class</sub> | <sub>Y</sub> | <sub>class instance of the opponent, i=1,2,3</sub> |
| <sub>.dealer_seat</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>seat ID of the dealer</sub> |
| <sub>.bonus_indicator</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>bonus indicators in 136-form</sub> |
| <sub>.round_number</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>round number</sub> |
| <sub>.reach_sticks</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>How many Riichi sticks are on the table. The player who win next will receive all the points of these Riichi sticks</sub> |
| <sub>.honba_sticks</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>How many honba sticks are on the table. The player will ha points according to honba sticks if it wins</sub> |
| <sub>.count_ramaining_tiles</sub> | <sub>integer</sub> | <sub>Y</sub> | <sub>The number of remaining unrea tiles</sub> |
| <sub>.re</sub> | <sub>list of integers</sub> | <sub>Y</sub> | <sub>Each element in the list indicates how many copies of this specific tile has been re (discards, open melds, bonus indicators etc)</sub> |
| <sub>.round_win</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>round wind is one kind of yaku</sub> |
| <sub>.bonus_tiles</sub> | <sub>list of integers</sub> | <sub>N</sub> | <sub>List of bonus tiles. Each occurance of bonus tiles in hand tiles counts as a yaku</sub> |
| <sub>.last_discard</sub> | <sub>integer</sub> | <sub>N</sub> | <sub>The very latest discard of opponent, this tile is absolutely safe by rules</sub> |
***
* *my professor Johannes Fürnkranz (TU Darmstadt Knowledge Engineering Group)*
* *my supervisor Tobias Joppen (TU Darmstadt Knowledge Engineering Group)*
开发语言:Python
项目类型:日麻 AI 智能体
Star 数:458
这是一套日麻 AI 智能体,代码结构清晰,适合开发者学习游戏的客户端渲染、服务端逻辑、场景管理与资源加载。项目涵盖了游戏的核心玩法、界面交互、地图与角色系统等模块,对理解游戏开发的完整流程具有较高的参考价值。
从技术实现角度看,游戏项目的难点在于:游戏循环与状态管理、资源加载与渲染优化、客户端与服务端的协议同步、以及多平台适配。本项目在这些方面提供了可运行的实现,开发者可以在此基础上进行二次开发或学习研究。
1. 下载源码压缩包,解压到本地开发环境。
2. 根据项目类型准备对应的运行环境(Node.js / Go / C# / Unity / Godot 等)。
3. 按照 README 中的步骤配置依赖、数据库和配置文件。
4. 先启动服务端,再运行客户端,即可进入游戏进行测试。
注意:本项目仅供学习研究使用,请勿用于商业或非法用途。
对于想进入游戏开发领域的开发者来说,这套源码是一个很好的入门参考。通过阅读代码,可以学习到游戏的核心玩法实现、场景与资源管理、网络协议设计、以及渲染优化等关键技术点。
同时,项目中的代码组织方式和模块划分,也展示了游戏项目在工程化方面的实践经验,值得开发者深入研究和借鉴。理解一个完整游戏项目的架构,对后续开发其他类型游戏也很有帮助。
本资源来源于开源社区,仅供学习研究和技术交流使用,请勿用于任何商业或违法违规用途。使用本项目产生的一切后果由使用者自行承担。若涉及第三方权益,请联系删除。
| 资源标题 | MahjongAI - 日麻 AI 智能体 |
| 所属分类 | 游戏源码 |
| 发布作者 | admin |
| 发布时间 | 2026-10-05 21:43:05 |
| 下载权限 | 免费下载 |
| 附件数量 | 2 个下载链接 |
| 浏览次数 | 208 |
| 讨论数量 | 0 |
| 主题状态 | 正常讨论中 |