feat(赛马): 为马匹添加序号并优化积分结算展示

- 为 Horse 模型添加 index 字段,用于唯一标识马匹序号
- 在报名时自动分配递增序号,并在所有展示中使用固定序号排序
- 新增积分变化计算功能,在比赛结果中展示每位用户的积分变化和总结描述
- 支持通过序号或马匹名下注,优化用户交互体验
- 添加用户上次马名记忆功能,允许重复使用马名报名
- 更新测试用例以验证序号展示和积分变化功能
This commit is contained in:
2026-04-04 22:43:46 +08:00
parent 2214e22b80
commit 64020cb0e6
5 changed files with 190 additions and 56 deletions

View File

@@ -84,6 +84,108 @@ def get_event_id(event: Event) -> str:
return str(event.user_id) return str(event.user_id)
def _normalize_horse_name(horse_name: str) -> str:
return horse_name.strip().casefold()
def _get_horses_in_order(room: Room) -> list[Horse]:
return sorted(room.horses.values(), key=lambda horse: horse.index)
def _format_horse_label(horse: Horse) -> str:
return f"{horse.index:02d}{horse.name}"
def _find_user_horse(room: Room, user_id: str) -> Horse | None:
for horse in _get_horses_in_order(room):
if horse.owner_id == user_id:
return horse
return None
def _find_duplicate_horse(room: Room, horse_name: str) -> Horse | None:
normalized_name = _normalize_horse_name(horse_name)
for horse in room.horses.values():
if _normalize_horse_name(horse.name) == normalized_name:
return horse
return None
def _resolve_horse_selector(room: Room, selector: str) -> Horse | None:
selector = selector.strip()
if selector.isdigit():
target_index = int(selector)
for horse in room.horses.values():
if horse.index == target_index:
return horse
return None
normalized_selector = _normalize_horse_name(selector)
for horse in room.horses.values():
if _normalize_horse_name(horse.name) == normalized_selector:
return horse
return None
def _describe_points_delta(delta: int) -> str:
if delta >= 300:
return "血赚翻倍"
if delta >= 150:
return "大赚特赚"
if delta > 0:
return "小有收获"
if delta == 0:
return "稳住不亏"
if delta <= -300:
return "倾家荡产"
if delta <= -150:
return "伤筋动骨"
return "略有损失"
def _build_point_changes(room: Room, odds: dict[str, float]) -> tuple[dict[str, int], dict[str, str]]:
point_changes: dict[str, int] = {}
for bet in room.bets:
point_changes[bet.user_id] = point_changes.get(bet.user_id, 0) - bet.amount
champion = room.horses.get(room.champion_name)
if champion:
point_changes[champion.owner_id] = point_changes.get(champion.owner_id, 0) + config.CHAMPION_REWARD
for horse in room.horses.values():
if champion and horse.owner_id != champion.owner_id:
point_changes[horse.owner_id] = point_changes.get(horse.owner_id, 0) + config.PARTICIPANT_REWARD
for bet in room.bets:
if bet.horse_name == room.champion_name:
payout = int(bet.amount * odds.get(bet.horse_name, config.MIN_ODDS))
point_changes[bet.user_id] = point_changes.get(bet.user_id, 0) + payout
point_summaries = {
user_id: _describe_points_delta(delta)
for user_id, delta in point_changes.items()
}
return point_changes, point_summaries
def _format_point_change_lines(room: Room, point_changes: dict[str, int], point_summaries: dict[str, str]) -> list[str]:
ordered_user_ids: list[str] = []
for horse in _get_horses_in_order(room):
if horse.owner_id not in ordered_user_ids:
ordered_user_ids.append(horse.owner_id)
for bet in room.bets:
if bet.user_id not in ordered_user_ids:
ordered_user_ids.append(bet.user_id)
lines = ["积分变化:"]
for user_id in ordered_user_ids:
delta = point_changes.get(user_id, 0)
summary = point_summaries.get(user_id, _describe_points_delta(delta))
lines.append(f" {user_id} {delta:+d} 积分 · {summary}")
return lines
def calculate_odds(room: Room) -> dict[str, float]: def calculate_odds(room: Room) -> dict[str, float]:
"""Calculate odds for each horse based on bet distribution.""" """Calculate odds for each horse based on bet distribution."""
total_bet = sum(b.amount for b in room.bets) total_bet = sum(b.amount for b in room.bets)
@@ -98,11 +200,11 @@ def calculate_odds(room: Room) -> dict[str, float]:
return odds return odds
async def settle_race(room: Room): async def settle_race(room: Room) -> RaceResult | None:
"""Settle bets and rewards after race finishes.""" """Settle bets and rewards after race finishes."""
champion = room.horses.get(room.champion_name) champion = room.horses.get(room.champion_name)
if not champion: if not champion:
return return None
# Reward champion owner # Reward champion owner
await points_service.reward_champion(champion.owner_id) await points_service.reward_champion(champion.owner_id)
@@ -119,17 +221,21 @@ async def settle_race(room: Room):
await points_service.payout_winnings(bet.user_id, bet.amount, odds.get(bet.horse_name, config.MIN_ODDS)) await points_service.payout_winnings(bet.user_id, bet.amount, odds.get(bet.horse_name, config.MIN_ODDS))
# Save race result # Save race result
point_changes, point_summaries = _build_point_changes(room, odds)
result = RaceResult( result = RaceResult(
race_id=str(uuid.uuid4()), race_id=str(uuid.uuid4()),
scope=room.scope, scope=room.scope,
champion_name=champion.name, champion_name=champion.name,
champion_owner=champion.owner_id, champion_owner=champion.owner_id,
participants=[h.name for h in room.horses.values()], participants=[h.name for h in _get_horses_in_order(room)],
bet_distribution={name: sum(b.amount for b in room.bets if b.horse_name == name) for name in room.horses}, bet_distribution={name: sum(b.amount for b in room.bets if b.horse_name == name) for name in room.horses},
duration_ticks=room.tick_count, duration_ticks=room.tick_count,
completed_at=datetime.now(), completed_at=datetime.now(),
point_changes=point_changes,
point_change_summaries=point_summaries,
) )
room_store.save_race_result(result) room_store.save_race_result(result)
return result
async def _send_to_scope(bot: Bot, scope: str, message: str): async def _send_to_scope(bot: Bot, scope: str, message: str):
@@ -152,8 +258,7 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
"""Run race with live progress updates and settlement.""" """Run race with live progress updates and settlement."""
room.state = RoomState.RUNNING room.state = RoomState.RUNNING
# Send start message with horse list horse_list = "\n".join(f" {_format_horse_label(h)}" for h in _get_horses_in_order(room))
horse_list = "\n".join(f" {h.name}" for h in room.horses.values())
await _send_to_scope(bot, scope, f"比赛开始!\n{horse_list}") await _send_to_scope(bot, scope, f"比赛开始!\n{horse_list}")
# Race loop with progress updates # Race loop with progress updates
@@ -177,13 +282,12 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
return return
# Settle # Settle
await settle_race(room) result = await settle_race(room)
# 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"比赛结束!冠军:{_format_horse_label(champion) if champion else 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]
@@ -192,6 +296,8 @@ async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
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}")
if result:
result_lines.extend(_format_point_change_lines(room, result.point_changes, result.point_change_summaries))
await _send_to_scope(bot, scope, "\n".join(result_lines)) await _send_to_scope(bot, scope, "\n".join(result_lines))
@@ -213,13 +319,13 @@ async def handle_register(bot: Bot, event: Event):
await register_cmd.finish("无权限访问此功能") await register_cmd.finish("无权限访问此功能")
return return
# Parse horse name from message
msg = str(event.get_message()).strip() msg = str(event.get_message()).strip()
parts = msg.split(None, 1) parts = msg.split(None, 1)
horse_name = parts[1].strip() if len(parts) > 1 else "" user_id = get_event_id(event)
horse_name = parts[1].strip() if len(parts) > 1 else room_store.get_last_horse_name(user_id) or ""
if not horse_name: if not horse_name:
await register_cmd.finish("请输入马匹名:/赛马报名 <马匹名>") await register_cmd.finish("请输入马匹名:/赛马报名 <马匹名>。若你之前报过名,也可以直接发送 /赛马报名 复用上一次绑定的马名")
return return
if len(horse_name) > 10: if len(horse_name) > 10:
@@ -227,7 +333,6 @@ async def handle_register(bot: Bot, event: Event):
return return
scope = get_scope(event) scope = get_scope(event)
user_id = get_event_id(event)
lock = room_store.get_lock(scope) lock = room_store.get_lock(scope)
async with lock: async with lock:
@@ -243,21 +348,24 @@ async def handle_register(bot: Bot, event: Event):
await register_cmd.finish("房间已满最多8匹马") await register_cmd.finish("房间已满最多8匹马")
return return
if horse_name in room.horses: duplicate_horse = _find_duplicate_horse(room, horse_name)
await register_cmd.finish(f"马匹名 \"{horse_name}\" 已被使用") if duplicate_horse:
await register_cmd.finish(f"马匹名 \"{horse_name}\" 已被 {_format_horse_label(duplicate_horse)} 使用")
return return
# Check if user already registered existing_user_horse = _find_user_horse(room, user_id)
for h in room.horses.values(): if existing_user_horse:
if h.owner_id == user_id: await register_cmd.finish(f"你已经报名了,当前马匹为 {_format_horse_label(existing_user_horse)}")
await register_cmd.finish("你已经报名了,不能重复报名") return
return
# Create horse horse_index = room.next_horse_index
room.horses[horse_name] = Horse(owner_id=user_id, name=horse_name) room.next_horse_index += 1
room.horses[horse_name] = Horse(owner_id=user_id, name=horse_name, index=horse_index)
room_store.set_last_horse_name(user_id, horse_name)
count = len(room.horses) count = len(room.horses)
await register_cmd.finish(f"报名成功!马匹 \"{horse_name}\" 已加入比赛({count}/8") registered_horse = room.horses[horse_name]
await register_cmd.finish(f"报名成功!{_format_horse_label(registered_horse)} 已加入比赛({count}/8")
cancel_cmd = on_command("赛马取消报名", priority=5) cancel_cmd = on_command("赛马取消报名", priority=5)
@@ -284,27 +392,19 @@ async def handle_cancel(bot: Bot, event: Event):
await cancel_cmd.finish("比赛正在进行中,无法取消报名") await cancel_cmd.finish("比赛正在进行中,无法取消报名")
return return
# Find user's horse user_horse = _find_user_horse(room, user_id)
user_horse = None
for name, horse in room.horses.items():
if horse.owner_id == user_id:
user_horse = name
break
if not user_horse: if not user_horse:
await cancel_cmd.finish("你还没有报名") await cancel_cmd.finish("你还没有报名")
return return
# Refund bets on this horse bets_to_refund = [b for b in room.bets if b.horse_name == user_horse.name]
bets_to_refund = [b for b in room.bets if b.horse_name == user_horse]
for bet in bets_to_refund: for bet in bets_to_refund:
await points_service.refund_bet_points(bet.user_id, bet.amount, "取消报名退还下注") await points_service.refund_bet_points(bet.user_id, bet.amount, "取消报名退还下注")
room.bets = [b for b in room.bets if b.horse_name != user_horse] room.bets = [b for b in room.bets if b.horse_name != user_horse.name]
# Remove horse del room.horses[user_horse.name]
del room.horses[user_horse]
await cancel_cmd.finish(f"已取消报名,马匹 \"{user_horse}\" 已退出") await cancel_cmd.finish(f"已取消报名,{_format_horse_label(user_horse)} 已退出")
bet_cmd = on_command("赛马下注", priority=5) bet_cmd = on_command("赛马下注", priority=5)
@@ -317,14 +417,13 @@ async def handle_bet(bot: Bot, event: Event):
await bet_cmd.finish("无权限访问此功能") await bet_cmd.finish("无权限访问此功能")
return return
# Parse arguments: /赛马下注 <马匹名> <金额>
msg = str(event.get_message()).strip() msg = str(event.get_message()).strip()
parts = msg.split() parts = msg.split()
if len(parts) < 3: if len(parts) < 3:
await bet_cmd.finish("请使用:/赛马下注 <马匹名> <金额>") await bet_cmd.finish("请使用:/赛马下注 <序号|马匹名> <金额>")
return return
horse_name = parts[1] horse_selector = parts[1]
try: try:
amount = int(parts[2]) amount = int(parts[2])
except ValueError: except ValueError:
@@ -349,26 +448,24 @@ async def handle_bet(bot: Bot, event: Event):
await bet_cmd.finish("比赛正在进行中,无法下注") await bet_cmd.finish("比赛正在进行中,无法下注")
return return
if horse_name not in room.horses: horse = _resolve_horse_selector(room, horse_selector)
await bet_cmd.finish(f"马匹 \"{horse_name}\" 不存在") if not horse:
await bet_cmd.finish(f"马匹序号/名称 \"{horse_selector}\" 不存在")
return return
# Can't bet on your own horse if horse.owner_id == user_id:
if room.horses[horse_name].owner_id == user_id:
await bet_cmd.finish("不能给自己的马下注") await bet_cmd.finish("不能给自己的马下注")
return return
# Deduct points first success, balance = await points_service.spend_bet_points(user_id, amount, f"下注 {_format_horse_label(horse)}")
success, balance = await points_service.spend_bet_points(user_id, amount, f"下注 {horse_name}")
if not success: if not success:
await bet_cmd.finish(f"积分不足(当前余额:{balance}") await bet_cmd.finish(f"积分不足(当前余额:{balance}")
return return
# Record bet room.bets.append(Bet(user_id=user_id, horse_name=horse.name, amount=amount))
room.bets.append(Bet(user_id=user_id, horse_name=horse_name, amount=amount))
odds = calculate_odds(room) odds = calculate_odds(room)
await bet_cmd.finish(f"下注成功!{horse_name} {amount}积分(当前赔率:{odds.get(horse_name, config.MIN_ODDS):.2f}") await bet_cmd.finish(f"下注成功!{_format_horse_label(horse)} {amount}积分(当前赔率:{odds.get(horse.name, config.MIN_ODDS):.2f}")
odds_cmd = on_command("赛马赔率", priority=5) odds_cmd = on_command("赛马赔率", priority=5)
@@ -394,9 +491,10 @@ async def handle_odds(bot: Bot, event: Event):
odds = calculate_odds(room) odds = calculate_odds(room)
lines = ["当前赔率:"] lines = ["当前赔率:"]
total_bet = sum(b.amount for b in room.bets) total_bet = sum(b.amount for b in room.bets)
for name, odd in odds.items(): for horse in _get_horses_in_order(room):
horse_bet = sum(b.amount for b in room.bets if b.horse_name == name) odd = odds.get(horse.name, config.MIN_ODDS)
lines.append(f" {name} - {odd:.2f}倍 (总下注: {horse_bet})") horse_bet = sum(b.amount for b in room.bets if b.horse_name == horse.name)
lines.append(f" {_format_horse_label(horse)} - {odd:.2f}倍 (总下注: {horse_bet})")
lines.append(f"总下注池: {total_bet}") lines.append(f"总下注池: {total_bet}")
await odds_cmd.finish("\n".join(lines)) await odds_cmd.finish("\n".join(lines))
@@ -449,7 +547,8 @@ async def handle_help(bot: Bot, event: Event):
help_text = """赛马命令帮助: help_text = """赛马命令帮助:
/赛马报名 <马匹名> - 报名参赛 /赛马报名 <马匹名> - 报名参赛
/赛马取消报名 - 取消报名并退还下注 /赛马取消报名 - 取消报名并退还下注
/赛马下注 <马匹名> <金额> - 下注(不能给自己的马下注) /赛马报名 - 复用上一次绑定的马名报名
/赛马下注 <序号|马匹名> <金额> - 下注(不能给自己的马下注)
/赛马赔率 - 查看当前赔率 /赛马赔率 - 查看当前赔率
/赛马开赛 - 开始比赛至少2匹马 /赛马开赛 - 开始比赛至少2匹马
/赛马帮助 - 显示此帮助""" /赛马帮助 - 显示此帮助"""

