This commit is contained in:
@@ -88,6 +88,7 @@ PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
|
|||||||
/贷款 [金额](/loan amount);/还款 [金额|全部](/repay)
|
/贷款 [金额](/loan amount);/还款 [金额|全部](/repay)
|
||||||
/彩票 开始|报名|当前状态|开奖|取消(/lottery)
|
/彩票 开始|报名|当前状态|开奖|取消(/lottery)
|
||||||
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
||||||
|
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
|
||||||
/游戏记录 [页码](/gamehistory)
|
/游戏记录 [页码](/gamehistory)
|
||||||
/游戏帮助(/games)
|
/游戏帮助(/games)
|
||||||
/自走棋开始|报名|开赛|状态|档案
|
/自走棋开始|报名|开赛|状态|档案
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ descriptor inventory; plugin code delegates to reviewed service profiles.
|
|||||||
| `/21点 停牌` | `/blackjack stand` | Stand and settle against the dealer. |
|
| `/21点 停牌` | `/blackjack stand` | Stand and settle against the dealer. |
|
||||||
| `/21点 状态` | `/blackjack status` | Show the current blackjack round. |
|
| `/21点 状态` | `/blackjack status` | Show the current blackjack round. |
|
||||||
| `/21点 结束` | `/blackjack end` | End the current blackjack round. |
|
| `/21点 结束` | `/blackjack end` | End the current blackjack round. |
|
||||||
|
| `/21点 多人 启动` | - | Open a Taffy-hosted blackjack table and auto-seat the host. |
|
||||||
|
| `/21点 加入` | - | Join the current blackjack table before betting starts. |
|
||||||
|
| `/21点 多人 开始` | - | Host-only close joining and start ordered betting prompts. |
|
||||||
|
| `/21点 放金额` | - | Place the prompted table stake, 1-10000 coins. |
|
||||||
| `/游戏记录 [页码]` | `/gamehistory` | Show indexed personal game history. |
|
| `/游戏记录 [页码]` | `/gamehistory` | Show indexed personal game history. |
|
||||||
| `/游戏帮助` | `/games` | Show local game rules. |
|
| `/游戏帮助` | `/games` | Show local game rules. |
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ alpha 0.6.3 将旧的个人即时彩票替换为群内多人轮次。三位数
|
|||||||
仅针对本人牌局。发牌、状态和结算回复以当前群内人物的配置展示名开头,
|
仅针对本人牌局。发牌、状态和结算回复以当前群内人物的配置展示名开头,
|
||||||
例如 `jacky #1:你的牌 ...`;别名解析到同一人物时沿用相同展示名、
|
例如 `jacky #1:你的牌 ...`;别名解析到同一人物时沿用相同展示名、
|
||||||
账户、局号和游戏记录。
|
账户、局号和游戏记录。
|
||||||
|
|
||||||
|
多人 21 点使用 `/21点 多人 启动` 创建 Taffy 牌桌,开桌者自动入座。
|
||||||
|
其他玩家在下注前用 `/21点 加入` 进入当前牌桌;主持人发送 `/21点 多人 开始`
|
||||||
|
后,Taffy 会按座位顺序提示每个人 `/21点 放200` 放筹码,范围 1~10000。
|
||||||
|
所有玩家下注后统一发牌,轮到本人时用 `/21点 要牌` 或 `/21点 停牌` 操作;
|
||||||
|
结算按座位顺序列出每个人的下注、返还、净变化和当前金币。
|
||||||
自然 21 点立即结算,返还 2.5 倍下注向下取整;普通胜利返还两倍,
|
自然 21 点立即结算,返还 2.5 倍下注向下取整;普通胜利返还两倍,
|
||||||
平局返还下注,失败不返还。每人最多一局;牌局进行中不接受另一笔
|
平局返还下注,失败不返还。每人最多一局;牌局进行中不接受另一笔
|
||||||
抽奖或 21 点开局,多人彩票报名仍可独立进行。
|
抽奖或 21 点开局,多人彩票报名仍可独立进行。
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# coin-taffy
|
||||||
|
|
||||||
|
Author: `jwebot`
|
||||||
|
|
||||||
|
Taffy金币
|
||||||
|
|
||||||
|
Help:
|
||||||
|
|
||||||
|
- `/Taffy金币`
|
||||||
|
|
||||||
|
Registered commands:
|
||||||
|
|
||||||
|
- `/Taffy金币`
|
||||||
|
- `/taffycoins`
|
||||||
|
|
||||||
|
The adapter has no direct Ledger, GUI, environment, shell, or network access.
|
||||||
|
It delegates to the reviewed `coin-taffy` Kernel service profile
|
||||||
|
and returns a structured `ReplyPlan`.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
id = "coin-taffy"
|
||||||
|
author = "jwebot"
|
||||||
|
description = "Taffy金币"
|
||||||
|
help = ["/Taffy金币"]
|
||||||
|
version = "1.0.0"
|
||||||
|
api_version = "1"
|
||||||
|
entrypoint = "coin_taffy:plugin"
|
||||||
|
type = "command"
|
||||||
|
|
||||||
|
[capabilities]
|
||||||
|
commands = ["/Taffy金币", "/taffycoins"]
|
||||||
|
storage = false
|
||||||
|
network_profiles = []
|
||||||
|
service_profiles = ["coin-taffy"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""Generated reviewed adapter for coin-taffy."""
|
||||||
|
|
||||||
|
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
|
PROFILE = 'coin-taffy'
|
||||||
|
AUTHOR = 'jwebot'
|
||||||
|
DESCRIPTION = 'Taffy金币'
|
||||||
|
HELP_ENTRIES = ('/Taffy金币',)
|
||||||
|
COMMANDS = ('/Taffy金币', '/taffycoins')
|
||||||
|
|
||||||
|
|
||||||
|
def _handler(context: SingleGroupContext, command: str):
|
||||||
|
def handle(event: Event, arguments: str) -> ExtensionResult:
|
||||||
|
response = context.services.call(
|
||||||
|
PROFILE,
|
||||||
|
operation="execute",
|
||||||
|
payload={
|
||||||
|
"command": command,
|
||||||
|
"arguments": arguments,
|
||||||
|
"event": event.to_mapping(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
texts = response.get("texts")
|
||||||
|
if isinstance(texts, list) and texts and all(
|
||||||
|
isinstance(item, str) and item.strip() for item in texts
|
||||||
|
):
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.multi_text(texts),
|
||||||
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
|
)
|
||||||
|
text = response.get("text")
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
raise ValueError("local service returned invalid text")
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.text(text),
|
||||||
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
|
)
|
||||||
|
|
||||||
|
return handle
|
||||||
|
|
||||||
|
|
||||||
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
|
for command in COMMANDS:
|
||||||
|
context.commands.register(command, _handler(context, command))
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
SOURCE = Path(__file__).resolve().parents[1] / "src"
|
||||||
|
sys.path.insert(0, str(SOURCE))
|
||||||
|
|
||||||
|
from coin_taffy import AUTHOR, DESCRIPTION, HELP_ENTRIES, plugin
|
||||||
|
|
||||||
|
|
||||||
|
class PluginTests(unittest.TestCase):
|
||||||
|
def test_registers_declared_commands_and_author(self):
|
||||||
|
context = mock.Mock()
|
||||||
|
plugin(context)
|
||||||
|
registered = [
|
||||||
|
call.args[0]
|
||||||
|
for call in context.commands.register.call_args_list
|
||||||
|
]
|
||||||
|
self.assertEqual(AUTHOR, 'jwebot')
|
||||||
|
self.assertEqual(DESCRIPTION, 'Taffy金币')
|
||||||
|
self.assertEqual(HELP_ENTRIES, ('/Taffy金币',))
|
||||||
|
self.assertIn('/Taffy金币', registered)
|
||||||
|
self.assertEqual(len(registered), 2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -9,8 +9,7 @@ Help:
|
|||||||
- `/金币 /wallet;/游戏记录 /gamehistory`
|
- `/金币 /wallet;/游戏记录 /gamehistory`
|
||||||
- `/抽奖 [金额];/draw`
|
- `/抽奖 [金额];/draw`
|
||||||
- `/彩票 开始|报名|状态|开奖|取消;/lottery`
|
- `/彩票 开始|报名|状态|开奖|取消;/lottery`
|
||||||
- `/21点 开始 [1~500];/blackjack start`
|
- `/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200`
|
||||||
- `/21点 要牌|停牌|状态|结束;/blackjack`
|
|
||||||
- `/游戏帮助;/games`
|
- `/游戏帮助;/games`
|
||||||
|
|
||||||
Registered commands:
|
Registered commands:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "single-player-games"
|
id = "single-player-games"
|
||||||
author = "jwebot"
|
author = "jwebot"
|
||||||
description = "金币游戏"
|
description = "金币游戏"
|
||||||
help = ["/金币 /wallet;/游戏记录 /gamehistory", "/抽奖 [金额];/draw", "/彩票 开始|报名|状态|开奖|取消;/lottery", "/21点 开始 [1~500];/blackjack start", "/21点 要牌|停牌|状态|结束;/blackjack", "/游戏帮助;/games"]
|
help = ["/金币 /wallet;/游戏记录 /gamehistory", "/抽奖 [金额];/draw", "/彩票 开始|报名|状态|开奖|取消;/lottery", "/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200", "/游戏帮助;/games"]
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
api_version = "1"
|
api_version = "1"
|
||||||
entrypoint = "single_player_games:plugin"
|
entrypoint = "single_player_games:plugin"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
|||||||
PROFILE = 'single-player-games'
|
PROFILE = 'single-player-games'
|
||||||
AUTHOR = 'jwebot'
|
AUTHOR = 'jwebot'
|
||||||
DESCRIPTION = '金币游戏'
|
DESCRIPTION = '金币游戏'
|
||||||
HELP_ENTRIES = ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 开始 [1~500];/blackjack start', '/21点 要牌|停牌|状态|结束;/blackjack', '/游戏帮助;/games')
|
HELP_ENTRIES = ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200', '/游戏帮助;/games')
|
||||||
COMMANDS = ('/金币', '/wallet', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏记录', '/个人记录', '/gamehistory', '/游戏帮助', '/games')
|
COMMANDS = ('/金币', '/wallet', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏记录', '/个人记录', '/gamehistory', '/游戏帮助', '/games')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class PluginTests(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(AUTHOR, 'jwebot')
|
self.assertEqual(AUTHOR, 'jwebot')
|
||||||
self.assertEqual(DESCRIPTION, '金币游戏')
|
self.assertEqual(DESCRIPTION, '金币游戏')
|
||||||
self.assertEqual(HELP_ENTRIES, ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 开始 [1~500];/blackjack start', '/21点 要牌|停牌|状态|结束;/blackjack', '/游戏帮助;/games'))
|
self.assertEqual(HELP_ENTRIES, ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200', '/游戏帮助;/games'))
|
||||||
self.assertIn('/金币', registered)
|
self.assertIn('/金币', registered)
|
||||||
self.assertEqual(len(registered), 15)
|
self.assertEqual(len(registered), 15)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Author: `official`
|
Author: `official`
|
||||||
|
|
||||||
提取当前群最近100、200或300条本地文字的上下文
|
上下文总结
|
||||||
|
|
||||||
Help:
|
Help:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
id = "context-summary"
|
id = "context-summary"
|
||||||
author = "official"
|
author = "official"
|
||||||
description = "提取当前群最近100、200或300条本地文字的上下文"
|
description = "上下文总结"
|
||||||
help = ["/上下文 [100|200|300];/context [100|200|300]"]
|
help = ["/上下文 [100|200|300];/context [100|200|300]"]
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
api_version = "1"
|
api_version = "1"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
|||||||
|
|
||||||
PROFILE = 'context-summary'
|
PROFILE = 'context-summary'
|
||||||
AUTHOR = 'official'
|
AUTHOR = 'official'
|
||||||
DESCRIPTION = '提取当前群最近100、200或300条本地文字的上下文'
|
DESCRIPTION = '上下文总结'
|
||||||
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
||||||
COMMANDS = ('/上下文', '/context')
|
COMMANDS = ('/上下文', '/context')
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class PluginTests(unittest.TestCase):
|
|||||||
for call in context.commands.register.call_args_list
|
for call in context.commands.register.call_args_list
|
||||||
]
|
]
|
||||||
self.assertEqual(AUTHOR, 'official')
|
self.assertEqual(AUTHOR, 'official')
|
||||||
self.assertEqual(DESCRIPTION, '提取当前群最近100、200或300条本地文字的上下文')
|
self.assertEqual(DESCRIPTION, '上下文总结')
|
||||||
self.assertEqual(HELP_ENTRIES, ('/上下文 [100|200|300];/context [100|200|300]',))
|
self.assertEqual(HELP_ENTRIES, ('/上下文 [100|200|300];/context [100|200|300]',))
|
||||||
self.assertIn('/上下文', registered)
|
self.assertIn('/上下文', registered)
|
||||||
self.assertEqual(len(registered), 2)
|
self.assertEqual(len(registered), 2)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+45
@@ -0,0 +1,45 @@
|
|||||||
|
"""Generated reviewed adapter for coin-taffy."""
|
||||||
|
|
||||||
|
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
||||||
|
|
||||||
|
|
||||||
|
PROFILE = 'coin-taffy'
|
||||||
|
AUTHOR = 'jwebot'
|
||||||
|
DESCRIPTION = 'Taffy金币'
|
||||||
|
HELP_ENTRIES = ('/Taffy金币',)
|
||||||
|
COMMANDS = ('/Taffy金币', '/taffycoins')
|
||||||
|
|
||||||
|
|
||||||
|
def _handler(context: SingleGroupContext, command: str):
|
||||||
|
def handle(event: Event, arguments: str) -> ExtensionResult:
|
||||||
|
response = context.services.call(
|
||||||
|
PROFILE,
|
||||||
|
operation="execute",
|
||||||
|
payload={
|
||||||
|
"command": command,
|
||||||
|
"arguments": arguments,
|
||||||
|
"event": event.to_mapping(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
texts = response.get("texts")
|
||||||
|
if isinstance(texts, list) and texts and all(
|
||||||
|
isinstance(item, str) and item.strip() for item in texts
|
||||||
|
):
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.multi_text(texts),
|
||||||
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
|
)
|
||||||
|
text = response.get("text")
|
||||||
|
if not isinstance(text, str) or not text.strip():
|
||||||
|
raise ValueError("local service returned invalid text")
|
||||||
|
return ExtensionResult(
|
||||||
|
reply=ReplyPlan.text(text),
|
||||||
|
metadata={"local_extension": PROFILE, "author": AUTHOR},
|
||||||
|
)
|
||||||
|
|
||||||
|
return handle
|
||||||
|
|
||||||
|
|
||||||
|
def plugin(context: SingleGroupContext) -> None:
|
||||||
|
for command in COMMANDS:
|
||||||
|
context.commands.register(command, _handler(context, command))
|
||||||
+1
-1
@@ -5,7 +5,7 @@ from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
|||||||
|
|
||||||
PROFILE = 'context-summary'
|
PROFILE = 'context-summary'
|
||||||
AUTHOR = 'official'
|
AUTHOR = 'official'
|
||||||
DESCRIPTION = '提取当前群最近100、200或300条本地文字的上下文'
|
DESCRIPTION = '上下文总结'
|
||||||
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
HELP_ENTRIES = ('/上下文 [100|200|300];/context [100|200|300]',)
|
||||||
COMMANDS = ('/上下文', '/context')
|
COMMANDS = ('/上下文', '/context')
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
|||||||
PROFILE = 'single-player-games'
|
PROFILE = 'single-player-games'
|
||||||
AUTHOR = 'jwebot'
|
AUTHOR = 'jwebot'
|
||||||
DESCRIPTION = '金币游戏'
|
DESCRIPTION = '金币游戏'
|
||||||
HELP_ENTRIES = ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 开始 [1~500];/blackjack start', '/21点 要牌|停牌|状态|结束;/blackjack', '/游戏帮助;/games')
|
HELP_ENTRIES = ('/金币 /wallet;/游戏记录 /gamehistory', '/抽奖 [金额];/draw', '/彩票 开始|报名|状态|开奖|取消;/lottery', '/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200', '/游戏帮助;/games')
|
||||||
COMMANDS = ('/金币', '/wallet', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏记录', '/个人记录', '/gamehistory', '/游戏帮助', '/games')
|
COMMANDS = ('/金币', '/wallet', '/抽奖', '/draw', '/彩票', '/买彩票', '/lottery', '/21点', '/二十一点', '/blackjack', '/游戏记录', '/个人记录', '/gamehistory', '/游戏帮助', '/games')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ LOCAL_COMMAND_ALIASES = {
|
|||||||
"金币排行榜": "coin_ranking",
|
"金币排行榜": "coin_ranking",
|
||||||
"大富翁榜": "coin_ranking",
|
"大富翁榜": "coin_ranking",
|
||||||
"richlist": "coin_ranking",
|
"richlist": "coin_ranking",
|
||||||
|
"Taffy金币": "taffy_coins",
|
||||||
|
"taffycoins": "taffy_coins",
|
||||||
"转账": "transfer",
|
"转账": "transfer",
|
||||||
"transfer": "transfer",
|
"transfer": "transfer",
|
||||||
"贷款": "loan",
|
"贷款": "loan",
|
||||||
@@ -143,7 +145,7 @@ LOCAL_EXTENSIONS = (
|
|||||||
LocalExtensionDescriptor(
|
LocalExtensionDescriptor(
|
||||||
extension_id="context-summary",
|
extension_id="context-summary",
|
||||||
author=OFFICIAL_AUTHOR,
|
author=OFFICIAL_AUTHOR,
|
||||||
description="提取当前群最近100、200或300条本地文字的上下文",
|
description="上下文总结",
|
||||||
help_entries=("/上下文 [100|200|300];/context [100|200|300]",),
|
help_entries=("/上下文 [100|200|300];/context [100|200|300]",),
|
||||||
command_kinds=("context_summary",),
|
command_kinds=("context_summary",),
|
||||||
),
|
),
|
||||||
@@ -155,8 +157,7 @@ LOCAL_EXTENSIONS = (
|
|||||||
"/金币 /wallet;/游戏记录 /gamehistory",
|
"/金币 /wallet;/游戏记录 /gamehistory",
|
||||||
"/抽奖 [金额];/draw",
|
"/抽奖 [金额];/draw",
|
||||||
"/彩票 开始|报名|状态|开奖|取消;/lottery",
|
"/彩票 开始|报名|状态|开奖|取消;/lottery",
|
||||||
"/21点 开始 [1~500];/blackjack start",
|
"/21点 单人开始|要牌|停牌|状态;多人启动|加入|开始|放200",
|
||||||
"/21点 要牌|停牌|状态|结束;/blackjack",
|
|
||||||
"/游戏帮助;/games",
|
"/游戏帮助;/games",
|
||||||
),
|
),
|
||||||
command_kinds=(
|
command_kinds=(
|
||||||
@@ -182,6 +183,13 @@ LOCAL_EXTENSIONS = (
|
|||||||
help_entries=("/大富翁榜",),
|
help_entries=("/大富翁榜",),
|
||||||
command_kinds=("coin_ranking",),
|
command_kinds=("coin_ranking",),
|
||||||
),
|
),
|
||||||
|
LocalExtensionDescriptor(
|
||||||
|
extension_id="coin-taffy",
|
||||||
|
author=JWEBOT_AUTHOR,
|
||||||
|
description="Taffy金币",
|
||||||
|
help_entries=("/Taffy金币",),
|
||||||
|
command_kinds=("taffy_coins",),
|
||||||
|
),
|
||||||
LocalExtensionDescriptor(
|
LocalExtensionDescriptor(
|
||||||
extension_id="coin-loan",
|
extension_id="coin-loan",
|
||||||
author=JWEBOT_AUTHOR,
|
author=JWEBOT_AUTHOR,
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ class LocalCommandEngine:
|
|||||||
if kind in {
|
if kind in {
|
||||||
"wallet",
|
"wallet",
|
||||||
"coin_ranking",
|
"coin_ranking",
|
||||||
|
"taffy_coins",
|
||||||
"transfer",
|
"transfer",
|
||||||
"loan",
|
"loan",
|
||||||
"repay",
|
"repay",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ MAX_LOTTERY_BET = 500
|
|||||||
DEFAULT_LOTTERY_BET = 10
|
DEFAULT_LOTTERY_BET = 10
|
||||||
MIN_BLACKJACK_BET = 1
|
MIN_BLACKJACK_BET = 1
|
||||||
MAX_BLACKJACK_BET = 500
|
MAX_BLACKJACK_BET = 500
|
||||||
|
MIN_BLACKJACK_TABLE_BET = 1
|
||||||
|
MAX_BLACKJACK_TABLE_BET = 10_000
|
||||||
|
MAX_BLACKJACK_TABLE_PLAYERS = 8
|
||||||
MIN_TRANSFER_AMOUNT = 1
|
MIN_TRANSFER_AMOUNT = 1
|
||||||
MAX_TRANSFER_AMOUNT = 100_000
|
MAX_TRANSFER_AMOUNT = 100_000
|
||||||
MIN_LOAN_AMOUNT = 1
|
MIN_LOAN_AMOUNT = 1
|
||||||
@@ -45,6 +48,10 @@ BLACKJACK_COMBINED_ACTION = re.compile(
|
|||||||
r"^(?:开始|start)(?P<bet>\d{1,3})?"
|
r"^(?:开始|start)(?P<bet>\d{1,3})?"
|
||||||
r"(?:要牌|hit|停牌|stand)$"
|
r"(?:要牌|hit|停牌|stand)$"
|
||||||
)
|
)
|
||||||
|
BLACKJACK_TABLE_BET = re.compile(
|
||||||
|
r"^(?:放|下注|bet)(?P<bet>\d{1,5})$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
TRANSFER_MARKED = re.compile(
|
TRANSFER_MARKED = re.compile(
|
||||||
r"^(?:给|to)?\s*(?P<target>.+?)\s*"
|
r"^(?:给|to)?\s*(?P<target>.+?)\s*"
|
||||||
r"(?:金额|金币|coin|coins|:|:)\s*(?P<amount>\d{1,6})$",
|
r"(?:金额|金币|coin|coins|:|:)\s*(?P<amount>\d{1,6})$",
|
||||||
@@ -161,6 +168,9 @@ class PlayerGames:
|
|||||||
"看到牌后再单独发送 /21点 要牌|停牌|状态|结束"
|
"看到牌后再单独发送 /21点 要牌|停牌|状态|结束"
|
||||||
"(/blackjack hit|stand|status|end);"
|
"(/blackjack hit|stand|status|end);"
|
||||||
"每人最多一局,10分钟无操作视为认输,结束不退下注。\n"
|
"每人最多一局,10分钟无操作视为认输,结束不退下注。\n"
|
||||||
|
"多人21点:/21点 多人 启动 创建牌桌;/21点 加入;"
|
||||||
|
"主持人 /21点 多人 开始 后按提示 /21点 放200"
|
||||||
|
"(1~10000);轮到自己 /21点 要牌|停牌。\n"
|
||||||
"21点获胜返2倍、平局返下注、自然21点返2.5倍向下取整,庄家软17停牌。"
|
"21点获胜返2倍、平局返下注、自然21点返2.5倍向下取整,庄家软17停牌。"
|
||||||
"以上均含本金;虚拟金币不能充值或兑换。"
|
"以上均含本金;虚拟金币不能充值或兑换。"
|
||||||
)
|
)
|
||||||
@@ -177,6 +187,8 @@ class PlayerGames:
|
|||||||
return self.group_lottery(row, arguments)
|
return self.group_lottery(row, arguments)
|
||||||
if kind == "coin_ranking":
|
if kind == "coin_ranking":
|
||||||
return self.coin_ranking(row, arguments)
|
return self.coin_ranking(row, arguments)
|
||||||
|
if kind == "taffy_coins":
|
||||||
|
return self.taffy_coins(row, arguments)
|
||||||
if kind == "transfer":
|
if kind == "transfer":
|
||||||
return self.transfer(row, arguments)
|
return self.transfer(row, arguments)
|
||||||
if kind in {"loan", "repay"}:
|
if kind in {"loan", "repay"}:
|
||||||
@@ -188,6 +200,15 @@ class PlayerGames:
|
|||||||
return "用法:/抽奖 [2~100金币](/draw),默认10金币"
|
return "用法:/抽奖 [2~100金币](/draw),默认10金币"
|
||||||
bet = int(arguments or 10)
|
bet = int(arguments or 10)
|
||||||
elif kind == "blackjack":
|
elif kind == "blackjack":
|
||||||
|
table_command = self.parse_blackjack_table_command(arguments)
|
||||||
|
if table_command is not None:
|
||||||
|
table_action, table_bet = table_command
|
||||||
|
return self.group_blackjack(
|
||||||
|
row,
|
||||||
|
table_action,
|
||||||
|
table_bet,
|
||||||
|
arguments,
|
||||||
|
)
|
||||||
compacted = re.sub(r"\s+", "", arguments.casefold()) or "状态"
|
compacted = re.sub(r"\s+", "", arguments.casefold()) or "状态"
|
||||||
start = BLACKJACK_START.fullmatch(compacted)
|
start = BLACKJACK_START.fullmatch(compacted)
|
||||||
if start is not None:
|
if start is not None:
|
||||||
@@ -209,6 +230,28 @@ class PlayerGames:
|
|||||||
"用法:先 /21点 开始 [1~500](金额是本局下注,默认50);"
|
"用法:先 /21点 开始 [1~500](金额是本局下注,默认50);"
|
||||||
"开局后再单独发送 /21点 要牌|停牌|状态|结束;空格可省略"
|
"开局后再单独发送 /21点 要牌|停牌|状态|结束;空格可省略"
|
||||||
)
|
)
|
||||||
|
group = str(row["group_fingerprint"])
|
||||||
|
if (
|
||||||
|
action in {"hit", "stand", "status"}
|
||||||
|
and self.ledger.has_active_group_blackjack(group)
|
||||||
|
):
|
||||||
|
return self.group_blackjack(row, action, 0, arguments)
|
||||||
|
if (
|
||||||
|
action == "start"
|
||||||
|
and self.ledger.has_active_group_blackjack(group)
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
"当前已有Taffy的21点多人牌桌;"
|
||||||
|
"发送 /21点 状态 查看,或等待本桌结束"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
action == "end"
|
||||||
|
and self.ledger.has_active_group_blackjack(group)
|
||||||
|
):
|
||||||
|
return (
|
||||||
|
"当前是多人21点牌桌;轮到你时请 /21点 停牌,"
|
||||||
|
"主持人可 /21点 多人 取消"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
bet = 0
|
bet = 0
|
||||||
if kind == "prize_draw" and not MIN_DRAW_BET <= bet <= MAX_DRAW_BET:
|
if kind == "prize_draw" and not MIN_DRAW_BET <= bet <= MAX_DRAW_BET:
|
||||||
@@ -341,6 +384,451 @@ class PlayerGames:
|
|||||||
)
|
)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def taffy_coins(self, row: dict[str, Any], arguments: str) -> str:
|
||||||
|
value = arguments.strip()
|
||||||
|
if value and not re.fullmatch(r"\d{1,2}", value):
|
||||||
|
return "用法:/Taffy金币 [人数]"
|
||||||
|
limit = int(value or 8)
|
||||||
|
if not 1 <= limit <= 20:
|
||||||
|
return "Taffy金币人数必须为 1~20"
|
||||||
|
stats = self.ledger.player_taffy_coin_stats(
|
||||||
|
str(row["group_fingerprint"]),
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
if int(stats["records"]) == 0:
|
||||||
|
return "Taffy金币:当前群暂无已结算游戏记录"
|
||||||
|
net = int(stats["taffy_net"])
|
||||||
|
result = f"Taffy累计赢 {net} 金币" if net >= 0 else f"Taffy累计倒贴 {-net} 金币"
|
||||||
|
lines = [
|
||||||
|
(
|
||||||
|
f"Taffy金币:已结算 {stats['records']} 局;"
|
||||||
|
f"玩家总下注 {stats['stake']},总返还 {stats['payout']};"
|
||||||
|
f"{result}"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if stats["players"]:
|
||||||
|
lines.append("最大上贡者排行(含负数):")
|
||||||
|
else:
|
||||||
|
lines.append("最大上贡者排行:暂无已结算玩家明细")
|
||||||
|
for index, record in enumerate(stats["players"], start=1):
|
||||||
|
player_net = int(record["taffy_net"])
|
||||||
|
if player_net > 0:
|
||||||
|
verdict = f"上贡 {player_net}"
|
||||||
|
elif player_net < 0:
|
||||||
|
verdict = f"从Taffy赢走 {-player_net}"
|
||||||
|
else:
|
||||||
|
verdict = "打平 0"
|
||||||
|
lines.append(
|
||||||
|
f"{index}. {record['display_name']}:{verdict};"
|
||||||
|
f"下注 {record['stake']},返还 {record['payout']},"
|
||||||
|
f"{record['records']} 局"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_blackjack_table_command(
|
||||||
|
arguments: str,
|
||||||
|
) -> tuple[str, int] | None:
|
||||||
|
compacted = re.sub(r"\s+", "", arguments.casefold())
|
||||||
|
if not compacted:
|
||||||
|
return None
|
||||||
|
if compacted in {"多人", "牌桌", "table", "multiplayer"}:
|
||||||
|
return "help", 0
|
||||||
|
if compacted in {
|
||||||
|
"多人启动",
|
||||||
|
"多人开桌",
|
||||||
|
"多人创建",
|
||||||
|
"牌桌启动",
|
||||||
|
"牌桌开桌",
|
||||||
|
"tablestart",
|
||||||
|
"multiplayerstart",
|
||||||
|
}:
|
||||||
|
return "start", 0
|
||||||
|
if compacted in {"加入", "join"}:
|
||||||
|
return "join", 0
|
||||||
|
if compacted in {
|
||||||
|
"多人开始",
|
||||||
|
"多人发牌",
|
||||||
|
"开始多人",
|
||||||
|
"开桌",
|
||||||
|
"发牌",
|
||||||
|
"tabledeal",
|
||||||
|
"multiplayerdeal",
|
||||||
|
}:
|
||||||
|
return "begin", 0
|
||||||
|
if compacted in {
|
||||||
|
"多人取消",
|
||||||
|
"牌桌取消",
|
||||||
|
"取消牌桌",
|
||||||
|
"tablecancel",
|
||||||
|
"multiplayercancel",
|
||||||
|
}:
|
||||||
|
return "cancel", 0
|
||||||
|
bet = BLACKJACK_TABLE_BET.fullmatch(compacted)
|
||||||
|
if bet is not None:
|
||||||
|
return "bet", int(bet["bet"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def blackjack_players_text(players: list[dict[str, Any]]) -> str:
|
||||||
|
names = [str(player["display_name"]) for player in players]
|
||||||
|
return "、".join(names) if names else "暂无"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def blackjack_hand_text(hand: list[int]) -> str:
|
||||||
|
if not hand:
|
||||||
|
return "未发牌"
|
||||||
|
return (
|
||||||
|
f"{' '.join(card_label(card) for card in hand)}"
|
||||||
|
f"({hand_value(hand)}点)"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def blackjack_prompt(player: dict[str, Any]) -> str:
|
||||||
|
return (
|
||||||
|
f"请{player['display_name']} 放自己的筹码\n"
|
||||||
|
"输入/21点 放200(1~10000)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def group_blackjack(
|
||||||
|
self,
|
||||||
|
row: dict[str, Any],
|
||||||
|
action: str,
|
||||||
|
amount: int,
|
||||||
|
arguments: str,
|
||||||
|
) -> str:
|
||||||
|
group = str(row["group_fingerprint"])
|
||||||
|
sender = str(row["sender"])
|
||||||
|
event_id = str(row["event_id"])
|
||||||
|
command = f"blackjack-table:{arguments}"
|
||||||
|
if action == "help":
|
||||||
|
return (
|
||||||
|
"多人21点用法:/21点 多人 启动;/21点 加入;"
|
||||||
|
"主持人 /21点 多人 开始;按提示 /21点 放200"
|
||||||
|
"(1~10000);轮到自己 /21点 要牌 或 /21点 停牌"
|
||||||
|
)
|
||||||
|
if action == "start":
|
||||||
|
return self.ledger.start_group_blackjack(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
max_players=MAX_BLACKJACK_TABLE_PLAYERS,
|
||||||
|
render=self.render_blackjack_table_start,
|
||||||
|
)
|
||||||
|
if action == "join":
|
||||||
|
return self.ledger.join_group_blackjack(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
max_players=MAX_BLACKJACK_TABLE_PLAYERS,
|
||||||
|
render=self.render_blackjack_table_join,
|
||||||
|
)
|
||||||
|
if action == "begin":
|
||||||
|
return self.ledger.begin_group_blackjack_betting(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
self.render_blackjack_table_begin,
|
||||||
|
)
|
||||||
|
if action == "cancel":
|
||||||
|
return self.ledger.cancel_group_blackjack(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
self.render_blackjack_table_cancel,
|
||||||
|
)
|
||||||
|
if action == "status":
|
||||||
|
return self.ledger.group_blackjack_status(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
self.render_blackjack_table_status,
|
||||||
|
)
|
||||||
|
if action == "bet":
|
||||||
|
if not MIN_BLACKJACK_TABLE_BET <= amount <= MAX_BLACKJACK_TABLE_BET:
|
||||||
|
return "21点多人牌桌筹码必须为 1~10000 的整数金币"
|
||||||
|
deck = list(range(52))
|
||||||
|
self.rng.shuffle(deck)
|
||||||
|
return self.ledger.place_group_blackjack_bet(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
amount,
|
||||||
|
deck,
|
||||||
|
self.render_blackjack_table_bet,
|
||||||
|
)
|
||||||
|
if action in {"hit", "stand"}:
|
||||||
|
return self.ledger.play_group_blackjack(
|
||||||
|
group,
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
command,
|
||||||
|
action,
|
||||||
|
self.render_blackjack_table_action,
|
||||||
|
)
|
||||||
|
raise ValueError(f"unsupported blackjack table action: {action}")
|
||||||
|
|
||||||
|
def render_blackjack_table_start(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "private_game":
|
||||||
|
return (
|
||||||
|
f"{data['display_name']}:你已有单人21点,"
|
||||||
|
"请先 /21点 停牌 或 /21点 结束"
|
||||||
|
)
|
||||||
|
players = data.get("players", [])
|
||||||
|
prefix = (
|
||||||
|
"当前已有Taffy 的黑杰克Blackjack 21点牌桌"
|
||||||
|
if status == "already_open"
|
||||||
|
else "欢迎来到Taffy 的黑杰克Blackjack 21点牌桌"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"{prefix}\n"
|
||||||
|
"输入/21点 加入 加入当前游戏!"
|
||||||
|
f"目前玩家:{self.blackjack_players_text(players)}\n"
|
||||||
|
"主持人发送 /21点 多人 开始 后,"
|
||||||
|
"Taffy会按顺序喊每个人放筹码"
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_blackjack_table_join(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "no_table":
|
||||||
|
return "当前没有21点多人牌桌;发送 /21点 多人 启动"
|
||||||
|
if status == "closed":
|
||||||
|
return "这桌已经停止加入;发送 /21点 状态 查看当前进度"
|
||||||
|
if status == "full":
|
||||||
|
return f"这桌已满,最多 {data['max_players']} 人"
|
||||||
|
if status == "private_game":
|
||||||
|
return (
|
||||||
|
f"{data['display_name']}:你已有单人21点,"
|
||||||
|
"请先 /21点 停牌 或 /21点 结束"
|
||||||
|
)
|
||||||
|
if status == "already_joined":
|
||||||
|
return (
|
||||||
|
f"{data['display_name']} 已经在这桌;"
|
||||||
|
f"目前玩家:{self.blackjack_players_text(data['players'])}"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"{data['display_name']} 已加入Taffy的21点牌桌;"
|
||||||
|
f"目前玩家:{self.blackjack_players_text(data['players'])}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_blackjack_table_begin(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "no_table":
|
||||||
|
return "当前没有21点多人牌桌;发送 /21点 多人 启动"
|
||||||
|
if status == "not_host":
|
||||||
|
return f"只有本桌主持人 {data['host_display_name']} 可以结束加入"
|
||||||
|
if status == "not_joining":
|
||||||
|
return "这桌已经结束加入;发送 /21点 状态 查看当前进度"
|
||||||
|
if status == "not_enough":
|
||||||
|
return (
|
||||||
|
"至少需要 2 名玩家才能开始多人21点;"
|
||||||
|
f"目前玩家:{self.blackjack_players_text(data['players'])}"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
"加入结束,Taffy 按顺序收筹码。\n"
|
||||||
|
f"{self.blackjack_prompt(data['prompt_player'])}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_blackjack_table_status(self, data: dict[str, Any]) -> str:
|
||||||
|
if data["status"] == "no_table":
|
||||||
|
return "当前没有21点多人牌桌;发送 /21点 多人 启动"
|
||||||
|
state = str(data["state"])
|
||||||
|
table_number = int(data["table_number"])
|
||||||
|
players = data["players"]
|
||||||
|
if state == "joining":
|
||||||
|
return (
|
||||||
|
f"21点牌桌第 {table_number} 桌正在加入;"
|
||||||
|
f"目前玩家:{self.blackjack_players_text(players)}\n"
|
||||||
|
"输入/21点 加入 加入当前游戏;"
|
||||||
|
"主持人 /21点 多人 开始"
|
||||||
|
)
|
||||||
|
if state == "betting":
|
||||||
|
prompt = next(
|
||||||
|
(
|
||||||
|
player for player in players
|
||||||
|
if str(player["person_key"]) == str(data["turn_person_key"])
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
lines = [f"21点牌桌第 {table_number} 桌正在下注:"]
|
||||||
|
for player in players:
|
||||||
|
bet = int(player["bet"])
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} #{player['player_game_index']}:"
|
||||||
|
f"{bet if bet else '未放'}"
|
||||||
|
)
|
||||||
|
if prompt is not None:
|
||||||
|
lines.append(self.blackjack_prompt(prompt))
|
||||||
|
return "\n".join(lines)
|
||||||
|
if state == "playing":
|
||||||
|
prompt = next(
|
||||||
|
(
|
||||||
|
player for player in players
|
||||||
|
if str(player["person_key"]) == str(data["turn_person_key"])
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
dealer = data.get("dealer", [])
|
||||||
|
dealer_text = (
|
||||||
|
f"{card_label(dealer[0])} + 暗牌"
|
||||||
|
if dealer
|
||||||
|
else "未发牌"
|
||||||
|
)
|
||||||
|
lines = [
|
||||||
|
f"21点牌桌第 {table_number} 桌正在出牌;Taffy {dealer_text}"
|
||||||
|
]
|
||||||
|
for player in players:
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} #{player['player_game_index']}:"
|
||||||
|
f"{self.blackjack_hand_text(player['hand'])};"
|
||||||
|
f"下注 {player['bet']};{player['state']}"
|
||||||
|
)
|
||||||
|
if prompt is not None:
|
||||||
|
lines.append(
|
||||||
|
f"请{prompt['display_name']} 操作:"
|
||||||
|
"/21点 要牌 或 /21点 停牌"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
return "当前没有进行中的21点多人牌桌"
|
||||||
|
|
||||||
|
def render_blackjack_table_bet(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "no_table":
|
||||||
|
return "当前没有21点多人牌桌;发送 /21点 多人 启动"
|
||||||
|
if status == "not_betting":
|
||||||
|
return "现在不是放筹码阶段;发送 /21点 状态 查看当前进度"
|
||||||
|
if status == "not_player":
|
||||||
|
return "你不在当前21点牌桌;下一桌可发送 /21点 加入"
|
||||||
|
if status == "wrong_turn":
|
||||||
|
return self.blackjack_prompt(data["prompt_player"])
|
||||||
|
if status == "insufficient":
|
||||||
|
return (
|
||||||
|
f"{data['display_name']}:金币不足,余额 {data['balance']},"
|
||||||
|
f"本次需要 {data['bet']}"
|
||||||
|
)
|
||||||
|
if status == "bet_accepted":
|
||||||
|
return (
|
||||||
|
f"{data['display_name']} 放下 {data['bet']};"
|
||||||
|
f"金币 {data['balance']}\n"
|
||||||
|
f"{self.blackjack_prompt(data['prompt_player'])}"
|
||||||
|
)
|
||||||
|
if status == "dealt":
|
||||||
|
return self.render_blackjack_table_dealt(data)
|
||||||
|
if status == "settled":
|
||||||
|
return self.render_blackjack_table_settlement(data)
|
||||||
|
raise ValueError(f"unsupported blackjack bet status: {status}")
|
||||||
|
|
||||||
|
def render_blackjack_table_dealt(self, data: dict[str, Any]) -> str:
|
||||||
|
dealer = data.get("dealer", [])
|
||||||
|
dealer_text = (
|
||||||
|
f"{card_label(dealer[0])} + 暗牌"
|
||||||
|
if dealer
|
||||||
|
else "未发牌"
|
||||||
|
)
|
||||||
|
lines = [
|
||||||
|
(
|
||||||
|
f"{data['display_name']} 放下 {data['bet']};"
|
||||||
|
"全部筹码已确认。Taffy 发牌。"
|
||||||
|
),
|
||||||
|
f"Taffy 明牌 {dealer_text}",
|
||||||
|
]
|
||||||
|
for player in data["players"]:
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} #{player['player_game_index']}:"
|
||||||
|
f"{self.blackjack_hand_text(player['hand'])};"
|
||||||
|
f"下注 {player['bet']}"
|
||||||
|
)
|
||||||
|
prompt = data["prompt_player"]
|
||||||
|
lines.append(
|
||||||
|
f"请{prompt['display_name']} 操作:/21点 要牌 或 /21点 停牌"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def render_blackjack_table_action(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "no_table":
|
||||||
|
return "当前没有21点多人牌桌;发送 /21点 多人 启动"
|
||||||
|
if status == "not_playing":
|
||||||
|
return "现在不是出牌阶段;发送 /21点 状态 查看当前进度"
|
||||||
|
if status == "not_player":
|
||||||
|
return "你不在当前21点牌桌;下一桌可发送 /21点 加入"
|
||||||
|
if status == "wrong_turn":
|
||||||
|
prompt = data["prompt_player"]
|
||||||
|
return (
|
||||||
|
f"现在轮到 {prompt['display_name']};"
|
||||||
|
"/21点 要牌 或 /21点 停牌"
|
||||||
|
)
|
||||||
|
if status == "not_actionable":
|
||||||
|
return "你这一手已经结束;等待本桌结算"
|
||||||
|
if status == "settled":
|
||||||
|
return self.render_blackjack_table_settlement(data)
|
||||||
|
player = data["player"]
|
||||||
|
lines: list[str] = []
|
||||||
|
if status == "hit":
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} 要牌,拿到 "
|
||||||
|
f"{card_label(data['drawn_card'])};"
|
||||||
|
f"当前 {self.blackjack_hand_text(player['hand'])}"
|
||||||
|
)
|
||||||
|
elif status == "busted":
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} 要牌,拿到 "
|
||||||
|
f"{card_label(data['drawn_card'])};"
|
||||||
|
f"{self.blackjack_hand_text(player['hand'])} 爆牌"
|
||||||
|
)
|
||||||
|
elif status == "stood":
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} 停牌;"
|
||||||
|
f"{self.blackjack_hand_text(player['hand'])}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unsupported blackjack action status: {status}")
|
||||||
|
prompt = data["prompt_player"]
|
||||||
|
lines.append(
|
||||||
|
f"请{prompt['display_name']} 操作:/21点 要牌 或 /21点 停牌"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def render_blackjack_table_settlement(self, data: dict[str, Any]) -> str:
|
||||||
|
dealer = data.get("dealer", [])
|
||||||
|
dealer_text = self.blackjack_hand_text(dealer)
|
||||||
|
lines = [
|
||||||
|
(
|
||||||
|
"Taffy 的黑杰克Blackjack 21点牌桌"
|
||||||
|
f"第 {data['table_number']} 桌结算:"
|
||||||
|
),
|
||||||
|
f"Taffy 庄家 {dealer_text}",
|
||||||
|
"这一桌每个人的变化:",
|
||||||
|
]
|
||||||
|
for player in data["players"]:
|
||||||
|
bet = int(player["bet"])
|
||||||
|
payout = int(player["payout"])
|
||||||
|
lines.append(
|
||||||
|
f"{player['display_name']} #{player['player_game_index']}:"
|
||||||
|
f"{player['outcome']};牌 {self.blackjack_hand_text(player['hand'])};"
|
||||||
|
f"下注 {bet},返还 {payout},净变化 {payout - bet:+d};"
|
||||||
|
f"金币 {player['balance']}"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def render_blackjack_table_cancel(self, data: dict[str, Any]) -> str:
|
||||||
|
status = data["status"]
|
||||||
|
if status == "no_table":
|
||||||
|
return "当前没有21点多人牌桌"
|
||||||
|
if status == "not_host":
|
||||||
|
return f"只有本桌主持人 {data['host_display_name']} 可以取消"
|
||||||
|
return (
|
||||||
|
f"21点牌桌第 {data['table_number']} 桌已取消;"
|
||||||
|
f"{data['refunded']} 人的下注已退回"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_transfer(
|
def parse_transfer(
|
||||||
arguments: str,
|
arguments: str,
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class LocalExtensionInventoryTests(unittest.TestCase):
|
|||||||
"coin": JWEBOT_AUTHOR,
|
"coin": JWEBOT_AUTHOR,
|
||||||
"coin-transfer": JWEBOT_AUTHOR,
|
"coin-transfer": JWEBOT_AUTHOR,
|
||||||
"coin-ranking": JWEBOT_AUTHOR,
|
"coin-ranking": JWEBOT_AUTHOR,
|
||||||
|
"coin-taffy": JWEBOT_AUTHOR,
|
||||||
"coin-loan": JWEBOT_AUTHOR,
|
"coin-loan": JWEBOT_AUTHOR,
|
||||||
"random-number": JWEBOT_AUTHOR,
|
"random-number": JWEBOT_AUTHOR,
|
||||||
"draw-lot": JWEBOT_AUTHOR,
|
"draw-lot": JWEBOT_AUTHOR,
|
||||||
|
|||||||
@@ -176,6 +176,56 @@ class Release063GameTests(unittest.TestCase):
|
|||||||
self.assertIn("下注 500,返还 500", history)
|
self.assertIn("下注 500,返还 500", history)
|
||||||
self.assertIn("1~500", self.command("/21点开始501"))
|
self.assertIn("1~500", self.command("/21点开始501"))
|
||||||
|
|
||||||
|
def test_multiplayer_blackjack_table_guides_turns_and_settles(self):
|
||||||
|
self.assertIn("黑杰克Blackjack 21点牌桌", self.command("/21点 多人 启动"))
|
||||||
|
self.assertIn("目前玩家:alice", self.command("/21点 状态"))
|
||||||
|
self.assertIn("bob 已加入", self.command("/21点 加入", sender="Bob"))
|
||||||
|
self.assertIn("请alice 放自己的筹码", self.command("/21点 多人 开始"))
|
||||||
|
|
||||||
|
alice_bet = self.command("/21点 放200", event_id="alice-table-bet")
|
||||||
|
replay = self.command(
|
||||||
|
"/21点 放200",
|
||||||
|
sender="a1",
|
||||||
|
event_id="alice-table-bet",
|
||||||
|
)
|
||||||
|
self.assertEqual(replay, alice_bet)
|
||||||
|
self.assertIn("请bob 放自己的筹码", alice_bet)
|
||||||
|
self.assertEqual(self.state()["balance"], 800)
|
||||||
|
|
||||||
|
def table_deck(cards):
|
||||||
|
first = [12, 8, 9, 24, 19, 32, 25]
|
||||||
|
cards[:] = first + [card for card in cards if card not in first]
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
self.engine.local.players.rng,
|
||||||
|
"shuffle",
|
||||||
|
side_effect=table_deck,
|
||||||
|
):
|
||||||
|
dealt = self.command("/21点 放100", sender="Bob")
|
||||||
|
self.assertIn("全部筹码已确认", dealt)
|
||||||
|
self.assertIn("alice #1:K Q(20点);下注 200", dealt)
|
||||||
|
self.assertIn("bob #1:9 7(16点);下注 100", dealt)
|
||||||
|
self.assertIn("请alice 操作", dealt)
|
||||||
|
self.assertEqual(self.state("Bob")["balance"], 900)
|
||||||
|
|
||||||
|
alice_stand = self.command("/21点 停牌")
|
||||||
|
self.assertIn("alice 停牌", alice_stand)
|
||||||
|
self.assertIn("请bob 操作", alice_stand)
|
||||||
|
|
||||||
|
settled = self.command("/21点 要牌", sender="Bob")
|
||||||
|
self.assertIn("这一桌每个人的变化", settled)
|
||||||
|
self.assertIn("alice #1:你赢了", settled)
|
||||||
|
self.assertIn("下注 200,返还 400,净变化 +200;金币 1200", settled)
|
||||||
|
self.assertIn("bob #1:你输了", settled)
|
||||||
|
self.assertIn("下注 100,返还 0,净变化 -100;金币 900", settled)
|
||||||
|
self.assertEqual(self.state()["balance"], 1200)
|
||||||
|
self.assertEqual(self.state("Bob")["balance"], 900)
|
||||||
|
self.assertIn("alice #1:21点 你赢了", self.command("/游戏记录"))
|
||||||
|
self.assertIn(
|
||||||
|
"bob #1:21点 你输了",
|
||||||
|
self.command("/游戏记录", sender="Bob"),
|
||||||
|
)
|
||||||
|
|
||||||
def test_prize_draw_has_indexed_history(self):
|
def test_prize_draw_has_indexed_history(self):
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
self.engine.local.players.rng,
|
self.engine.local.players.rng,
|
||||||
@@ -262,6 +312,29 @@ class Release063GameTests(unittest.TestCase):
|
|||||||
self.assertNotIn("Dave", ranking)
|
self.assertNotIn("Dave", ranking)
|
||||||
self.assertIn("金币排行人数必须为 1~20", self.command("/金币排行 0"))
|
self.assertIn("金币排行人数必须为 1~20", self.command("/金币排行 0"))
|
||||||
|
|
||||||
|
def test_taffy_coin_stats_sums_all_game_history(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.engine.local.players.rng,
|
||||||
|
"randrange",
|
||||||
|
return_value=99,
|
||||||
|
):
|
||||||
|
self.command("/抽奖10")
|
||||||
|
with mock.patch.object(
|
||||||
|
self.engine.local.players.rng,
|
||||||
|
"randrange",
|
||||||
|
return_value=0,
|
||||||
|
):
|
||||||
|
self.command("/抽奖10", sender="Bob")
|
||||||
|
|
||||||
|
stats = self.command("/Taffy金币")
|
||||||
|
|
||||||
|
self.assertIn("玩家总下注 20,总返还 50", stats)
|
||||||
|
self.assertIn("Taffy累计倒贴 30 金币", stats)
|
||||||
|
self.assertIn("最大上贡者排行(含负数):", stats)
|
||||||
|
self.assertIn("alice:上贡 10", stats)
|
||||||
|
self.assertIn("bob:从Taffy赢走 40", stats)
|
||||||
|
self.assertIn("Taffy金币人数必须为 1~20", self.command("/Taffy金币 0"))
|
||||||
|
|
||||||
def test_open_round_and_history_survive_restart(self):
|
def test_open_round_and_history_survive_restart(self):
|
||||||
root = Path(self.temp.name)
|
root = Path(self.temp.name)
|
||||||
self.command("/彩票开始")
|
self.command("/彩票开始")
|
||||||
|
|||||||
Reference in New Issue
Block a user