parent
0db865854e
commit
3c416f39be
|
|
@ -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/
|
||||
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
#### **5. 核心代码目录 (`src/`)**
|
||||
|
||||
#### **5. 核心代码目录 (`src/`)**
|
||||
|
||||
##### **游戏引擎 (`engine/`)**
|
||||
|
||||
- `mahjong_engine.py`:实现成都麻将规则,包括摸牌、打牌、碰、杠、胡牌等逻辑。
|
||||
|
|
@ -116,4 +114,6 @@
|
|||
|
||||
这些番数可以叠加,例如,如果一个玩家同时满足了清一色和七对,那么他的总番数就是2番(清一色)+ 2番(七对)= 4番。
|
||||
|
||||
## 成都麻将规则建模
|
||||
## 成都麻将规则建模
|
||||
|
||||
麻将游戏引擎建模代码于项目根src/engine/目录下
|
||||
|
|
@ -0,0 +1 @@
|
|||
pytest tests/
|
||||
|
|
@ -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,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: 杠牌成功")
|
||||
Loading…
Reference in New Issue