first commit

main
wsy182 2024-11-30 13:17:42 +08:00
commit cea5f9f519
13 changed files with 238 additions and 0 deletions

0
.gitignore vendored Normal file
View File

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="uvloop" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (mjAi)" />
</component>
</project>

10
.idea/mjAi.iml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (mjAi)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mjAi.iml" filepath="$PROJECT_DIR$/.idea/mjAi.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

119
README.md Normal file
View File

@ -0,0 +1,119 @@
# 成都麻将Ai
## 项目目录结构
#### **1. 项目根目录**
- `README.md`:描述项目的目标、规则实现、训练流程和使用方法。
- `requirements.txt`:列出项目依赖的库,例如 `torch`、`gym`、`numpy` 等。
- `setup.py`:如果希望将项目打包为模块供他人使用,可以在此配置。
#### **2. 配置目录 (`configs/`)**
- `training_config.yaml`:包含训练超参数,如学习率、批量大小、强化学习算法的参数。
- `game_rules.yaml`:定义麻将规则,如番种加分规则、牌的花色、缺一门规则等。
#### **3. 数据目录 (`data/`)**
- `raw/`:存储原始麻将对局数据(如网络抓取或模拟对局)。
- `processed/`:存储格式化后的训练数据,例如 Numpy 数组或 Tensor 格式。
- `self_play/`:存储自我对弈产生的对局记录,用于强化学习。
#### **4. 模型目录 (`models/`)**
- `checkpoints/`:保存模型的中间状态,用于断点恢复训练或版本管理。
- `trained_model.py`:最终导出的模型文件,可直接加载和部署。
#### **5. 核心代码目录 (`src/`)**
#### **5. 核心代码目录 (`src/`)**
##### **游戏引擎 (`engine/`)**
- `mahjong_engine.py`:实现成都麻将规则,包括摸牌、打牌、碰、杠、胡牌等逻辑。
- `game_state.py`:描述麻将局面状态的建模,包括手牌、牌河、明牌等。
- `utils.py`:通用工具函数,例如洗牌、牌序转换等。
##### **AI 模块 (`ai/`)**
- `model.py`:定义 AI 的深度学习模型(例如基于卷积或 Transformer 的网络)。
- `training.py`:训练脚本,包含监督学习或强化学习的逻辑。
- `evaluation.py`:评估模型表现的代码,支持对局胜率、番数统计等。
- `self_play.py`:实现自我对弈逻辑,用当前模型生成新训练数据。
##### **强化学习环境 (`environment/`)**
- `gym_env.py`:封装麻将引擎为 OpenAI Gym 环境,用于强化学习训练。
- `reward.py`:定义奖励函数,例如胡牌加分、放炮减分等。
##### **数据处理 (`data_processing/`)**
- `preprocess.py`:从原始数据中提取特征并转化为模型输入格式。
- `augmentation.py`:数据增强方法,例如随机调整牌序、模拟不同对局风格。
- `data_loader.py`:实现批量加载数据的逻辑。
##### **单元测试 (`tests/`)**
- 测试麻将引擎、AI 模型和强化学习环境的模块,确保代码可靠性。
#### **6. 脚本目录 (`scripts/`)**
- `train.py`:启动训练流程,包括加载数据、定义模型和运行训练。
- `evaluate.py`:加载模型并运行评估对局。
- `self_play.py`:启动 AI 自我对弈,生成强化学习数据。
- `preprocess_data.py`:将原始数据预处理为训练格式。
#### **7. 日志目录 (`logs/`)**
- 记录训练过程中的指标、对局日志和评估结果。
## 成都麻将规则:
- 成都麻将使用的是108张牌包括条、筒、万三种花色每种花色从1到9各四张没有东南西北风牌和中发白字牌。
### 基本规则
- **缺一门**:玩家必须选择缺少一种花色(条、筒、万中的任意一种),即只能用两种花色来胡牌。如果手中只有单一花色,则为清一色。
- **定缺**:游戏开始时,每位玩家需要扣下一张牌作为自己缺的那门,并且不能更改。如果本身就是两门牌,则可以报“天缺”而不扣牌。
- **起牌与打牌**:庄家通过掷骰子决定起牌位置,然后按顺序抓牌。庄家先出牌,之后每家依次摸牌打牌。
- **碰、杠**:允许碰牌和杠牌,但不允许吃牌。杠牌分为明杠和暗杠,明杠是其他玩家打出的牌被你碰后又摸到相同的牌;暗杠则是你自己摸到四张相同的牌。
- **胡牌**:胡牌的基本条件是拥有一个对子加上四个顺子或刻子(三个相同牌)。自摸为三家给分,点炮则由放炮者给分。
- **血战到底**:一家胡牌后,其他未胡牌的玩家继续游戏,直到只剩下最后一位玩家或者黄庄(所有牌都被摸完)为止。
### 特殊规则
- **下雨**:指杠牌,分为明杠和暗杠,明杠只收一家的钱,而暗杠可以收三家的钱。
- **查叫**:当出现黄庄时,需要检查每个玩家是否已经听牌(即差一张牌就能胡牌的状态),如果没有听牌,则可能需要赔偿其他玩家。
### 胡牌番数
- 根据不同的胡牌方式,有不同的计分方法。例如,清一色、带根(有额外的杠)、对子胡等都有相应的加分规则。
#### 详细番数计算
1. **平胡(基本胡)**四坎牌加一对将四坎牌可以是刻子或顺子计为1番。
2. **清一色**
- 不带杠的清一色称为“素清”计为2番。
- 带杠的清一色或清一色对子胡简称“清对”计为3番称为“极品”点炮40分。
- 带两杠的清一色或清一色对子胡带杠计为4番称为“极中极”或“精品”点炮80分。
3. **带幺九**手牌中含有1或9的牌计为3番。
4. **七对**手牌由7个对子组成计为2番。
5. **全求人**所有牌都是通过碰、杠、吃别人打出的牌来完成的计为6番。
6. **龙七对**七对中有一对是三张相同的牌计为12番。
7. **清七对**全部由一种花色组成的七对计为12番。
8. **杠上开花**在杠牌之后立即自摸胡牌计为1番。
9. **抢杠胡**当其他玩家明杠时你正好可以胡那张牌计为1番。
10. **天胡**庄家起牌后直接胡牌计为12番。
11. **地胡**闲家在第一轮打牌时就胡牌计为12番。
12. **大对子**手牌由四个对子加一个刻子组成计为2番。
13. **小七对**有六对加上一个对子计为2番。
14. **杠上炮**:在杠牌之后放炮让他人胡牌,通常不加分,但有时会根据地方规则有所调整。
15. **金钩吊**手上只剩下一张牌等别人打出然后胡牌计为1番。
16. **海底捞月**最后一张牌被玩家摸到并胡牌计为1番。
17. **海底炮**最后一张牌被打出导致玩家胡牌计为1番。
这些番数可以叠加例如如果一个玩家同时满足了清一色和七对那么他的总番数就是2番清一色+ 2番七对= 4番。
## 成都麻将规则建模

