diff --git a/pages-game/game/minesweeper/play.vue b/pages-game/game/minesweeper/play.vue index c4f3a7e..f4f813d 100644 --- a/pages-game/game/minesweeper/play.vue +++ b/pages-game/game/minesweeper/play.vue @@ -615,6 +615,9 @@ export default { // 自动重连 try { + if (this._isReconnecting) return; + this._isReconnecting = true; + console.log('[重连] 开始重新连接...'); const session = await nakamaManager.authenticateWithGameToken(this.gameToken, this.stableUserId); this.myUserId = session.user_id; @@ -636,6 +639,8 @@ export default { } catch (e) { console.error('[重连] 失败:', e); this.addLog('system', `❌ 重连失败: ${e.message}`); + } finally { + this._isReconnecting = false; } } }); @@ -666,6 +671,8 @@ export default { if (opCode === 1) { this.gameState = data; + const limit = data.turnDuration || 15; + this.resetTurnTimer(limit, limit); this.addLog('system', '战局开始,准备翻格!'); } else if (opCode === 2) { // 状态更新 - 检测 HP 变化触发特效 @@ -691,10 +698,13 @@ export default { } }); } - this.gameState = data; - // 收到状态更新时重置计时器(处理重连场景) + // 收到状态更新时重置计时器(使用服务器时间校准) if (data.gameStarted) { - this.resetTurnTimer(); + const serverTime = data.serverTime || Math.floor(Date.now() / 1000); + const elapsed = serverTime - (data.lastMoveTimestamp || serverTime); + const limit = data.turnDuration || 15; + const remaining = Math.max(0, limit - elapsed); + this.resetTurnTimer(remaining, limit); } } else if (opCode === 5) { this.handleEvent(data); @@ -817,9 +827,15 @@ export default { handleCellClick(idx) { // 前置条件检查 if (this.isSpectator) return; - if (!this.gameState?.gameStarted) return; + if (!this.gameState?.gameStarted) { + uni.showToast({ title: '游戏尚未开始', icon: 'none' }); + return; + } if (this.showResultModal || this.showSettlement) return; - if (!this.isMyTurn) return; + if (!this.isMyTurn) { + uni.showToast({ title: '等待对方行动...', icon: 'none' }); + return; + } if (this.gameState.grid[idx].revealed) return; // 发送移动指令 @@ -832,8 +848,9 @@ export default { closeResultModal() { this.showResultModal = false; }, - resetTurnTimer() { - this.turnTimer = 15; + resetTurnTimer(initialTime, limit) { + const turnLimit = limit || (this.gameState?.turnDuration) || 15; + this.turnTimer = Math.floor(initialTime !== undefined ? initialTime : turnLimit); clearInterval(this.turnInterval); this.turnInterval = setInterval(() => { if (this.turnTimer > 0) this.turnTimer--; diff --git a/utils/nakamaManager.js b/utils/nakamaManager.js index d26c336..39d0c3b 100644 --- a/utils/nakamaManager.js +++ b/utils/nakamaManager.js @@ -15,6 +15,7 @@ class NakamaManager { this.gameToken = null; this.socketTask = null; this.isConnected = false; + this.isConnecting = false; // 正在连接标志位 // 消息 ID 和待处理的 Promise this.nextCid = 1; @@ -132,7 +133,24 @@ class NakamaManager { * 建立 WebSocket 连接 */ _connectWebSocket() { + if (this.isConnecting) { + console.log('[Nakama] Already connecting, skipping...'); + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + // 确保清理旧连接 + if (this.socketTask) { + console.log('[Nakama] Closing existing socket before new connection'); + try { + this.socketTask.close(); + } catch (e) { + console.warn('[Nakama] Error closing old socket:', e); + } + this.socketTask = null; + } + + this.isConnecting = true; const scheme = this.useSSL ? 'wss://' : 'ws://'; const portSuffix = (this.useSSL && this.port === '443') || (!this.useSSL && this.port === '80') ? '' : `:${this.port}`; const wsUrl = `${scheme}${this.host}${portSuffix}/ws?lang=en&status=true&token=${encodeURIComponent(this.session.token)}`; @@ -151,6 +169,7 @@ class NakamaManager { this.socketTask.onOpen(() => { clearTimeout(connectTimeout); this.isConnected = true; + this.isConnecting = false; console.log('[Nakama] WebSocket connected'); this._startHeartbeat(); resolve(); @@ -169,6 +188,7 @@ class NakamaManager { clearTimeout(connectTimeout); console.error('[Nakama] WebSocket error:', err); this.isConnected = false; + this.isConnecting = false; if (this.listeners.onerror) { this.listeners.onerror(err); }