fix: bound chat latency and clarify blackjack flow

This commit is contained in:
JWeBot Contributors
2026-10-15 13:37:57 +08:00
parent f7fedc54e4
commit 0731e5cebb
25 changed files with 364 additions and 53 deletions
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-official-extensions"
version = "0.6.1a0+1015"
version = "0.6.2a0+1015"
description = "Reviewed local command adapters and broker implementation for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-sdk==0.6.1a0+1015",
"jwebot-sdk==0.6.2a0+1015",
]
[tool.setuptools.packages.find]
@@ -62,4 +62,4 @@ __all__ = [
"render_feature_list",
]
__version__ = "0.6.1a0+1015"
__version__ = "0.6.2a0+1015"
@@ -6,7 +6,7 @@ from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
PROFILE = 'single-player-games'
AUTHOR = 'jwebot'
DESCRIPTION = '本人虚拟金币账户、抽奖、彩票和单人21点'
HELP_ENTRIES = ('/金币 /wallet;/抽奖 [金额] /draw;/买彩票 [号码] [金额] /lottery', '/21点 开始|要牌|停牌|状态|结束;/blackjack start|hit|stand|status|end', '/游戏帮助;/games')
HELP_ENTRIES = ('/金币 /wallet;/抽奖 [金额] /draw;/买彩票 [号码] [金额] /lottery', '/21点 开始 [金额](金额=本局下注,默认50,空格可省略)', '/21点 要牌|停牌|状态|结束;/blackjack hit|stand|status|end', '/游戏帮助;/games')
COMMANDS = ('/金币', '/wallet', '/抽奖', '/draw', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏帮助', '/games')
@@ -123,7 +123,8 @@ LOCAL_EXTENSIONS = (
description="本人虚拟金币账户、抽奖、彩票和单人21点",
help_entries=(
"/金币 /wallet;/抽奖 [金额] /draw;/买彩票 [号码] [金额] /lottery",
"/21点 开始|要牌|停牌|状态|结束;/blackjack start|hit|stand|status|end",
"/21点 开始 [金额](金额=本局下注,默认50,空格可省略)",
"/21点 要牌|停牌|状态|结束;/blackjack hit|stand|status|end",
"/游戏帮助;/games",
),
command_kinds=("wallet", "prize_draw", "lottery", "blackjack", "game_rules"),
@@ -11,6 +11,23 @@ from typing import Any
INITIAL_COINS = 1000
MAX_BET = 100
GAME_TIMEOUT_SECONDS = 600
BLACKJACK_ACTIONS = {
"要牌": "hit",
"hit": "hit",
"停牌": "stand",
"stand": "stand",
"状态": "status",
"status": "status",
"结束": "end",
"end": "end",
}
BLACKJACK_START = re.compile(
r"^(?:开始|start)(?P<bet>\d{1,3})?$"
)
BLACKJACK_COMBINED_ACTION = re.compile(
r"^(?:开始|start)(?P<bet>\d{1,3})?"
r"(?:要牌|hit|停牌|stand)$"
)
def card_label(card: int) -> str:
@@ -37,8 +54,9 @@ class PlayerGames:
return (
"单人游戏:每人首次使用获 1000 虚拟金币;每次下注 2~100。\n"
"/金币 /wallet;/抽奖 [金额] /draw;/买彩票 [000~999] [金额] /lottery;"
"/21点 开始 [金额] /blackjack start。\n"
"21点:/21点 要牌|停牌|状态|结束(hit|stand|status|end);"
"21点先用 /21点 开始 [金额] 进场,金额是本局下注,默认50;空格可省略。\n"
"看到牌后再单独发送 /21点 要牌|停牌|状态|结束"
"(/blackjack hit|stand|status|end);"
"每人最多一局,10分钟无操作视为认输,结束不退下注。\n"
"抽奖:5%返5倍、20%返2倍,其余无返还;彩票命中返500倍;"
"21点获胜返2倍、平局返下注、自然21点返2.5倍向下取整,庄家软17停牌。"
@@ -64,25 +82,40 @@ class PlayerGames:
number = int(match[1]) if match[1] is not None else None
bet = int(match[2] or 10)
elif kind == "blackjack":
parts = arguments.casefold().split()
action = parts[0] if parts else "状态"
actions = {"开始": "start", "start": "start", "要牌": "hit", "hit": "hit",
"停牌": "stand", "stand": "stand", "状态": "status", "status": "status",
"结束": "end", "end": "end"}
action = actions.get(action, "")
if not action or (action != "start" and len(parts) > 1) or len(parts) > 2:
return "用法:/21点 开始 [金额]|要牌|停牌|状态|结束(/blackjack start|hit|stand|status|end)"
if action == "start":
if len(parts) == 2 and not re.fullmatch(r"\d{1,3}", parts[1]):
return "21点下注必须为 2~100 的整数金币"
bet = int(parts[1]) if len(parts) == 2 else 50
compacted = re.sub(r"\s+", "", arguments.casefold()) or "状态"
start = BLACKJACK_START.fullmatch(compacted)
if start is not None:
action = "start"
bet = int(start["bet"] or 50)
else:
action = BLACKJACK_ACTIONS.get(compacted, "")
bet = 0
if not action:
combined = BLACKJACK_COMBINED_ACTION.fullmatch(compacted)
if combined is not None:
amount = combined["bet"] or "50"
return (
"21点要分两步:先发送 /21点 开始 "
f"{amount}({amount} 是本局下注);看到牌后再单独发送 "
"/21点 要牌 或 /21点 停牌"
)
return (
"用法:先 /21点 开始 [2~100](金额是本局下注,默认50);"
"开局后再单独发送 /21点 要牌|停牌|状态|结束;空格可省略"
)
else:
bet = 0
if kind in {"prize_draw", "lottery"} or kind == "blackjack" and action == "start":
if not 2 <= bet <= MAX_BET:
return "下注必须为 2~100 的整数金币"
person = self.ledger.resolve_person(
str(row["group_fingerprint"]),
str(row["sender"]),
)
player_label = self.display_label(person["display_name"])
def blackjack_reply(text: str) -> str:
return f"{player_label}:{text}"
def execute(account: dict[str, Any]) -> str:
expired = ""
@@ -97,11 +130,15 @@ class PlayerGames:
f"已结算胜 {account['wins']} / 负 {account['losses']} / 平 {account['ties']}。"
"首次赠送1000仅发一次;/游戏帮助 查看规则")
if kind == "blackjack" and action in {"hit", "stand", "status", "end"}:
return expired + self.play_blackjack(account, action)
return blackjack_reply(
expired + self.play_blackjack(account, action)
)
if account.get("game"):
return expired + "你已有一局21点,请先 /21点 停牌 或 /21点 结束"
message = expired + "你已有一局21点,请先 /21点 停牌 或 /21点 结束"
return blackjack_reply(message) if kind == "blackjack" else message
if account["balance"] < bet:
return expired + f"金币不足:余额 {account['balance']},本次需要 {bet}"
message = expired + f"金币不足:余额 {account['balance']},本次需要 {bet}"
return blackjack_reply(message) if kind == "blackjack" else message
account["balance"] -= bet
if kind == "blackjack":
deck = list(range(52))
@@ -110,8 +147,10 @@ class PlayerGames:
"dealer": deck[2:4], "updated_at": time.time()}
account["game"] = game
if hand_value(game["player"]) == 21 or hand_value(game["dealer"]) == 21:
return expired + self.finish_blackjack(account, natural=True)
return expired + self.show_hand(game)
return blackjack_reply(
expired + self.finish_blackjack(account, natural=True)
)
return blackjack_reply(expired + self.show_hand(game))
if kind == "prize_draw":
roll = self.rng.randrange(100)
payout = bet * (5 if roll < 5 else 2 if roll < 25 else 0)