View File

@@ -21,6 +21,7 @@ class HorseState(str, Enum):
class Horse: class Horse:
owner_id: str owner_id: str
name: str name: str
index: int = 0
position: float = 0.0 position: float = 0.0
state: HorseState = HorseState.READY state: HorseState = HorseState.READY
@@ -41,6 +42,7 @@ class Room:
bets: list[Bet] = field(default_factory=list) bets: list[Bet] = field(default_factory=list)
champion_name: Optional[str] = None champion_name: Optional[str] = None
tick_count: int = 0 tick_count: int = 0
next_horse_index: int = 1
@dataclass @dataclass
@@ -53,3 +55,5 @@ class RaceResult:
bet_distribution: dict[str, int] bet_distribution: dict[str, int]
duration_ticks: int duration_ticks: int
completed_at: datetime completed_at: datetime
point_changes: dict[str, int] = field(default_factory=dict)
point_change_summaries: dict[str, str] = field(default_factory=dict)

View File

@@ -57,12 +57,11 @@ class RaceEngine:
bar_width = 20 bar_width = 20
lines = [f"【第{room.tick_count}回合】"] lines = [f"【第{room.tick_count}回合】"]
# Sort by position descending for readability sorted_horses = sorted(room.horses.values(), key=lambda h: h.index)
sorted_horses = sorted(room.horses.values(), key=lambda h: h.position, reverse=True)
for horse in sorted_horses: for horse in sorted_horses:
progress = min(horse.position / distance, 1.0) progress = min(horse.position / distance, 1.0)
filled = int(progress * bar_width) filled = int(progress * bar_width)
bar = "" * filled + "" * (bar_width - filled) bar = "" * filled + "" * (bar_width - filled)
lines.append(f" {horse.name:<6} |{bar}| {horse.position:.1f}m") lines.append(f" {horse.index:02d}{horse.name} |{bar}| {horse.position:.1f}m")
return "\n".join(lines) return "\n".join(lines)

