wsy182 2024-12-01 16:23:45 +08:00
parent efc71af70c
commit 66310aa2f5
4 changed files with 112 additions and 118 deletions

View File

@ -76,7 +76,7 @@
- **缺一门**:玩家必须选择缺少一种花色(条、筒、万中的任意一种),即只能用两种花色来胡牌。如果手中只有单一花色,则为清一色。
- **定缺**:游戏开始时,每位玩家需要扣下一张牌作为自己缺的那门,并且不能更改。如果本身就是两门牌,则可以报“天缺”而不扣牌。
- **起牌与打牌**:庄家通过掷骰子决定起牌位置,然后按顺序抓牌。庄家先出牌,之后每家依次摸牌打牌。
- **碰、杠**:允许碰牌和杠牌,但不允许吃牌。杠牌分为明杠和暗杠,明杠是其他玩家打出的牌被你碰后又摸到相同的牌;暗杠则是你自己摸到四张相同的牌。
- **碰、杠**:允许碰牌和杠牌,但不允许吃牌。杠牌分为明杠和暗杠,明杠是其他玩家打出的牌刚好与你手里有三张的牌相同;暗杠则是你自己摸到四张相同的牌。
- **胡牌**胡牌的基本条件是拥有一个对子加上四个顺子或刻子三个相同牌。自摸为三家给分点炮则由放炮者给分。n*AAA+m*ABC+DD mn可以等于0。
- **血战到底**:一家胡牌后,其他未胡牌的玩家继续游戏,直到只剩下最后一位玩家或者黄庄(所有牌都被摸完)为止。

View File

