enhance(reversi): some tweaks

This commit is contained in:
syuilo 2024-01-22 12:03:32 +09:00
parent 67f6157d42
commit 259992c65f
3 changed files with 83 additions and 61 deletions

View File

@ -42,7 +42,7 @@ class ReversiGameChannel extends Channel {
case 'updateSettings': this.updateSettings(body.key, body.value); break; case 'updateSettings': this.updateSettings(body.key, body.value); break;
case 'cancel': this.cancelGame(); break; case 'cancel': this.cancelGame(); break;
case 'putStone': this.putStone(body.pos, body.id); break; case 'putStone': this.putStone(body.pos, body.id); break;
case 'checkState': this.checkState(body.crc32); break; case 'resync': this.resync(body.crc32); break;
case 'claimTimeIsUp': this.claimTimeIsUp(); break; case 'claimTimeIsUp': this.claimTimeIsUp(); break;
} }
} }
@ -76,12 +76,10 @@ class ReversiGameChannel extends Channel {
} }
@bindThis @bindThis
private async checkState(crc32: string | number) { private async resync(crc32: string | number) {
if (crc32 != null) return;
const game = await this.reversiService.checkCrc(this.gameId!, crc32); const game = await this.reversiService.checkCrc(this.gameId!, crc32);
if (game) { if (game) {
this.send('rescue', game); this.send('resynced', game);
} }
} }

View File

