summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2024-01-21 17:08:36 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2024-01-21 17:08:36 +0900
commitb3cc17ea0d53edca3b7f7a648ae94a591ec98d44 (patch)
tree6e6254c8a3128b24e40176f5ed31b2c2848a5c49
parentenhance(reversi): tweak reversi (diff)
downloadsharkey-b3cc17ea0d53edca3b7f7a648ae94a591ec98d44.tar.gz
sharkey-b3cc17ea0d53edca3b7f7a648ae94a591ec98d44.tar.bz2
sharkey-b3cc17ea0d53edca3b7f7a648ae94a591ec98d44.zip
enhance(reversi): tweak reversi
-rw-r--r--packages/frontend/assets/reversi/lose.mp3bin0 -> 9565 bytes
-rw-r--r--packages/frontend/assets/reversi/win.mp3bin0 -> 25703 bytes
-rw-r--r--packages/frontend/src/pages/reversi/game.board.vue124
-rw-r--r--packages/frontend/src/pages/reversi/game.setting.vue123
4 files changed, 148 insertions, 99 deletions
diff --git a/packages/frontend/assets/reversi/lose.mp3 b/packages/frontend/assets/reversi/lose.mp3
new file mode 100644
index 0000000000..b62d50baf7
--- /dev/null
+++ b/packages/frontend/assets/reversi/lose.mp3
Binary files differ
diff --git a/packages/frontend/assets/reversi/win.mp3 b/packages/frontend/assets/reversi/win.mp3
new file mode 100644
index 0000000000..25402ce2a6
--- /dev/null
+++ b/packages/frontend/assets/reversi/win.mp3
Binary files differ
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 5e28f55902..6ad779c605 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkSpacer :contentMax="600">
+<MkSpacer :contentMax="500">
<div :class="$style.root" class="_gaps">
<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
<span>({{ i18n.ts._reversi.black }})</span>
@@ -35,53 +35,55 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.board">
- <div v-if="showBoardLabels" :class="$style.labelsX">
- <span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
- </div>
- <div style="display: flex;">
- <div v-if="showBoardLabels" :class="$style.labelsY">
- <div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+ <div :class="$style.boardInner">
+ <div v-if="showBoardLabels" :class="$style.labelsX">
+ <span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
</div>
- <div :class="$style.boardCells" :style="cellsStyle">
- <div
- v-for="(stone, i) in engine.board"
- :key="i"
- v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
- :class="[$style.boardCell, {
- [$style.boardCell_empty]: stone == null,
- [$style.boardCell_none]: engine.map[i] === 'null',
- [$style.boardCell_isEnded]: game.isEnded,
- [$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
- [$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
- [$style.boardCell_prev]: engine.prevPos === i
- }]"
- @click="putStone(i)"
- >
- <Transition
- :enterActiveClass="$style.transition_flip_enterActive"
- :leaveActiveClass="$style.transition_flip_leaveActive"
- :enterFromClass="$style.transition_flip_enterFrom"
- :leaveToClass="$style.transition_flip_leaveTo"
- mode="default"
+ <div style="display: flex;">
+ <div v-if="showBoardLabels" :class="$style.labelsY">
+ <div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+ </div>
+ <div :class="$style.boardCells" :style="cellsStyle">
+ <div
+ v-for="(stone, i) in engine.board"
+ :key="i"
+ v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
+ :class="[$style.boardCell, {
+ [$style.boardCell_empty]: stone == null,
+ [$style.boardCell_none]: engine.map[i] === 'null',
+ [$style.boardCell_isEnded]: game.isEnded,
+ [$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
+ [$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
+ [$style.boardCell_prev]: engine.prevPos === i
+ }]"
+ @click="putStone(i)"
>
- <template v-if="useAvatarAsStone">
- <img v-if="stone === true" :class="$style.boardCellStone" :src="blackUser.avatarUrl"/>
- <img v-else-if="stone === false" :class="$style.boardCellStone" :src="whiteUser.avatarUrl"/>
- </template>
- <template v-else>
- <img v-if="stone === true" :class="$style.boardCellStone" src="/client-assets/reversi/stone_b.png"/>
- <img v-else-if="stone === false" :class="$style.boardCellStone" src="/client-assets/reversi/stone_w.png"/>
- </template>
- </Transition>
+ <Transition
+ :enterActiveClass="$style.transition_flip_enterActive"
+ :leaveActiveClass="$style.transition_flip_leaveActive"
+ :enterFromClass="$style.transition_flip_enterFrom"
+ :leaveToClass="$style.transition_flip_leaveTo"
+ mode="default"
+ >
+ <template v-if="useAvatarAsStone">
+ <img v-if="stone === true" :class="$style.boardCellStone" :src="blackUser.avatarUrl"/>
+ <img v-else-if="stone === false" :class="$style.boardCellStone" :src="whiteUser.avatarUrl"/>
+ </template>
+ <template v-else>
+ <img v-if="stone === true" :class="$style.boardCellStone" src="/client-assets/reversi/stone_b.png"/>
+ <img v-else-if="stone === false" :class="$style.boardCellStone" src="/client-assets/reversi/stone_w.png"/>
+ </template>
+ </Transition>
+ </div>
+ </div>
+ <div v-if="showBoardLabels" :class="$style.labelsY">
+ <div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
</div>
</div>
- <div v-if="showBoardLabels" :class="$style.labelsY">
- <div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+ <div v-if="showBoardLabels" :class="$style.labelsX">
+ <span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
</div>
</div>
- <div v-if="showBoardLabels" :class="$style.labelsX">
- <span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
- </div>
</div>
<div v-if="game.isEnded" class="_panel _gaps_s" style="padding: 16px;">
@@ -155,6 +157,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { userPage } from '@/filters/user.js';
import * as sound from '@/scripts/sound.js';
import * as os from '@/os.js';
+import { confetti } from '@/scripts/confetti.js';
const $i = signinRequired();
@@ -329,6 +332,22 @@ function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
function onStreamEnded(x) {
game.value = deepClone(x.game);
+
+ if (game.value.winnerId === $i.id) {
+ confetti({
+ duration: 1000 * 3,
+ });
+
+ sound.playUrl('/client-assets/reversi/win.mp3', {
+ volume: 1,
+ playbackRate: 1,
+ });
+ } else {
+ sound.playUrl('/client-assets/reversi/lose.mp3', {
+ volume: 1,
+ playbackRate: 1,
+ });
+ }
}
function checkEnd() {
@@ -465,8 +484,27 @@ $gap: 4px;
.board {
width: 100%;
- max-width: 500px;
+ box-sizing: border-box;
margin: 0 auto;
+
+ padding: 7px;
+ background: #8C4F26;
+ box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c;
+ border-radius: 12px;
+}
+
+.boardInner {
+ padding: 32px;
+
+ background: var(--panel);
+ box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
+ border-radius: 8px;
+}
+
+@container (max-width: 400px) {
+ .boardInner {
+ padding: 16px;
+ }
}
.labelsX {
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 9ca107278b..d69176e25a 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -8,72 +8,74 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="600">
<div style="text-align: center;"><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></div>
- <div class="_gaps">
- <div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
+ <div :class="{ [$style.disallow]: isReady }">
+ <div class="_gaps" :class="{ [$style.disallowInner]: isReady }">
+ <div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
- <div class="_panel">
- <div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
- <div>{{ mapName }}</div>
- <MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
- </div>
+ <div class="_panel">
+ <div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
+ <div>{{ mapName }}</div>
+ <MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
+ </div>
- <div style="padding: 16px;">
- <div v-if="game.map == null"><i class="ti ti-dice"></i></div>
- <div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
- <div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
- <i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
+ <div style="padding: 16px;">
+ <div v-if="game.map == null"><i class="ti ti-dice"></i></div>
+ <div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
+ <div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
+ <i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
+ </div>
</div>
</div>
</div>
- </div>
- <MkFolder :defaultOpen="true">
- <template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
- <MkRadios v-model="game.bw">
- <option value="random">{{ i18n.ts.random }}</option>
- <option :value="'1'">
- <I18n :src="i18n.ts._reversi.blackIs" tag="span">
- <template #name>
- <b><MkUserName :user="game.user1"/></b>
- </template>
- </I18n>
- </option>
- <option :value="'2'">
- <I18n :src="i18n.ts._reversi.blackIs" tag="span">
- <template #name>
- <b><MkUserName :user="game.user2"/></b>
- </template>
- </I18n>
- </option>
- </MkRadios>
- </MkFolder>
+ <MkRadios v-model="game.bw">
+ <option value="random">{{ i18n.ts.random }}</option>
+ <option :value="'1'">
+ <I18n :src="i18n.ts._reversi.blackIs" tag="span">
+ <template #name>
+ <b><MkUserName :user="game.user1"/></b>
+ </template>
+ </I18n>
+ </option>
+ <option :value="'2'">
+ <I18n :src="i18n.ts._reversi.blackIs" tag="span">
+ <template #name>
+ <b><MkUserName :user="game.user2"/></b>
+ </template>
+ </I18n>
+ </option>
+ </MkRadios>
+ </MkFolder>
- <MkFolder :defaultOpen="true">
- <template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
- <template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
+ <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>
+ <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>
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts._reversi.rules }}</template>
- <div class="_gaps_s">
- <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
- <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
- <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
- </div>
- </MkFolder>
+ <div class="_gaps_s">
+ <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
+ <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
+ <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
+ </div>
+ </MkFolder>
+ </div>
</div>
</MkSpacer>
<template #footer>
@@ -123,7 +125,7 @@ const props = defineProps<{
}>();
const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game));
-const isLlotheo = ref<boolean>(false);
+
const mapName = computed(() => {
if (game.value.map == null) return 'Random';
const found = Object.values(Reversi.maps).find(x => x.data.join('') === game.value.map.join(''));
@@ -236,6 +238,15 @@ onUnmounted(() => {
</script>
<style lang="scss" module>
+.disallow {
+ cursor: not-allowed;
+}
+.disallowInner {
+ pointer-events: none;
+ user-select: none;
+ opacity: 0.7;
+}
+
.board {
display: grid;
grid-gap: 4px;