@ -2,6 +2,7 @@ from random import random
from loguru import logger
from src.engine.mahjong_tile import MahjongTile
from src.engine.calculate_fan import calculate_fan
def draw_tile(engine):
@ -204,21 +205,21 @@ def check_other_players(self, tile):
# 优先检查胡牌
if self.can_win(self.state.hands[player], self.state.melds[player], self.state.missing_suits[player]):
logger.info(f"玩家 {player} 可以胡玩家 {current_player} 的牌: {tile}")
self.handle_win(player, current_player, tile)
handle_win(player, current_player, tile)
actions_taken = True
break # 胡牌后结束
# 检查是否可以杠牌
if self.state.hands[player].tile_count[tile] >= 3:
logger.info(f"玩家 {player} 可以杠玩家 {current_player} 的牌: {tile}")
if self.handle_gang(player, tile, mode="ming"): # 执行明杠逻辑
if handle_gang(player, tile, mode="ming"): # 执行明杠逻辑
actions_taken = True
break # 杠牌后不检查其他玩家
# 检查是否可以碰牌
if self.state.hands[player].tile_count[tile] >= 2:
logger.info(f"玩家 {player} 可以碰玩家 {current_player} 的牌: {tile}")
if self.handle_peng(player, tile): # 执行碰牌逻辑
if handle_peng(player, tile): # 执行碰牌逻辑
actions_taken = True
break # 碰牌后不检查其他玩家
@ -241,22 +242,10 @@ def handle_peng(self, player, tile):
logger.info(f"玩家 {player} 碰了牌: {tile}。当前明牌: {self.state.melds[player]}")
# 设置出牌顺序为碰牌的玩家
self.state.current_player = player
# 提供玩家手牌信息,等待玩家选择打出的牌
chosen_tile = self.get_player_discard_choice(player)
# 验证玩家选择的牌是否合法
if chosen_tile not in self.state.hands[player].tiles:
logger.error(f"玩家 {player} 选择了不合法的牌: {chosen_tile}")
raise ValueError("打出的牌必须存在于玩家的手牌中")
# 玩家选择打出这张牌
self.discard_tile(player, chosen_tile)
return True
def get_player_discard_choice(self, player):
"""
模拟获取玩家打牌的选择
@ -273,28 +262,53 @@ def get_player_discard_choice(self, player):
def handle_gang(self, player, tile, mode):
"""
处理玩家杠牌逻辑并更新状态
处理玩家杠牌逻辑计算分数并更新状态
:param player: 杠牌玩家索引
:param tile: 杠牌的那张牌
:param mode: 杠牌的类型 ("ming" "an")
:return: True 如果操作成功否则 False
"""
if mode == "ming" and self.state.hands[player].tile_count[tile] < 3:
logger.error(f"玩家 {player} 无法明杠: {tile}")
return False
base_score = self.state.bottom_score # 底分
# 减少牌数量并更新明牌
if mode == "ming":
if mode == "ming": # 明杠逻辑
if self.state.hands[player].tile_count[tile] < 3:
logger.error(f"玩家 {player} 无法明杠: {tile}")
return None
# 减少牌数量并更新明牌
self.state.hands[player].tile_count[tile] -= 3
self.state.melds[player].append(("ming_gang", tile))
self.state.melds[player].append(("", tile))
logger.info(f"玩家 {player} 明杠了牌: {tile}")
elif mode == "an":
# 明杠分数计算
gang_score = base_score * 2 # 明杠的分数倍率
for i in range(4):
if i != player:
self.state.scores[i] -= gang_score # 每个其他玩家扣分
self.state.scores[player] += gang_score * 3 # 杠牌玩家得总分
logger.info(f"玩家 {player} 明杠,总分变化: +{gang_score * 3},其他玩家每人扣分: -{gang_score}")
return True
elif mode == "an": # 暗杠逻辑
if self.state.hands[player].tile_count[tile] < 4:
logger.error(f"玩家 {player} 无法暗杠: {tile}")
return False
# 减少牌数量并更新暗牌
self.state.hands[player].tile_count[tile] -= 4
self.state.melds[player].append(("an_gang", tile))
logger.info(f"玩家 {player} 暗杠了牌: {tile}")
# 杠牌后玩家补摸一张牌
self.draw_tile_for_player(player)
return True
# 暗杠分数计算
gang_score = base_score * 4 # 暗杠的分数倍率
for i in range(4):
if i != player:
self.state.scores[i] -= gang_score # 每个其他玩家扣分
self.state.scores[player] += gang_score * 3 # 杠牌玩家得总分
logger.info(f"玩家 {player} 暗杠,总分变化: +{gang_score * 3},其他玩家每人扣分: -{gang_score}")
return True
def random_discard_tile(engine):
@ -401,3 +415,40 @@ def select_discard_tile(self, player):
return tile
# 如果没有缺门牌,随机打出一张
return hand.tiles[0]
def handle_win(self, player, current_player, tile):
"""
处理胡牌逻辑包括判断地胡计算番数和分数
:param player: 胡牌玩家索引
:param current_player: 打出牌的玩家索引
:param tile: 胡牌的那张牌
"""
logger.info(f"玩家 {player} 胡牌!胡的牌是: {tile}")
# 判断是否地胡
is_dihu = self.state.draw_counts[player] == 0 and player != self.state.current_player
if is_dihu:
logger.info(f"玩家 {player} 地胡!")
else:
logger.info(f"玩家 {player} 不是地胡")
# 计算分数
fan_count = 5 # 按你逻辑固定番数为5
base_score = self.state.bottom_score # 底分
win_score = base_score * (2 ** fan_count)
self.state.scores[player] += win_score * 3 # 胡牌玩家得总分
self.state.scores[current_player] -= win_score # 点炮玩家扣除分数
# 更新赢家状态
self.state.winners.append(player)
self.state.print_game_state(player)
# 输出日志
logger.info(f"玩家 {player} 胡牌类型: {'地胡' if is_dihu else '普通胡牌'}")
logger.info(f"玩家 {player} 总番数: {fan_count}")
logger.info(f"玩家 {current_player} 点炮,扣分: {win_score}")
logger.info(f"玩家 {player} 加分: {win_score * 3}")
logger.info(f"当前分数: {self.state.scores}")

View File

@ -1,6 +1,6 @@
import random
from loguru import logger
from src.engine.actions import set_missing_suit
from src.engine.actions import set_missing_suit, check_other_players
from src.engine.chengdu_mahjong_state import ChengduMahjongState
from src.engine.actions import draw_tile, discard_tile, peng, gang, check_blood_battle,should_gang,random_choice
@ -84,7 +84,7 @@ class ChengduMahjongEngine:
logger.info(f"玩家 {self.current_player} 杠牌,类型: {gang_type}")
else:
random_choice(self.state.hands[self.current_player], self.state.missing_suits[self.current_player])
check_other_players(self.state.hands[self.current_player], self.state.missing_suits[self.current_player])

View File

@ -1,96 +1,39 @@
def test_draw_tile():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
def test_mahjong_engine():
"""
测试成都麻将引擎包括初始化发牌轮次逻辑等
"""
# 初始化麻将引擎
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}")
# 初始化游戏
engine.initialize_game()
# 发牌
engine.deal_tiles()
def test_discard_tile():
from src.engine.chengdu_mahjong_engine import ChengduMahjongEngine
# 检查发牌后的状态
print(f"庄家: 玩家 {engine.state.current_player}")
for player in range(4):
hand = engine.state.hands[player]
print(f"玩家 {player} 的手牌: {hand}")
print(f"玩家 {player} 的缺门: {engine.state.missing_suits[player]}")
engine = ChengduMahjongEngine()
tile = engine.draw_tile() # 玩家先摸牌
engine.discard_tile(tile) # 打出摸到的牌
# 模拟游戏主循环
try:
engine.run()
except Exception as e:
print(f"运行时出错: {e}")
# 验证手牌数量减少
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}")
# 打印游戏结束后的状态
print("\n游戏结束!")
for player in range(4):
print(f"玩家 {player} 的分数: {engine.state.scores[player]}")
print(f"玩家 {player} 的明牌: {engine.state.melds[player]}")
print(f"赢家: {engine.state.winners}")
def test_set_missing_suit():
from src.engine.chengdu_mahjong_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.chengdu_mahjong_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: 杠牌成功")
# 运行测试
if __name__ == "__main__":
test_mahjong_engine()