From cea5f9f5194c60bd214381771aabf78c28c8bfde Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Sat, 30 Nov 2024 13:17:42 +0800 Subject: [PATCH] first commit --- .gitignore | 0 .idea/.gitignore | 8 ++ .idea/inspectionProfiles/Project_Default.xml | 14 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 6 + .idea/mjAi.iml | 10 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + README.md | 119 ++++++++++++++++++ main.py | 20 +++ requirements.txt | 0 src/engine/__init__.py | 0 src/engine/chengdu_mahjong_engine.py | 41 ++++++ 13 files changed, 238 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/mjAi.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 src/engine/__init__.py create mode 100644 src/engine/chengdu_mahjong_engine.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..cdf1146 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8f36ae8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/mjAi.iml b/.idea/mjAi.iml new file mode 100644 index 0000000..57ac469 --- /dev/null +++ b/.idea/mjAi.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2dcf8cb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..74b3519 --- /dev/null +++ b/README.md @@ -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番。 + +## 成都麻将规则建模 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..2d5b481 --- /dev/null +++ b/main.py @@ -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, {} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/engine/__init__.py b/src/engine/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/engine/chengdu_mahjong_engine.py b/src/engine/chengdu_mahjong_engine.py new file mode 100644 index 0000000..8614fa7 --- /dev/null +++ b/src/engine/chengdu_mahjong_engine.py @@ -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("杠牌条件不满足")