parent
efc71af70c
commit
66310aa2f5
|
|
@ -76,7 +76,7 @@
|
|||
- **缺一门**:玩家必须选择缺少一种花色(条、筒、万中的任意一种),即只能用两种花色来胡牌。如果手中只有单一花色,则为清一色。
|
||||
- **定缺**:游戏开始时,每位玩家需要扣下一张牌作为自己缺的那门,并且不能更改。如果本身就是两门牌,则可以报“天缺”而不扣牌。
|
||||
- **起牌与打牌**:庄家通过掷骰子决定起牌位置,然后按顺序抓牌。庄家先出牌,之后每家依次摸牌打牌。
|
||||
- **碰、杠**:允许碰牌和杠牌,但不允许吃牌。杠牌分为明杠和暗杠,明杠是其他玩家打出的牌被你碰后又摸到相同的牌;暗杠则是你自己摸到四张相同的牌。
|
||||
- **碰、杠**:允许碰牌和杠牌,但不允许吃牌。杠牌分为明杠和暗杠,明杠是其他玩家打出的牌刚好与你手里有三张的牌相同;暗杠则是你自己摸到四张相同的牌。
|
||||
- **胡牌**:胡牌的基本条件是拥有一个对子加上四个顺子或刻子(三个相同牌)。自摸为三家给分,点炮则由放炮者给分。n*AAA+m*ABC+DD ,mn可以等于0。
|
||||
- **血战到底**:一家胡牌后,其他未胡牌的玩家继续游戏,直到只剩下最后一位玩家或者黄庄(所有牌都被摸完)为止。
|
||||
|
||||
|
|
|
|||
|
|
@ -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,27 +262,52 @@ 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:
|
||||
base_score = self.state.bottom_score # 底分
|
||||
|
||||
if mode == "ming": # 明杠逻辑
|
||||
if self.state.hands[player].tile_count[tile] < 3:
|
||||
logger.error(f"玩家 {player} 无法明杠: {tile}")
|
||||
return False
|
||||
return None
|
||||
|
||||
# 减少牌数量并更新明牌
|
||||
if mode == "ming":
|
||||
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)
|
||||
# 暗杠分数计算
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue