enhance(reversi): improve desync handling
This commit is contained in:
parent
f48f7149f8
commit
e8ba0b3f54
17 changed files with 206 additions and 60 deletions
|
@ -107,7 +107,6 @@
|
||||||
"cli-highlight": "2.1.11",
|
"cli-highlight": "2.1.11",
|
||||||
"color-convert": "2.0.1",
|
"color-convert": "2.0.1",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"crc-32": "^1.2.2",
|
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "4.25.2",
|
"fastify": "4.25.2",
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import CRC32 from 'crc-32';
|
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import * as Reversi from 'misskey-reversi';
|
import * as Reversi from 'misskey-reversi';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
|
@ -255,7 +254,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
bw = parseInt(game.bw, 10);
|
bw = parseInt(game.bw, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
const crc32 = CRC32.str(JSON.stringify(game.logs)).toString();
|
const engine = new Reversi.Game(game.map, {
|
||||||
|
isLlotheo: game.isLlotheo,
|
||||||
|
canPutEverywhere: game.canPutEverywhere,
|
||||||
|
loopedBoard: game.loopedBoard,
|
||||||
|
});
|
||||||
|
|
||||||
|
const crc32 = engine.calcCrc32().toString();
|
||||||
|
|
||||||
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
|
@ -276,12 +281,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
||||||
const engine = new Reversi.Game(updatedGame.map, {
|
|
||||||
isLlotheo: updatedGame.isLlotheo,
|
|
||||||
canPutEverywhere: updatedGame.canPutEverywhere,
|
|
||||||
loopedBoard: updatedGame.loopedBoard,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (engine.isEnded) {
|
if (engine.isEnded) {
|
||||||
let winnerId;
|
let winnerId;
|
||||||
if (engine.winner === true) {
|
if (engine.winner === true) {
|
||||||
|
@ -406,7 +405,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
const serializeLogs = Reversi.Serializer.serializeLogs(logs);
|
const serializeLogs = Reversi.Serializer.serializeLogs(logs);
|
||||||
|
|
||||||
const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString();
|
const crc32 = engine.calcCrc32().toString();
|
||||||
|
|
||||||
const updatedGame = {
|
const updatedGame = {
|
||||||
...game,
|
...game,
|
||||||
|
@ -536,7 +535,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
if (game == null) throw new Error('game not found');
|
if (game == null) throw new Error('game not found');
|
||||||
|
|
||||||
if (crc32.toString() !== game.crc32) {
|
if (crc32.toString() !== game.crc32) {
|
||||||
return await this.reversiGameEntityService.packDetail(game);
|
return game;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,6 +372,7 @@ import * as ep___reversi_match from './endpoints/reversi/match.js';
|
||||||
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
||||||
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
||||||
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
||||||
|
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||||
import { GetterService } from './GetterService.js';
|
import { GetterService } from './GetterService.js';
|
||||||
import { ApiLoggerService } from './ApiLoggerService.js';
|
import { ApiLoggerService } from './ApiLoggerService.js';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
@ -742,6 +743,7 @@ const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___r
|
||||||
const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
|
const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
|
||||||
const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
|
const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
|
||||||
const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
|
const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
|
||||||
|
const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -1116,6 +1118,7 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
|
||||||
$reversi_invitations,
|
$reversi_invitations,
|
||||||
$reversi_showGame,
|
$reversi_showGame,
|
||||||
$reversi_surrender,
|
$reversi_surrender,
|
||||||
|
$reversi_verify,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
$admin_meta,
|
$admin_meta,
|
||||||
|
@ -1481,6 +1484,7 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass
|
||||||
$reversi_invitations,
|
$reversi_invitations,
|
||||||
$reversi_showGame,
|
$reversi_showGame,
|
||||||
$reversi_surrender,
|
$reversi_surrender,
|
||||||
|
$reversi_verify,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class EndpointsModule {}
|
export class EndpointsModule {}
|
||||||
|
|
|
@ -373,6 +373,7 @@ import * as ep___reversi_match from './endpoints/reversi/match.js';
|
||||||
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
|
||||||
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
|
||||||
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
|
||||||
|
import * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||||
|
|
||||||
const eps = [
|
const eps = [
|
||||||
['admin/meta', ep___admin_meta],
|
['admin/meta', ep___admin_meta],
|
||||||
|
@ -741,6 +742,7 @@ const eps = [
|
||||||
['reversi/invitations', ep___reversi_invitations],
|
['reversi/invitations', ep___reversi_invitations],
|
||||||
['reversi/show-game', ep___reversi_showGame],
|
['reversi/show-game', ep___reversi_showGame],
|
||||||
['reversi/surrender', ep___reversi_surrender],
|
['reversi/surrender', ep___reversi_surrender],
|
||||||
|
['reversi/verify', ep___reversi_verify],
|
||||||
];
|
];
|
||||||
|
|
||||||
interface IEndpointMetaBase {
|
interface IEndpointMetaBase {
|
||||||
|
|
64
packages/backend/src/server/api/endpoints/reversi/verify.ts
Normal file
64
packages/backend/src/server/api/endpoints/reversi/verify.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { ReversiService } from '@/core/ReversiService.js';
|
||||||
|
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
errors: {
|
||||||
|
noSuchGame: {
|
||||||
|
message: 'No such game.',
|
||||||
|
code: 'NO_SUCH_GAME',
|
||||||
|
id: '8fb05624-b525-43dd-90f7-511852bdfeee',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
desynced: { type: 'boolean' },
|
||||||
|
game: {
|
||||||
|
type: 'object',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
ref: 'ReversiGameDetailed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
gameId: { type: 'string', format: 'misskey:id' },
|
||||||
|
crc32: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['gameId', 'crc32'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private reversiService: ReversiService,
|
||||||
|
private reversiGameEntityService: ReversiGameEntityService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const game = await this.reversiService.checkCrc(ps.gameId, ps.crc32);
|
||||||
|
if (game) {
|
||||||
|
return {
|
||||||
|
desynced: true,
|
||||||
|
game: await this.reversiGameEntityService.packDetail(game),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
desynced: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { MiReversiGame, ReversiGamesRepository } from '@/models/_.js';
|
import type { MiReversiGame } from '@/models/_.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ReversiService } from '@/core/ReversiService.js';
|
import { ReversiService } from '@/core/ReversiService.js';
|
||||||
|
@ -19,7 +19,6 @@ class ReversiGameChannel extends Channel {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private reversiService: ReversiService,
|
private reversiService: ReversiService,
|
||||||
private reversiGamesRepository: ReversiGamesRepository,
|
|
||||||
private reversiGameEntityService: ReversiGameEntityService,
|
private reversiGameEntityService: ReversiGameEntityService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -42,7 +41,6 @@ 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 'resync': this.resync(body.crc32); break;
|
|
||||||
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,14 +73,6 @@ class ReversiGameChannel extends Channel {
|
||||||
this.reversiService.putStoneToGame(this.gameId!, this.user, pos, id);
|
this.reversiService.putStoneToGame(this.gameId!, this.user, pos, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async resync(crc32: string | number) {
|
|
||||||
const game = await this.reversiService.checkCrc(this.gameId!, crc32);
|
|
||||||
if (game) {
|
|
||||||
this.send('resynced', game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async claimTimeIsUp() {
|
private async claimTimeIsUp() {
|
||||||
if (this.user == null) return;
|
if (this.user == null) return;
|
||||||
|
@ -104,9 +94,6 @@ export class ReversiGameChannelService implements MiChannelService<false> {
|
||||||
public readonly kind = ReversiGameChannel.kind;
|
public readonly kind = ReversiGameChannel.kind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.reversiGamesRepository)
|
|
||||||
private reversiGamesRepository: ReversiGamesRepository,
|
|
||||||
|
|
||||||
private reversiService: ReversiService,
|
private reversiService: ReversiService,
|
||||||
private reversiGameEntityService: ReversiGameEntityService,
|
private reversiGameEntityService: ReversiGameEntityService,
|
||||||
) {
|
) {
|
||||||
|
@ -116,7 +103,6 @@ export class ReversiGameChannelService implements MiChannelService<false> {
|
||||||
public create(id: string, connection: Channel['connection']): ReversiGameChannel {
|
public create(id: string, connection: Channel['connection']): ReversiGameChannel {
|
||||||
return new ReversiGameChannel(
|
return new ReversiGameChannel(
|
||||||
this.reversiService,
|
this.reversiService,
|
||||||
this.reversiGamesRepository,
|
|
||||||
this.reversiGameEntityService,
|
this.reversiGameEntityService,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"chromatic": "10.3.1",
|
"chromatic": "10.3.1",
|
||||||
"compare-versions": "6.1.0",
|
"compare-versions": "6.1.0",
|
||||||
"crc-32": "^1.2.2",
|
|
||||||
"cropperjs": "2.0.0-beta.4",
|
"cropperjs": "2.0.0-beta.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
|
|
|
@ -143,7 +143,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
|
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
|
||||||
import * as CRC32 from 'crc-32';
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import * as Reversi from 'misskey-reversi';
|
import * as Reversi from 'misskey-reversi';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -240,11 +239,17 @@ watch(logPos, (v) => {
|
||||||
|
|
||||||
if (game.value.isStarted && !game.value.isEnded) {
|
if (game.value.isStarted && !game.value.isEnded) {
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
if (game.value.isEnded || props.connection == null) return;
|
if (game.value.isEnded) return;
|
||||||
const crc32 = CRC32.str(JSON.stringify(game.value.logs)).toString();
|
const crc32 = engine.value.calcCrc32();
|
||||||
if (_DEV_) console.log('crc32', crc32);
|
if (_DEV_) console.log('crc32', crc32);
|
||||||
props.connection.send('resync', {
|
misskeyApi('reversi/verify', {
|
||||||
crc32: crc32,
|
gameId: game.value.id,
|
||||||
|
crc32: crc32.toString(),
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.desynced) {
|
||||||
|
console.log('resynced');
|
||||||
|
restoreGame(res.game!);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 10000, { immediate: false, afterMounted: true });
|
}, 10000, { immediate: false, afterMounted: true });
|
||||||
}
|
}
|
||||||
|
@ -392,12 +397,6 @@ function restoreGame(_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',
|
||||||
|
@ -450,7 +449,6 @@ function share() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.on('log', onStreamLog);
|
props.connection.on('log', onStreamLog);
|
||||||
props.connection.on('resynced', onStreamResynced);
|
|
||||||
props.connection.on('ended', onStreamEnded);
|
props.connection.on('ended', onStreamEnded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -458,7 +456,6 @@ onMounted(() => {
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.on('log', onStreamLog);
|
props.connection.on('log', onStreamLog);
|
||||||
props.connection.on('resynced', onStreamResynced);
|
|
||||||
props.connection.on('ended', onStreamEnded);
|
props.connection.on('ended', onStreamEnded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -466,7 +463,6 @@ onActivated(() => {
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.off('log', onStreamLog);
|
props.connection.off('log', onStreamLog);
|
||||||
props.connection.off('resynced', onStreamResynced);
|
|
||||||
props.connection.off('ended', onStreamEnded);
|
props.connection.off('ended', onStreamEnded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -474,7 +470,6 @@ onDeactivated(() => {
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.off('log', onStreamLog);
|
props.connection.off('log', onStreamLog);
|
||||||
props.connection.off('resynced', onStreamResynced);
|
|
||||||
props.connection.off('ended', onStreamEnded);
|
props.connection.off('ended', onStreamEnded);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1633,6 +1633,8 @@ declare namespace entities {
|
||||||
ReversiShowGameRequest,
|
ReversiShowGameRequest,
|
||||||
ReversiShowGameResponse,
|
ReversiShowGameResponse,
|
||||||
ReversiSurrenderRequest,
|
ReversiSurrenderRequest,
|
||||||
|
ReversiVerifyRequest,
|
||||||
|
ReversiVerifyResponse,
|
||||||
Error_2 as Error,
|
Error_2 as Error,
|
||||||
UserLite,
|
UserLite,
|
||||||
UserDetailedNotMeOnly,
|
UserDetailedNotMeOnly,
|
||||||
|
@ -2644,6 +2646,12 @@ type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
|
type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type Role = components['schemas']['Role'];
|
type Role = components['schemas']['Role'];
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.2
|
* version: 2024.2.0-beta.3
|
||||||
* generatedAt: 2024-01-22T07:11:08.412Z
|
* generatedAt: 2024-01-23T01:22:13.177Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SwitchCaseResponseType } from '../api.js';
|
import type { SwitchCaseResponseType } from '../api.js';
|
||||||
|
@ -4073,5 +4073,16 @@ declare module '../api.js' {
|
||||||
params: P,
|
params: P,
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
request<E extends 'reversi/verify', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.2
|
* version: 2024.2.0-beta.3
|
||||||
* generatedAt: 2024-01-22T07:11:08.410Z
|
* generatedAt: 2024-01-23T01:22:13.175Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -554,6 +554,8 @@ import type {
|
||||||
ReversiShowGameRequest,
|
ReversiShowGameRequest,
|
||||||
ReversiShowGameResponse,
|
ReversiShowGameResponse,
|
||||||
ReversiSurrenderRequest,
|
ReversiSurrenderRequest,
|
||||||
|
ReversiVerifyRequest,
|
||||||
|
ReversiVerifyResponse,
|
||||||
} from './entities.js';
|
} from './entities.js';
|
||||||
|
|
||||||
export type Endpoints = {
|
export type Endpoints = {
|
||||||
|
@ -923,4 +925,5 @@ export type Endpoints = {
|
||||||
'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse };
|
'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse };
|
||||||
'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse };
|
'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse };
|
||||||
'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse };
|
'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse };
|
||||||
|
'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.2
|
* version: 2024.2.0-beta.3
|
||||||
* generatedAt: 2024-01-22T07:11:08.408Z
|
* generatedAt: 2024-01-23T01:22:13.173Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { operations } from './types.js';
|
import { operations } from './types.js';
|
||||||
|
@ -556,3 +556,5 @@ export type ReversiInvitationsResponse = operations['reversi/invitations']['resp
|
||||||
export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json'];
|
export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json'];
|
||||||
export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json'];
|
export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json'];
|
||||||
export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
|
export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
|
||||||
|
export type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json'];
|
||||||
|
export type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json'];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.2
|
* version: 2024.2.0-beta.3
|
||||||
* generatedAt: 2024-01-22T07:11:08.408Z
|
* generatedAt: 2024-01-23T01:22:13.172Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { components } from './types.js';
|
import { components } from './types.js';
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* version: 2024.2.0-beta.2
|
* version: 2024.2.0-beta.3
|
||||||
* generatedAt: 2024-01-22T07:11:08.327Z
|
* generatedAt: 2024-01-23T01:22:13.093Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3526,6 +3526,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['reversi/surrender'];
|
post: operations['reversi/surrender'];
|
||||||
};
|
};
|
||||||
|
'/reversi/verify': {
|
||||||
|
/**
|
||||||
|
* reversi/verify
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
post: operations['reversi/verify'];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
|
@ -25984,5 +25993,63 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* reversi/verify
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
'reversi/verify': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
gameId: string;
|
||||||
|
crc32: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
desynced: boolean;
|
||||||
|
game?: components['schemas']['ReversiGameDetailed'] | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@
|
||||||
"built"
|
"built"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"crc-32": "1.2.2",
|
||||||
"esbuild": "0.19.11",
|
"esbuild": "0.19.11",
|
||||||
"glob": "^10.3.10"
|
"glob": "10.3.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import CRC32 from 'crc-32';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* true ... 黒
|
* true ... 黒
|
||||||
* false ... 白
|
* false ... 白
|
||||||
|
@ -204,6 +206,13 @@ export class Game {
|
||||||
return ([] as number[]).concat(...diffVectors.map(effectsInLine));
|
return ([] as number[]).concat(...diffVectors.map(effectsInLine));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public calcCrc32(): number {
|
||||||
|
return CRC32.str(JSON.stringify({
|
||||||
|
board: this.board,
|
||||||
|
turn: this.turn,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
public get isEnded(): boolean {
|
public get isEnded(): boolean {
|
||||||
return this.turn === null;
|
return this.turn === null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,9 +185,6 @@ importers:
|
||||||
content-disposition:
|
content-disposition:
|
||||||
specifier: 0.5.4
|
specifier: 0.5.4
|
||||||
version: 0.5.4
|
version: 0.5.4
|
||||||
crc-32:
|
|
||||||
specifier: ^1.2.2
|
|
||||||
version: 1.2.2
|
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: 2.30.0
|
specifier: 2.30.0
|
||||||
version: 2.30.0
|
version: 2.30.0
|
||||||
|
@ -742,9 +739,6 @@ importers:
|
||||||
compare-versions:
|
compare-versions:
|
||||||
specifier: 6.1.0
|
specifier: 6.1.0
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
crc-32:
|
|
||||||
specifier: ^1.2.2
|
|
||||||
version: 1.2.2
|
|
||||||
cropperjs:
|
cropperjs:
|
||||||
specifier: 2.0.0-beta.4
|
specifier: 2.0.0-beta.4
|
||||||
version: 2.0.0-beta.4
|
version: 2.0.0-beta.4
|
||||||
|
@ -1177,11 +1171,14 @@ importers:
|
||||||
|
|
||||||
packages/misskey-reversi:
|
packages/misskey-reversi:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
crc-32:
|
||||||
|
specifier: 1.2.2
|
||||||
|
version: 1.2.2
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: 0.19.11
|
specifier: 0.19.11
|
||||||
version: 0.19.11
|
version: 0.19.11
|
||||||
glob:
|
glob:
|
||||||
specifier: ^10.3.10
|
specifier: 10.3.10
|
||||||
version: 10.3.10
|
version: 10.3.10
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@misskey-dev/eslint-plugin':
|
'@misskey-dev/eslint-plugin':
|
||||||
|
|
Loading…
Reference in a new issue