@ -163,7 +163,7 @@ const $i = signinRequired();
const props = defineProps<{ const props = defineProps<{
game: Misskey.entities.ReversiGameDetailed; game: Misskey.entities.ReversiGameDetailed;
connection: Misskey.ChannelConnection; connection?: Misskey.ChannelConnection | null;
}>(); }>();
const showBoardLabels = ref<boolean>(false); const showBoardLabels = ref<boolean>(false);
@ -240,10 +240,10 @@ watch(logPos, (v) => {
if (game.value.isStarted && !game.value.isEnded) { if (game.value.isStarted && !game.value.isEnded) {
useInterval(() => { useInterval(() => {
if (game.value.isEnded) return; if (game.value.isEnded || props.connection == null) return;
const crc32 = CRC32.str(JSON.stringify(game.value.logs)).toString(); const crc32 = CRC32.str(JSON.stringify(game.value.logs)).toString();
if (_DEV_) console.log('crc32', crc32); if (_DEV_) console.log('crc32', crc32);
props.connection.send('checkState', { props.connection.send('resync', {
crc32: crc32, crc32: crc32,
}); });
}, 10000, { immediate: false, afterMounted: true }); }, 10000, { immediate: false, afterMounted: true });
@ -267,7 +267,7 @@ function putStone(pos) {
}); });
const id = Math.random().toString(36).slice(2); const id = Math.random().toString(36).slice(2);
props.connection.send('putStone', { props.connection!.send('putStone', {
pos: pos, pos: pos,
id, id,
}); });
@ -283,22 +283,24 @@ const myTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
const opTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn); const opTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
const TIMER_INTERVAL_SEC = 3; const TIMER_INTERVAL_SEC = 3;
useInterval(() => { if (!props.game.isEnded) {
if (myTurnTimerRmain.value > 0) { useInterval(() => {
myTurnTimerRmain.value = Math.max(0, myTurnTimerRmain.value - TIMER_INTERVAL_SEC); 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 (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', {});
} }
}
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) { if (iAmPlayer.value) {
if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) {
props.connection!.send('claimTimeIsUp', {});
}
}
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
}
async function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
game.value.logs = Reversi.Serializer.serializeLogs([ game.value.logs = Reversi.Serializer.serializeLogs([
...Reversi.Serializer.deserializeLogs(game.value.logs), ...Reversi.Serializer.deserializeLogs(game.value.logs),
log, log,
@ -309,17 +311,25 @@ function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
if (log.id == null || !appliedOps.includes(log.id)) { if (log.id == null || !appliedOps.includes(log.id)) {
switch (log.operation) { switch (log.operation) {
case 'put': { case 'put': {
sound.playUrl('/client-assets/reversi/put.mp3', {
volume: 1,
playbackRate: 1,
});
if (log.player !== engine.value.turn) { // = desync
const _game = await misskeyApi('reversi/show-game', {
gameId: props.game.id,
});
restoreGame(_game);
return;
}
engine.value.putStone(log.pos); engine.value.putStone(log.pos);
triggerRef(engine); triggerRef(engine);
myTurnTimerRmain.value = game.value.timeLimitForEachTurn; myTurnTimerRmain.value = game.value.timeLimitForEachTurn;
opTurnTimerRmain.value = game.value.timeLimitForEachTurn; opTurnTimerRmain.value = game.value.timeLimitForEachTurn;
sound.playUrl('/client-assets/reversi/put.mp3', {
volume: 1,
playbackRate: 1,
});
checkEnd(); checkEnd();
break; break;
} }
@ -366,9 +376,7 @@ function checkEnd() {
} }
} }
function onStreamRescue(_game) { function restoreGame(_game) {
console.log('rescue');
game.value = deepClone(_game); game.value = deepClone(_game);
engine.value = Reversi.Serializer.restoreGame({ engine.value = Reversi.Serializer.restoreGame({
@ -384,6 +392,12 @@ function onStreamRescue(_game) {
checkEnd(); checkEnd();
} }
function onStreamResynced(_game) {
console.log('resynced');
restoreGame(_game);
}
async function surrender() { async function surrender() {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: 'warning', type: 'warning',
@ -434,27 +448,35 @@ function share() {
} }
onMounted(() => { onMounted(() => {
props.connection.on('log', onStreamLog); if (props.connection != null) {
props.connection.on('rescue', onStreamRescue); props.connection.on('log', onStreamLog);
props.connection.on('ended', onStreamEnded); props.connection.on('resynced', onStreamResynced);
props.connection.on('ended', onStreamEnded);
}
}); });
onActivated(() => { onActivated(() => {
props.connection.on('log', onStreamLog); if (props.connection != null) {
props.connection.on('rescue', onStreamRescue); props.connection.on('log', onStreamLog);
props.connection.on('ended', onStreamEnded); props.connection.on('resynced', onStreamResynced);
props.connection.on('ended', onStreamEnded);
}
}); });
onDeactivated(() => { onDeactivated(() => {
props.connection.off('log', onStreamLog); if (props.connection != null) {
props.connection.off('rescue', onStreamRescue); props.connection.off('log', onStreamLog);
props.connection.off('ended', onStreamEnded); props.connection.off('resynced', onStreamResynced);
props.connection.off('ended', onStreamEnded);
}
}); });
onUnmounted(() => { onUnmounted(() => {
props.connection.off('log', onStreamLog); if (props.connection != null) {
props.connection.off('rescue', onStreamRescue); props.connection.off('log', onStreamLog);
props.connection.off('ended', onStreamEnded); props.connection.off('resynced', onStreamResynced);
props.connection.off('ended', onStreamEnded);
}
}); });
</script> </script>

View File

@ -4,8 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div v-if="game == null || connection == null"><MkLoading/></div> <div v-if="game == null || (!game.isEnded && connection == null)"><MkLoading/></div>
<GameSetting v-else-if="!game.isStarted" :game="game" :connection="connection"/> <GameSetting v-else-if="!game.isStarted" :game="game" :connection="connection!"/>
<GameBoard v-else :game="game" :connection="connection"/> <GameBoard v-else :game="game" :connection="connection"/>
</template> </template>
@ -47,23 +47,25 @@ async function fetchGame() {
if (connection.value) { if (connection.value) {
connection.value.dispose(); connection.value.dispose();
} }
connection.value = useStream().useChannel('reversiGame', { if (!game.value.isEnded) {
gameId: game.value.id, connection.value = useStream().useChannel('reversiGame', {
}); gameId: game.value.id,
connection.value.on('started', x => { });
game.value = x.game; connection.value.on('started', x => {
}); game.value = x.game;
connection.value.on('canceled', x => { });
connection.value?.dispose(); connection.value.on('canceled', x => {
connection.value?.dispose();
if (x.userId !== $i.id) { if (x.userId !== $i.id) {
os.alert({ os.alert({
type: 'warning', type: 'warning',
text: i18n.ts._reversi.gameCanceled, text: i18n.ts._reversi.gameCanceled,
}); });
router.push('/reversi'); router.push('/reversi');
} }
}); });
}
} }
onMounted(() => { onMounted(() => {