View File

@@ -13,6 +13,7 @@ class RoomStore:
self.config = config self.config = config
self.rooms: dict[str, Room] = {} self.rooms: dict[str, Room] = {}
self._locks: dict[str, asyncio.Lock] = {} self._locks: dict[str, asyncio.Lock] = {}
self._last_horse_names: dict[str, str] = {}
self.db_path = Path(config.RACE_DB_FILE) self.db_path = Path(config.RACE_DB_FILE)
self.db_path.parent.mkdir(parents=True, exist_ok=True) self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._init_db() self._init_db()
@@ -72,6 +73,12 @@ class RoomStore:
if scope in self.rooms: if scope in self.rooms:
del self.rooms[scope] del self.rooms[scope]
def get_last_horse_name(self, user_id: str) -> Optional[str]:
return self._last_horse_names.get(user_id)
def set_last_horse_name(self, user_id: str, horse_name: str):
self._last_horse_names[user_id] = horse_name
def _save_snapshot(self, room: Room): def _save_snapshot(self, room: Room):
"""Save room snapshot to database.""" """Save room snapshot to database."""
import json import json
@@ -80,6 +87,7 @@ class RoomStore:
name: { name: {
"owner_id": horse.owner_id, "owner_id": horse.owner_id,
"name": horse.name, "name": horse.name,
"index": horse.index,
"position": horse.position, "position": horse.position,
"state": horse.state.value, "state": horse.state.value,
} }

