From 7a750eccc325bb3644da4ccdfd8fc6ee7e5030c1 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:38:17 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix(frontend):=20=E3=83=AA=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=81=AE=E5=AF=BE=E5=B1=80=E4=B8=AD=E3=81=AB?= =?UTF-8?q?=E5=88=B6=E9=99=90=E6=99=82=E9=96=93=E3=81=AE=E7=B5=8C=E9=81=8E?= =?UTF-8?q?=E3=81=8C=E5=AE=9F=E9=9A=9B=E3=82=88=E3=82=8A=E9=81=85=E3=81=8F?= =?UTF-8?q?=E8=A8=88=E6=B8=AC=E3=81=95=E3=82=8C=E3=82=8B=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../frontend/src/pages/reversi/game.board.vue | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7908a6f9f..73be9c4d642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix: チャートの描画終了後にリソースが解放されない問題を修正 - Fix: ドライブの「このファイルからノートを作成」やギャラリーの「ノートで共有」、誕生日ウィジェットからのノート作成において、通常投稿の下書きが表示される問題を修正 - Fix: QRコードリーダーがページを離れても停止しない問題を修正 +- Fix: リバーシの対局中に制限時間の経過が実際より遅く計測されることがある問題を修正 ### Server - Feat: OpenTelemetryサポート diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 59886046523..9c1124c21d3 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -279,29 +279,32 @@ function putStone(pos: number) { }); appliedOps.push(id); - myTurnTimerRmain.value = game.value.timeLimitForEachTurn; - opTurnTimerRmain.value = game.value.timeLimitForEachTurn; + resetTurnTimer(); checkEnd(); } const myTurnTimerRmain = ref(game.value.timeLimitForEachTurn); const opTurnTimerRmain = ref(game.value.timeLimitForEachTurn); +let turnTimerDeadline = Date.now() + game.value.timeLimitForEachTurn * 1000; + +function resetTurnTimer() { + turnTimerDeadline = Date.now() + game.value.timeLimitForEachTurn * 1000; + myTurnTimerRmain.value = game.value.timeLimitForEachTurn; + opTurnTimerRmain.value = game.value.timeLimitForEachTurn; +} const TIMER_INTERVAL_SEC = 3; if (!props.game.isEnded) { useInterval(() => { - if (myTurnTimerRmain.value > 0) { - myTurnTimerRmain.value = Math.max(0, myTurnTimerRmain.value - TIMER_INTERVAL_SEC); - } - if (opTurnTimerRmain.value > 0) { - opTurnTimerRmain.value = Math.max(0, opTurnTimerRmain.value - TIMER_INTERVAL_SEC); - } - - if (iAmPlayer.value) { - if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) { - props.connection!.send('claimTimeIsUp', {}); - } + // バックグラウンドではタイマーがスロットリングされて呼び出し間隔が延びるため、 + // 固定値の減算ではなく期限の絶対時刻との差分で残り時間を計算する + const remain = Math.max(0, Math.ceil((turnTimerDeadline - Date.now()) / 1000)); + myTurnTimerRmain.value = remain; + opTurnTimerRmain.value = remain; + + if (iAmPlayer.value && remain === 0) { + props.connection!.send('claimTimeIsUp', {}); } }, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true }); } @@ -333,8 +336,7 @@ async function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) engine.value.putStone(log.pos); triggerRef(engine); - myTurnTimerRmain.value = game.value.timeLimitForEachTurn; - opTurnTimerRmain.value = game.value.timeLimitForEachTurn; + resetTurnTimer(); checkEnd(); break; From 77323fd1e18b3e36e3f59b631da0e619a9792271 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:39:15 +0900 Subject: [PATCH 2/3] clean up --- packages/frontend/src/pages/reversi/game.board.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 9c1124c21d3..34c1b648183 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -297,8 +297,6 @@ function resetTurnTimer() { const TIMER_INTERVAL_SEC = 3; if (!props.game.isEnded) { useInterval(() => { - // バックグラウンドではタイマーがスロットリングされて呼び出し間隔が延びるため、 - // 固定値の減算ではなく期限の絶対時刻との差分で残り時間を計算する const remain = Math.max(0, Math.ceil((turnTimerDeadline - Date.now()) / 1000)); myTurnTimerRmain.value = remain; opTurnTimerRmain.value = remain; From bf7b94ec2e2657f96258d8afdee09f8fa5d2d972 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:53:23 +0900 Subject: [PATCH 3/3] fix --- .../frontend/src/pages/reversi/game.board.vue | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 34c1b648183..a43aba71ae0 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -288,18 +288,38 @@ const myTurnTimerRmain = ref(game.value.timeLimitForEachTurn); const opTurnTimerRmain = ref(game.value.timeLimitForEachTurn); let turnTimerDeadline = Date.now() + game.value.timeLimitForEachTurn * 1000; +// バックグラウンドではタイマーがスロットリングされて呼び出し間隔が延びるため、 +// 固定値の減算ではなく期限の絶対時刻との差分で残り時間を計算する +function updateTurnTimerRmain(): number { + const remain = Math.min(game.value.timeLimitForEachTurn, Math.max(0, Math.ceil((turnTimerDeadline - Date.now()) / 1000))); + myTurnTimerRmain.value = remain; + opTurnTimerRmain.value = remain; + return remain; +} + +// 着手を検知した直後は、その時点のローカル時刻を基準に期限を設定する (サーバーとの時計のズレの影響を受けないため) function resetTurnTimer() { turnTimerDeadline = Date.now() + game.value.timeLimitForEachTurn * 1000; - myTurnTimerRmain.value = game.value.timeLimitForEachTurn; - opTurnTimerRmain.value = game.value.timeLimitForEachTurn; + updateTurnTimerRmain(); +} + +// ページを開いた時点や再取得時は手番の開始時刻をローカルでは知り得ないため、 +// サーバーが記録したログの時刻 (絶対時刻) を基準に期限を復元する +function restoreTurnTimer() { + const logs = Reversi.Serializer.deserializeLogs(game.value.logs); + const turnStartedAt = logs.at(-1)?.time ?? (game.value.startedAt != null ? Date.parse(game.value.startedAt) : Date.now()); + turnTimerDeadline = turnStartedAt + game.value.timeLimitForEachTurn * 1000; + updateTurnTimerRmain(); } +restoreTurnTimer(); + const TIMER_INTERVAL_SEC = 3; if (!props.game.isEnded) { useInterval(() => { - const remain = Math.max(0, Math.ceil((turnTimerDeadline - Date.now()) / 1000)); - myTurnTimerRmain.value = remain; - opTurnTimerRmain.value = remain; + if (game.value.isEnded) return; + + const remain = updateTurnTimerRmain(); if (iAmPlayer.value && remain === 0) { props.connection!.send('claimTimeIsUp', {}); @@ -398,6 +418,8 @@ function restoreGame(_game: Misskey.entities.ReversiGameDetailed) { logPos.value = game.value.logs.length; + restoreTurnTimer(); + checkEnd(); }