enhance(reversi): tweak reversi

This commit is contained in:
syuilo
2024-01-21 10:07:43 +09:00
parent 576484835e
commit a17251d913
19 changed files with 395 additions and 135 deletions

View File

@@ -15,19 +15,20 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div style="overflow: clip; line-height: 28px;">
<div v-if="!iAmPlayer && !game.isEnded && turnUser" class="turn">
<div v-if="!iAmPlayer && !game.isEnded && turnUser">
<Mfm :key="'turn:' + turnUser.id" :text="i18n.tsx._reversi.turnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
<MkEllipsis/>
</div>
<div v-if="(logPos !== game.logs.length) && turnUser" class="turn">
<div v-if="(logPos !== game.logs.length) && turnUser">
<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.tsx._reversi.pastTurnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
</div>
<div v-if="iAmPlayer && !game.isEnded && !isMyTurn" class="turn1">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/><soan v-if="opponentNotResponding" style="margin-left: 8px;">({{ i18n.ts.notResponding }})</soan></div>
<div v-if="iAmPlayer && !game.isEnded && isMyTurn" class="turn2" style="animation: tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</div>
<div v-if="game.isEnded && logPos == game.logs.length" class="result">
<div v-if="iAmPlayer && !game.isEnded && !isMyTurn">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/><span style="margin-left: 1em; opacity: 0.7;">({{ i18n.tsx.remainingN({ n: opTurnTimerRmain }) }})</span></div>
<div v-if="iAmPlayer && !game.isEnded && isMyTurn"><span style="display: inline-block; font-weight: bold; animation: tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</span><span style="margin-left: 1em; opacity: 0.7;">({{ i18n.tsx.remainingN({ n: myTurnTimerRmain }) }})</span></div>
<div v-if="game.isEnded && logPos == game.logs.length">
<template v-if="game.winner">
<Mfm :key="'won'" :text="i18n.tsx._reversi.won({ name: game.winner.name ?? game.winner.username })" :plain="true" :customEmojis="game.winner.emojis"/>
<span v-if="game.surrendered != null"> ({{ i18n.ts._reversi.surrendered }})</span>
<span v-if="game.surrenderedUserId != null"> ({{ i18n.ts._reversi.surrendered }})</span>
<span v-if="game.timeoutUserId != null"> ({{ i18n.ts._reversi.timeout }})</span>
</template>
<template v-else>{{ i18n.ts._reversi.drawn }}</template>
</div>
@@ -239,7 +240,7 @@ if (game.value.isStarted && !game.value.isEnded) {
if (game.value.isEnded) return;
const crc32 = CRC32.str(JSON.stringify(game.value.logs)).toString();
if (_DEV_) console.log('crc32', crc32);
props.connection.send('heatbeat', {
props.connection.send('checkState', {
crc32: crc32,
});
}, 10000, { immediate: false, afterMounted: true });
@@ -269,9 +270,31 @@ function putStone(pos) {
});
appliedOps.push(id);
myTurnTimerRmain.value = game.value.timeLimitForEachTurn;
opTurnTimerRmain.value = game.value.timeLimitForEachTurn;
checkEnd();
}
const myTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
const opTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
const TIMER_INTERVAL_SEC = 3;
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', {});
}
}
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
game.value.logs = Reversi.Serializer.serializeLogs([
...Reversi.Serializer.deserializeLogs(game.value.logs),
@@ -286,6 +309,9 @@ 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;
sound.playUrl('/client-assets/reversi/put.mp3', {
volume: 1,
playbackRate: 1,
@@ -339,27 +365,6 @@ function onStreamRescue(_game) {
checkEnd();
}
const opponentLastHeatbeatedAt = ref<number>(Date.now());
const opponentNotResponding = ref<boolean>(false);
useInterval(() => {
if (game.value.isEnded) return;
if (!iAmPlayer.value) return;
if (Date.now() - opponentLastHeatbeatedAt.value > 20000) {
opponentNotResponding.value = true;
} else {
opponentNotResponding.value = false;
}
}, 1000, { immediate: false, afterMounted: true });
function onStreamHeatbeat({ userId }) {
if ($i.id === userId) return;
opponentNotResponding.value = false;
opponentLastHeatbeatedAt.value = Date.now();
}
async function surrender() {
const { canceled } = await os.confirm({
type: 'warning',
@@ -411,28 +416,24 @@ function share() {
onMounted(() => {
props.connection.on('log', onStreamLog);
props.connection.on('heatbeat', onStreamHeatbeat);
props.connection.on('rescue', onStreamRescue);
props.connection.on('ended', onStreamEnded);
});
onActivated(() => {
props.connection.on('log', onStreamLog);
props.connection.on('heatbeat', onStreamHeatbeat);
props.connection.on('rescue', onStreamRescue);
props.connection.on('ended', onStreamEnded);
});
onDeactivated(() => {
props.connection.off('log', onStreamLog);
props.connection.off('heatbeat', onStreamHeatbeat);
props.connection.off('rescue', onStreamRescue);
props.connection.off('ended', onStreamEnded);
});
onUnmounted(() => {
props.connection.off('log', onStreamLog);
props.connection.off('heatbeat', onStreamHeatbeat);
props.connection.off('rescue', onStreamRescue);
props.connection.off('ended', onStreamEnded);
});

View File

@@ -49,6 +49,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkRadios>
</MkFolder>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
<MkRadios v-model="game.timeLimitForEachTurn">
<option :value="5">5{{ i18n.ts._time.second }}</option>
<option :value="10">10{{ i18n.ts._time.second }}</option>
<option :value="30">30{{ i18n.ts._time.second }}</option>
<option :value="60">60{{ i18n.ts._time.second }}</option>
<option :value="90">90{{ i18n.ts._time.second }}</option>
<option :value="120">120{{ i18n.ts._time.second }}</option>
<option :value="180">180{{ i18n.ts._time.second }}</option>
<option :value="3600">3600{{ i18n.ts._time.second }}</option>
</MkRadios>
</MkFolder>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.rules }}</template>
@@ -125,6 +141,10 @@ watch(() => game.value.bw, () => {
updateSettings('bw');
});
watch(() => game.value.timeLimitForEachTurn, () => {
updateSettings('timeLimitForEachTurn');
});
function chooseMap(ev: MouseEvent) {
const menu: MenuItem[] = [];