1
pull/1/head
wsy182 2024-11-30 14:36:32 +08:00
parent 0db865854e
commit 3c416f39be
6 changed files with 166 additions and 30 deletions

31
.gitignore vendored
View File

@ -1,2 +1,33 @@
./idea/
.idea/workspace.xml
# Python 缓存文件
__pycache__/
*.py[cod]
*$py.class
# 虚拟环境
.venv/
# IDE 文件
.idea/
.vscode/
*.swp
*.swo
# 日志和调试文件
*.log
debug.log
# 单元测试覆盖率报告
htmlcov/
.coverage
*.cover
.coverage.*
.cache
nosetests.xml
coverage.xml
*.mocha.json
test-results/

View File

@ -28,8 +28,6 @@
#### **5. 核心代码目录 (`src/`)**
#### **5. 核心代码目录 (`src/`)**
##### **游戏引擎 (`engine/`)**
- `mahjong_engine.py`:实现成都麻将规则,包括摸牌、打牌、碰、杠、胡牌等逻辑。
@ -116,4 +114,6 @@
这些番数可以叠加例如如果一个玩家同时满足了清一色和七对那么他的总番数就是2番清一色+ 2番七对= 4番。
## 成都麻将规则建模
## 成都麻将规则建模
麻将游戏引擎建模代码于项目根src/engine/目录下

View File

@ -0,0 +1 @@
pytest tests/

View File

@ -1,4 +1,4 @@
from utils import get_suit, get_tile_name # 确保 get_tile_name 方法已经定义
from .utils import get_suit,get_tile_name
from loguru import logger
@ -42,41 +42,49 @@ class ChengduMahjongState:
def can_win(self, hand):
"""
判断玩家的手牌是否满足胡牌条件
参数:
- hand: 玩家当前的手牌长度为108的列表
返回:
- bool: 是否满足胡牌条件
判断是否满足胡牌条件四组顺子或刻子+ 一对将
"""
from collections import Counter
# 判断缺一门
suits = [get_suit(tile) for tile, count in enumerate(hand) if count > 0]
if len(set(suits)) > 2:
logger.info(f"手牌花色: {set(suits)},未满足缺一门条件")
return False # 不满足缺一门
# 判断是否满足四组+一对
def is_valid_group(tiles):
# 判断是否为顺子、刻子或对子
return len(tiles) == 3 and (tiles[0] == tiles[1] == tiles[2] or
tiles[0] + 1 == tiles[1] and tiles[1] + 1 == tiles[2])
"""
判断是否为合法的顺子或刻子
"""
if len(tiles) != 3:
return False
tiles.sort() # 确保顺子检查按顺序排列
return (tiles[0] == tiles[1] == tiles[2]) or \
(tiles[0] + 1 == tiles[1] and tiles[1] + 1 == tiles[2])
counter = Counter(hand)
def try_win(remaining_tiles, depth=0):
"""
递归检查是否可以将剩余牌分为合法组合
"""
if not remaining_tiles:
return depth == 4 # 必须分成四组
for i in range(len(remaining_tiles)):
for j in range(i + 1, len(remaining_tiles)):
for k in range(j + 1, len(remaining_tiles)):
group = [remaining_tiles[i], remaining_tiles[j], remaining_tiles[k]]
if is_valid_group(group):
next_tiles = remaining_tiles[:i] + remaining_tiles[i + 1:j] + \
remaining_tiles[j + 1:k] + remaining_tiles[k + 1:]
# 确保顺子检查按顺序排列
next_tiles.sort()
if try_win(next_tiles, depth + 1):
return True
return False
counter = Counter({tile: count for tile, count in enumerate(hand) if count > 0})
pairs = [tile for tile, count in counter.items() if count >= 2]
for pair in pairs:
temp_hand = hand[:]
temp_hand[pair] -= 2
# 记录尝试的对子
logger.debug(f"尝试对子: {get_tile_name(pair)}(索引 {pair}")
if is_valid_group(temp_hand):
logger.info(f"满足胡牌条件的对子: {get_tile_name(pair)}(索引 {pair}")
temp_hand[pair] -= 2 # 移除将牌
remaining_tiles = [tile for tile, count in enumerate(temp_hand) for _ in range(count)]
remaining_tiles.sort() # 确保顺子检查按顺序排列
if try_win(remaining_tiles):
return True
logger.info("未找到满足胡牌条件的组合")
return False

0
tests/__init__.py Normal file
View File

View File

@ -0,0 +1,96 @@
def test_draw_tile():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
engine = ChengduMahjongEngine()
initial_remaining = engine.state.remaining_tiles
tile = engine.draw_tile()
# 验证牌堆数量减少
assert engine.state.remaining_tiles == initial_remaining - 1, "牌堆数量未正确减少"
# 验证牌已加入当前玩家手牌
assert engine.state.hands[engine.state.current_player][tile] > 0, "摸牌未加入玩家手牌"
print(f"test_draw_tile passed: 摸到了 {tile}")
def test_discard_tile():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
engine = ChengduMahjongEngine()
tile = engine.draw_tile() # 玩家先摸牌
engine.discard_tile(tile) # 打出摸到的牌
# 验证手牌数量减少
assert engine.state.hands[engine.state.current_player][tile] == 0, "手牌未正确移除"
# 验证牌加入了牌河
assert tile in engine.state.discards[engine.state.current_player], "牌未正确加入牌河"
print(f"test_discard_tile passed: 打出了 {tile}")
def test_set_missing_suit():
from src.engine.game_state import ChengduMahjongState
state = ChengduMahjongState()
player = 0
missing_suit = ""
state.set_missing_suit(player, missing_suit)
# 验证缺门是否正确设置
assert state.missing_suits[player] == missing_suit, "缺门设置错误"
print(f"test_set_missing_suit passed: 缺门设置为 {missing_suit}")
def test_can_win():
from src.engine.game_state import ChengduMahjongState
state = ChengduMahjongState()
hand = [0] * 108
hand[0] = 2 # 两张1条对子
hand[3] = 1 # 2条
hand[4] = 1 # 3条
hand[5] = 1 # 4条
hand[10] = 1 # 5条
hand[11] = 1 # 6条
hand[12] = 1 # 7条
hand[20] = 1 # 8条
hand[21] = 1 # 9条
hand[22] = 1 # 1筒
hand[30] = 1 # 2筒
hand[31] = 1 # 3筒
hand[32] = 1 # 4筒
result = state.can_win(hand)
assert result is True, "胡牌判断失败"
print(f"test_can_win passed: 胡牌条件正确")
def test_peng():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
engine = ChengduMahjongEngine()
tile = 5 # 模拟手牌中有3张牌
engine.state.hands[engine.state.current_player][tile] = 3
engine.peng(tile)
# 验证手牌减少
assert engine.state.hands[engine.state.current_player][tile] == 1, "碰牌后手牌数量错误"
# 验证明牌记录
assert ("peng", tile) in engine.state.melds[engine.state.current_player], "碰牌未正确记录"
print(f"test_peng passed: 碰牌成功")
def test_gang():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
engine = ChengduMahjongEngine()
tile = 10 # 模拟手牌中有4张牌
engine.state.hands[engine.state.current_player][tile] = 4
engine.gang(tile, mode="an")
# 验证手牌减少
assert engine.state.hands[engine.state.current_player][tile] == 0, "杠牌后手牌数量错误"
# 验证明牌记录
assert ("an_gang", tile) in engine.state.melds[engine.state.current_player], "杠牌未正确记录"
print(f"test_gang passed: 杠牌成功")