20
main.py Normal file
View File

@ -0,0 +1,20 @@
import gym
from gym import spaces
import numpy as np
class ChengduMahjongEnv(gym.Env):
def __init__(self):
super(ChengduMahjongEnv, self).__init__()
self.observation_space = spaces.Box(low=0, high=4, shape=(136,), dtype=np.int32) # 每张牌的状态
self.action_space = spaces.Discrete(136) # 可选择打出的牌
def reset(self):
# 初始化麻将牌局
self.state = np.zeros(136)
return self.state
def step(self, action):
# 模拟玩家动作
reward = 0 # 根据规则计算得分
done = False
return self.state, reward, done, {}

0
requirements.txt Normal file
View File

0
src/engine/__init__.py Normal file
View File

View File

@ -0,0 +1,41 @@
class ChengduMahjongEngine:
def __init__(self):
self.state = ChengduMahjongState()
self.game_over = False
def draw_tile(self):
# 当前玩家摸牌
if self.state.remaining_tiles == 0:
self.game_over = True
return "牌堆已空"
tile = self.state.deck.pop(0)
self.state.remaining_tiles -= 1
self.state.hands[self.state.current_player][tile] += 1
return tile
def discard_tile(self, tile):
# 当前玩家打牌
if self.state.hands[self.state.current_player][tile] == 0:
raise ValueError("当前玩家没有这张牌")
self.state.hands[self.state.current_player][tile] -= 1
self.state.discards[self.state.current_player].append(tile)
def peng(self, tile):
# 碰牌逻辑
player = self.state.current_player
if self.state.hands[player][tile] < 2:
raise ValueError("碰牌条件不满足")
self.state.hands[player][tile] -= 2
self.state.melds[player].append(("peng", tile))
def gang(self, tile, mode="ming"):
# 杠牌逻辑
player = self.state.current_player
if mode == "ming" and self.state.hands[player][tile] == 3:
self.state.hands[player][tile] -= 3
self.state.melds[player].append(("ming_gang", tile))
elif mode == "an" and self.state.hands[player][tile] == 4:
self.state.hands[player][tile] -= 4
self.state.melds[player].append(("an_gang", tile))
else:
raise ValueError("杠牌条件不满足")