View File

@@ -258,7 +258,8 @@ async def handle_test_simulate_race(bot: Bot, event: Event):
horse_names = _generate_random_horse_names(8) horse_names = _generate_random_horse_names(8)
for idx, horse_name in enumerate(horse_names, start=1): for idx, horse_name in enumerate(horse_names, start=1):
owner_id = f"sim_user_{idx}" owner_id = f"sim_user_{idx}"
room.horses[horse_name] = Horse(owner_id=owner_id, name=horse_name, state=HorseState.RACING) room.horses[horse_name] = Horse(owner_id=owner_id, name=horse_name, index=idx, state=HorseState.RACING)
room.next_horse_index = len(horse_names) + 1
bet_amount = max(commands_mod.config.MIN_BET, 10) bet_amount = max(commands_mod.config.MIN_BET, 10)
room.bets.append(Bet(user_id="bettor_1", horse_name=horse_names[0], amount=bet_amount)) room.bets.append(Bet(user_id="bettor_1", horse_name=horse_names[0], amount=bet_amount))
@@ -282,15 +283,31 @@ async def handle_test_simulate_race(bot: Bot, event: Event):
if "比赛开始!" not in messages[0]: if "比赛开始!" not in messages[0]:
await test_simulate_race_cmd.send("完全模拟失败:未发送开赛消息") await test_simulate_race_cmd.send("完全模拟失败:未发送开赛消息")
return return
for idx, horse_name in enumerate(horse_names, start=1):
if f"{idx:02d}{horse_name}" not in messages[0]:
await test_simulate_race_cmd.send("完全模拟失败:开赛名单未按序号展示")
return
if not any("【第" in msg and "回合】" in msg for msg in messages): progress_messages = [msg for msg in messages if "【第" in msg and "回合】" in msg]
if not progress_messages:
await test_simulate_race_cmd.send("完全模拟失败:未发送回合进度消息") await test_simulate_race_cmd.send("完全模拟失败:未发送回合进度消息")
return return
progress_lines = progress_messages[0].splitlines()[1:]
if len(progress_lines) != len(horse_names):
await test_simulate_race_cmd.send("完全模拟失败:回合进度展示条目数量不匹配")
return
for idx, line in enumerate(progress_lines, start=1):
if not line.strip().startswith(f"{idx:02d}"):
await test_simulate_race_cmd.send("完全模拟失败:回合进度未按报名序号固定排序")
return
result_msg = messages[-1] result_msg = messages[-1]
if "比赛结束!冠军:" not in result_msg: if "比赛结束!冠军:" not in result_msg:
await test_simulate_race_cmd.send("完全模拟失败:未发送结束结算消息") await test_simulate_race_cmd.send("完全模拟失败:未发送结束结算消息")
return return
if "积分变化:" not in result_msg:
await test_simulate_race_cmd.send("完全模拟失败:未发送积分变化总结")
return
if not fake_room_store.saved_results: if not fake_room_store.saved_results:
await test_simulate_race_cmd.send("完全模拟失败:未写入赛史结果(内存)") await test_simulate_race_cmd.send("完全模拟失败:未写入赛史结果(内存)")
@@ -300,6 +317,12 @@ async def handle_test_simulate_race(bot: Bot, event: Event):
if saved.champion_name not in room.horses: if saved.champion_name not in room.horses:
await test_simulate_race_cmd.send("完全模拟失败:赛史冠军不在参赛马匹中") await test_simulate_race_cmd.send("完全模拟失败:赛史冠军不在参赛马匹中")
return return
if not saved.point_changes:
await test_simulate_race_cmd.send("完全模拟失败:赛史未记录积分变化")
return
if not saved.point_change_summaries:
await test_simulate_race_cmd.send("完全模拟失败:赛史未记录积分变化总结")
return
champion_owner_id = room.horses[saved.champion_name].owner_id champion_owner_id = room.horses[saved.champion_name].owner_id
reward_champion_calls = [c for c in fake_points_service.calls if c[0] == "reward_champion"] reward_champion_calls = [c for c in fake_points_service.calls if c[0] == "reward_champion"]
@@ -321,6 +344,7 @@ async def handle_test_simulate_race(bot: Bot, event: Event):
f"总回合:{saved.duration_ticks}", f"总回合:{saved.duration_ticks}",
f"消息条数:{len(messages)}(开赛/进度/结算均已覆盖)", f"消息条数:{len(messages)}(开赛/进度/结算均已覆盖)",
f"结算调用:{len(fake_points_service.calls)}(冠军/参赛/下注派奖)", f"结算调用:{len(fake_points_service.calls)}(冠军/参赛/下注派奖)",
f"积分变化用户数:{len(saved.point_changes)}",
f"过程展示:{'开启' if stream_progress else '关闭'}", f"过程展示:{'开启' if stream_progress else '关闭'}",
] ]
) )