Skip to content

Commit 20672de

Browse files
committed
feat: 添加LLM插件系统和游戏控制命令
1 parent 471224f commit 20672de

File tree

12 files changed

+1603
-15
lines changed

12 files changed

+1603
-15
lines changed

src/main.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
# 插件系统(新)
1919
from plugin_sdk import GameServerBridge
2020
from plugin_manager.app_paths import get_env_for_subprocess
21-
from shared_types.commands import NewGameCommand
21+
from shared_types.commands import NewGameCommand, MouseClickCommand
22+
from shared_types.enums import GameLevel
2223
import subprocess
2324

2425
os.environ["QT_FONT_DPI"] = "96"
@@ -202,12 +203,34 @@ def cli_check_file(file_path: str) -> int:
202203

203204
# 注册控制命令处理器(自动在主线程执行)
204205
def handle_new_game(cmd: NewGameCommand):
205-
print(f"[NewGameCommand] rows={cmd.rows}, cols={cmd.cols}, mines={cmd.mines}")
206-
ui.setBoard_and_start(cmd.rows, cmd.cols, cmd.mines)
206+
"""处理新游戏命令"""
207207
from lib_zmq_plugins.shared.base import CommandResponse
208+
209+
# 根据 level 确定参数
210+
if cmd.level == GameLevel.BEGINNER.value:
211+
rows, cols, mines = 8, 8, 10
212+
elif cmd.level == GameLevel.INTERMEDIATE.value:
213+
rows, cols, mines = 16, 16, 40
214+
elif cmd.level == GameLevel.EXPERT.value:
215+
rows, cols, mines = 16, 30, 99
216+
else:
217+
# 自定义模式,使用传入的参数
218+
rows, cols, mines = cmd.rows, cmd.cols, cmd.mines
219+
220+
print(f"[NewGameCommand] level={cmd.level}, rows={rows}, cols={cols}, mines={mines}")
221+
ui.setBoard_and_start(rows, cols, mines)
208222
return CommandResponse(request_id=cmd.request_id, success=True)
209223

224+
def handle_mouse_click(cmd: MouseClickCommand):
225+
"""处理鼠标点击命令"""
226+
from lib_zmq_plugins.shared.base import CommandResponse
227+
228+
print(f"[MouseClickCommand] row={cmd.row}, col={cmd.col}, button={cmd.button}")
229+
success = ui.execute_cell_click(cmd.row, cmd.col, cmd.button)
230+
return CommandResponse(request_id=cmd.request_id, success=success)
231+
210232
GameServerBridge.instance().register_handler(NewGameCommand, handle_new_game)
233+
GameServerBridge.instance().register_handler(MouseClickCommand, handle_mouse_click)
211234

212235
# _translate = QtCore.QCoreApplication.translate
213236
hwnd = int(ui.mainWindow.winId())

src/mineSweeperGUI.py

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# from PyQt5.QtWidgets import QApplication, QFileDialog, QWidget
88
import gameDefinedParameter
99
from plugin_sdk.server_bridge import GameServerBridge
10-
from shared_types.events import VideoSaveEvent
10+
from shared_types.events import VideoSaveEvent, BoardUpdateEvent, GameStatusChangeEvent
1111
import superGUI
1212
import gameAbout
1313
import gameSettings
@@ -219,6 +219,8 @@ def game_state(self):
219219
@game_state.setter
220220
def game_state(self, game_state: str):
221221
# print(self._game_state, " -> " ,game_state)
222+
last_state = self._game_state
223+
222224
match self._game_state:
223225
case "playing":
224226
self.try_append_evfs(game_state)
@@ -252,7 +254,29 @@ def game_state(self, game_state: str):
252254
self.label.paint_cursor = False
253255
self.label.paintProbability = False
254256
self.num_bar_ui.QWidget.close()
257+
255258
self._game_state = game_state
259+
260+
# 发送游戏状态变化事件
261+
state_map = {
262+
"ready": 1,
263+
"playing": 2,
264+
"win": 3,
265+
"fail": 4,
266+
"joking": 2, # joking 也视为游戏中
267+
"jowin": 3,
268+
"jofail": 4,
269+
"show": 5,
270+
"study": 6,
271+
"display": 7,
272+
"showdisplay": 8,
273+
}
274+
if last_state != game_state:
275+
event = GameStatusChangeEvent(
276+
last_status=state_map.get(last_state, 0),
277+
current_status=state_map.get(game_state, 0),
278+
)
279+
GameServerBridge.instance().send_event(event)
256280

257281
@property
258282
def row(self):
@@ -477,6 +501,100 @@ def mineNumWheel(self, i):
477501
# self.timer_mine_num.setSingleShot(True)
478502
# self.timer_mine_num.start(3000)
479503

