diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2024-01-21 12:05:51 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2024-01-21 12:05:51 +0900 |
| commit | 6039f27bd50ef1fbbbe6bffe12b18614c9e5b85c (patch) | |
| tree | ec41e4fe86f5b9ddc685d5817d853a0854c741b0 /packages | |
| parent | enhance(sw): オフライン表示のデザインを改善 (#13052) (diff) | |
| download | misskey-6039f27bd50ef1fbbbe6bffe12b18614c9e5b85c.tar.gz misskey-6039f27bd50ef1fbbbe6bffe12b18614c9e5b85c.tar.bz2 misskey-6039f27bd50ef1fbbbe6bffe12b18614c9e5b85c.zip | |
enhance(reversi): tweak reversi
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/backend/src/core/GlobalEventService.ts | 3 | ||||
| -rw-r--r-- | packages/backend/src/core/ReversiService.ts | 157 | ||||
| -rw-r--r-- | packages/backend/src/server/api/stream/channels/reversi-game.ts | 8 | ||||
| -rw-r--r-- | packages/frontend/src/pages/reversi/game.setting.vue | 17 | ||||
| -rw-r--r-- | packages/frontend/src/pages/reversi/game.vue | 19 |
5 files changed, 135 insertions, 69 deletions
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 5ddd100e6c..5b4c8cb44f 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -188,6 +188,9 @@ export interface ReversiGameEventTypes { winnerId: MiUser['id'] | null; game: Packed<'ReversiGameDetailed'>; }; + canceled: { + userId: MiUser['id']; + }; } //#endregion diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index b2a4032d4b..f97f71eb43 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -62,6 +62,11 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } @bindThis + private async deleteGameCache(gameId: MiReversiGame['id']) { + await this.redisClient.del(`reversi:game:cache:${gameId}`); + } + + @bindThis public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> { if (targetUser.id === me.id) { throw new Error('You cannot match yourself.'); @@ -239,86 +244,91 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { if (isBothReady) { // 3秒後、両者readyならゲーム開始 setTimeout(async () => { - const freshGame = await this.reversiGamesRepository.findOneBy({ id: game.id }); + const freshGame = await this.get(game.id); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Ready || !freshGame.user2Ready) return; - let bw: number; - if (freshGame.bw === 'random') { - bw = Math.random() > 0.5 ? 1 : 2; - } else { - bw = parseInt(freshGame.bw, 10); - } - - function getRandomMap() { - const mapCount = Object.entries(Reversi.maps).length; - const rnd = Math.floor(Math.random() * mapCount); - return Object.values(Reversi.maps)[rnd].data; - } + this.startGame(freshGame); + }, 3000); + } + } - const map = freshGame.map != null ? freshGame.map : getRandomMap(); + @bindThis + private async startGame(game: MiReversiGame) { + let bw: number; + if (game.bw === 'random') { + bw = Math.random() > 0.5 ? 1 : 2; + } else { + bw = parseInt(game.bw, 10); + } - const crc32 = CRC32.str(JSON.stringify(freshGame.logs)).toString(); + function getRandomMap() { + const mapCount = Object.entries(Reversi.maps).length; + const rnd = Math.floor(Math.random() * mapCount); + return Object.values(Reversi.maps)[rnd].data; + } - const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() - .set({ - startedAt: new Date(), - isStarted: true, - black: bw, - map: map, - crc32, - }) - .where('id = :id', { id: game.id }) - .returning('*') - .execute() - .then((response) => response.raw[0]); - this.cacheGame(updatedGame); + const map = game.map != null ? game.map : getRandomMap(); - //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 - const engine = new Reversi.Game(map, { - isLlotheo: freshGame.isLlotheo, - canPutEverywhere: freshGame.canPutEverywhere, - loopedBoard: freshGame.loopedBoard, - }); + const crc32 = CRC32.str(JSON.stringify(game.logs)).toString(); - if (engine.isEnded) { - let winner; - if (engine.winner === true) { - winner = bw === 1 ? freshGame.user1Id : freshGame.user2Id; - } else if (engine.winner === false) { - winner = bw === 1 ? freshGame.user2Id : freshGame.user1Id; - } else { - winner = null; - } + const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() + .set({ + startedAt: new Date(), + isStarted: true, + black: bw, + map: map, + crc32, + }) + .where('id = :id', { id: game.id }) + .returning('*') + .execute() + .then((response) => response.raw[0]); + this.cacheGame(updatedGame); - const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() - .set({ - isEnded: true, - endedAt: new Date(), - winnerId: winner, - }) - .where('id = :id', { id: game.id }) - .returning('*') - .execute() - .then((response) => response.raw[0]); - this.cacheGame(updatedGame); + //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 + const engine = new Reversi.Game(map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard, + }); - this.globalEventService.publishReversiGameStream(game.id, 'ended', { - winnerId: winner, - game: await this.reversiGameEntityService.packDetail(game.id), - }); + if (engine.isEnded) { + let winner; + if (engine.winner === true) { + winner = bw === 1 ? game.user1Id : game.user2Id; + } else if (engine.winner === false) { + winner = bw === 1 ? game.user2Id : game.user1Id; + } else { + winner = null; + } - return; - } - //#endregion + const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() + .set({ + isEnded: true, + endedAt: new Date(), + winnerId: winner, + }) + .where('id = :id', { id: game.id }) + .returning('*') + .execute() + .then((response) => response.raw[0]); + this.cacheGame(updatedGame); - this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, ''); + this.globalEventService.publishReversiGameStream(game.id, 'ended', { + winnerId: winner, + game: await this.reversiGameEntityService.packDetail(game.id), + }); - this.globalEventService.publishReversiGameStream(game.id, 'started', { - game: await this.reversiGameEntityService.packDetail(game.id), - }); - }, 3000); + return; } + //#endregion + + this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, ''); + + this.globalEventService.publishReversiGameStream(game.id, 'started', { + game: await this.reversiGameEntityService.packDetail(game.id), + }); } @bindThis @@ -511,6 +521,21 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } @bindThis + public async cancelGame(gameId: MiReversiGame['id'], user: MiUser) { + const game = await this.get(gameId); + if (game == null) throw new Error('game not found'); + if (game.isStarted) return; + if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return; + + await this.reversiGamesRepository.delete(game.id); + this.deleteGameCache(game.id); + + this.globalEventService.publishReversiGameStream(game.id, 'canceled', { + userId: user.id, + }); + } + + @bindThis public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> { const cached = await this.redisClient.get(`reversi:game:cache:${id}`); if (cached != null) { diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index 77eaa6d1d3..df92137f51 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -40,6 +40,7 @@ class ReversiGameChannel extends Channel { switch (type) { case 'ready': this.ready(body); break; case 'updateSettings': this.updateSettings(body.key, body.value); break; + case 'cancel': this.cancelGame(); break; case 'putStone': this.putStone(body.pos, body.id); break; case 'checkState': this.checkState(body.crc32); break; case 'claimTimeIsUp': this.claimTimeIsUp(); break; @@ -61,6 +62,13 @@ class ReversiGameChannel extends Channel { } @bindThis + private async cancelGame() { + if (this.user == null) return; + + this.reversiService.cancelGame(this.gameId!, this.user); + } + + @bindThis private async putStone(pos: number, id: string) { if (this.user == null) return; diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 360b75745c..9ca107278b 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -86,7 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template v-if="!isReady && !isOpReady">{{ i18n.ts._reversi.waitingBoth }}<MkEllipsis/></template> </div> <div class="_buttonsCenter"> - <MkButton rounded danger @click="exit">{{ i18n.ts.cancel }}</MkButton> + <MkButton rounded danger @click="cancel">{{ i18n.ts.cancel }}</MkButton> <MkButton v-if="!isReady" rounded primary @click="ready">{{ i18n.ts._reversi.ready }}</MkButton> <MkButton v-if="isReady" rounded @click="unready">{{ i18n.ts._reversi.cancelReady }}</MkButton> </div> @@ -109,9 +109,12 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os.js'; import { MenuItem } from '@/types/menu.js'; +import { useRouter } from '@/global/router/supplier.js'; const $i = signinRequired(); +const router = useRouter(); + const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.category))); const props = defineProps<{ @@ -171,8 +174,16 @@ function chooseMap(ev: MouseEvent) { os.popupMenu(menu, ev.currentTarget ?? ev.target); } -function exit() { - props.connection.send('exit', {}); +async function cancel() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.areYouSure, + }); + if (canceled) return; + + props.connection.send('cancel', {}); + + router.push('/reversi'); } function ready() { diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue index dbbeb20f42..0bdbfbcf54 100644 --- a/packages/frontend/src/pages/reversi/game.vue +++ b/packages/frontend/src/pages/reversi/game.vue @@ -17,6 +17,14 @@ import GameBoard from './game.board.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { useStream } from '@/stream.js'; +import { signinRequired } from '@/account.js'; +import { useRouter } from '@/global/router/supplier.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; + +const $i = signinRequired(); + +const router = useRouter(); const props = defineProps<{ gameId: string; @@ -45,6 +53,17 @@ async function fetchGame() { connection.value.on('started', x => { game.value = x.game; }); + connection.value.on('canceled', x => { + connection.value?.dispose(); + + if (x.userId !== $i.id) { + os.alert({ + type: 'warning', + text: i18n.ts._reversi.gameCanceled, + }); + router.push('/reversi'); + } + }); } onMounted(() => { |