shortdeck1.3:ui and fix
This commit is contained in:
19
shortdeck_arena/__init__.py
Normal file
19
shortdeck_arena/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from .simulation import Simulation
|
||||
from .agent import Agent, HumanAgent
|
||||
from .game_stage import GameStage, BlindConfig
|
||||
from .hand_evaluator import HandEvaluator
|
||||
from .hand_ranking import HandRanking
|
||||
from .card import Card
|
||||
from .side_pot import SidePot
|
||||
|
||||
__all__ = [
|
||||
"Simulation",
|
||||
"Agent",
|
||||
"HumanAgent",
|
||||
"GameStage",
|
||||
"BlindConfig",
|
||||
"HandEvaluator",
|
||||
"HandRanking",
|
||||
"Card",
|
||||
"SidePot"
|
||||
]
|
||||
BIN
shortdeck_arena/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
shortdeck_arena/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,3 @@
|
||||
"""ShortDeck card model (6-A, 36 cards)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
@@ -73,6 +73,7 @@ class HandRanking:
|
||||
def get_strength(self) -> int:
|
||||
# 返回牌力 还是 牌型+点数
|
||||
# 基础强度 = 牌型强度 * 1000000
|
||||
# todo
|
||||
strength = self.hand_type.strength * 1000000
|
||||
|
||||
for i, rank in enumerate(self.key_ranks):
|
||||
|
||||
@@ -99,6 +99,5 @@ class SidePotManager:
|
||||
return winnings
|
||||
|
||||
def reset(self):
|
||||
"""重置边池管理器"""
|
||||
self.pots.clear()
|
||||
self.player_total_investment.clear()
|
||||
@@ -42,6 +42,9 @@ class Simulation:
|
||||
self.side_pot_manager = SidePotManager()
|
||||
self.stacks: List[int] = [1000] * len(agents) # 默认筹码
|
||||
|
||||
# 用于结算
|
||||
self.hand_completed = False
|
||||
|
||||
self.new_round()
|
||||
|
||||
def new_round(self):
|
||||
@@ -54,6 +57,7 @@ class Simulation:
|
||||
self.current_stage = GameStage.PREFLOP
|
||||
self.player_states = [PlayerState.ACTIVE] * len(self.agents)
|
||||
self.betting_round_complete = False
|
||||
self.hand_completed = False # 重置完成标志
|
||||
|
||||
# 重置下注状态
|
||||
self.pot = [0] * len(self.agents)
|
||||
@@ -141,25 +145,19 @@ class Simulation:
|
||||
return max(0, max_pot - self.pot[pid])
|
||||
|
||||
def get_min_raise_amount(self, pid) -> int:
|
||||
"""最小加注金额"""
|
||||
call_amount = self.get_call_amount(pid)
|
||||
min_raise = call_amount + max(self.last_raise_amount, self.blind_config.big_blind)
|
||||
return min_raise
|
||||
|
||||
def get_max_bet_amount(self, pid) -> int:
|
||||
"""最大下注金额(剩余筹码)"""
|
||||
if pid >= len(self.stacks):
|
||||
return 0
|
||||
return self.stacks[pid]
|
||||
|
||||
def is_all_in_amount(self, pid, amount) -> bool:
|
||||
"""检查是否为allin"""
|
||||
return amount >= self.stacks[pid]
|
||||
|
||||
def validate_bet_amount(self, pid, action, amount) -> tuple[bool, str, int]:
|
||||
"""
|
||||
验证下注金额合法性
|
||||
"""
|
||||
if pid >= len(self.stacks):
|
||||
return False, "无效玩家", amount
|
||||
|
||||
@@ -178,12 +176,16 @@ class Simulation:
|
||||
if call_amount == 0:
|
||||
return False, "不需要跟注", 0
|
||||
|
||||
# All-in call
|
||||
if call_amount >= available_stack:
|
||||
return True, "", available_stack
|
||||
|
||||
return True, "", call_amount
|
||||
|
||||
elif action == "allin":
|
||||
if available_stack <= 0:
|
||||
return False, "没有筹码进行 all-in", 0
|
||||
return True, "", available_stack
|
||||
|
||||
elif action in ["bet", "raise"]:
|
||||
if amount <= 0:
|
||||
return False, "无效下注金额", amount
|
||||
@@ -207,6 +209,9 @@ class Simulation:
|
||||
return False, "无效行为", amount
|
||||
|
||||
def get_available_actions(self, pid: int) -> dict:
|
||||
if self.current_stage in [GameStage.FINISHED, GameStage.SHOWDOWN]:
|
||||
return {"can_act": False, "reason": "游戏已结束" if self.current_stage == GameStage.FINISHED else "摊牌阶段"}
|
||||
|
||||
if pid != self.current_turn:
|
||||
return {"can_act": False, "reason": "不是你的回合"}
|
||||
|
||||
@@ -214,7 +219,7 @@ class Simulation:
|
||||
return {"can_act": False, "reason": "无效玩家"}
|
||||
|
||||
state = self.player_states[pid]
|
||||
if state in [PlayerState.FOLDED, PlayerState.ALLIN, PlayerState.OUT]:
|
||||
if state in [PlayerState.FOLDED, PlayerState.ALLIN]:
|
||||
return {"can_act": False, "reason": f"Player state: {state}"}
|
||||
|
||||
call_amount = self.get_call_amount(pid)
|
||||
@@ -225,7 +230,7 @@ class Simulation:
|
||||
"can_fold": True,
|
||||
"can_check": call_amount == 0,
|
||||
"can_call": call_amount > 0 and call_amount < available_stack,
|
||||
"can_bet": max(self.pot) == 0 and available_stack > 0,
|
||||
"can_bet": call_amount == 0 and available_stack > 0,
|
||||
"can_raise": call_amount > 0 and available_stack > call_amount,
|
||||
"can_allin": available_stack > 0,
|
||||
"call_amount": call_amount,
|
||||
@@ -241,27 +246,33 @@ class Simulation:
|
||||
"""
|
||||
检查当前下注轮是否完成
|
||||
"""
|
||||
active_players = [i for i, state in enumerate(self.player_states)
|
||||
if state in (PlayerState.ACTIVE, PlayerState.CALLED)]
|
||||
# 首先检查是否只剩一个未弃牌的玩家
|
||||
non_folded_players = [i for i, state in enumerate(self.player_states)
|
||||
if state != PlayerState.FOLDED]
|
||||
|
||||
if len(active_players) <= 1:
|
||||
if len(non_folded_players) <= 1:
|
||||
return True
|
||||
|
||||
active_or_allin_players = [i for i, state in enumerate(self.player_states)
|
||||
if state in (PlayerState.ACTIVE, PlayerState.CALLED, PlayerState.ALLIN)]
|
||||
|
||||
all_allin_or_folded = all(state in (PlayerState.ALLIN, PlayerState.FOLDED)
|
||||
for state in self.player_states)
|
||||
if all_allin_or_folded:
|
||||
return True
|
||||
|
||||
# 检查所有active玩家是否都已投入相同金额,且所有人都已经行动过
|
||||
max_pot = self.get_current_max_bet()
|
||||
|
||||
# 统计还需要行动的玩家
|
||||
players_need_action = []
|
||||
for i in active_players:
|
||||
for i in active_or_allin_players:
|
||||
# allin
|
||||
if self.player_states[i] == PlayerState.ALLIN:
|
||||
continue
|
||||
# 投入金额不足的玩家需要行动
|
||||
continue
|
||||
if self.pot[i] < max_pot:
|
||||
players_need_action.append(i)
|
||||
# Active状态的玩家如果还没有在本轮行动过,也需要行动
|
||||
elif self.player_states[i] == PlayerState.ACTIVE:
|
||||
# 在翻前,大盲玩家即使投入了足够金额,也有权行动一次
|
||||
|
||||
if (self.current_stage == GameStage.PREFLOP and
|
||||
i == self.blind_config.get_bb_position(len(self.agents), self.dealer_position)):
|
||||
# 检查大盲是否已经行动过(除了盲注)
|
||||
@@ -289,6 +300,12 @@ class Simulation:
|
||||
self.complete_hand()
|
||||
return
|
||||
|
||||
|
||||
if self.current_stage == GameStage.SHOWDOWN:
|
||||
self.current_stage = GameStage.FINISHED
|
||||
self.complete_hand()
|
||||
return
|
||||
|
||||
# 重置下注轮状态
|
||||
self.betting_round_complete = False
|
||||
|
||||
@@ -306,6 +323,7 @@ class Simulation:
|
||||
def get_next_active_player(self, start_pos) -> Optional[int]:
|
||||
for i in range(len(self.agents)):
|
||||
pos = (start_pos + i) % len(self.agents)
|
||||
# 只有ACTIVE状态的玩家可以行动,ALLIN和FOLDED的玩家不能行动
|
||||
if self.player_states[pos] == PlayerState.ACTIVE:
|
||||
return pos
|
||||
return None
|
||||
@@ -313,7 +331,7 @@ class Simulation:
|
||||
def get_side_pots(self) -> List:
|
||||
active_players = [
|
||||
i for i, state in enumerate(self.player_states)
|
||||
if state not in [PlayerState.FOLDED, PlayerState.OUT]
|
||||
if state not in [PlayerState.FOLDED]
|
||||
]
|
||||
return self.side_pot_manager.create_side_pots(active_players)
|
||||
|
||||
@@ -375,6 +393,29 @@ class Simulation:
|
||||
raise ValueError("跟注金额>0, 无法过牌,需要跟注或弃牌")
|
||||
self.player_states[pid] = PlayerState.CALLED
|
||||
|
||||
elif action == "allin":
|
||||
# all-in
|
||||
actual_amount = self.stacks[pid]
|
||||
if actual_amount <= 0:
|
||||
raise ValueError("没有可用筹码进行 all-in")
|
||||
|
||||
self.player_states[pid] = PlayerState.ALLIN
|
||||
self.pot[pid] += actual_amount
|
||||
self.stacks[pid] = 0
|
||||
self.total_pot += actual_amount
|
||||
self.side_pot_manager.add_investment(pid, actual_amount)
|
||||
|
||||
# 更新最后加注金额(如果 all-in 金额超过跟注金额)
|
||||
call_amount = self.get_call_amount(pid)
|
||||
raise_amount = actual_amount - call_amount
|
||||
if raise_amount > 0:
|
||||
self.last_raise_amount = raise_amount
|
||||
self.min_raise = raise_amount
|
||||
|
||||
for i, state in enumerate(self.player_states):
|
||||
if i != pid and state == PlayerState.CALLED:
|
||||
self.player_states[i] = PlayerState.ACTIVE
|
||||
|
||||
elif action in ("bet", "raise"):
|
||||
if amount is None:
|
||||
raise ValueError(f"{action} 需要指定金额")
|
||||
@@ -446,7 +487,6 @@ class Simulation:
|
||||
|
||||
|
||||
def evaluate_player_hand(self, pid: int) -> Optional[HandRanking]:
|
||||
"""评估玩家手牌强度"""
|
||||
if pid >= len(self.agents):
|
||||
return None
|
||||
|
||||
@@ -454,22 +494,17 @@ class Simulation:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 获取玩家手牌
|
||||
player_cards = self.player_cards(pid)
|
||||
|
||||
# 获取公共牌
|
||||
board_cards = self.board_cards(self.current_stage.value)
|
||||
|
||||
# 至少需要5张牌才能评估
|
||||
all_cards = player_cards + board_cards
|
||||
if len(all_cards) < 5:
|
||||
return None
|
||||
|
||||
# 如果正好5张牌,直接评估
|
||||
|
||||
if len(all_cards) == 5:
|
||||
return HandEvaluator.evaluate5Cards(all_cards)
|
||||
|
||||
# 如果超过5张牌,找最佳组合
|
||||
|
||||
return HandEvaluator.evaluateHand(all_cards)
|
||||
|
||||
except Exception as e:
|
||||
@@ -478,7 +513,7 @@ class Simulation:
|
||||
|
||||
def get_active_players(self) -> List[int]:
|
||||
return [i for i, state in enumerate(self.player_states)
|
||||
if state not in [PlayerState.FOLDED, PlayerState.OUT]]
|
||||
if state not in [PlayerState.FOLDED]]
|
||||
|
||||
def is_hand_complete(self) -> bool:
|
||||
active_players = self.get_active_players()
|
||||
@@ -522,9 +557,8 @@ class Simulation:
|
||||
winner_id = list(winners.keys())[0]
|
||||
return {winner_id: self.total_pot}
|
||||
|
||||
# 多人摊牌,使用边池分配
|
||||
# 多人摊牌
|
||||
if len(winners) > 1:
|
||||
#转换HandRanking为数值强度
|
||||
hand_strengths = {}
|
||||
for pid, ranking in winners.items():
|
||||
if ranking is not None:
|
||||
@@ -539,16 +573,31 @@ class Simulation:
|
||||
def complete_hand(self) -> Dict:
|
||||
if not self.is_hand_complete():
|
||||
return {"complete": False, "message": "牌局未结束"}
|
||||
|
||||
if self.hand_completed:
|
||||
return {
|
||||
"complete": True,
|
||||
"winners": [],
|
||||
"winnings": {},
|
||||
"final_stacks": self.stacks.copy(),
|
||||
"showdown_hands": {},
|
||||
"message": "手牌已完成"
|
||||
}
|
||||
|
||||
winners = self.determine_winners()
|
||||
|
||||
winnings = self.distribute_pot()
|
||||
|
||||
# 更新筹码
|
||||
for pid, amount in winnings.items():
|
||||
if pid < len(self.stacks):
|
||||
self.stacks[pid] += amount
|
||||
|
||||
self.total_pot = 0
|
||||
self.pot = [0] * len(self.agents)
|
||||
self.side_pot_manager.reset()
|
||||
|
||||
self.current_stage = GameStage.FINISHED
|
||||
self.hand_completed = True
|
||||
|
||||
result = {
|
||||
"complete": True,
|
||||
@@ -557,14 +606,22 @@ class Simulation:
|
||||
"final_stacks": self.stacks.copy(),
|
||||
"showdown_hands": {}
|
||||
}
|
||||
|
||||
active_players = [i for i, state in enumerate(self.player_states)
|
||||
if state != PlayerState.FOLDED]
|
||||
|
||||
# 摊牌信息
|
||||
for pid, ranking in winners.items():
|
||||
if ranking is not None:
|
||||
for pid in active_players:
|
||||
player_hand = self.player_cards(pid)
|
||||
if len(player_hand) >= 2:
|
||||
evaluator = HandEvaluator()
|
||||
board_cards = self.board_cards()
|
||||
ranking = evaluator.evaluate(player_hand, board_cards)
|
||||
|
||||
result["showdown_hands"][pid] = {
|
||||
"cards": [str(card) for card in self.player_cards(pid)],
|
||||
"hand_type": ranking.hand_type.type_name,
|
||||
"description": str(ranking)
|
||||
"cards": [str(card) for card in player_hand],
|
||||
"hand_type": ranking.hand_type.type_name if ranking else "无牌型",
|
||||
"description": str(ranking) if ranking else "无效手牌",
|
||||
"is_winner": pid in winners
|
||||
}
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user