504+
def _send_board_update_event(self):
505+
"""发送棋盘更新事件给插件"""
506+
try:
507+
ms_board = self.label.ms_board
508+
# 将 game_board 转换为列表格式
509+
game_board_list = []
510+
for row in ms_board.game_board:
511+
game_board_list.append(list(row))
512+
513+
event = BoardUpdateEvent(
514+
rows=self.row,
515+
cols=self.column,
516+
game_board=game_board_list,
517+
mines_remaining=self.mineUnFlagedNum,
518+
game_time=ms_board.time if hasattr(ms_board, 'time') else 0.0,
519+
)
520+
GameServerBridge.instance().send_event(event)
521+
except Exception:
522+
pass # 忽略发送失败
523+
524+
def execute_cell_click(self, row: int, col: int, button: int) -> bool:
525+
"""
526+
执行格子点击(供外部命令调用)
527+
528+
Args:
529+
row: 行索引(从 0 开始)
530+
col: 列索引(从 0 开始)
531+
button: 鼠标按钮(0=左键, 2=右键)
532+
533+
Returns:
534+
True 表示成功执行
535+
"""
536+
# 检查游戏状态
537+
if self.game_state not in ('ready', 'playing', 'joking'):
538+
return False
539+
540+
# 检查坐标有效性
541+
if row < 0 or row >= self.row or col < 0 or col >= self.column:
542+
return False
543+
544+
# 转换为像素坐标(中心点)
545+
i = row * self.pixSize + self.pixSize // 2
546+
j = col * self.pixSize + self.pixSize // 2
547+
548+
try:
549+
if button == 0: # 左键
550+
# 模拟点击流程:按下 -> 抬起
551+
self.label.ms_board.step('lc', (i, j))
552+
553+
# 处理第一次点击埋雷
554+
if self.game_state == 'ready':
555+
if self.label.ms_board.mouse_state == 4:
556+
if self.board_constraint:
557+
self.game_state = 'joking'
558+
else:
559+
self.game_state = 'playing'
560+
if self.player_identifier[:6] != "[live]":
561+
self.disable_screenshot()
562+
if self.cursor_limit:
563+
self.limit_cursor()
564+
self.start_time_unix_2 = QtCore.QDateTime.currentDateTime().toMSecsSinceEpoch()
565+
self.timer_10ms.start()
566+
self.score_board_manager.editing_row = -2
567+
# 埋雷
568+
self.layMine(row, col)
569+
570+
self.label.ms_board.step('lr', (i, j))
571+
572+
# 检查游戏结束
573+
if self.label.ms_board.game_board_state == 3:
574+
self.gameWin()
575+
elif self.label.ms_board.game_board_state == 4:
576+
self.gameFailed()
577+
578+
elif button == 2: # 右键
579+
# 更新剩余雷数
580+
cell_state = self.label.ms_board.game_board[row][col]
581+
if cell_state == 11: # 已标旗,取消标旗
582+
self.mineUnFlagedNum += 1
583+
self.showMineNum(self.mineUnFlagedNum)
584+
elif cell_state == 10: # 未揭开,标旗
585+
self.mineUnFlagedNum -= 1
586+
self.showMineNum(self.mineUnFlagedNum)
587+
588+
self.label.ms_board.step('rc', (i, j))
589+
self.label.ms_board.step('rr', (i, j))
590+
591+
self.label.update()
592+
self._send_board_update_event()
593+
return True
594+
595+
except Exception:
596+
return False
597+
480598
def gameStart(self):
481599
# 画界面,但是不埋雷。等价于点脸、f2、设置确定后的效果
482600
self.mineUnFlagedNum = self.minenum # 没有标出的雷,显示在左上角
@@ -570,6 +688,9 @@ def gameFinished(self):
570688
data[key] = getattr(ms_board, key)
571689
event = VideoSaveEvent(**data)
572690
GameServerBridge.instance().send_event(event)
691+
692+
# 发送棋盘更新事件,让插件知道最终状态
693+
self._send_board_update_event()
573694

574695
def gameWin(self): # 成功后改脸和状态变量,停时间
575696
self.timer_10ms.stop()

src/mineSweeperGUIEvent.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def mineAreaLeftRelease(self, i, j):
6969
return
7070
else:
7171
self.label.update()
72+
self._send_board_update_event()
7273
self.set_face(14)
7374
elif self.game_state == 'playing' or self.game_state == 'joking':
7475
# 如果是游戏中,且是左键抬起(不是双击),且是在10上,且在局面内,则用ai劫持、处理下
@@ -88,6 +89,7 @@ def mineAreaLeftRelease(self, i, j):
8889
self.label.update()
8990
return
9091
self.label.update()
92+
self._send_board_update_event()
9193
self.set_face(14)
9294

9395
elif self.game_state == 'show':
@@ -121,6 +123,7 @@ def mineAreaRightRelease(self, i, j):
121123
self.chording_ai(i // self.pixSize, j // self.pixSize)
122124
self.label.ms_board.step('rr', (i, j))
123125
self.label.update()
126+
self._send_board_update_event()
124127
self.set_face(14)
125128
elif self.game_state == 'show':
126129
# 看概率时,所有操作都移出局面外
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
llm_minesweeper_controller - 使用requests进行反向控制扫雷并处理func calling的插件
3+
"""
4+
from __future__ import annotations
5+
6+
from .plugin import LlmMinesweeperControllerPlugin
7+
8+
__all__ = ["LlmMinesweeperControllerPlugin"]

0 commit comments

Comments
 (0)