添加赛马实时进度播报
将比赛循环从race_engine移到commands中,每回合发送进度条: 马匹名 |████████░░░░░░| 45.2m race_engine改为提供tick/determine_champion/format_progress方法 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,40 +97,55 @@ async def settle_race(room: Room):
|
|||||||
room_store.save_race_result(result)
|
room_store.save_race_result(result)
|
||||||
|
|
||||||
|
|
||||||
async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
|
async def _send_to_scope(bot: Bot, scope: str, message: str):
|
||||||
"""Run race and handle settlement + cleanup."""
|
"""Send message to group or private chat based on scope."""
|
||||||
task = await race_engine.start_race(room)
|
|
||||||
|
|
||||||
# Send start message
|
|
||||||
horse_list = "\n".join(f" {h.name} (主人: {h.owner_id})" for h in room.horses.values())
|
|
||||||
try:
|
try:
|
||||||
await bot.send_msg(
|
await bot.send_msg(
|
||||||
message_type="group" if scope.startswith("group_") else "private",
|
message_type="group" if scope.startswith("group_") else "private",
|
||||||
group_id=int(scope.split("_", 1)[1]) if scope.startswith("group_") else None,
|
group_id=int(scope.split("_", 1)[1]) if scope.startswith("group_") else None,
|
||||||
user_id=int(scope.split("_", 1)[1]) if scope.startswith("test_") else None,
|
user_id=int(scope.split("_", 1)[1]) if scope.startswith("test_") else None,
|
||||||
message=f"比赛开始!参赛马匹:\n{horse_list}",
|
message=message,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Wait for race to finish
|
|
||||||
|
async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
|
||||||
|
"""Run race with live progress updates and settlement."""
|
||||||
|
room.state = RoomState.RUNNING
|
||||||
|
|
||||||
|
# Send start message with horse list
|
||||||
|
horse_list = "\n".join(f" {h.name}" for h in room.horses.values())
|
||||||
|
await _send_to_scope(bot, scope, f"比赛开始!\n{horse_list}")
|
||||||
|
|
||||||
|
# Race loop with progress updates
|
||||||
try:
|
try:
|
||||||
await task
|
while room.state == RoomState.RUNNING:
|
||||||
|
await asyncio.sleep(config.RACE_TICK_INTERVAL)
|
||||||
|
|
||||||
|
finished = race_engine.tick(room)
|
||||||
|
progress = race_engine.format_progress(room)
|
||||||
|
await _send_to_scope(bot, scope, progress)
|
||||||
|
|
||||||
|
if finished:
|
||||||
|
champion = race_engine.determine_champion(finished)
|
||||||
|
room.champion_name = champion.name
|
||||||
|
room.state = RoomState.FINISHED
|
||||||
|
break
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
room.state = RoomState.INTERRUPTED
|
room.state = RoomState.INTERRUPTED
|
||||||
# Refund all bets on interruption
|
|
||||||
for bet in room.bets:
|
for bet in room.bets:
|
||||||
await points_service.refund_bet_points(bet.user_id, bet.amount, "比赛中断退还")
|
await points_service.refund_bet_points(bet.user_id, bet.amount, "比赛中断退还")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Race finished - settle
|
# Settle
|
||||||
await settle_race(room)
|
await settle_race(room)
|
||||||
|
|
||||||
# Build result message
|
# Build result message
|
||||||
odds = calculate_odds(room)
|
odds = calculate_odds(room)
|
||||||
champion = room.horses.get(room.champion_name)
|
champion = room.horses.get(room.champion_name)
|
||||||
result_lines = [
|
result_lines = [
|
||||||
f"比赛结束!冠军:{room.champion_name} 🏆",
|
f"比赛结束!冠军:{room.champion_name}",
|
||||||
f"马主 {champion.owner_id if champion else '?'} 获得 {config.CHAMPION_REWARD} 积分",
|
f"马主 {champion.owner_id if champion else '?'} 获得 {config.CHAMPION_REWARD} 积分",
|
||||||
]
|
]
|
||||||
winning_bets = [b for b in room.bets if b.horse_name == room.champion_name]
|
winning_bets = [b for b in room.bets if b.horse_name == room.champion_name]
|
||||||
@@ -138,17 +153,9 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
|
|||||||
result_lines.append("下注中奖:")
|
result_lines.append("下注中奖:")
|
||||||
for b in winning_bets:
|
for b in winning_bets:
|
||||||
payout = int(b.amount * odds.get(b.horse_name, config.MIN_ODDS))
|
payout = int(b.amount * odds.get(b.horse_name, config.MIN_ODDS))
|
||||||
result_lines.append(f" {b.user_id} 下注 {b.amount} → 获得 {payout}")
|
result_lines.append(f" {b.user_id} 下注 {b.amount} -> 获得 {payout}")
|
||||||
|
|
||||||
try:
|
await _send_to_scope(bot, scope, "\n".join(result_lines))
|
||||||
await bot.send_msg(
|
|
||||||
message_type="group" if scope.startswith("group_") else "private",
|
|
||||||
group_id=int(scope.split("_", 1)[1]) if scope.startswith("group_") else None,
|
|
||||||
user_id=int(scope.split("_", 1)[1]) if scope.startswith("test_") else None,
|
|
||||||
message="\n".join(result_lines),
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
race_engine.stop_race(scope)
|
race_engine.stop_race(scope)
|
||||||
@@ -391,7 +398,8 @@ async def handle_start(bot: Bot, event: Event):
|
|||||||
await start_cmd.finish("比赛开始!")
|
await start_cmd.finish("比赛开始!")
|
||||||
|
|
||||||
# Run race in background (outside command handler)
|
# Run race in background (outside command handler)
|
||||||
asyncio.create_task(run_race_with_settlement(bot, room, scope))
|
task = asyncio.create_task(run_race_with_settlement(bot, room, scope))
|
||||||
|
race_engine.register_task(scope, task)
|
||||||
|
|
||||||
|
|
||||||
help_cmd = on_command("赛马帮助", priority=5)
|
help_cmd = on_command("赛马帮助", priority=5)
|
||||||
|
|||||||
@@ -11,37 +11,23 @@ class RaceEngine:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.active_tasks: dict[str, asyncio.Task] = {}
|
self.active_tasks: dict[str, asyncio.Task] = {}
|
||||||
|
|
||||||
async def start_race(self, room: Room) -> asyncio.Task:
|
def tick(self, room: Room):
|
||||||
"""Start race progression loop."""
|
"""Advance race by one tick. Returns list of finished horses."""
|
||||||
task = asyncio.create_task(self._race_loop(room))
|
room.tick_count += 1
|
||||||
self.active_tasks[room.scope] = task
|
|
||||||
return task
|
|
||||||
|
|
||||||
async def _race_loop(self, room: Room):
|
for horse in room.horses.values():
|
||||||
"""Main race progression loop."""
|
if horse.state == HorseState.RACING:
|
||||||
room.state = RoomState.RUNNING
|
distance = max(0, random.gauss(10, 3))
|
||||||
|
horse.position += distance
|
||||||
|
|
||||||
while room.state == RoomState.RUNNING:
|
finished_horses = [
|
||||||
await asyncio.sleep(self.config.RACE_TICK_INTERVAL)
|
h for h in room.horses.values()
|
||||||
room.tick_count += 1
|
if h.position >= self.config.RACE_DISTANCE
|
||||||
|
]
|
||||||
|
|
||||||
for horse in room.horses.values():
|
return finished_horses
|
||||||
if horse.state == HorseState.RACING:
|
|
||||||
distance = max(0, random.gauss(10, 3))
|
|
||||||
horse.position += distance
|
|
||||||
|
|
||||||
finished_horses = [
|
def determine_champion(self, horses: list[Horse]) -> Horse:
|
||||||
h for h in room.horses.values()
|
|
||||||
if h.position >= self.config.RACE_DISTANCE
|
|
||||||
]
|
|
||||||
|
|
||||||
if finished_horses:
|
|
||||||
champion = self._determine_champion(finished_horses)
|
|
||||||
room.champion_name = champion.name
|
|
||||||
room.state = RoomState.FINISHED
|
|
||||||
break
|
|
||||||
|
|
||||||
def _determine_champion(self, horses: list[Horse]) -> Horse:
|
|
||||||
"""Determine champion from tied horses."""
|
"""Determine champion from tied horses."""
|
||||||
if len(horses) == 1:
|
if len(horses) == 1:
|
||||||
return horses[0]
|
return horses[0]
|
||||||
@@ -53,6 +39,10 @@ class RaceEngine:
|
|||||||
|
|
||||||
return horses[0]
|
return horses[0]
|
||||||
|
|
||||||
|
def register_task(self, scope: str, task: asyncio.Task):
|
||||||
|
"""Register an active race task."""
|
||||||
|
self.active_tasks[scope] = task
|
||||||
|
|
||||||
def stop_race(self, scope: str):
|
def stop_race(self, scope: str):
|
||||||
"""Stop race and cancel task."""
|
"""Stop race and cancel task."""
|
||||||
if scope in self.active_tasks:
|
if scope in self.active_tasks:
|
||||||
@@ -60,3 +50,19 @@ class RaceEngine:
|
|||||||
if not task.done():
|
if not task.done():
|
||||||
task.cancel()
|
task.cancel()
|
||||||
del self.active_tasks[scope]
|
del self.active_tasks[scope]
|
||||||
|
|
||||||
|
def format_progress(self, room: Room) -> str:
|
||||||
|
"""Format race progress as visual bar for each horse."""
|
||||||
|
distance = self.config.RACE_DISTANCE
|
||||||
|
bar_width = 20
|
||||||
|
lines = [f"【第{room.tick_count}回合】"]
|
||||||
|
|
||||||
|
# Sort by position descending for readability
|
||||||
|
sorted_horses = sorted(room.horses.values(), key=lambda h: h.position, reverse=True)
|
||||||
|
for horse in sorted_horses:
|
||||||
|
progress = min(horse.position / distance, 1.0)
|
||||||
|
filled = int(progress * bar_width)
|
||||||
|
bar = "█" * filled + "░" * (bar_width - filled)
|
||||||
|
lines.append(f" {horse.name:<6} |{bar}| {horse.position:.1f}m")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user