Merge pull request '合并task/gomoku_2025-10-28_1五子棋游戏' (#1) from task/gomoku_2025-10-28_1 into main
Reviewed-on: #1
This commit is contained in:
271
.tasks/2025-10-28_1_gomoku.md
Normal file
271
.tasks/2025-10-28_1_gomoku.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# 背景
|
||||
文件名:2025-10-28_1_gomoku.md
|
||||
创建于:2025-10-28_17:08:29
|
||||
创建者:User
|
||||
主分支:main
|
||||
任务分支:task/gomoku_2025-10-28_1
|
||||
Yolo模式:Off
|
||||
|
||||
# 任务描述
|
||||
创建一个五子棋(Gomoku)游戏模块,支持双人对战功能。
|
||||
|
||||
## 核心需求
|
||||
1. **游戏模式**:双人对战(两个用户在同一个聊天中对战)
|
||||
2. **棋盘规格**:标准15x15棋盘
|
||||
3. **禁手规则**:需要实现禁手规则(三三禁手、四四禁手、长连禁手)
|
||||
4. **超时规则**:不需要回合时间限制
|
||||
5. **并发对战**:允许多轮对战同时存在,只要交战双方不同即可
|
||||
6. **显示方式**:使用emoji绘制棋盘(⚫⚪➕)+ 坐标系统(A-O列,1-15行)
|
||||
|
||||
## 功能清单
|
||||
- 开始游戏:`.gomoku start @对手` 或 `.gomoku @对手`
|
||||
- 落子:`.gomoku A1` 或 `.gomoku 落子 A1`
|
||||
- 认输:`.gomoku resign` 或 `.gomoku 认输`
|
||||
- 查看棋盘:`.gomoku show` 或 `.gomoku 查看`
|
||||
- 查看战绩:`.gomoku stats` 或 `.gomoku 战绩`
|
||||
- 帮助信息:`.gomoku help` 或 `.gomoku 帮助`
|
||||
|
||||
## 技术要点
|
||||
1. 继承`BaseGame`基类
|
||||
2. 游戏状态存储在数据库中(使用chat_id + 对战双方ID作为键)
|
||||
3. 需要实现五子棋禁手规则的判定逻辑
|
||||
4. 需要实现胜负判定(五子连珠)
|
||||
5. 棋盘使用二维数组表示,支持坐标转换(A-O, 1-15)
|
||||
|
||||
# 项目概览
|
||||
|
||||
## 现有架构
|
||||
- **框架**:FastAPI
|
||||
- **数据库**:SQLite(使用标准库sqlite3)
|
||||
- **游戏基类**:`games/base.py - BaseGame`
|
||||
- **路由处理**:`routers/callback.py`
|
||||
- **数据库操作**:`core/database.py - Database类`
|
||||
|
||||
## 现有游戏
|
||||
- 石头剪刀布(rps)
|
||||
- 问答游戏(quiz)
|
||||
- 猜数字(guess)
|
||||
- 成语接龙(idiom)
|
||||
- 骰娘系统(dice)
|
||||
- 运势占卜(fortune)
|
||||
|
||||
## 数据库表结构
|
||||
1. **users**:用户基本信息
|
||||
2. **game_states**:游戏状态(支持chat_id, user_id, game_type的唯一约束)
|
||||
3. **game_stats**:游戏统计(wins, losses, draws, total_plays)
|
||||
|
||||
# 分析
|
||||
|
||||
## 核心挑战
|
||||
|
||||
### 1. 游戏状态管理
|
||||
- 现有的`game_states`表使用`(chat_id, user_id, game_type)`作为唯一键
|
||||
- 五子棋需要双人对战,需要同时记录两个玩家
|
||||
- 需要设计状态数据结构,存储:
|
||||
- 对战双方ID(player1_id, player2_id)
|
||||
- 当前轮到谁(current_player_id)
|
||||
- 棋盘状态(15x15二维数组)
|
||||
- 游戏状态(waiting, playing, finished)
|
||||
- 胜者ID(winner_id,如果有)
|
||||
|
||||
### 2. 多轮对战并发
|
||||
- 允许同一个chat中有多轮对战,只要对战双方不同
|
||||
- 需要一个机制来标识不同的对战局(可以用对战双方ID的组合)
|
||||
- 状态查询需要能够找到特定用户参与的对战
|
||||
|
||||
### 3. 禁手规则实现
|
||||
禁手规则(仅对黑方,即先手玩家):
|
||||
- **三三禁手**:一手棋同时形成两个或以上的活三
|
||||
- **四四禁手**:一手棋同时形成两个或以上的活四或冲四
|
||||
- **长连禁手**:一手棋形成六子或以上的连珠
|
||||
|
||||
需要实现:
|
||||
- 判断某个位置的四个方向(横、竖、左斜、右斜)的连珠情况
|
||||
- 判断活三、活四、冲四的定义
|
||||
- 在落子时检查是否触发禁手
|
||||
|
||||
### 4. 坐标系统
|
||||
- 列:A-O(15列)
|
||||
- 行:1-15(15行)
|
||||
- 需要坐标转换函数:`parse_coord("A1") -> (0, 0)`
|
||||
- 需要显示转换函数:`format_coord(0, 0) -> "A1"`
|
||||
|
||||
### 5. 棋盘显示
|
||||
使用emoji:
|
||||
- ⚫ 黑子(先手)
|
||||
- ⚪ 白子(后手)
|
||||
- ➕ 空位
|
||||
- 需要添加行号和列号标注
|
||||
|
||||
示例:
|
||||
```
|
||||
A B C D E F G H I J K L M N O
|
||||
1 ➕➕➕➕➕➕➕➕➕➕➕➕➕➕➕
|
||||
2 ➕➕➕➕➕➕➕➕➕➕➕➕➕➕➕
|
||||
3 ➕➕⚫➕➕➕➕➕➕➕➕➕➕➕➕
|
||||
...
|
||||
```
|
||||
|
||||
## 数据结构设计
|
||||
|
||||
### state_data结构
|
||||
```python
|
||||
{
|
||||
"player1_id": 123456, # 黑方(先手)
|
||||
"player2_id": 789012, # 白方(后手)
|
||||
"current_player": 123456, # 当前轮到谁
|
||||
"board": [[0]*15 for _ in range(15)], # 0:空, 1:黑, 2:白
|
||||
"status": "playing", # waiting, playing, finished
|
||||
"winner_id": None, # 胜者ID
|
||||
"moves": [], # 历史落子记录 [(row, col, player_id), ...]
|
||||
"last_move": None # 最后一手 (row, col)
|
||||
}
|
||||
```
|
||||
|
||||
### 游戏状态存储策略
|
||||
- 使用chat_id作为会话ID
|
||||
- 使用较小的user_id作为主键中的user_id(保证唯一性)
|
||||
- 在state_data中存储完整的对战信息
|
||||
- 查询时需要检查用户是否是player1或player2
|
||||
|
||||
# 提议的解决方案
|
||||
|
||||
## 方案选择
|
||||
使用现有的数据库表结构,通过精心设计state_data来支持双人对战。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 游戏类:`games/gomoku.py`
|
||||
继承`BaseGame`,实现以下方法:
|
||||
- `handle()` - 主处理逻辑
|
||||
- `get_help()` - 帮助信息
|
||||
- `_start_game()` - 开始游戏
|
||||
- `_make_move()` - 落子
|
||||
- `_show_board()` - 显示棋盘
|
||||
- `_resign()` - 认输
|
||||
- `_get_stats()` - 查看战绩
|
||||
|
||||
### 2. 五子棋逻辑:单独模块或工具类
|
||||
- `_parse_coord()` - 解析坐标
|
||||
- `_format_coord()` - 格式化坐标
|
||||
- `_render_board()` - 渲染棋盘
|
||||
- `_check_win()` - 检查胜负
|
||||
- `_check_forbidden()` - 检查禁手
|
||||
- `_is_valid_move()` - 检查落子是否合法
|
||||
|
||||
### 3. 禁手检测逻辑
|
||||
实现辅助方法:
|
||||
- `_count_line()` - 统计某方向的连珠情况
|
||||
- `_is_live_three()` - 判断活三
|
||||
- `_is_live_four()` - 判断活四
|
||||
- `_is_rush_four()` - 判断冲四
|
||||
- `_check_three_three()` - 检查三三禁手
|
||||
- `_check_four_four()` - 检查四四禁手
|
||||
- `_check_overline()` - 检查长连禁手
|
||||
|
||||
### 4. 状态管理
|
||||
- 使用`min(player1_id, player2_id)`作为数据库中的user_id
|
||||
- 在state_data中完整存储对战信息
|
||||
- 提供辅助方法查找用户当前参与的游戏
|
||||
|
||||
### 5. 路由注册
|
||||
在`routers/callback.py`的`handle_command()`函数中添加:
|
||||
```python
|
||||
if game_type == 'gomoku':
|
||||
from games.gomoku import GomokuGame
|
||||
game = GomokuGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
```
|
||||
|
||||
### 6. 指令解析
|
||||
在`utils/parser.py`的`CommandParser`类中添加gomoku指令识别
|
||||
|
||||
### 7. 配置更新
|
||||
在`config.py`中添加五子棋相关配置(如果需要)
|
||||
|
||||
# 当前执行步骤:"已完成所有实施步骤"
|
||||
|
||||
# 任务进度
|
||||
|
||||
## [2025-10-28 17:18:21]
|
||||
- 已修改:
|
||||
- config.py - 添加gomoku配置
|
||||
- utils/parser.py - 添加gomoku指令映射
|
||||
- games/gomoku_logic.py - 创建五子棋逻辑模块(新文件)
|
||||
- games/gomoku.py - 创建五子棋游戏类(新文件)
|
||||
- routers/callback.py - 添加gomoku路由
|
||||
- games/base.py - 更新帮助信息和统计信息
|
||||
- 更改:完成五子棋游戏的完整实现,包括:
|
||||
- 群级游戏池管理(支持多轮对战并存)
|
||||
- 标准15x15棋盘
|
||||
- 完整的禁手规则(三三、四四、长连)
|
||||
- 坐标系统(A-O列,1-15行)
|
||||
- emoji棋盘渲染(⚫⚪➕)
|
||||
- 胜负判定
|
||||
- 战绩统计
|
||||
- 原因:实现用户需求的双人对战五子棋游戏
|
||||
- 阻碍因素:用户识别和显示格式错误
|
||||
- 状态:不成功
|
||||
|
||||
## [2025-10-28 17:36:07]
|
||||
- 已修改:
|
||||
- games/gomoku.py - 修复用户识别和显示格式
|
||||
- 更改:
|
||||
- 修复 `_parse_opponent()` 方法,使用正确的WPS @用户格式 `<at user_id="xxx"></at>` 进行解析
|
||||
- 修改所有用户显示,从 `@用户{user_id}` 改为 `<at user_id="{user_id}"></at>`,以正确显示用户的群名称
|
||||
- 涉及修改:开始游戏、落子、显示棋盘、认输、列出对战等所有用户显示位置
|
||||
- 原因:修复用户识别失败和显示错误的问题
|
||||
- 阻碍因素:用户识别仍然失败
|
||||
- 状态:不成功
|
||||
|
||||
## [2025-10-28 17:42:24]
|
||||
- 已修改:
|
||||
- games/gomoku.py - 改进用户ID解析和添加调试日志
|
||||
- routers/callback.py - 增强日志输出
|
||||
- 更改:
|
||||
- 改进 `_parse_opponent()` 方法,支持多种@用户格式(双引号、单引号、不同的标签格式)
|
||||
- 在 `handle()` 方法中添加详细的调试日志(command, args, action, opponent_id)
|
||||
- 改进错误提示,显示实际接收到的参数内容
|
||||
- 将 callback.py 中的消息内容日志级别从 DEBUG 改为 INFO,便于追踪
|
||||
- 原因:进一步诊断用户ID识别失败的问题,添加调试信息帮助定位问题
|
||||
- 阻碍因素:WPS callback不提供被@用户的ID信息
|
||||
- 状态:不成功
|
||||
|
||||
## [2025-10-28 17:55:00]
|
||||
- 已修改:
|
||||
- games/gomoku.py - 重构游戏发起机制,从@用户改为挑战-接受模式
|
||||
- games/base.py - 更新全局帮助信息
|
||||
- routers/callback.py - 添加完整callback数据日志
|
||||
- 更改:
|
||||
- **核心架构变更**:从"@用户发起对战"改为"挑战-接受"机制
|
||||
- 新增 `_create_challenge()` 方法 - 用户发起挑战
|
||||
- 新增 `_accept_challenge()` 方法 - 其他用户接受挑战
|
||||
- 新增 `_cancel_challenge()` 方法 - 取消自己的挑战
|
||||
- 删除 `_parse_opponent()` 方法(不再需要)
|
||||
- 删除 `_start_game()` 方法(由新方法替代)
|
||||
- 更新游戏池数据结构,添加 `challenges` 列表
|
||||
- 更新所有帮助信息和错误提示
|
||||
- 指令变更:
|
||||
- `.gomoku challenge` / `.gomoku start` - 发起挑战
|
||||
- `.gomoku accept` / `.gomoku join` - 接受挑战
|
||||
- `.gomoku cancel` - 取消挑战
|
||||
- 原因:WPS callback消息内容中@用户只是文本形式(如"@揭英飙"),不包含user_id,无法实现@用户发起对战
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
## [2025-10-28 17:56:03]
|
||||
- 已修改:
|
||||
- games/gomoku_logic.py - 修复棋盘对齐问题
|
||||
- 更改:
|
||||
- 优化 `render_board()` 函数的格式化逻辑
|
||||
- 列标题:每个字母后面加一个空格,确保与棋子列对齐
|
||||
- 行号:调整前导空格,从 " {row_num} " 改为 "{row_num} "
|
||||
- 棋子:每个emoji后面加一个空格,行尾去除多余空格
|
||||
- 整体对齐:确保列标题、行号、棋子三者在Markdown代码块中正确对齐
|
||||
- 原因:修复用户反馈的棋盘文本对齐问题
|
||||
- 阻碍因素:无
|
||||
- 状态:未确认
|
||||
|
||||
# 最终审查
|
||||
(待完成后填写)
|
||||
|
||||
@@ -68,5 +68,9 @@ GAME_CONFIG = {
|
||||
"平步青云", "云程发轫", "刃迎缕解", "解甲归田"
|
||||
]
|
||||
},
|
||||
"gomoku": {
|
||||
"max_concurrent_games": 5, # 每个聊天最多同时进行的游戏数
|
||||
"board_size": 15, # 棋盘大小
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,15 @@ def get_help_message() -> str:
|
||||
- `.idiom reject [词语]` - 拒绝词语加入黑名单(仅发起人)
|
||||
- `.idiom blacklist` - 查看黑名单
|
||||
|
||||
### ⚫ 五子棋
|
||||
- `.gomoku challenge` - 发起挑战
|
||||
- `.gomoku accept` - 接受挑战
|
||||
- `.gomoku A1` - 落子
|
||||
- `.gomoku show` - 显示棋盘
|
||||
- `.gomoku resign` - 认输
|
||||
- `.gomoku list` - 列出所有对战
|
||||
- `.gomoku stats` - 查看战绩
|
||||
|
||||
### 其他
|
||||
- `.help` - 显示帮助
|
||||
- `.stats` - 查看个人统计
|
||||
@@ -105,7 +114,8 @@ def get_stats_message(user_id: int) -> str:
|
||||
'rps': '✊ 石头剪刀布',
|
||||
'guess': '🔢 猜数字',
|
||||
'quiz': '📝 问答游戏',
|
||||
'idiom': '🀄 成语接龙'
|
||||
'idiom': '🀄 成语接龙',
|
||||
'gomoku': '⚫ 五子棋'
|
||||
}
|
||||
|
||||
for row in stats:
|
||||
|
||||
567
games/gomoku.py
Normal file
567
games/gomoku.py
Normal file
@@ -0,0 +1,567 @@
|
||||
"""五子棋游戏"""
|
||||
import time
|
||||
import re
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from games.base import BaseGame
|
||||
from games import gomoku_logic as logic
|
||||
from utils.parser import CommandParser
|
||||
from config import GAME_CONFIG
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GomokuGame(BaseGame):
|
||||
"""五子棋游戏"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
super().__init__()
|
||||
self.config = GAME_CONFIG.get('gomoku', {})
|
||||
self.max_concurrent_games = self.config.get('max_concurrent_games', 5)
|
||||
self.board_size = self.config.get('board_size', 15)
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理五子棋指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".gomoku @对手" 或 ".gomoku A1"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip()
|
||||
|
||||
# 调试日志
|
||||
logger.info(f"五子棋指令解析 - command: {command}")
|
||||
logger.info(f"五子棋指令解析 - args: {args}")
|
||||
|
||||
# 没有参数,显示帮助
|
||||
if not args:
|
||||
return self.get_help()
|
||||
|
||||
# 解析参数
|
||||
parts = args.split(maxsplit=1)
|
||||
action = parts[0].lower()
|
||||
logger.info(f"五子棋指令解析 - action: {action}")
|
||||
|
||||
# 帮助
|
||||
if action in ['help', '帮助']:
|
||||
return self.get_help()
|
||||
|
||||
# 发起挑战
|
||||
if action in ['challenge', 'start', '挑战', '开始']:
|
||||
return self._create_challenge(chat_id, user_id)
|
||||
|
||||
# 接受挑战
|
||||
if action in ['accept', 'join', '接受', '加入']:
|
||||
return self._accept_challenge(chat_id, user_id)
|
||||
|
||||
# 取消挑战
|
||||
if action in ['cancel', '取消']:
|
||||
return self._cancel_challenge(chat_id, user_id)
|
||||
|
||||
# 列出所有对战
|
||||
if action in ['list', '列表', '查看']:
|
||||
return self._list_games(chat_id)
|
||||
|
||||
# 查看战绩
|
||||
if action in ['stats', '战绩', '统计']:
|
||||
return self._get_stats(user_id)
|
||||
|
||||
# 显示棋盘
|
||||
if action in ['show', '显示', '棋盘']:
|
||||
return self._show_board(chat_id, user_id)
|
||||
|
||||
# 认输
|
||||
if action in ['resign', '认输', '投降']:
|
||||
return self._resign(chat_id, user_id)
|
||||
|
||||
# 尝试解析为坐标(落子)
|
||||
coord = logic.parse_coord(action)
|
||||
if coord is not None:
|
||||
return self._make_move(chat_id, user_id, action)
|
||||
|
||||
# 未识别的指令
|
||||
logger.warning(f"五子棋未识别的指令 - args: {args}")
|
||||
return f"❌ 未识别的指令:{args}\n\n💡 提示:\n- 发起挑战:`.gomoku challenge`\n- 接受挑战:`.gomoku accept`\n- 落子:`.gomoku A1`\n- 查看帮助:`.gomoku help`"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理五子棋指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _get_game_pool(self, chat_id: int) -> Dict[str, Any]:
|
||||
"""获取游戏池
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
|
||||
Returns:
|
||||
游戏池数据
|
||||
"""
|
||||
state = self.db.get_game_state(chat_id, 0, 'gomoku')
|
||||
if state:
|
||||
return state['state_data']
|
||||
else:
|
||||
return {
|
||||
"games": [],
|
||||
"max_concurrent_games": self.max_concurrent_games
|
||||
}
|
||||
|
||||
def _save_game_pool(self, chat_id: int, pool_data: Dict[str, Any]):
|
||||
"""保存游戏池
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
pool_data: 游戏池数据
|
||||
"""
|
||||
self.db.save_game_state(chat_id, 0, 'gomoku', pool_data)
|
||||
|
||||
def _find_user_game(self, chat_id: int, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""查找用户参与的游戏
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
游戏数据或None
|
||||
"""
|
||||
pool = self._get_game_pool(chat_id)
|
||||
|
||||
for game in pool.get("games", []):
|
||||
if game["status"] == "playing":
|
||||
if game["player_black"] == user_id or game["player_white"] == user_id:
|
||||
return game
|
||||
|
||||
return None
|
||||
|
||||
def _create_challenge(self, chat_id: int, user_id: int) -> str:
|
||||
"""创建挑战
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 发起者ID
|
||||
|
||||
Returns:
|
||||
提示消息
|
||||
"""
|
||||
# 获取游戏池
|
||||
pool = self._get_game_pool(chat_id)
|
||||
games = pool.get("games", [])
|
||||
challenges = pool.get("challenges", [])
|
||||
|
||||
# 检查用户是否已经在对战中
|
||||
user_game = self._find_user_game(chat_id, user_id)
|
||||
if user_game:
|
||||
return "⚠️ 你已经在对战中!\n\n输入 `.gomoku show` 查看棋盘"
|
||||
|
||||
# 检查用户是否已经发起了挑战
|
||||
for challenge in challenges:
|
||||
if challenge["challenger_id"] == user_id:
|
||||
return "⚠️ 你已经发起了一个挑战!\n\n等待其他人接受,或输入 `.gomoku cancel` 取消挑战"
|
||||
|
||||
# 创建挑战
|
||||
current_time = int(time.time())
|
||||
challenge = {
|
||||
"challenger_id": user_id,
|
||||
"created_at": current_time
|
||||
}
|
||||
|
||||
challenges.append(challenge)
|
||||
pool["challenges"] = challenges
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
text = f"## 🎯 五子棋挑战\n\n"
|
||||
text += f"<at user_id=\"{user_id}\"></at> 发起了五子棋挑战!\n\n"
|
||||
text += f"💡 想要应战吗?输入 `.gomoku accept` 接受挑战"
|
||||
|
||||
return text
|
||||
|
||||
def _accept_challenge(self, chat_id: int, user_id: int) -> str:
|
||||
"""接受挑战
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 接受者ID
|
||||
|
||||
Returns:
|
||||
提示消息
|
||||
"""
|
||||
# 获取游戏池
|
||||
pool = self._get_game_pool(chat_id)
|
||||
games = pool.get("games", [])
|
||||
challenges = pool.get("challenges", [])
|
||||
|
||||
if not challenges:
|
||||
return "⚠️ 当前没有挑战可以接受\n\n输入 `.gomoku challenge` 发起挑战"
|
||||
|
||||
# 检查用户是否已经在对战中
|
||||
user_game = self._find_user_game(chat_id, user_id)
|
||||
if user_game:
|
||||
return "⚠️ 你已经在对战中!\n\n输入 `.gomoku show` 查看棋盘"
|
||||
|
||||
# 获取最新的挑战
|
||||
challenge = challenges[-1]
|
||||
challenger_id = challenge["challenger_id"]
|
||||
|
||||
# 不能接受自己的挑战
|
||||
if challenger_id == user_id:
|
||||
return "❌ 不能接受自己的挑战!"
|
||||
|
||||
# 检查是否已达到最大并发数
|
||||
active_games = [g for g in games if g["status"] == "playing"]
|
||||
if len(active_games) >= self.max_concurrent_games:
|
||||
return f"⚠️ 当前聊天已有 {len(active_games)} 局对战,已达到最大并发数限制"
|
||||
|
||||
# 创建游戏
|
||||
current_time = int(time.time())
|
||||
game_id = f"p{challenger_id}_p{user_id}_{current_time}"
|
||||
|
||||
new_game = {
|
||||
"game_id": game_id,
|
||||
"player_black": challenger_id, # 挑战者执黑(先手)
|
||||
"player_white": user_id, # 接受者执白(后手)
|
||||
"current_player": challenger_id, # 黑方先手
|
||||
"board": logic.create_empty_board(),
|
||||
"status": "playing",
|
||||
"winner": None,
|
||||
"moves": [],
|
||||
"last_move": None,
|
||||
"created_at": current_time,
|
||||
"updated_at": current_time
|
||||
}
|
||||
|
||||
games.append(new_game)
|
||||
|
||||
# 移除已接受的挑战
|
||||
challenges.remove(challenge)
|
||||
|
||||
pool["games"] = games
|
||||
pool["challenges"] = challenges
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
text = f"## ⚫ 五子棋对战开始!\n\n"
|
||||
text += f"**黑方(先手)**:<at user_id=\"{challenger_id}\"></at> ⚫\n\n"
|
||||
text += f"**白方(后手)**:<at user_id=\"{user_id}\"></at> ⚪\n\n"
|
||||
text += f"**轮到**:<at user_id=\"{challenger_id}\"></at> ⚫\n\n"
|
||||
text += "💡 提示:\n"
|
||||
text += "- 黑方有禁手规则(三三、四四、长连禁手)\n"
|
||||
text += "- 输入 `.gomoku A1` 在A1位置落子\n"
|
||||
text += "- 输入 `.gomoku show` 查看棋盘"
|
||||
|
||||
return text
|
||||
|
||||
def _cancel_challenge(self, chat_id: int, user_id: int) -> str:
|
||||
"""取消挑战
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
提示消息
|
||||
"""
|
||||
# 获取游戏池
|
||||
pool = self._get_game_pool(chat_id)
|
||||
challenges = pool.get("challenges", [])
|
||||
|
||||
# 查找用户的挑战
|
||||
user_challenge = None
|
||||
for challenge in challenges:
|
||||
if challenge["challenger_id"] == user_id:
|
||||
user_challenge = challenge
|
||||
break
|
||||
|
||||
if not user_challenge:
|
||||
return "⚠️ 你没有发起挑战"
|
||||
|
||||
# 移除挑战
|
||||
challenges.remove(user_challenge)
|
||||
pool["challenges"] = challenges
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
return "✅ 已取消挑战"
|
||||
|
||||
def _make_move(self, chat_id: int, user_id: int, coord: str) -> str:
|
||||
"""落子
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
coord: 坐标字符串
|
||||
|
||||
Returns:
|
||||
结果消息
|
||||
"""
|
||||
# 查找用户的游戏
|
||||
game = self._find_user_game(chat_id, user_id)
|
||||
if not game:
|
||||
return "⚠️ 你当前没有进行中的对战\n\n输入 `.gomoku @对手` 开始新游戏"
|
||||
|
||||
# 检查是否轮到该用户
|
||||
if game["current_player"] != user_id:
|
||||
opponent_id = game["player_white"] if game["player_black"] == user_id else game["player_black"]
|
||||
return f"⚠️ 现在轮到 <at user_id=\"{opponent_id}\"></at> 落子"
|
||||
|
||||
# 解析坐标
|
||||
position = logic.parse_coord(coord)
|
||||
if position is None:
|
||||
return f"❌ 无效的坐标:{coord}\n\n坐标格式如:A1, O15"
|
||||
|
||||
row, col = position
|
||||
|
||||
# 检查位置是否已有棋子
|
||||
if game["board"][row][col] != 0:
|
||||
return f"❌ 位置 {coord.upper()} 已有棋子"
|
||||
|
||||
# 确定当前玩家颜色
|
||||
player = 1 if game["player_black"] == user_id else 2
|
||||
player_name = "黑方" if player == 1 else "白方"
|
||||
player_emoji = "⚫" if player == 1 else "⚪"
|
||||
|
||||
# 检查黑方禁手
|
||||
if player == 1:
|
||||
is_forbidden, forbidden_type = logic.check_forbidden(game["board"], row, col)
|
||||
if is_forbidden:
|
||||
text = f"## ❌ {forbidden_type}!\n\n"
|
||||
text += f"位置 {coord.upper()} 触发禁手,黑方判负!\n\n"
|
||||
text += f"**获胜者**:<at user_id=\"{game['player_white']}\"></at> ⚪ 白方\n\n"
|
||||
text += f"📊 战绩已更新"
|
||||
|
||||
# 更新战绩
|
||||
self.db.update_game_stats(game['player_white'], 'gomoku', win=True)
|
||||
self.db.update_game_stats(game['player_black'], 'gomoku', loss=True)
|
||||
|
||||
# 移除游戏
|
||||
pool = self._get_game_pool(chat_id)
|
||||
pool["games"] = [g for g in pool["games"] if g["game_id"] != game["game_id"]]
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
return text
|
||||
|
||||
# 落子
|
||||
game["board"][row][col] = player
|
||||
game["moves"].append((row, col, player))
|
||||
game["last_move"] = (row, col)
|
||||
game["updated_at"] = int(time.time())
|
||||
|
||||
# 检查是否获胜
|
||||
if logic.check_win(game["board"], row, col, player):
|
||||
text = f"## 🎉 五连珠!游戏结束!\n\n"
|
||||
text += f"**获胜者**:<at user_id=\"{user_id}\"></at> {player_emoji} {player_name}\n\n"
|
||||
|
||||
# 渲染棋盘
|
||||
board_str = logic.render_board(game["board"], game["last_move"])
|
||||
text += f"```\n{board_str}\n```\n\n"
|
||||
|
||||
text += f"📊 战绩已更新"
|
||||
|
||||
# 更新战绩
|
||||
opponent_id = game["player_white"] if player == 1 else game["player_black"]
|
||||
self.db.update_game_stats(user_id, 'gomoku', win=True)
|
||||
self.db.update_game_stats(opponent_id, 'gomoku', loss=True)
|
||||
|
||||
# 移除游戏
|
||||
pool = self._get_game_pool(chat_id)
|
||||
pool["games"] = [g for g in pool["games"] if g["game_id"] != game["game_id"]]
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
return text
|
||||
|
||||
# 切换玩家
|
||||
opponent_id = game["player_white"] if player == 1 else game["player_black"]
|
||||
game["current_player"] = opponent_id
|
||||
opponent_emoji = "⚪" if player == 1 else "⚫"
|
||||
opponent_name = "白方" if player == 1 else "黑方"
|
||||
|
||||
# 更新游戏池
|
||||
pool = self._get_game_pool(chat_id)
|
||||
for i, g in enumerate(pool["games"]):
|
||||
if g["game_id"] == game["game_id"]:
|
||||
pool["games"][i] = game
|
||||
break
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
# 渲染棋盘
|
||||
board_str = logic.render_board(game["board"], game["last_move"])
|
||||
|
||||
text = f"## ✅ 落子成功!\n\n"
|
||||
text += f"**位置**:{coord.upper()} {player_emoji}\n\n"
|
||||
text += f"**轮到**:<at user_id=\"{opponent_id}\"></at> {opponent_emoji} {opponent_name}\n\n"
|
||||
text += f"```\n{board_str}\n```"
|
||||
|
||||
return text
|
||||
|
||||
def _show_board(self, chat_id: int, user_id: int) -> str:
|
||||
"""显示棋盘
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
棋盘显示
|
||||
"""
|
||||
game = self._find_user_game(chat_id, user_id)
|
||||
if not game:
|
||||
return "⚠️ 你当前没有进行中的对战\n\n输入 `.gomoku @对手` 开始新游戏"
|
||||
|
||||
# 渲染棋盘
|
||||
board_str = logic.render_board(game["board"], game["last_move"])
|
||||
|
||||
# 获取当前玩家信息
|
||||
current_id = game["current_player"]
|
||||
current_emoji = "⚫" if game["player_black"] == current_id else "⚪"
|
||||
current_name = "黑方" if game["player_black"] == current_id else "白方"
|
||||
|
||||
text = f"## ⚫ 五子棋对战\n\n"
|
||||
text += f"**黑方**:<at user_id=\"{game['player_black']}\"></at> ⚫\n\n"
|
||||
text += f"**白方**:<at user_id=\"{game['player_white']}\"></at> ⚪\n\n"
|
||||
text += f"**轮到**:<at user_id=\"{current_id}\"></at> {current_emoji} {current_name}\n\n"
|
||||
text += f"**手数**:{len(game['moves'])}\n\n"
|
||||
text += f"```\n{board_str}\n```"
|
||||
|
||||
return text
|
||||
|
||||
def _resign(self, chat_id: int, user_id: int) -> str:
|
||||
"""认输
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
结果消息
|
||||
"""
|
||||
game = self._find_user_game(chat_id, user_id)
|
||||
if not game:
|
||||
return "⚠️ 你当前没有进行中的对战"
|
||||
|
||||
# 确定胜者
|
||||
if game["player_black"] == user_id:
|
||||
winner_id = game["player_white"]
|
||||
loser_name = "黑方"
|
||||
winner_emoji = "⚪"
|
||||
else:
|
||||
winner_id = game["player_black"]
|
||||
loser_name = "白方"
|
||||
winner_emoji = "⚫"
|
||||
|
||||
text = f"## 🏳️ 认输\n\n"
|
||||
text += f"<at user_id=\"{user_id}\"></at> {loser_name} 认输\n\n"
|
||||
text += f"**获胜者**:<at user_id=\"{winner_id}\"></at> {winner_emoji}\n\n"
|
||||
text += f"📊 战绩已更新"
|
||||
|
||||
# 更新战绩
|
||||
self.db.update_game_stats(winner_id, 'gomoku', win=True)
|
||||
self.db.update_game_stats(user_id, 'gomoku', loss=True)
|
||||
|
||||
# 移除游戏
|
||||
pool = self._get_game_pool(chat_id)
|
||||
pool["games"] = [g for g in pool["games"] if g["game_id"] != game["game_id"]]
|
||||
self._save_game_pool(chat_id, pool)
|
||||
|
||||
return text
|
||||
|
||||
def _list_games(self, chat_id: int) -> str:
|
||||
"""列出所有进行中的游戏
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
|
||||
Returns:
|
||||
游戏列表
|
||||
"""
|
||||
pool = self._get_game_pool(chat_id)
|
||||
active_games = [g for g in pool.get("games", []) if g["status"] == "playing"]
|
||||
|
||||
if not active_games:
|
||||
return "📋 当前没有进行中的对战\n\n输入 `.gomoku @对手` 开始新游戏"
|
||||
|
||||
text = f"## 📋 进行中的对战 ({len(active_games)}/{self.max_concurrent_games})\n\n"
|
||||
|
||||
for idx, game in enumerate(active_games, 1):
|
||||
current_emoji = "⚫" if game["player_black"] == game["current_player"] else "⚪"
|
||||
text += f"### {idx}. 对战\n"
|
||||
text += f"- **黑方**:<at user_id=\"{game['player_black']}\"></at> ⚫\n"
|
||||
text += f"- **白方**:<at user_id=\"{game['player_white']}\"></at> ⚪\n"
|
||||
text += f"- **轮到**:<at user_id=\"{game['current_player']}\"></at> {current_emoji}\n"
|
||||
text += f"- **手数**:{len(game['moves'])}\n\n"
|
||||
|
||||
return text
|
||||
|
||||
def _get_stats(self, user_id: int) -> str:
|
||||
"""获取用户战绩
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
战绩信息
|
||||
"""
|
||||
stats = self.db.get_game_stats(user_id, 'gomoku')
|
||||
|
||||
total = stats['total_plays']
|
||||
if total == 0:
|
||||
return "📊 你还没有五子棋对战记录\n\n快来挑战吧!输入 `.gomoku @对手` 开始游戏"
|
||||
|
||||
wins = stats['wins']
|
||||
losses = stats['losses']
|
||||
win_rate = (wins / total * 100) if total > 0 else 0
|
||||
|
||||
text = f"## 📊 五子棋战绩\n\n"
|
||||
text += f"**总局数**:{total} 局\n\n"
|
||||
text += f"**胜利**:{wins} 次 🎉\n\n"
|
||||
text += f"**失败**:{losses} 次\n\n"
|
||||
text += f"**胜率**:<font color='#4CAF50'>{win_rate:.1f}%</font>"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return """## ⚫ 五子棋
|
||||
|
||||
### 基础用法
|
||||
- `.gomoku challenge` - 发起挑战
|
||||
- `.gomoku accept` - 接受挑战
|
||||
- `.gomoku A1` - 在A1位置落子
|
||||
- `.gomoku show` - 显示当前棋盘
|
||||
- `.gomoku resign` - 认输
|
||||
|
||||
### 其他指令
|
||||
- `.gomoku cancel` - 取消自己的挑战
|
||||
- `.gomoku list` - 列出所有进行中的对战
|
||||
- `.gomoku stats` - 查看个人战绩
|
||||
|
||||
### 游戏规则
|
||||
- 标准15×15棋盘,五子连珠获胜
|
||||
- 黑方先手,但有禁手规则:
|
||||
- **三三禁手**:一手棋同时形成两个活三
|
||||
- **四四禁手**:一手棋同时形成两个四(活四或冲四)
|
||||
- **长连禁手**:一手棋形成六子或以上连珠
|
||||
- 触发禁手者判负
|
||||
- 允许多轮对战同时进行(对战双方不同即可)
|
||||
|
||||
### 坐标系统
|
||||
- 列:A-O(15列)
|
||||
- 行:1-15(15行)
|
||||
- 示例:A1(左上角)、O15(右下角)、H8(中心)
|
||||
|
||||
### 示例
|
||||
```
|
||||
.gomoku challenge # 发起挑战
|
||||
.gomoku accept # 接受挑战
|
||||
.gomoku H8 # 在中心位置落子
|
||||
.gomoku show # 查看棋盘
|
||||
.gomoku resign # 认输
|
||||
```
|
||||
|
||||
💡 提示:黑方虽然先手,但需要注意禁手规则
|
||||
"""
|
||||
|
||||
287
games/gomoku_logic.py
Normal file
287
games/gomoku_logic.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""五子棋游戏逻辑模块"""
|
||||
from typing import Optional, Tuple, List, Dict, Any
|
||||
|
||||
|
||||
def create_empty_board() -> List[List[int]]:
|
||||
"""创建空棋盘
|
||||
|
||||
Returns:
|
||||
15x15的二维列表,0表示空位
|
||||
"""
|
||||
return [[0] * 15 for _ in range(15)]
|
||||
|
||||
|
||||
def parse_coord(coord_str: str) -> Optional[Tuple[int, int]]:
|
||||
"""解析坐标字符串
|
||||
|
||||
Args:
|
||||
coord_str: 如 "A1", "O15", "h8"
|
||||
|
||||
Returns:
|
||||
(row, col) 或 None
|
||||
"""
|
||||
coord_str = coord_str.strip().upper()
|
||||
|
||||
if len(coord_str) < 2:
|
||||
return None
|
||||
|
||||
# 解析列(A-O)
|
||||
col_char = coord_str[0]
|
||||
if not ('A' <= col_char <= 'O'):
|
||||
return None
|
||||
col = ord(col_char) - ord('A')
|
||||
|
||||
# 解析行(1-15)
|
||||
try:
|
||||
row = int(coord_str[1:]) - 1
|
||||
if not (0 <= row <= 14):
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
return (row, col)
|
||||
|
||||
|
||||
def format_coord(row: int, col: int) -> str:
|
||||
"""格式化坐标
|
||||
|
||||
Args:
|
||||
row: 0-14
|
||||
col: 0-14
|
||||
|
||||
Returns:
|
||||
如 "A1", "O15"
|
||||
"""
|
||||
col_char = chr(ord('A') + col)
|
||||
row_num = row + 1
|
||||
return f"{col_char}{row_num}"
|
||||
|
||||
|
||||
def is_valid_position(row: int, col: int) -> bool:
|
||||
"""检查坐标是否在棋盘范围内
|
||||
|
||||
Args:
|
||||
row: 行号
|
||||
col: 列号
|
||||
|
||||
Returns:
|
||||
是否有效
|
||||
"""
|
||||
return 0 <= row <= 14 and 0 <= col <= 14
|
||||
|
||||
|
||||
def count_consecutive(board: List[List[int]], row: int, col: int,
|
||||
direction: Tuple[int, int], player: int) -> int:
|
||||
"""统计某方向连续同色棋子数(包括当前位置)
|
||||
|
||||
Args:
|
||||
board: 棋盘状态
|
||||
row, col: 起始位置
|
||||
direction: 方向向量 (dr, dc)
|
||||
player: 玩家 (1:黑, 2:白)
|
||||
|
||||
Returns:
|
||||
连续棋子数
|
||||
"""
|
||||
dr, dc = direction
|
||||
count = 1 # 包括当前位置
|
||||
|
||||
# 正方向
|
||||
r, c = row + dr, col + dc
|
||||
while is_valid_position(r, c) and board[r][c] == player:
|
||||
count += 1
|
||||
r += dr
|
||||
c += dc
|
||||
|
||||
# 反方向
|
||||
r, c = row - dr, col - dc
|
||||
while is_valid_position(r, c) and board[r][c] == player:
|
||||
count += 1
|
||||
r -= dr
|
||||
c -= dc
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def check_win(board: List[List[int]], row: int, col: int, player: int) -> bool:
|
||||
"""检查是否获胜(恰好五连珠)
|
||||
|
||||
Args:
|
||||
board: 棋盘状态
|
||||
row, col: 最后落子位置
|
||||
player: 玩家 (1:黑, 2:白)
|
||||
|
||||
Returns:
|
||||
是否五连珠获胜
|
||||
"""
|
||||
# 四个方向:横、竖、左斜、右斜
|
||||
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
|
||||
|
||||
for direction in directions:
|
||||
count = count_consecutive(board, row, col, direction, player)
|
||||
if count == 5:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def analyze_line(board: List[List[int]], row: int, col: int,
|
||||
direction: Tuple[int, int], player: int) -> Dict[str, Any]:
|
||||
"""分析某方向的棋型
|
||||
|
||||
Args:
|
||||
board: 棋盘状态
|
||||
row, col: 待分析位置(假设已落子)
|
||||
direction: 方向向量
|
||||
player: 玩家
|
||||
|
||||
Returns:
|
||||
{
|
||||
"consecutive": int, # 连续数
|
||||
"left_open": bool, # 左侧是否开放
|
||||
"right_open": bool, # 右侧是否开放
|
||||
"pattern": str # 棋型类型
|
||||
}
|
||||
"""
|
||||
dr, dc = direction
|
||||
|
||||
# 统计正方向连续数
|
||||
right_count = 0
|
||||
r, c = row + dr, col + dc
|
||||
while is_valid_position(r, c) and board[r][c] == player:
|
||||
right_count += 1
|
||||
r += dr
|
||||
c += dc
|
||||
right_open = is_valid_position(r, c) and board[r][c] == 0
|
||||
|
||||
# 统计反方向连续数
|
||||
left_count = 0
|
||||
r, c = row - dr, col - dc
|
||||
while is_valid_position(r, c) and board[r][c] == player:
|
||||
left_count += 1
|
||||
r -= dr
|
||||
c -= dc
|
||||
left_open = is_valid_position(r, c) and board[r][c] == 0
|
||||
|
||||
# 总连续数(包括当前位置)
|
||||
consecutive = left_count + 1 + right_count
|
||||
|
||||
# 判定棋型
|
||||
pattern = "none"
|
||||
|
||||
if consecutive >= 6:
|
||||
pattern = "overline"
|
||||
elif consecutive == 5:
|
||||
pattern = "five"
|
||||
elif consecutive == 4:
|
||||
if left_open and right_open:
|
||||
pattern = "live_four"
|
||||
elif left_open or right_open:
|
||||
pattern = "rush_four"
|
||||
elif consecutive == 3:
|
||||
if left_open and right_open:
|
||||
pattern = "live_three"
|
||||
elif left_open or right_open:
|
||||
pattern = "sleep_three"
|
||||
|
||||
return {
|
||||
"consecutive": consecutive,
|
||||
"left_open": left_open,
|
||||
"right_open": right_open,
|
||||
"pattern": pattern
|
||||
}
|
||||
|
||||
|
||||
def check_forbidden(board: List[List[int]], row: int, col: int) -> Tuple[bool, str]:
|
||||
"""检查黑方禁手
|
||||
|
||||
Args:
|
||||
board: 棋盘状态(不包含待落子)
|
||||
row, col: 待落子位置
|
||||
|
||||
Returns:
|
||||
(是否禁手, 禁手类型)
|
||||
"""
|
||||
# 只有黑方(玩家1)有禁手
|
||||
player = 1
|
||||
|
||||
# 临时落子
|
||||
original_value = board[row][col]
|
||||
board[row][col] = player
|
||||
|
||||
# 四个方向
|
||||
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
|
||||
|
||||
live_threes = 0
|
||||
fours = 0
|
||||
has_overline = False
|
||||
|
||||
for direction in directions:
|
||||
analysis = analyze_line(board, row, col, direction, player)
|
||||
|
||||
if analysis["pattern"] == "overline":
|
||||
has_overline = True
|
||||
elif analysis["pattern"] == "live_three":
|
||||
live_threes += 1
|
||||
elif analysis["pattern"] in ["live_four", "rush_four"]:
|
||||
fours += 1
|
||||
|
||||
# 恢复棋盘
|
||||
board[row][col] = original_value
|
||||
|
||||
# 判定禁手
|
||||
if has_overline:
|
||||
return True, "长连禁手"
|
||||
if live_threes >= 2:
|
||||
return True, "三三禁手"
|
||||
if fours >= 2:
|
||||
return True, "四四禁手"
|
||||
|
||||
return False, ""
|
||||
|
||||
|
||||
def render_board(board: List[List[int]], last_move: Optional[Tuple[int, int]] = None) -> str:
|
||||
"""渲染棋盘为字符串
|
||||
|
||||
Args:
|
||||
board: 棋盘状态
|
||||
last_move: 最后落子位置(可选,用于标记)
|
||||
|
||||
Returns:
|
||||
棋盘的字符串表示
|
||||
"""
|
||||
lines = []
|
||||
|
||||
# 列标题 - 使用全角空格确保对齐
|
||||
col_labels = "\t " + " ".join([chr(ord('A') + i) + "" for i in range(15)])
|
||||
lines.append(col_labels.rstrip())
|
||||
|
||||
# 绘制棋盘
|
||||
for row in range(15):
|
||||
row_num = f"{row + 1:2d}" # 右对齐行号
|
||||
row_cells = []
|
||||
|
||||
for col in range(15):
|
||||
cell = board[row][col]
|
||||
|
||||
# 标记最后落子
|
||||
if last_move and last_move == (row, col):
|
||||
if cell == 1:
|
||||
row_cells.append("⚫")
|
||||
elif cell == 2:
|
||||
row_cells.append("⚪")
|
||||
else:
|
||||
row_cells.append("➕")
|
||||
else:
|
||||
if cell == 0:
|
||||
row_cells.append("➕")
|
||||
elif cell == 1:
|
||||
row_cells.append("⚫")
|
||||
elif cell == 2:
|
||||
row_cells.append("⚪")
|
||||
|
||||
# 每个emoji后面加一个空格
|
||||
lines.append(f"{row_num} " + "".join([cell + " " for cell in row_cells]).rstrip())
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -28,7 +28,8 @@ async def callback_receive(request: Request):
|
||||
# 解析请求数据
|
||||
data = await request.json()
|
||||
logger.info(f"收到消息: chatid={data.get('chatid')}, creator={data.get('creator')}")
|
||||
logger.debug(f"消息内容: {data.get('content')}")
|
||||
logger.info(f"消息内容: {data.get('content')}")
|
||||
logger.info(f"完整callback数据: {data}")
|
||||
|
||||
# 验证请求
|
||||
try:
|
||||
@@ -152,6 +153,12 @@ async def handle_command(game_type: str, command: str,
|
||||
game = IdiomGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
|
||||
# 五子棋
|
||||
if game_type == 'gomoku':
|
||||
from games.gomoku import GomokuGame
|
||||
game = GomokuGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
|
||||
# 未知游戏类型
|
||||
logger.warning(f"未知游戏类型: {game_type}")
|
||||
return "❌ 未知的游戏类型"
|
||||
|
||||
@@ -35,6 +35,11 @@ class CommandParser:
|
||||
'.成语接龙': 'idiom',
|
||||
'.成语': 'idiom',
|
||||
|
||||
# 五子棋
|
||||
'.gomoku': 'gomoku',
|
||||
'.五子棋': 'gomoku',
|
||||
'.gobang': 'gomoku',
|
||||
|
||||
# 帮助
|
||||
'.help': 'help',
|
||||
'.帮助': 'help',
|
||||
|
||||
Reference in New Issue
Block a user