From a637b4e28259e89285fc1c67589c731a053f5562 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 19 Jan 2024 20:51:49 +0900
Subject: [PATCH] feat: reversi

Resolve #12962
---
 locales/index.d.ts                            |  35 +
 locales/ja-JP.yml                             |  35 +
 .../migration/1705475608437-reversi.js        |  22 +
 .../migration/1705654039457-reversi-2.js      |  18 +
 packages/backend/package.json                 |   2 +
 packages/backend/src/core/CoreModule.ts       |  27 +
 .../backend/src/core/GlobalEventService.ts    |  57 +-
 packages/backend/src/core/ReversiService.ts   | 411 ++++++++++
 .../core/entities/ReversiGameEntityService.ts | 115 +++
 packages/backend/src/di-symbols.ts            |   1 +
 packages/backend/src/misc/json-schema.ts      |   3 +
 .../backend/src/models/RepositoryModule.ts    |  12 +-
 packages/backend/src/models/ReversiGame.ts    | 127 ++++
 packages/backend/src/models/_.ts              |   4 +
 .../src/models/json-schema/reversi-game.ts    | 234 ++++++
 packages/backend/src/postgres.ts              |   2 +
 packages/backend/src/server/ServerModule.ts   |  11 +-
 .../backend/src/server/api/EndpointsModule.ts |  24 +
 packages/backend/src/server/api/endpoints.ts  |  12 +
 .../api/endpoints/renote-mute/create.ts       |   2 +-
 .../api/endpoints/reversi/cancel-match.ts     |  44 ++
 .../src/server/api/endpoints/reversi/games.ts |  61 ++
 .../api/endpoints/reversi/invitations.ts      |  39 +
 .../src/server/api/endpoints/reversi/match.ts |  66 ++
 .../server/api/endpoints/reversi/show-game.ts |  54 ++
 .../server/api/endpoints/reversi/surrender.ts |  68 ++
 .../src/server/api/stream/ChannelsService.ts  |   6 +
 .../api/stream/channels/reversi-game.ts       | 130 ++++
 .../src/server/api/stream/channels/reversi.ts |  52 ++
 packages/frontend/assets/reversi/logo.png     | Bin 0 -> 96293 bytes
 packages/frontend/package.json                |   2 +
 packages/frontend/src/components/MkRadios.vue |   3 +
 packages/frontend/src/components/MkSelect.vue |   4 +-
 .../src/components/MkUserSelectDialog.vue     |  12 +-
 .../frontend/src/global/router/definition.ts  |  17 +-
 packages/frontend/src/os.ts                   |   2 +-
 .../frontend/src/pages/drop-and-fusion.vue    |   2 +-
 packages/frontend/src/pages/games.vue         |  15 +-
 .../frontend/src/pages/reversi/game.board.vue | 428 +++++++++++
 .../src/pages/reversi/game.setting.vue        | 236 ++++++
 packages/frontend/src/pages/reversi/game.vue  |  68 ++
 packages/frontend/src/pages/reversi/index.vue | 271 +++++++
 packages/frontend/vite.config.ts              |   4 +-
 packages/misskey-js/etc/misskey-js.api.md     |  50 +-
 .../misskey-js/src/autogen/apiClientJSDoc.ts  |  68 +-
 packages/misskey-js/src/autogen/endpoint.ts   |  18 +-
 packages/misskey-js/src/autogen/entities.ts   |  12 +-
 packages/misskey-js/src/autogen/models.ts     |   4 +-
 packages/misskey-js/src/autogen/types.ts      | 442 ++++++++++-
 packages/misskey-reversi/package.json         |  26 +
 packages/misskey-reversi/src/game.ts          | 216 ++++++
 packages/misskey-reversi/src/index.ts         |   7 +
 packages/misskey-reversi/src/maps.ts          | 715 ++++++++++++++++++
 packages/misskey-reversi/tsconfig.json        |  33 +
 pnpm-lock.yaml                                | 479 ++++++++++--
 pnpm-workspace.yaml                           |   1 +
 56 files changed, 4701 insertions(+), 108 deletions(-)
 create mode 100644 packages/backend/migration/1705475608437-reversi.js
 create mode 100644 packages/backend/migration/1705654039457-reversi-2.js
 create mode 100644 packages/backend/src/core/ReversiService.ts
 create mode 100644 packages/backend/src/core/entities/ReversiGameEntityService.ts
 create mode 100644 packages/backend/src/models/ReversiGame.ts
 create mode 100644 packages/backend/src/models/json-schema/reversi-game.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/cancel-match.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/games.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/invitations.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/match.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/show-game.ts
 create mode 100644 packages/backend/src/server/api/endpoints/reversi/surrender.ts
 create mode 100644 packages/backend/src/server/api/stream/channels/reversi-game.ts
 create mode 100644 packages/backend/src/server/api/stream/channels/reversi.ts
 create mode 100644 packages/frontend/assets/reversi/logo.png
 create mode 100644 packages/frontend/src/pages/reversi/game.board.vue
 create mode 100644 packages/frontend/src/pages/reversi/game.setting.vue
 create mode 100644 packages/frontend/src/pages/reversi/game.vue
 create mode 100644 packages/frontend/src/pages/reversi/index.vue
 create mode 100644 packages/misskey-reversi/package.json
 create mode 100644 packages/misskey-reversi/src/game.ts
 create mode 100644 packages/misskey-reversi/src/index.ts
 create mode 100644 packages/misskey-reversi/src/maps.ts
 create mode 100644 packages/misskey-reversi/tsconfig.json

diff --git a/locales/index.d.ts b/locales/index.d.ts
index a22cb63507..85e0c6b244 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -2633,6 +2633,41 @@ export interface Locale extends ILocale {
             "description": string;
         };
     };
+    "_reversi": {
+        "reversi": string;
+        "gameSettings": string;
+        "chooseBoard": string;
+        "blackOrWhite": string;
+        "blackIs": ParameterizedString<"name">;
+        "rules": string;
+        "thisGameIsStartedSoon": string;
+        "waitingForOther": string;
+        "waitingForMe": string;
+        "waitingBoth": string;
+        "ready": string;
+        "cancelReady": string;
+        "opponentTurn": string;
+        "myTurn": string;
+        "turnOf": ParameterizedString<"name">;
+        "pastTurnOf": ParameterizedString<"name">;
+        "surrender": string;
+        "surrendered": string;
+        "drawn": string;
+        "won": ParameterizedString<"name">;
+        "black": string;
+        "white": string;
+        "total": string;
+        "turnCount": ParameterizedString<"count">;
+        "myGames": string;
+        "allGames": string;
+        "ended": string;
+        "playing": string;
+        "isLlotheo": string;
+        "loopedMap": string;
+        "canPutEverywhere": string;
+        "freeMatch": string;
+        "lookingForPlayer": string;
+    };
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 8749a5f49f..6c8a453023 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2506,3 +2506,38 @@ _dataSaver:
   _code:
     title: "コードハイライト"
     description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
+
+_reversi:
+  reversi: "リバーシ"
+  gameSettings: "対局の設定"
+  chooseBoard: "ボードを選択"
+  blackOrWhite: "先行/後攻"
+  blackIs: "{name}が黒(先行)"
+  rules: "ルール"
+  thisGameIsStartedSoon: "対局はまもなく開始されます"
+  waitingForOther: "相手の準備が完了するのを待っています"
+  waitingForMe: "あなたの準備が完了するのを待っています"
+  waitingBoth: "準備してください"
+  ready: "準備完了"
+  cancelReady: "準備を再開"
+  opponentTurn: "相手のターンです"
+  myTurn: "あなたのターンです"
+  turnOf: "{name}のターンです"
+  pastTurnOf: "{name}のターン"
+  surrender: "投了"
+  surrendered: "投了により"
+  drawn: "引き分け"
+  won: "{name}の勝ち"
+  black: "é»’"
+  white: "白"
+  total: "合計"
+  turnCount: "{count}ターン目"
+  myGames: "自分の対局"
+  allGames: "みんなの対局"
+  ended: "終了"
+  playing: "対局中"
+  isLlotheo: "石の少ない方が勝ち(ロセオ)"
+  loopedMap: "ループマップ"
+  canPutEverywhere: "どこでも置けるモード"
+  freeMatch: "フリーマッチ"
+  lookingForPlayer: "対戦相手を探しています"
diff --git a/packages/backend/migration/1705475608437-reversi.js b/packages/backend/migration/1705475608437-reversi.js
new file mode 100644
index 0000000000..c9d69e2c7c
--- /dev/null
+++ b/packages/backend/migration/1705475608437-reversi.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi1705475608437 {
+    name = 'Reversi1705475608437'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_b46ec40746efceac604142be1c"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_b604d92d6c7aec38627f6eaf16"`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "createdAt"`);
+        await queryRunner.query(`ALTER TABLE "reversi_matching" DROP COLUMN "createdAt"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_matching" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
+        await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
+        await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
+    }
+}
diff --git a/packages/backend/migration/1705654039457-reversi-2.js b/packages/backend/migration/1705654039457-reversi-2.js
new file mode 100644
index 0000000000..33747ba9f7
--- /dev/null
+++ b/packages/backend/migration/1705654039457-reversi-2.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi21705654039457 {
+    name = 'Reversi21705654039457'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Accepted" TO "user1Ready"`);
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Accepted" TO "user2Ready"`);
+    }
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Ready" TO "user1Accepted"`);
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Ready" TO "user2Accepted"`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 5ab476295c..f8e82c5a1c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -107,6 +107,7 @@
 		"cli-highlight": "2.1.11",
 		"color-convert": "2.0.1",
 		"content-disposition": "0.5.4",
+		"crc-32": "^1.2.2",
 		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
 		"fastify": "4.24.3",
@@ -133,6 +134,7 @@
 		"microformats-parser": "2.0.2",
 		"mime-types": "2.1.35",
 		"misskey-js": "workspace:*",
+		"misskey-reversi": "workspace:*",
 		"ms": "3.0.0-canary.1",
 		"nanoid": "5.0.4",
 		"nested-property": "4.0.0",
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index bc6d24b951..c9e285346e 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -66,6 +66,8 @@ import { FeaturedService } from './FeaturedService.js';
 import { FanoutTimelineService } from './FanoutTimelineService.js';
 import { ChannelFollowingService } from './ChannelFollowingService.js';
 import { RegistryApiService } from './RegistryApiService.js';
+import { ReversiService } from './ReversiService.js';
+
 import { ChartLoggerService } from './chart/ChartLoggerService.js';
 import FederationChart from './chart/charts/federation.js';
 import NotesChart from './chart/charts/notes.js';
@@ -80,6 +82,7 @@ import PerUserFollowingChart from './chart/charts/per-user-following.js';
 import PerUserDriveChart from './chart/charts/per-user-drive.js';
 import ApRequestChart from './chart/charts/ap-request.js';
 import { ChartManagementService } from './chart/ChartManagementService.js';
+
 import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
 import { AntennaEntityService } from './entities/AntennaEntityService.js';
 import { AppEntityService } from './entities/AppEntityService.js';
@@ -112,6 +115,8 @@ import { UserListEntityService } from './entities/UserListEntityService.js';
 import { FlashEntityService } from './entities/FlashEntityService.js';
 import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
 import { RoleEntityService } from './entities/RoleEntityService.js';
+import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+
 import { ApAudienceService } from './activitypub/ApAudienceService.js';
 import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
 import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -199,6 +204,7 @@ const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', use
 const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
 const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
 const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
+const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
 
 const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
 const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -247,6 +253,7 @@ const $UserListEntityService: Provider = { provide: 'UserListEntityService', use
 const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
 const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
 const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
+const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
 
 const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
 const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -336,6 +343,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		ChartLoggerService,
 		FederationChart,
 		NotesChart,
@@ -350,6 +359,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -382,6 +392,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -466,6 +478,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$ChartLoggerService,
 		$FederationChart,
 		$NotesChart,
@@ -480,6 +494,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -512,6 +527,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
@@ -597,6 +614,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		FederationChart,
 		NotesChart,
 		UsersChart,
@@ -610,6 +629,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -642,6 +662,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -726,6 +748,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$FederationChart,
 		$NotesChart,
 		$UsersChart,
@@ -739,6 +763,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -771,6 +796,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index d175f21f2f..11a8935be2 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -18,7 +18,7 @@ import type { MiSignin } from '@/models/Signin.js';
 import type { MiPage } from '@/models/Page.js';
 import type { MiWebhook } from '@/models/Webhook.js';
 import type { MiMeta } from '@/models/Meta.js';
-import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
+import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
@@ -159,6 +159,43 @@ export interface AdminEventTypes {
 		comment: string;
 	};
 }
+
+export interface ReversiEventTypes {
+	matched: {
+		game: Packed<'ReversiGameDetailed'>;
+	};
+	invited: {
+		user: Packed<'User'>;
+	};
+}
+
+export interface ReversiGameEventTypes {
+	changeReadyStates: {
+		user1: boolean;
+		user2: boolean;
+	};
+	updateSettings: {
+		userId: MiUser['id'];
+		key: string;
+		value: any;
+	};
+	putStone: {
+		at: number;
+		color: boolean;
+		pos: number;
+		next: boolean;
+	};
+	syncState: {
+		crc32: string;
+	};
+	started: {
+		game: Packed<'ReversiGameDetailed'>;
+	};
+	ended: {
+		winnerId: MiUser['id'] | null;
+		game: Packed<'ReversiGameDetailed'>;
+	};
+}
 //#endregion
 
 // 辞書(interface or type)から{ type, body }ユニオンを定義
@@ -249,6 +286,14 @@ export type GlobalEvents = {
 		name: 'notesStream';
 		payload: Serialized<Packed<'Note'>>;
 	};
+	reversi: {
+		name: `reversiStream:${MiUser['id']}`;
+		payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
+	};
+	reversiGame: {
+		name: `reversiGameStream:${MiReversiGame['id']}`;
+		payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
+	};
 };
 
 // API event definitions
@@ -338,4 +383,14 @@ export class GlobalEventService {
 	public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
 		this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
+
+	@bindThis
+	public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
+		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	@bindThis
+	public publishReversiGameStream<K extends keyof ReversiGameEventTypes>(gameId: MiReversiGame['id'], type: K, value?: ReversiGameEventTypes[K]): void {
+		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
+	}
 }
diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts
new file mode 100644
index 0000000000..cd990ba775
--- /dev/null
+++ b/packages/backend/src/core/ReversiService.ts
@@ -0,0 +1,411 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import CRC32 from 'crc-32';
+import { ModuleRef } from '@nestjs/core';
+import * as Reversi from 'misskey-reversi';
+import { IsNull } from 'typeorm';
+import type {
+	MiReversiGame,
+	ReversiGamesRepository,
+	UsersRepository,
+} from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { NotificationService } from '@/core/NotificationService.js';
+import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
+
+const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
+
+@Injectable()
+export class ReversiService implements OnApplicationShutdown, OnModuleInit {
+	private notificationService: NotificationService;
+
+	constructor(
+		private moduleRef: ModuleRef,
+
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private cacheService: CacheService,
+		private userEntityService: UserEntityService,
+		private globalEventService: GlobalEventService,
+		private reversiGameEntityService: ReversiGameEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	async onModuleInit() {
+		this.notificationService = this.moduleRef.get(NotificationService.name);
+	}
+
+	@bindThis
+	public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
+		if (targetUser.id === me.id) {
+			throw new Error('You cannot match yourself.');
+		}
+
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${me.id}`,
+			Date.now() - MATCHING_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+
+		if (invitations.includes(targetUser.id)) {
+			await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
+
+			const game = await this.reversiGamesRepository.insert({
+				id: this.idService.gen(),
+				user1Id: targetUser.id,
+				user2Id: me.id,
+				user1Ready: false,
+				user2Ready: false,
+				isStarted: false,
+				isEnded: false,
+				logs: [],
+				map: Reversi.maps.eighteight.data,
+				bw: 'random',
+				isLlotheo: false,
+			}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
+
+			const packed = await this.reversiGameEntityService.packDetail(game, { id: targetUser.id });
+			this.globalEventService.publishReversiStream(targetUser.id, 'matched', { game: packed });
+
+			return game;
+		} else {
+			this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
+
+			this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
+				user: await this.userEntityService.pack(me, targetUser),
+			});
+
+			return null;
+		}
+	}
+
+	@bindThis
+	public async matchAnyUser(me: MiUser): Promise<MiReversiGame | null> {
+		//#region まず自分宛ての招待を探す
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${me.id}`,
+			Date.now() - MATCHING_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+
+		if (invitations.length > 0) {
+			const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
+			await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
+
+			const game = await this.reversiGamesRepository.insert({
+				id: this.idService.gen(),
+				user1Id: invitorId,
+				user2Id: me.id,
+				user1Ready: false,
+				user2Ready: false,
+				isStarted: false,
+				isEnded: false,
+				logs: [],
+				map: Reversi.maps.eighteight.data,
+				bw: 'random',
+				isLlotheo: false,
+			}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
+
+			const packed = await this.reversiGameEntityService.packDetail(game, { id: invitorId });
+			this.globalEventService.publishReversiStream(invitorId, 'matched', { game: packed });
+
+			return game;
+		}
+		//#endregion
+
+		const matchings = await this.redisClient.zrange(
+			'reversi:matchAny',
+			Date.now() - MATCHING_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+
+		const userIds = matchings.filter(id => id !== me.id);
+
+		if (userIds.length > 0) {
+			// pick random
+			const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)];
+
+			await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
+
+			const game = await this.reversiGamesRepository.insert({
+				id: this.idService.gen(),
+				user1Id: matchedUserId,
+				user2Id: me.id,
+				user1Ready: false,
+				user2Ready: false,
+				isStarted: false,
+				isEnded: false,
+				logs: [],
+				map: Reversi.maps.eighteight.data,
+				bw: 'random',
+				isLlotheo: false,
+			}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
+
+			const packed = await this.reversiGameEntityService.packDetail(game, { id: matchedUserId });
+			this.globalEventService.publishReversiStream(matchedUserId, 'matched', { game: packed });
+
+			return game;
+		} else {
+			await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id);
+			return null;
+		}
+	}
+
+	@bindThis
+	public async matchSpecificUserCancel(user: MiUser, targetUserId: MiUser['id']) {
+		await this.redisClient.zrem(`reversi:matchSpecific:${targetUserId}`, user.id);
+	}
+
+	@bindThis
+	public async matchAnyUserCancel(user: MiUser) {
+		await this.redisClient.zrem('reversi:matchAny', user.id);
+	}
+
+	@bindThis
+	public async gameReady(game: MiReversiGame, user: MiUser, ready: boolean) {
+		if (game.isStarted) return;
+
+		let isBothReady = false;
+
+		if (game.user1Id === user.id) {
+			await this.reversiGamesRepository.update(game.id, {
+				user1Ready: ready,
+			});
+
+			this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
+				user1: ready,
+				user2: game.user2Ready,
+			});
+
+			if (ready && game.user2Ready) isBothReady = true;
+		} else if (game.user2Id === user.id) {
+			await this.reversiGamesRepository.update(game.id, {
+				user2Ready: ready,
+			});
+
+			this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
+				user1: game.user1Ready,
+				user2: ready,
+			});
+
+			if (ready && game.user1Ready) isBothReady = true;
+		} else {
+			return;
+		}
+
+		if (isBothReady) {
+			// 3秒後、両者readyならゲーム開始
+			setTimeout(async () => {
+				const freshGame = await this.reversiGamesRepository.findOneBy({ id: 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;
+				}
+
+				const map = freshGame.map != null ? freshGame.map : getRandomMap();
+
+				await this.reversiGamesRepository.update(game.id, {
+					startedAt: new Date(),
+					isStarted: true,
+					black: bw,
+					map: map,
+				});
+
+				//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
+				const o = new Reversi.Game(map, {
+					isLlotheo: freshGame.isLlotheo,
+					canPutEverywhere: freshGame.canPutEverywhere,
+					loopedBoard: freshGame.loopedBoard,
+				});
+
+				if (o.isEnded) {
+					let winner;
+					if (o.winner === true) {
+						winner = freshGame.black === 1 ? freshGame.user1Id : freshGame.user2Id;
+					} else if (o.winner === false) {
+						winner = freshGame.black === 1 ? freshGame.user2Id : freshGame.user1Id;
+					} else {
+						winner = null;
+					}
+
+					await this.reversiGamesRepository.update(game.id, {
+						isEnded: true,
+						winnerId: winner,
+					});
+
+					this.globalEventService.publishReversiGameStream(game.id, 'ended', {
+						winnerId: winner,
+						game: await this.reversiGameEntityService.packDetail(game.id, user),
+					});
+				}
+				//#endregion
+
+				this.globalEventService.publishReversiGameStream(game.id, 'started', {
+					game: await this.reversiGameEntityService.packDetail(game.id, user),
+				});
+			}, 3000);
+		}
+	}
+
+	@bindThis
+	public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${user.id}`,
+			Date.now() - MATCHING_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+		return invitations;
+	}
+
+	@bindThis
+	public async updateSettings(game: MiReversiGame, user: MiUser, key: string, value: any) {
+		if (game.isStarted) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+		if ((game.user1Id === user.id) && game.user1Ready) return;
+		if ((game.user2Id === user.id) && game.user2Ready) return;
+
+		if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return;
+
+		await this.reversiGamesRepository.update(game.id, {
+			[key]: value,
+		});
+
+		this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
+			userId: user.id,
+			key: key,
+			value: value,
+		});
+	}
+
+	@bindThis
+	public async putStoneToGame(game: MiReversiGame, user: MiUser, pos: number) {
+		if (!game.isStarted) return;
+		if (game.isEnded) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+
+		const myColor =
+			((game.user1Id === user.id) && game.black === 1) || ((game.user2Id === user.id) && game.black === 2)
+				? true
+				: false;
+
+		const o = new Reversi.Game(game.map, {
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+		});
+
+		// 盤面の状態を再生
+		for (const log of game.logs) {
+			o.put(log.color, log.pos);
+		}
+
+		if (o.turn !== myColor) return;
+
+		if (!o.canPut(myColor, pos)) return;
+		o.put(myColor, pos);
+
+		let winner;
+		if (o.isEnded) {
+			if (o.winner === true) {
+				winner = game.black === 1 ? game.user1Id : game.user2Id;
+			} else if (o.winner === false) {
+				winner = game.black === 1 ? game.user2Id : game.user1Id;
+			} else {
+				winner = null;
+			}
+		}
+
+		const log = {
+			at: Date.now(),
+			color: myColor,
+			pos,
+		};
+
+		const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString();
+
+		game.logs.push(log);
+
+		await this.reversiGamesRepository.update(game.id, {
+			crc32,
+			isEnded: o.isEnded,
+			winnerId: winner,
+			logs: game.logs,
+		});
+
+		this.globalEventService.publishReversiGameStream(game.id, 'putStone', {
+			...log,
+			next: o.turn,
+		});
+
+		if (o.isEnded) {
+			this.globalEventService.publishReversiGameStream(game.id, 'ended', {
+				winnerId: winner,
+				game: await this.reversiGameEntityService.packDetail(game.id, user),
+			});
+		}
+	}
+
+	@bindThis
+	public async surrender(game: MiReversiGame, user: MiUser) {
+		if (game.isEnded) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+
+		const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
+
+		await this.reversiGamesRepository.update(game.id, {
+			surrendered: user.id,
+			isEnded: true,
+			winnerId: winnerId,
+		});
+
+		this.globalEventService.publishReversiGameStream(game.id, 'ended', {
+			winnerId: winnerId,
+			game: await this.reversiGameEntityService.packDetail(game.id, user),
+		});
+	}
+
+	@bindThis
+	public async get(id: MiReversiGame['id']) {
+		return this.reversiGamesRepository.findOneBy({ id });
+	}
+
+	@bindThis
+	public dispose(): void {
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
+}
diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts
new file mode 100644
index 0000000000..8d95204928
--- /dev/null
+++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts
@@ -0,0 +1,115 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { ReversiGamesRepository } from '@/models/_.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/json-schema.js';
+import type { } from '@/models/Blocking.js';
+import type { MiUser } from '@/models/User.js';
+import type { MiReversiGame } from '@/models/ReversiGame.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class ReversiGameEntityService {
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private userEntityService: UserEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	@bindThis
+	public async packDetail(
+		src: MiReversiGame['id'] | MiReversiGame,
+		me?: { id: MiUser['id'] } | null | undefined,
+	): Promise<Packed<'ReversiGameDetailed'>> {
+		const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+
+		return await awaitAll({
+			id: game.id,
+			createdAt: this.idService.parse(game.id).date.toISOString(),
+			startedAt: game.startedAt && game.startedAt.toISOString(),
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			form1: game.form1,
+			form2: game.form2,
+			user1Ready: game.user1Ready,
+			user2Ready: game.user2Ready,
+			user1Id: game.user1Id,
+			user2Id: game.user2Id,
+			user1: this.userEntityService.pack(game.user1Id, me),
+			user2: this.userEntityService.pack(game.user2Id, me),
+			winnerId: game.winnerId,
+			winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
+			surrendered: game.surrendered,
+			black: game.black,
+			bw: game.bw,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			logs: game.logs.map(log => ({
+				at: log.at,
+				color: log.color,
+				pos: log.pos,
+			})),
+			map: game.map,
+		});
+	}
+
+	@bindThis
+	public packDetailMany(
+		xs: MiReversiGame[],
+		me?: { id: MiUser['id'] } | null | undefined,
+	) {
+		return Promise.all(xs.map(x => this.packDetail(x, me)));
+	}
+
+	@bindThis
+	public async packLite(
+		src: MiReversiGame['id'] | MiReversiGame,
+		me?: { id: MiUser['id'] } | null | undefined,
+	): Promise<Packed<'ReversiGameLite'>> {
+		const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+
+		return await awaitAll({
+			id: game.id,
+			createdAt: this.idService.parse(game.id).date.toISOString(),
+			startedAt: game.startedAt && game.startedAt.toISOString(),
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			form1: game.form1,
+			form2: game.form2,
+			user1Ready: game.user1Ready,
+			user2Ready: game.user2Ready,
+			user1Id: game.user1Id,
+			user2Id: game.user2Id,
+			user1: this.userEntityService.pack(game.user1Id, me),
+			user2: this.userEntityService.pack(game.user2Id, me),
+			winnerId: game.winnerId,
+			winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
+			surrendered: game.surrendered,
+			black: game.black,
+			bw: game.bw,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+		});
+	}
+
+	@bindThis
+	public packLiteMany(
+		xs: MiReversiGame[],
+		me?: { id: MiUser['id'] } | null | undefined,
+	) {
+		return Promise.all(xs.map(x => this.packLite(x, me)));
+	}
+}
+
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index e29fee3f96..73de01f33a 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -79,5 +79,6 @@ export const DI = {
 	flashLikesRepository: Symbol('flashLikesRepository'),
 	userMemosRepository: Symbol('userMemosRepository'),
 	bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
+	reversiGamesRepository: Symbol('reversiGamesRepository'),
 	//#endregion
 };
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 176978d35f..b4f0541712 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -39,6 +39,7 @@ import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
 import { packedSigninSchema } from '@/models/json-schema/signin.js';
 import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
 import { packedAdSchema } from '@/models/json-schema/ad.js';
+import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
 
 export const refs = {
 	UserLite: packedUserLiteSchema,
@@ -78,6 +79,8 @@ export const refs = {
 	Signin: packedSigninSchema,
 	RoleLite: packedRoleLiteSchema,
 	Role: packedRoleSchema,
+	ReversiGameLite: packedReversiGameLiteSchema,
+	ReversiGameDetailed: packedReversiGameDetailedSchema,
 };
 
 export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 0399536c3e..2b2aaeb91c 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -5,7 +5,7 @@
 
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord } from './_.js';
+import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, MiBubbleGameRecord, MiReversiGame } from './_.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -399,12 +399,18 @@ const $userMemosRepository: Provider = {
 	inject: [DI.db],
 };
 
-export const $bubbleGameRecordsRepository: Provider = {
+const $bubbleGameRecordsRepository: Provider = {
 	provide: DI.bubbleGameRecordsRepository,
 	useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
 	inject: [DI.db],
 };
 
+const $reversiGamesRepository: Provider = {
+	provide: DI.reversiGamesRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
+	inject: [DI.db],
+};
+
 @Module({
 	imports: [
 	],
@@ -475,6 +481,7 @@ export const $bubbleGameRecordsRepository: Provider = {
 		$flashLikesRepository,
 		$userMemosRepository,
 		$bubbleGameRecordsRepository,
+		$reversiGamesRepository,
 	],
 	exports: [
 		$usersRepository,
@@ -543,6 +550,7 @@ export const $bubbleGameRecordsRepository: Provider = {
 		$flashLikesRepository,
 		$userMemosRepository,
 		$bubbleGameRecordsRepository,
+		$reversiGamesRepository,
 	],
 })
 export class RepositoryModule {}
diff --git a/packages/backend/src/models/ReversiGame.ts b/packages/backend/src/models/ReversiGame.ts
new file mode 100644
index 0000000000..d297d1f01d
--- /dev/null
+++ b/packages/backend/src/models/ReversiGame.ts
@@ -0,0 +1,127 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('reversi_game')
+export class MiReversiGame {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Column('timestamp with time zone', {
+		nullable: true,
+		comment: 'The started date of the ReversiGame.',
+	})
+	public startedAt: Date | null;
+
+	@Column(id())
+	public user1Id: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user1: MiUser | null;
+
+	@Column(id())
+	public user2Id: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user2: MiUser | null;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public user1Ready: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public user2Ready: boolean;
+
+	/**
+	 * どちらのプレイヤーが先行(黒)か
+	 * 1 ... user1
+	 * 2 ... user2
+	 */
+	@Column('integer', {
+		nullable: true,
+	})
+	public black: number | null;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isStarted: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isEnded: boolean;
+
+	@Column({
+		...id(),
+		nullable: true,
+	})
+	public winnerId: MiUser['id'] | null;
+
+	@Column({
+		...id(),
+		nullable: true,
+	})
+	public surrendered: MiUser['id'] | null;
+
+	@Column('jsonb', {
+		default: [],
+	})
+	public logs: {
+		at: number;
+		color: boolean;
+		pos: number;
+	}[];
+
+	@Column('varchar', {
+		array: true, length: 64,
+	})
+	public map: string[];
+
+	@Column('varchar', {
+		length: 32,
+	})
+	public bw: string;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isLlotheo: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public canPutEverywhere: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public loopedBoard: boolean;
+
+	@Column('jsonb', {
+		nullable: true, default: null,
+	})
+	public form1: any | null;
+
+	@Column('jsonb', {
+		nullable: true, default: null,
+	})
+	public form2: any | null;
+
+	/**
+	 * ログのposを文字列としてすべて連結したもののCRC32値
+	 */
+	@Column('varchar', {
+		length: 32, nullable: true,
+	})
+	public crc32: string | null;
+}
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index a1c4b0743e..a1a0d8823d 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -69,6 +69,8 @@ import { MiFlash } from '@/models/Flash.js';
 import { MiFlashLike } from '@/models/FlashLike.js';
 import { MiUserListFavorite } from '@/models/UserListFavorite.js';
 import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
+import { MiReversiGame } from '@/models/ReversiGame.js';
+
 import type { Repository } from 'typeorm';
 
 export {
@@ -138,6 +140,7 @@ export {
 	MiFlashLike,
 	MiUserMemo,
 	MiBubbleGameRecord,
+	MiReversiGame,
 };
 
 export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
@@ -206,3 +209,4 @@ export type FlashsRepository = Repository<MiFlash>;
 export type FlashLikesRepository = Repository<MiFlashLike>;
 export type UserMemoRepository = Repository<MiUserMemo>;
 export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
+export type ReversiGamesRepository = Repository<MiReversiGame>;
diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts
new file mode 100644
index 0000000000..0d23b9dc79
--- /dev/null
+++ b/packages/backend/src/models/json-schema/reversi-game.ts
@@ -0,0 +1,234 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedReversiGameLiteSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		startedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		isStarted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isEnded: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		form1: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		form2: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		user1Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user2Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user1Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user2Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user1: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		user2: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		winnerId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		winner: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'User',
+		},
+		surrendered: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		black: {
+			type: 'number',
+			optional: false, nullable: true,
+		},
+		bw: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		isLlotheo: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPutEverywhere: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		loopedBoard: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+export const packedReversiGameDetailedSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		startedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		isStarted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isEnded: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		form1: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		form2: {
+			type: 'any',
+			optional: false, nullable: true,
+		},
+		user1Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user2Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user1Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user2Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user1: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		user2: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'User',
+		},
+		winnerId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		winner: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'User',
+		},
+		surrendered: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		black: {
+			type: 'number',
+			optional: false, nullable: true,
+		},
+		bw: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		isLlotheo: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPutEverywhere: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		loopedBoard: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		logs: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'object',
+				optional: false, nullable: false,
+				properties: {
+					at: {
+						type: 'number',
+						optional: false, nullable: false,
+					},
+					color: {
+						type: 'boolean',
+						optional: false, nullable: false,
+					},
+					pos: {
+						type: 'number',
+						optional: false, nullable: false,
+					},
+				},
+			},
+		},
+		map: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	},
+} as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 0430e9ca19..1e063c8673 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -77,6 +77,7 @@ import { MiFlash } from '@/models/Flash.js';
 import { MiFlashLike } from '@/models/FlashLike.js';
 import { MiUserMemo } from '@/models/UserMemo.js';
 import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
+import { MiReversiGame } from '@/models/ReversiGame.js';
 
 import { Config } from '@/config.js';
 import MisskeyLogger from '@/logger.js';
@@ -192,6 +193,7 @@ export const entities = [
 	MiFlashLike,
 	MiUserMemo,
 	MiBubbleGameRecord,
+	MiReversiGame,
 	...charts,
 ];
 
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index fa81380f01..aed352d15e 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -22,9 +22,13 @@ import { SigninApiService } from './api/SigninApiService.js';
 import { SigninService } from './api/SigninService.js';
 import { SignupApiService } from './api/SignupApiService.js';
 import { StreamingApiServerService } from './api/StreamingApiServerService.js';
+import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
 import { ClientServerService } from './web/ClientServerService.js';
 import { FeedService } from './web/FeedService.js';
 import { UrlPreviewService } from './web/UrlPreviewService.js';
+import { ClientLoggerService } from './web/ClientLoggerService.js';
+import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
+
 import { MainChannelService } from './api/stream/channels/main.js';
 import { AdminChannelService } from './api/stream/channels/admin.js';
 import { AntennaChannelService } from './api/stream/channels/antenna.js';
@@ -38,10 +42,9 @@ import { LocalTimelineChannelService } from './api/stream/channels/local-timelin
 import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
 import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
 import { UserListChannelService } from './api/stream/channels/user-list.js';
-import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
-import { ClientLoggerService } from './web/ClientLoggerService.js';
 import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
-import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
+import { ReversiChannelService } from './api/stream/channels/reversi.js';
+import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
 
 @Module({
 	imports: [
@@ -77,6 +80,8 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
 		GlobalTimelineChannelService,
 		HashtagChannelService,
 		RoleTimelineChannelService,
+		ReversiChannelService,
+		ReversiGameChannelService,
 		HomeTimelineChannelService,
 		HybridTimelineChannelService,
 		LocalTimelineChannelService,
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 781332d349..df69ce2385 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -366,6 +366,12 @@ import * as ep___fetchExternalResources from './endpoints/fetch-external-resourc
 import * as ep___retention from './endpoints/retention.js';
 import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
 import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
+import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
+import * as ep___reversi_games from './endpoints/reversi/games.js';
+import * as ep___reversi_match from './endpoints/reversi/match.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_surrender from './endpoints/reversi/surrender.js';
 import { GetterService } from './GetterService.js';
 import { ApiLoggerService } from './ApiLoggerService.js';
 import type { Provider } from '@nestjs/common';
@@ -730,6 +736,12 @@ const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resource
 const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
 const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
 const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
+const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default };
+const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default };
+const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.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_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
 
 @Module({
 	imports: [
@@ -1098,6 +1110,12 @@ const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useCl
 		$retention,
 		$bubbleGame_register,
 		$bubbleGame_ranking,
+		$reversi_cancelMatch,
+		$reversi_games,
+		$reversi_match,
+		$reversi_invitations,
+		$reversi_showGame,
+		$reversi_surrender,
 	],
 	exports: [
 		$admin_meta,
@@ -1457,6 +1475,12 @@ const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useCl
 		$retention,
 		$bubbleGame_register,
 		$bubbleGame_ranking,
+		$reversi_cancelMatch,
+		$reversi_games,
+		$reversi_match,
+		$reversi_invitations,
+		$reversi_showGame,
+		$reversi_surrender,
 	],
 })
 export class EndpointsModule {}
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index f17db41a5d..0f2c8cb754 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -367,6 +367,12 @@ import * as ep___fetchExternalResources from './endpoints/fetch-external-resourc
 import * as ep___retention from './endpoints/retention.js';
 import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
 import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
+import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
+import * as ep___reversi_games from './endpoints/reversi/games.js';
+import * as ep___reversi_match from './endpoints/reversi/match.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_surrender from './endpoints/reversi/surrender.js';
 
 const eps = [
 	['admin/meta', ep___admin_meta],
@@ -729,6 +735,12 @@ const eps = [
 	['retention', ep___retention],
 	['bubble-game/register', ep___bubbleGame_register],
 	['bubble-game/ranking', ep___bubbleGame_ranking],
+	['reversi/cancel-match', ep___reversi_cancelMatch],
+	['reversi/games', ep___reversi_games],
+	['reversi/match', ep___reversi_match],
+	['reversi/invitations', ep___reversi_invitations],
+	['reversi/show-game', ep___reversi_showGame],
+	['reversi/surrender', ep___reversi_surrender],
 ];
 
 interface IEndpointMetaBase {
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index 7ff7b5de3a..2d853b94f3 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// Get mutee
-			const mutee = await getterService.getUser(ps.userId).catch(err => {
+			const mutee = await this.getterService.getUser(ps.userId).catch(err => {
 				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 				throw err;
 			});
diff --git a/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts
new file mode 100644
index 0000000000..8edc049500
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts
@@ -0,0 +1,44 @@
+/*
+ * 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';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+	},
+
+	res: {
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			if (ps.userId) {
+				await this.reversiService.matchSpecificUserCancel(me, ps.userId);
+				return;
+			} else {
+				await this.reversiService.matchAnyUserCancel(me);
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/games.ts b/packages/backend/src/server/api/endpoints/reversi/games.ts
new file mode 100644
index 0000000000..5322cd0987
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/games.ts
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Brackets } from 'typeorm';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { DI } from '@/di-symbols.js';
+import type { ReversiGamesRepository } from '@/models/_.js';
+import { QueryService } from '@/core/QueryService.js';
+
+export const meta = {
+	requireCredential: false,
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: { ref: 'ReversiGameLite' },
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		my: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private reversiGameEntityService: ReversiGameEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
+				.andWhere('game.isStarted = TRUE');
+
+			if (ps.my && me) {
+				query.andWhere(new Brackets(qb => {
+					qb
+						.where('game.user1Id = :userId', { userId: me.id })
+						.orWhere('game.user2Id = :userId', { userId: me.id });
+				}));
+			}
+
+			const games = await query.take(ps.limit).getMany();
+
+			return await this.reversiGameEntityService.packLiteMany(games, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/invitations.ts b/packages/backend/src/server/api/endpoints/reversi/invitations.ts
new file mode 100644
index 0000000000..0b7107bb0d
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/invitations.ts
@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ReversiService } from '@/core/ReversiService.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'read:account',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: { ref: 'UserLite' },
+	},
+} as const;
+
+export const paramDef = {
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private userEntityService: UserEntityService,
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const invitations = await this.reversiService.getInvitations(me);
+
+			return await this.userEntityService.packMany(invitations, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts
new file mode 100644
index 0000000000..da5a3409ef
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/match.ts
@@ -0,0 +1,66 @@
+/*
+ * 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';
+import { GetterService } from '../../GetterService.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '0b4f0559-b484-4e31-9581-3f73cee89b28',
+		},
+
+		isYourself: {
+			message: 'Target user is yourself.',
+			code: 'TARGET_IS_YOURSELF',
+			id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e',
+		},
+	},
+
+	res: {
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private getterService: GetterService,
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			if (ps.userId === me.id) throw new ApiError(meta.errors.isYourself);
+
+			const target = ps.userId ? await this.getterService.getUser(ps.userId).catch(err => {
+				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+				throw err;
+			}) : null;
+
+			const game = target ? await this.reversiService.matchSpecificUser(me, target) : await this.reversiService.matchAnyUser(me);
+
+			if (game == null) return;
+
+			return await this.reversiGameEntityService.packDetail(game, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/show-game.ts b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
new file mode 100644
index 0000000000..de571053e1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 = {
+	requireCredential: false,
+
+	errors: {
+		noSuchGame: {
+			message: 'No such game.',
+			code: 'NO_SUCH_GAME',
+			id: 'f13a03db-fae1-46c9-87f3-43c8165419e1',
+		},
+	},
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		ref: 'ReversiGameDetailed',
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['gameId'],
+} 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.get(ps.gameId);
+
+			if (game == null) {
+				throw new ApiError(meta.errors.noSuchGame);
+			}
+
+			return await this.reversiGameEntityService.packDetail(game, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/surrender.ts b/packages/backend/src/server/api/endpoints/reversi/surrender.ts
new file mode 100644
index 0000000000..c47d36be33
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/surrender.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { ApiError } from '../../error.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchGame: {
+			message: 'No such game.',
+			code: 'NO_SUCH_GAME',
+			id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df',
+		},
+
+		alreadyEnded: {
+			message: 'That game has already ended.',
+			code: 'ALREADY_ENDED',
+			id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d',
+		},
+
+		accessDenied: {
+			message: 'Access denied.',
+			code: 'ACCESS_DENIED',
+			id: '6e04164b-a992-4c93-8489-2123069973e1',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['gameId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const game = await this.reversiService.get(ps.gameId);
+
+			if (game == null) {
+				throw new ApiError(meta.errors.noSuchGame);
+			}
+
+			if (game.isEnded) {
+				throw new ApiError(meta.errors.alreadyEnded);
+			}
+
+			if ((game.user1Id !== me.id) && (game.user2Id !== me.id)) {
+				throw new ApiError(meta.errors.accessDenied);
+			}
+
+			await this.reversiService.surrender(game, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts
index 3bc5380132..998429dd0a 100644
--- a/packages/backend/src/server/api/stream/ChannelsService.ts
+++ b/packages/backend/src/server/api/stream/ChannelsService.ts
@@ -19,6 +19,8 @@ import { AntennaChannelService } from './channels/antenna.js';
 import { DriveChannelService } from './channels/drive.js';
 import { HashtagChannelService } from './channels/hashtag.js';
 import { RoleTimelineChannelService } from './channels/role-timeline.js';
+import { ReversiChannelService } from './channels/reversi.js';
+import { ReversiGameChannelService } from './channels/reversi-game.js';
 import { type MiChannelService } from './channel.js';
 
 @Injectable()
@@ -38,6 +40,8 @@ export class ChannelsService {
 		private serverStatsChannelService: ServerStatsChannelService,
 		private queueStatsChannelService: QueueStatsChannelService,
 		private adminChannelService: AdminChannelService,
+		private reversiChannelService: ReversiChannelService,
+		private reversiGameChannelService: ReversiGameChannelService,
 	) {
 	}
 
@@ -58,6 +62,8 @@ export class ChannelsService {
 			case 'serverStats': return this.serverStatsChannelService;
 			case 'queueStats': return this.queueStatsChannelService;
 			case 'admin': return this.adminChannelService;
+			case 'reversi': return this.reversiChannelService;
+			case 'reversiGame': return this.reversiGameChannelService;
 
 			default:
 				throw new Error(`no such channel: ${name}`);
diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts
new file mode 100644
index 0000000000..c67c05fb09
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts
@@ -0,0 +1,130 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { MiReversiGame, ReversiGamesRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ReversiGameChannel extends Channel {
+	public readonly chName = 'reversiGame';
+	public static shouldShare = false;
+	public static requireCredential = false as const;
+	private gameId: MiReversiGame['id'] | null = null;
+
+	constructor(
+		private reversiService: ReversiService,
+		private reversiGamesRepository: ReversiGamesRepository,
+		private reversiGameEntityService: ReversiGameEntityService,
+
+		id: string,
+		connection: Channel['connection'],
+	) {
+		super(id, connection);
+	}
+
+	@bindThis
+	public async init(params: any) {
+		this.gameId = params.gameId as string;
+
+		const game = await this.reversiGamesRepository.findOneBy({
+			id: this.gameId,
+		});
+		if (game == null) return;
+
+		this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
+	}
+
+	@bindThis
+	public onMessage(type: string, body: any) {
+		switch (type) {
+			case 'ready': this.ready(body); break;
+			case 'updateSettings': this.updateSettings(body.key, body.value); break;
+			case 'putStone': this.putStone(body.pos); break;
+			case 'syncState': this.syncState(body.crc32); break;
+		}
+	}
+
+	@bindThis
+	private async updateSettings(key: string, value: any) {
+		if (this.user == null) return;
+
+		// TODO: キャッシュしたい
+		const game = await this.reversiGamesRepository.findOneBy({ id: this.gameId! });
+		if (game == null) throw new Error('game not found');
+
+		this.reversiService.updateSettings(game, this.user, key, value);
+	}
+
+	@bindThis
+	private async ready(ready: boolean) {
+		if (this.user == null) return;
+
+		const game = await this.reversiGamesRepository.findOneBy({ id: this.gameId! });
+		if (game == null) throw new Error('game not found');
+
+		this.reversiService.gameReady(game, this.user, ready);
+	}
+
+	@bindThis
+	private async putStone(pos: number) {
+		if (this.user == null) return;
+
+		// TODO: キャッシュしたい
+		const game = await this.reversiGamesRepository.findOneBy({ id: this.gameId! });
+		if (game == null) throw new Error('game not found');
+
+		this.reversiService.putStoneToGame(game, this.user, pos);
+	}
+
+	@bindThis
+	private async syncState(crc32: string | number) {
+		// TODO: キャッシュしたい
+		const game = await this.reversiGamesRepository.findOneBy({ id: this.gameId! });
+		if (game == null) throw new Error('game not found');
+
+		if (!game.isStarted) return;
+
+		if (crc32.toString() !== game.crc32) {
+			this.send('rescue', await this.reversiGameEntityService.packDetail(game, this.user));
+		}
+	}
+
+	@bindThis
+	public dispose() {
+		// Unsubscribe events
+		this.subscriber.off(`reversiGameStream:${this.gameId}`, this.send);
+	}
+}
+
+@Injectable()
+export class ReversiGameChannelService implements MiChannelService<false> {
+	public readonly shouldShare = ReversiGameChannel.shouldShare;
+	public readonly requireCredential = ReversiGameChannel.requireCredential;
+	public readonly kind = ReversiGameChannel.kind;
+
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+	}
+
+	@bindThis
+	public create(id: string, connection: Channel['connection']): ReversiGameChannel {
+		return new ReversiGameChannel(
+			this.reversiService,
+			this.reversiGamesRepository,
+			this.reversiGameEntityService,
+			id,
+			connection,
+		);
+	}
+}
diff --git a/packages/backend/src/server/api/stream/channels/reversi.ts b/packages/backend/src/server/api/stream/channels/reversi.ts
new file mode 100644
index 0000000000..cb4b1b8d5a
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/reversi.ts
@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { bindThis } from '@/decorators.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ReversiChannel extends Channel {
+	public readonly chName = 'reversi';
+	public static shouldShare = true;
+	public static requireCredential = true as const;
+	public static kind = 'read:account';
+
+	constructor(
+		id: string,
+		connection: Channel['connection'],
+	) {
+		super(id, connection);
+	}
+
+	@bindThis
+	public async init(params: any) {
+		this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
+	}
+
+	@bindThis
+	public dispose() {
+		// Unsubscribe events
+		this.subscriber.off(`reversiStream:${this.user!.id}`, this.send);
+	}
+}
+
+@Injectable()
+export class ReversiChannelService implements MiChannelService<true> {
+	public readonly shouldShare = ReversiChannel.shouldShare;
+	public readonly requireCredential = ReversiChannel.requireCredential;
+	public readonly kind = ReversiChannel.kind;
+
+	constructor(
+	) {
+	}
+
+	@bindThis
+	public create(id: string, connection: Channel['connection']): ReversiChannel {
+		return new ReversiChannel(
+			id,
+			connection,
+		);
+	}
+}
diff --git a/packages/frontend/assets/reversi/logo.png b/packages/frontend/assets/reversi/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..7d807ef1dc57af5ca60cad70277d9112e307d0fc
GIT binary patch
literal 96293
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
R^%ZKXoRqR;iTG>({{v=rb<_X=

literal 0
HcmV?d00001

diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 8c3ce30668..a9a68601fc 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -41,6 +41,7 @@
 		"chartjs-plugin-zoom": "2.0.1",
 		"chromatic": "10.1.0",
 		"compare-versions": "6.1.0",
+		"crc-32": "^1.2.2",
 		"cropperjs": "2.0.0-beta.4",
 		"date-fns": "2.30.0",
 		"escape-regexp": "0.0.1",
@@ -53,6 +54,7 @@
 		"matter-js": "0.19.0",
 		"mfm-js": "0.24.0",
 		"misskey-js": "workspace:*",
+		"misskey-reversi": "workspace:*",
 		"photoswipe": "5.4.3",
 		"punycode": "2.3.1",
 		"rollup": "4.9.1",
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index d9178f3362..22e7ed1ef7 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -18,6 +18,9 @@ export default defineComponent({
 		watch(value, () => {
 			context.emit('update:modelValue', value.value);
 		});
+		watch(() => props.modelValue, v => {
+			value.value = v;
+		});
 		if (!context.slots.default) return null;
 		let options = context.slots.default();
 		const label = context.slots.label && context.slots.label();
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 33b8a9a86d..16416fd2e4 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -52,7 +52,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'changeByUser'): void;
 	(ev: 'update:modelValue', value: string | null): void;
 }>();
 
@@ -77,7 +77,6 @@ const height =
 const focus = () => inputEl.value.focus();
 const onInput = (ev) => {
 	changed.value = true;
-	emit('change', ev);
 };
 
 const updated = () => {
@@ -136,6 +135,7 @@ function show(ev: MouseEvent) {
 			active: computed(() => v.value === option.props.value),
 			action: () => {
 				v.value = option.props.value;
+				emit('changeByUser', v.value);
 			},
 		});
 	};
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index f4aa06950d..ad11ba1940 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -85,7 +85,7 @@ const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
 const selected = ref<Misskey.entities.UserDetailed | null>(null);
 const dialogEl = ref();
 
-const search = () => {
+function search() {
 	if (username.value === '' && host.value === '') {
 		users.value = [];
 		return;
@@ -98,9 +98,9 @@ const search = () => {
 	}).then(_users => {
 		users.value = _users;
 	});
-};
+}
 
-const ok = () => {
+function ok() {
 	if (selected.value == null) return;
 	emit('ok', selected.value);
 	dialogEl.value.close();
@@ -110,12 +110,12 @@ const ok = () => {
 	recents = recents.filter(x => x !== selected.value.id);
 	recents.unshift(selected.value.id);
 	defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
-};
+}
 
-const cancel = () => {
+function cancel() {
 	emit('cancel');
 	dialogEl.value.close();
-};
+}
 
 onMounted(() => {
 	misskeyApi('users/show', {
diff --git a/packages/frontend/src/global/router/definition.ts b/packages/frontend/src/global/router/definition.ts
index 8e1c178ea2..0333770a64 100644
--- a/packages/frontend/src/global/router/definition.ts
+++ b/packages/frontend/src/global/router/definition.ts
@@ -15,6 +15,7 @@ const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
 	loadingComponent: MkLoading,
 	errorComponent: MkError,
 });
+
 const routes = [{
 	path: '/@:initUser/pages/:initPageName/view-source',
 	component: page(() => import('@/pages/page-editor/page-editor.vue')),
@@ -528,18 +529,26 @@ const routes = [{
 	path: '/timeline/antenna/:antennaId',
 	component: page(() => import('@/pages/antenna-timeline.vue')),
 	loginRequired: true,
-}, {
-	path: '/games',
-	component: page(() => import('@/pages/games.vue')),
-	loginRequired: true,
 }, {
 	path: '/clicker',
 	component: page(() => import('@/pages/clicker.vue')),
 	loginRequired: true,
+}, {
+	path: '/games',
+	component: page(() => import('@/pages/games.vue')),
+	loginRequired: false,
 }, {
 	path: '/bubble-game',
 	component: page(() => import('@/pages/drop-and-fusion.vue')),
 	loginRequired: true,
+}, {
+	path: '/reversi',
+	component: page(() => import('@/pages/reversi/index.vue')),
+	loginRequired: false,
+}, {
+	path: '/reversi/g/:gameId',
+	component: page(() => import('@/pages/reversi/game.vue')),
+	loginRequired: false,
 }, {
 	path: '/timeline',
 	component: page(() => import('@/pages/timeline.vue')),
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index a63d61bb8f..9fc3603af0 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -419,7 +419,7 @@ export function form(title, form) {
 	});
 }
 
-export async function selectUser(opts: { includeSelf?: boolean } = {}) {
+export async function selectUser(opts: { includeSelf?: boolean } = {}): Promise<Misskey.entities.UserLite> {
 	return new Promise((resolve, reject) => {
 		popup(defineAsyncComponent(() => import('@/components/MkUserSelectDialog.vue')), {
 			includeSelf: opts.includeSelf,
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index dd3b189c9d..beb2e714e0 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -123,7 +123,7 @@ function onGameEnd() {
 
 definePageMetadata({
 	title: i18n.ts.bubbleGame,
-	icon: 'ti ti-apple',
+	icon: 'ti ti-device-gamepad',
 });
 </script>
 
diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue
index 5d2482ded1..45a135a459 100644
--- a/packages/frontend/src/pages/games.vue
+++ b/packages/frontend/src/pages/games.vue
@@ -7,10 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
 	<MkSpacer :contentMax="800">
-		<div class="_panel">
-			<MkA to="/bubble-game">
-				<img src="/client-assets/drop-and-fusion/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
-			</MkA>
+		<div class="_gaps">
+			<div class="_panel">
+				<MkA to="/bubble-game">
+					<img src="/client-assets/drop-and-fusion/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+				</MkA>
+			</div>
+			<div class="_panel">
+				<MkA to="/reversi">
+					<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+				</MkA>
+			</div>
 		</div>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
new file mode 100644
index 0000000000..18fd74427c
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -0,0 +1,428 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer :contentMax="600">
+	<div :class="$style.root" class="_gaps">
+		<header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ i18n.ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ i18n.ts._reversi.white }})</header>
+
+		<div style="overflow: clip; line-height: 28px;">
+			<div v-if="!iAmPlayer && !game.isEnded && turnUser" class="turn">
+				<Mfm :key="'turn:' + turnUser.id" :text="i18n.t('_reversi.turnOf', { name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
+				<MkEllipsis/>
+			</div>
+			<div v-if="(logPos !== logs.length) && turnUser" class="turn">
+				<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.t('_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/></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 == logs.length" class="result">
+				<template v-if="game.winner">
+					<Mfm :key="'won'" :text="i18n.t('_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>
+				</template>
+				<template v-else>{{ i18n.ts._reversi.drawn }}</template>
+			</div>
+		</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>
+				<div :class="$style.boardCells" :style="cellsStyle">
+					<div
+						v-for="(stone, i) in engine.board"
+						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)"
+					>
+						<img v-if="stone === true" style="pointer-events: none; user-select: none; display: block; width: 100%; height: 100%;" :src="blackUser.avatarUrl">
+						<img v-if="stone === false" style="pointer-events: none; user-select: none; display: block; width: 100%; height: 100%;" :src="whiteUser.avatarUrl">
+					</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.labelsX">
+				<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
+			</div>
+		</div>
+
+		<div class="status"><b>{{ i18n.t('_reversi.turnCount', { count: logPos }) }}</b> {{ i18n.ts._reversi.black }}:{{ engine.blackCount }} {{ i18n.ts._reversi.white }}:{{ engine.whiteCount }} {{ i18n.ts._reversi.total }}:{{ engine.blackCount + engine.whiteCount }}</div>
+
+		<div v-if="!game.isEnded && iAmPlayer" class="_buttonsCenter">
+			<MkButton danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
+		</div>
+
+		<div v-if="game.isEnded" class="_panel _gaps_s" style="padding: 16px;">
+			<div>{{ logPos }} / {{ logs.length }}</div>
+			<div v-if="!autoplaying" class="_buttonsCenter">
+				<MkButton :disabled="logPos === 0" @click="logPos = 0"><i class="ti ti-chevrons-left"></i></MkButton>
+				<MkButton :disabled="logPos === 0" @click="logPos--"><i class="ti ti-chevron-left"></i></MkButton>
+				<MkButton :disabled="logPos === logs.length" @click="logPos++"><i class="ti ti-chevron-right"></i></MkButton>
+				<MkButton :disabled="logPos === logs.length" @click="logPos = logs.length"><i class="ti ti-chevrons-right"></i></MkButton>
+			</div>
+			<MkButton style="margin: auto;" :disabled="autoplaying" @click="autoplay()"><i class="ti ti-player-play"></i></MkButton>
+		</div>
+
+		<div>
+			<p v-if="game.isLlotheo">{{ i18n.ts._reversi.isLlotheo }}</p>
+			<p v-if="game.loopedBoard">{{ i18n.ts._reversi.loopedMap }}</p>
+			<p v-if="game.canPutEverywhere">{{ i18n.ts._reversi.canPutEverywhere }}</p>
+		</div>
+
+		<MkA v-if="game.isEnded" :to="`/reversi`">
+			<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; width: 200px; margin: auto;"/>
+		</MkA>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
+import * as CRC32 from 'crc-32';
+import * as Misskey from 'misskey-js';
+import * as Reversi from 'misskey-reversi';
+import MkButton from '@/components/MkButton.vue';
+import { deepClone } from '@/scripts/clone.js';
+import { useInterval } from '@/scripts/use-interval.js';
+import { signinRequired } from '@/account.js';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { userPage } from '@/filters/user.js';
+
+const $i = signinRequired();
+
+const props = defineProps<{
+	game: Misskey.entities.ReversiGameDetailed;
+	connection: Misskey.ChannelConnection;
+}>();
+
+const showBoardLabels = true;
+const autoplaying = ref<boolean>(false);
+const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game));
+const logs = ref<Misskey.entities.ReversiLog[]>(game.value.logs);
+const logPos = ref<number>(logs.value.length);
+const engine = shallowRef<Reversi.Game>(new Reversi.Game(game.value.map, {
+	isLlotheo: game.value.isLlotheo,
+	canPutEverywhere: game.value.canPutEverywhere,
+	loopedBoard: game.value.loopedBoard,
+}));
+
+for (const log of game.value.logs) {
+	engine.value.put(log.color, log.pos);
+}
+
+const iAmPlayer = computed(() => {
+	return game.value.user1Id === $i.id || game.value.user2Id === $i.id;
+});
+
+const myColor = computed(() => {
+	if (!iAmPlayer.value) return null;
+	if (game.value.user1Id === $i.id && game.value.black === 1) return true;
+	if (game.value.user2Id === $i.id && game.value.black === 2) return true;
+	return false;
+});
+
+const opColor = computed(() => {
+	if (!iAmPlayer.value) return null;
+	return !myColor.value;
+});
+
+const blackUser = computed(() => {
+	return game.value.black === 1 ? game.value.user1 : game.value.user2;
+});
+
+const whiteUser = computed(() => {
+	return game.value.black === 1 ? game.value.user2 : game.value.user1;
+});
+
+const turnUser = computed(() => {
+	if (engine.value.turn === true) {
+		return game.value.black === 1 ? game.value.user1 : game.value.user2;
+	} else if (engine.value.turn === false) {
+		return game.value.black === 1 ? game.value.user2 : game.value.user1;
+	} else {
+		return null;
+	}
+});
+
+const isMyTurn = computed(() => {
+	if (!iAmPlayer.value) return false;
+	const u = turnUser.value;
+	if (u == null) return false;
+	return u.id === $i.id;
+});
+
+const cellsStyle = computed(() => {
+	return {
+		'grid-template-rows': `repeat(${game.value.map.length}, 1fr)`,
+		'grid-template-columns': `repeat(${game.value.map[0].length}, 1fr)`,
+	};
+});
+
+watch(logPos, (v) => {
+	if (!game.value.isEnded) return;
+	const _o = new Reversi.Game(game.value.map, {
+		isLlotheo: game.value.isLlotheo,
+		canPutEverywhere: game.value.canPutEverywhere,
+		loopedBoard: game.value.loopedBoard,
+	});
+	for (const log of logs.value.slice(0, v)) {
+		_o.put(log.color, log.pos);
+	}
+	engine.value = _o;
+});
+
+if (game.value.isStarted && !game.value.isEnded) {
+	useInterval(() => {
+		if (game.value.isEnded) return;
+		const crc32 = CRC32.str(logs.value.map(x => x.pos.toString()).join(''));
+		props.connection.send('syncState', {
+			crc32: crc32,
+		});
+	}, 5000, { immediate: false, afterMounted: true });
+}
+
+function putStone(pos) {
+	if (game.value.isEnded) return;
+	if (!iAmPlayer.value) return;
+	if (!isMyTurn.value) return;
+	if (!engine.value.canPut(myColor.value!, pos)) return;
+
+	engine.value.put(myColor.value!, pos);
+	triggerRef(engine);
+
+	// サウンドを再生する
+	//sound.play(myColor.value ? 'reversiPutBlack' : 'reversiPutWhite');
+
+	props.connection.send('putStone', {
+		pos: pos,
+	});
+
+	checkEnd();
+}
+
+function onPutStone(x) {
+	logs.value.push(x);
+	logPos.value++;
+	engine.value.put(x.color, x.pos);
+	triggerRef(engine);
+	checkEnd();
+
+	// サウンドを再生する
+	if (x.color !== myColor.value) {
+		//sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
+	}
+}
+
+function onEnded(x) {
+	game.value = deepClone(x.game);
+}
+
+function checkEnd() {
+	game.value.isEnded = engine.value.isEnded;
+	if (game.value.isEnded) {
+		if (engine.value.winner === true) {
+			game.value.winnerId = game.value.black === 1 ? game.value.user1Id : game.value.user2Id;
+			game.value.winner = game.value.black === 1 ? game.value.user1 : game.value.user2;
+		} else if (engine.value.winner === false) {
+			game.value.winnerId = game.value.black === 1 ? game.value.user2Id : game.value.user1Id;
+			game.value.winner = game.value.black === 1 ? game.value.user2 : game.value.user1;
+		} else {
+			game.value.winnerId = null;
+			game.value.winner = null;
+		}
+	}
+}
+
+function onRescue(_game) {
+	game.value = deepClone(_game);
+
+	engine.value = new Reversi.Game(game.value.map, {
+		isLlotheo: game.value.isLlotheo,
+		canPutEverywhere: game.value.canPutEverywhere,
+		loopedBoard: game.value.loopedBoard,
+	});
+
+	for (const log of game.value.logs) {
+		engine.value.put(log.color, log.pos);
+	}
+
+	triggerRef(engine);
+
+	logs.value = game.value.logs;
+	logPos.value = logs.value.length;
+
+	checkEnd();
+}
+
+function surrender() {
+	misskeyApi('reversi/surrender', {
+		gameId: game.value.id,
+	});
+}
+
+function autoplay() {
+	autoplaying.value = true;
+	logPos.value = 0;
+
+	window.setTimeout(() => {
+		logPos.value = 1;
+
+		let i = 1;
+		let previousLog = game.value.logs[0];
+		const tick = () => {
+			const log = game.value.logs[i];
+			const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime();
+			setTimeout(() => {
+				i++;
+				logPos.value++;
+				previousLog = log;
+
+				if (i < game.value.logs.length) {
+					tick();
+				} else {
+					autoplaying.value = false;
+				}
+			}, time);
+		};
+
+		tick();
+	}, 1000);
+}
+
+onMounted(() => {
+	props.connection.on('putStone', onPutStone);
+	props.connection.on('rescue', onRescue);
+	props.connection.on('ended', onEnded);
+});
+
+onUnmounted(() => {
+	props.connection.off('putStone', onPutStone);
+	props.connection.off('rescue', onRescue);
+	props.connection.off('ended', onEnded);
+});
+</script>
+
+<style lang="scss" module>
+@use "sass:math";
+
+$label-size: 16px;
+$gap: 4px;
+
+.root {
+	text-align: center;
+}
+
+.board {
+	width: calc(100% - 16px);
+	max-width: 500px;
+	margin: 0 auto;
+}
+
+.labelsX {
+	height: $label-size;
+	padding: 0 $label-size;
+	display: flex;
+}
+
+.labelsXLabel {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 0.8em;
+
+	&:first-child {
+		margin-left: -(math.div($gap, 2));
+	}
+
+	&:last-child {
+		margin-right: -(math.div($gap, 2));
+	}
+}
+
+.labelsY {
+	width: $label-size;
+	display: flex;
+	flex-direction: column;
+}
+
+.labelsYLabel {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 12px;
+
+	&:first-child {
+		margin-top: -(math.div($gap, 2));
+	}
+
+	&:last-child {
+		margin-bottom: -(math.div($gap, 2));
+	}
+}
+
+.boardCells {
+	flex: 1;
+	display: grid;
+	grid-gap: $gap;
+}
+
+.boardCell {
+	background: transparent;
+	border-radius: 6px;
+	overflow: clip;
+
+	&.boardCell_empty {
+		border: solid 2px var(--divider);
+	}
+
+	&.boardCell_empty.boardCell_can {
+		border-color: var(--accent);
+		opacity: 0.5;
+	}
+
+	&.boardCell_empty.boardCell_myTurn {
+		border-color: var(--divider);
+		opacity: 1;
+
+		&.boardCell_can {
+			border-color: var(--accent);
+			cursor: pointer;
+
+			&:hover {
+				background: var(--accent);
+			}
+		}
+	}
+
+	&.boardCell_prev {
+		box-shadow: 0 0 0 4px var(--accent);
+	}
+
+	&.boardCell_isEnded {
+		border-color: var(--divider);
+	}
+
+	&.boardCell_none {
+		border-color: transparent !important;
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
new file mode 100644
index 0000000000..301a177de1
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -0,0 +1,236 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<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="_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>
+					</div>
+				</div>
+			</div>
+
+			<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>
+
+			<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>
+	</MkSpacer>
+	<template #footer>
+		<div :class="$style.footer">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
+				<div style="text-align: center; margin-bottom: 10px;">
+					<template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template>
+					<template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template>
+					<template v-if="!isReady && isOpReady">{{ i18n.ts._reversi.waitingForMe }}</template>
+					<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 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>
+			</MkSpacer>
+		</div>
+	</template>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import * as Reversi from 'misskey-reversi';
+import { i18n } from '@/i18n.js';
+import { signinRequired } from '@/account.js';
+import { deepClone } from '@/scripts/clone.js';
+import MkButton from '@/components/MkButton.vue';
+import MkRadios from '@/components/MkRadios.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import * as os from '@/os.js';
+import { MenuItem } from '@/types/menu.js';
+
+const $i = signinRequired();
+
+const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.category)));
+
+const props = defineProps<{
+	game: Misskey.entities.ReversiGameDetailed;
+	connection: Misskey.ChannelConnection;
+}>();
+
+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(''));
+	return found ? found.name! : '-Custom-';
+});
+const isReady = computed(() => {
+	if (game.value.user1Id === $i.id && game.value.user1Ready) return true;
+	if (game.value.user2Id === $i.id && game.value.user2Ready) return true;
+	return false;
+});
+const isOpReady = computed(() => {
+	if (game.value.user1Id !== $i.id && game.value.user1Ready) return true;
+	if (game.value.user2Id !== $i.id && game.value.user2Ready) return true;
+	return false;
+});
+
+watch(() => game.value.bw, () => {
+	updateSettings('bw');
+});
+
+function chooseMap(ev: MouseEvent) {
+	const menu: MenuItem[] = [];
+
+	for (const c of mapCategories) {
+		const maps = Object.values(Reversi.maps).filter(x => x.category === c);
+		if (maps.length === 0) continue;
+		if (c != null) {
+			menu.push({
+				type: 'label',
+				text: c,
+			});
+		}
+		for (const m of maps) {
+			menu.push({
+				text: m.name!,
+				action: () => {
+					game.value.map = m.data;
+					updateSettings('map');
+				},
+			});
+		}
+	}
+
+	os.popupMenu(menu, ev.currentTarget ?? ev.target);
+}
+
+function exit() {
+	props.connection.send('exit', {});
+}
+
+function ready() {
+	props.connection.send('ready', true);
+}
+
+function unready() {
+	props.connection.send('ready', false);
+}
+
+function onChangeReadyStates(states) {
+	game.value.user1Ready = states.user1;
+	game.value.user2Ready = states.user2;
+}
+
+function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) {
+	props.connection.send('updateSettings', {
+		key: key,
+		value: game.value[key],
+	});
+}
+
+function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) {
+	if (userId === $i.id) return;
+	if (game.value[key] === value) return;
+	game.value[key] = value;
+}
+
+function onMapCellClick(pos: number, pixel: string) {
+	const x = pos % game.value.map[0].length;
+	const y = Math.floor(pos / game.value.map[0].length);
+	const newPixel =
+		pixel === ' ' ? '-' :
+		pixel === '-' ? 'b' :
+		pixel === 'b' ? 'w' :
+		' ';
+	const line = game.value.map[y].split('');
+	line[x] = newPixel;
+	game.value.map[y] = line.join('');
+	updateSettings('map');
+}
+
+props.connection.on('changeReadyStates', onChangeReadyStates);
+props.connection.on('updateSettings', onUpdateSettings);
+
+onUnmounted(() => {
+	props.connection.off('changeReadyStates', onChangeReadyStates);
+	props.connection.off('updateSettings', onUpdateSettings);
+});
+</script>
+
+<style lang="scss" module>
+.board {
+	display: grid;
+	grid-gap: 4px;
+	width: 300px;
+	height: 300px;
+	margin: 0 auto;
+	color: var(--fg);
+}
+
+.boardCell {
+	display: grid;
+	place-items: center;
+	background: transparent;
+	border: solid 2px var(--divider);
+	border-radius: 6px;
+	overflow: clip;
+	cursor: pointer;
+}
+.boardCellNone {
+	border-color: transparent;
+}
+
+.footer {
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+	background: var(--acrylicBg);
+	border-top: solid 0.5px var(--divider);
+}
+</style>
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
new file mode 100644
index 0000000000..dbbeb20f42
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -0,0 +1,68 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div v-if="game == null || connection == null"><MkLoading/></div>
+<GameSetting v-else-if="!game.isStarted" :game="game" :connection="connection"/>
+<GameBoard v-else :game="game" :connection="connection"/>
+</template>
+
+<script lang="ts" setup>
+import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import GameSetting from './game.setting.vue';
+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';
+
+const props = defineProps<{
+	gameId: string;
+}>();
+
+const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null);
+const connection = shallowRef<Misskey.ChannelConnection | null>(null);
+
+watch(() => props.gameId, () => {
+	fetchGame();
+});
+
+async function fetchGame() {
+	const _game = await misskeyApi('reversi/show-game', {
+		gameId: props.gameId,
+	});
+
+	game.value = _game;
+
+	if (connection.value) {
+		connection.value.dispose();
+	}
+	connection.value = useStream().useChannel('reversiGame', {
+		gameId: game.value.id,
+	});
+	connection.value.on('started', x => {
+		game.value = x.game;
+	});
+}
+
+onMounted(() => {
+	fetchGame();
+});
+
+onUnmounted(() => {
+	if (connection.value) {
+		connection.value.dispose();
+	}
+});
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePageMetadata(computed(() => ({
+	title: 'Reversi',
+	icon: 'ti ti-device-gamepad',
+})));
+</script>
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
new file mode 100644
index 0000000000..c483e36c24
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -0,0 +1,271 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer v-if="!matchingAny && !matchingUser" :contentMax="600">
+	<div class="_gaps">
+		<div>
+			<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+		</div>
+
+		<div class="_buttonsCenter">
+			<MkButton primary gradate rounded @click="matchAny">{{ i18n.ts._reversi.freeMatch }}</MkButton>
+			<MkButton primary gradate rounded @click="matchUser">{{ i18n.ts.invite }}</MkButton>
+		</div>
+
+		<MkFolder v-if="invitations.length > 0" :defaultOpen="true">
+			<template #label>{{ i18n.ts.invitations }}</template>
+			<div class="_gaps_s">
+				<button v-for="user in invitations" :key="user.id" v-panel :class="$style.invitation" class="_button" tabindex="-1" @click="accept(user)">
+					<MkAvatar style="width: 32px; height: 32px; margin-right: 8px;" :user="user" :showIndicator="true"/>
+					<span style="margin-right: 8px;"><b><MkUserName :user="user"/></b></span>
+					<span>@{{ user.username }}</span>
+				</button>
+			</div>
+		</MkFolder>
+
+		<MkFolder v-if="$i" :defaultOpen="true">
+			<template #label>{{ i18n.ts._reversi.myGames }}</template>
+			<MkPagination :pagination="myGamesPagination">
+				<template #default="{ items }">
+					<div :class="$style.gamePreviews">
+						<MkA v-for="g in items" :key="g.id" v-panel :class="$style.gamePreview" tabindex="-1" :to="`/reversi/g/${g.id}`">
+							<div :class="$style.gamePreviewPlayers">
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
+							</div>
+							<div :class="$style.gamePreviewFooter">
+								<span :style="!g.isEnded ? 'color: var(--accent);' : ''">{{ g.isEnded ? i18n.ts._reversi.ended : i18n.ts._reversi.playing }}</span>
+								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/>
+							</div>
+						</MkA>
+					</div>
+				</template>
+			</MkPagination>
+		</MkFolder>
+
+		<MkFolder :defaultOpen="true">
+			<template #label>{{ i18n.ts._reversi.allGames }}</template>
+			<MkPagination :pagination="gamesPagination">
+				<template #default="{ items }">
+					<div :class="$style.gamePreviews">
+						<MkA v-for="g in items" :key="g.id" v-panel :class="$style.gamePreview" tabindex="-1" :to="`/reversi/g/${g.id}`">
+							<div :class="$style.gamePreviewPlayers">
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
+							</div>
+							<div :class="$style.gamePreviewFooter">
+								<span :style="!g.isEnded ? 'color: var(--accent);' : ''">{{ g.isEnded ? i18n.ts._reversi.ended : i18n.ts._reversi.playing }}</span>
+								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/>
+							</div>
+						</MkA>
+					</div>
+				</template>
+			</MkPagination>
+		</MkFolder>
+	</div>
+</MkSpacer>
+<MkSpacer v-else :contentMax="600">
+	<div :class="$style.waitingScreen">
+		<div v-if="matchingUser" :class="$style.waitingScreenTitle">
+			<I18n :src="i18n.ts.waitingFor" tag="span">
+				<template #x>
+					<b><MkUserName :user="matchingUser"/></b>
+				</template>
+			</I18n>
+			<MkEllipsis/>
+		</div>
+		<div v-else :class="$style.waitingScreenTitle">
+			{{ i18n.ts._reversi.lookingForPlayer }}<MkEllipsis/>
+		</div>
+		<div class="cancel">
+			<MkButton inline rounded @click="cancelMatching">{{ i18n.ts.cancel }}</MkButton>
+		</div>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, onUnmounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useStream } from '@/stream.js';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import { i18n } from '@/i18n.js';
+import { $i } from '@/account.js';
+import MkPagination from '@/components/MkPagination.vue';
+import { useRouter } from '@/global/router/supplier.js';
+import * as os from '@/os.js';
+import { useInterval } from '@/scripts/use-interval.js';
+
+const myGamesPagination = {
+	endpoint: 'reversi/games' as const,
+	limit: 10,
+	params: {
+		my: true,
+	},
+};
+
+const gamesPagination = {
+	endpoint: 'reversi/games' as const,
+	limit: 10,
+};
+
+const router = useRouter();
+
+if ($i) {
+	const connection = useStream().useChannel('reversi');
+
+	connection.on('matched', x => {
+		startGame(x.game);
+	});
+
+	connection.on('invited', invitation => {
+		if (invitations.value.some(x => x.id === invitation.user.id)) return;
+		invitations.value.unshift(invitation.user);
+	});
+
+	onUnmounted(() => {
+		connection.dispose();
+	});
+}
+
+const invitations = ref<Misskey.entities.UserLite[]>([]);
+const matchingUser = ref<Misskey.entities.UserLite | null>(null);
+const matchingAny = ref<boolean>(false);
+
+function startGame(game: Misskey.entities.ReversiGameDetailed) {
+	matchingUser.value = null;
+	matchingAny.value = false;
+	router.push(`/reversi/g/${game.id}`);
+}
+
+async function matchHeatbeat() {
+	if (matchingUser.value) {
+		const res = await misskeyApi('reversi/match', {
+			userId: matchingUser.value.id,
+		});
+
+		if (res != null) {
+			startGame(res);
+		}
+	} else if (matchingAny.value) {
+		const res = await misskeyApi('reversi/match', {
+			userId: null,
+		});
+
+		if (res != null) {
+			startGame(res);
+		}
+	}
+}
+
+async function matchUser() {
+	const user = await os.selectUser({ local: true });
+	if (user == null) return;
+
+	matchingUser.value = user;
+
+	matchHeatbeat();
+}
+
+async function matchAny() {
+	matchingAny.value = true;
+
+	matchHeatbeat();
+}
+
+function cancelMatching() {
+	if (matchingUser.value) {
+		misskeyApi('reversi/cancel-match', { userId: matchingUser.value.id });
+		matchingUser.value = null;
+	} else if (matchingAny.value) {
+		misskeyApi('reversi/cancel-match', { userId: null });
+		matchingAny.value = false;
+	}
+}
+
+async function accept(user) {
+	const game = await misskeyApi('reversi/match', {
+		userId: user.id,
+	});
+	if (game) {
+		startGame(game);
+	}
+}
+
+useInterval(matchHeatbeat, 1000 * 10, { immediate: false, afterMounted: true });
+
+onMounted(() => {
+	misskeyApi('reversi/invitations').then(_invitations => {
+		invitations.value = _invitations;
+	});
+});
+
+definePageMetadata(computed(() => ({
+	title: 'Reversi',
+	icon: 'ti ti-device-gamepad',
+})));
+</script>
+
+<style lang="scss" module>
+.invitation {
+	display: flex;
+	box-sizing: border-box;
+	width: 100%;
+	padding: 16px;
+	line-height: 32px;
+	text-align: left;
+}
+
+.gamePreviews {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+	grid-gap: var(--margin);
+}
+
+.gamePreview {
+	font-size: 90%;
+	border-radius: 8px;
+	overflow: clip;
+}
+
+.gamePreviewPlayers {
+	text-align: center;
+	padding: 16px;
+	line-height: 32px;
+}
+
+.gamePreviewPlayersAvatar {
+	width: 32px;
+	height: 32px;
+
+	&:first-child {
+		margin-right: 8px;
+	}
+
+	&:last-child {
+		margin-left: 8px;
+	}
+}
+
+.gamePreviewFooter {
+	display: flex;
+	align-items: baseline;
+	border-top: solid 0.5px var(--divider);
+	padding: 6px 10px;
+	font-size: 0.9em;
+}
+
+.waitingScreen {
+	text-align: center;
+}
+
+.waitingScreenTitle {
+	font-size: 1.5em;
+	margin-bottom: 16px;
+	margin-top: 32px;
+}
+</style>
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 98fe0043c1..8cdc7b59c6 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -103,7 +103,7 @@ export function getConfig(): UserConfig {
 
 		// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
 		optimizeDeps: {
-			include: ['misskey-js'],
+			include: ['misskey-js', 'misskey-reversi'],
 		},
 
 		build: {
@@ -135,7 +135,7 @@ export function getConfig(): UserConfig {
 
 			// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
 			commonjsOptions: {
-				include: [/misskey-js/, /node_modules/],
+				include: [/misskey-js/, /misskey-reversi/, /node_modules/],
 			},
 		},
 
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index f955cc5cc1..2b95e01533 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1623,6 +1623,16 @@ declare namespace entities {
         BubbleGameRegisterResponse,
         BubbleGameRankingRequest,
         BubbleGameRankingResponse,
+        ReversiCancelMatchRequest,
+        ReversiCancelMatchResponse,
+        ReversiGamesRequest,
+        ReversiGamesResponse,
+        ReversiMatchRequest,
+        ReversiMatchResponse,
+        ReversiInvitationsResponse,
+        ReversiShowGameRequest,
+        ReversiShowGameResponse,
+        ReversiSurrenderRequest,
         Error_2 as Error,
         UserLite,
         UserDetailedNotMeOnly,
@@ -1659,7 +1669,9 @@ declare namespace entities {
         Flash,
         Signin,
         RoleLite,
-        Role
+        Role,
+        ReversiGameLite,
+        ReversiGameDetailed
     }
 }
 export { entities }
@@ -2596,6 +2608,42 @@ type ResetPasswordRequest = operations['reset-password']['requestBody']['content
 // @public (undocumented)
 type RetentionResponse = operations['retention']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
+
+// @public (undocumented)
+type ReversiGameLite = components['schemas']['ReversiGameLite'];
+
+// @public (undocumented)
+type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type Role = components['schemas']['Role'];
 
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index b60f449a71..e4e7d13668 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-13T04:31:38.782Z
+ * generatedAt: 2024-01-19T11:00:07.160Z
  */
 
 import type { SwitchCaseResponseType } from '../api.js';
@@ -4007,5 +4007,71 @@ declare module '../api.js' {
       params: P,
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/cancel-match', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'reversi/games', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/match', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    request<E extends 'reversi/invitations', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'reversi/show-game', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/surrender', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
   }
 }
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index dc591a7046..671abd78ce 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-13T04:31:38.778Z
+ * generatedAt: 2024-01-19T11:00:07.158Z
  */
 
 import type {
@@ -544,6 +544,16 @@ import type {
 	BubbleGameRegisterResponse,
 	BubbleGameRankingRequest,
 	BubbleGameRankingResponse,
+	ReversiCancelMatchRequest,
+	ReversiCancelMatchResponse,
+	ReversiGamesRequest,
+	ReversiGamesResponse,
+	ReversiMatchRequest,
+	ReversiMatchResponse,
+	ReversiInvitationsResponse,
+	ReversiShowGameRequest,
+	ReversiShowGameResponse,
+	ReversiSurrenderRequest,
 } from './entities.js';
 
 export type Endpoints = {
@@ -907,4 +917,10 @@ export type Endpoints = {
 	'retention': { req: EmptyRequest; res: RetentionResponse };
 	'bubble-game/register': { req: BubbleGameRegisterRequest; res: BubbleGameRegisterResponse };
 	'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse };
+	'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: ReversiCancelMatchResponse };
+	'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse };
+	'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse };
+	'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse };
+	'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse };
+	'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse };
 }
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index dfe24ce0d8..c14876c0e3 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-13T04:31:38.775Z
+ * generatedAt: 2024-01-19T11:00:07.156Z
  */
 
 import { operations } from './types.js';
@@ -546,3 +546,13 @@ export type BubbleGameRegisterRequest = operations['bubble-game/register']['requ
 export type BubbleGameRegisterResponse = operations['bubble-game/register']['responses']['200']['content']['application/json'];
 export type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json'];
 export type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json'];
+export type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json'];
+export type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json'];
+export type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json'];
+export type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json'];
+export type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json'];
+export type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json'];
+export type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['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 ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 5c6bebf2fd..78f14d2250 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-13T04:31:38.773Z
+ * generatedAt: 2024-01-19T11:00:07.155Z
  */
 
 import { components } from './types.js';
@@ -41,3 +41,5 @@ export type Flash = components['schemas']['Flash'];
 export type Signin = components['schemas']['Signin'];
 export type RoleLite = components['schemas']['RoleLite'];
 export type Role = components['schemas']['Role'];
+export type ReversiGameLite = components['schemas']['ReversiGameLite'];
+export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 76e2b5309c..36facf6e28 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
 
 /*
  * version: 2023.12.2
- * generatedAt: 2024-01-13T04:31:38.633Z
+ * generatedAt: 2024-01-19T11:00:07.077Z
  */
 
 /**
@@ -3472,6 +3472,60 @@ export type paths = {
      */
     post: operations['bubble-game/ranking'];
   };
+  '/reversi/cancel-match': {
+    /**
+     * reversi/cancel-match
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/cancel-match'];
+  };
+  '/reversi/games': {
+    /**
+     * reversi/games
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['reversi/games'];
+  };
+  '/reversi/match': {
+    /**
+     * reversi/match
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/match'];
+  };
+  '/reversi/invitations': {
+    /**
+     * reversi/invitations
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    post: operations['reversi/invitations'];
+  };
+  '/reversi/show-game': {
+    /**
+     * reversi/show-game
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['reversi/show-game'];
+  };
+  '/reversi/surrender': {
+    /**
+     * reversi/surrender
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/surrender'];
+  };
 };
 
 export type webhooks = Record<string, never>;
@@ -4404,6 +4458,72 @@ export type components = {
       };
       usersCount: number;
     });
+    ReversiGameLite: {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** Format: date-time */
+      startedAt: string | null;
+      isStarted: boolean;
+      isEnded: boolean;
+      form1: Record<string, never> | null;
+      form2: Record<string, never> | null;
+      user1Ready: boolean;
+      user2Ready: boolean;
+      /** Format: id */
+      user1Id: string;
+      /** Format: id */
+      user2Id: string;
+      user1: components['schemas']['User'];
+      user2: components['schemas']['User'];
+      /** Format: id */
+      winnerId: string | null;
+      winner: components['schemas']['User'] | null;
+      /** Format: id */
+      surrendered: string | null;
+      black: number | null;
+      bw: string;
+      isLlotheo: boolean;
+      canPutEverywhere: boolean;
+      loopedBoard: boolean;
+    };
+    ReversiGameDetailed: {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** Format: date-time */
+      startedAt: string | null;
+      isStarted: boolean;
+      isEnded: boolean;
+      form1: Record<string, never> | null;
+      form2: Record<string, never> | null;
+      user1Ready: boolean;
+      user2Ready: boolean;
+      /** Format: id */
+      user1Id: string;
+      /** Format: id */
+      user2Id: string;
+      user1: components['schemas']['User'];
+      user2: components['schemas']['User'];
+      /** Format: id */
+      winnerId: string | null;
+      winner: components['schemas']['User'] | null;
+      /** Format: id */
+      surrendered: string | null;
+      black: number | null;
+      bw: string;
+      isLlotheo: boolean;
+      canPutEverywhere: boolean;
+      loopedBoard: boolean;
+      logs: {
+          at: number;
+          color: boolean;
+          pos: number;
+        }[];
+      map: string[];
+    };
   };
   responses: never;
   parameters: never;
@@ -25542,5 +25662,325 @@ export type operations = {
       };
     };
   };
+  /**
+   * reversi/cancel-match
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/cancel-match': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId?: string | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': unknown;
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/games
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'reversi/games': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default 10 */
+          limit?: number;
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          /** @default false */
+          my?: boolean;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['ReversiGameLite'][];
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/match
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/match': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId?: string | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': unknown;
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/invitations
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:account*
+   */
+  'reversi/invitations': {
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['UserLite'][];
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/show-game
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'reversi/show-game': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          gameId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['ReversiGameDetailed'];
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/surrender
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/surrender': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          gameId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
 };
 
diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json
new file mode 100644
index 0000000000..8d3ca30166
--- /dev/null
+++ b/packages/misskey-reversi/package.json
@@ -0,0 +1,26 @@
+{
+	"name": "misskey-reversi",
+	"version": "0.0.1",
+	"main": "./built/index.js",
+	"types": "./built/index.d.ts",
+	"scripts": {
+		"build": "tsc",
+		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build\"",
+		"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
+		"typecheck": "tsc --noEmit",
+		"lint": "pnpm typecheck && pnpm eslint"
+	},
+	"devDependencies": {
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@types/node": "20.11.5",
+		"@typescript-eslint/eslint-plugin": "6.19.0",
+		"@typescript-eslint/parser": "6.19.0",
+		"eslint": "8.56.0",
+		"typescript": "5.3.3"
+	},
+	"files": [
+		"built"
+	],
+	"dependencies": {
+	}
+}
diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts
new file mode 100644
index 0000000000..55d0b84da7
--- /dev/null
+++ b/packages/misskey-reversi/src/game.ts
@@ -0,0 +1,216 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/**
+ * true ... é»’
+ * false ... 白
+ */
+export type Color = boolean;
+const BLACK = true;
+const WHITE = false;
+
+export type MapCell = 'null' | 'empty';
+
+export type Options = {
+	isLlotheo: boolean;
+	canPutEverywhere: boolean;
+	loopedBoard: boolean;
+};
+
+export type Undo = {
+	color: Color;
+	pos: number;
+
+	/**
+	 * 反転した石の位置の配列
+	 */
+	effects: number[];
+
+	turn: Color | null;
+};
+
+export class Game {
+	public map: MapCell[];
+	public mapWidth: number;
+	public mapHeight: number;
+	public board: (Color | null | undefined)[];
+	public turn: Color | null = BLACK;
+	public opts: Options;
+
+	public prevPos = -1;
+	public prevColor: Color | null = null;
+
+	private logs: Undo[] = [];
+
+	constructor(map: string[], opts: Options) {
+		//#region binds
+		this.put = this.put.bind(this);
+		//#endregion
+
+		//#region Options
+		this.opts = opts;
+		if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
+		if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
+		if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
+		//#endregion
+
+		//#region Parse map data
+		this.mapWidth = map[0].length;
+		this.mapHeight = map.length;
+		const mapData = map.join('');
+
+		this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
+
+		this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
+		//#endregion
+
+		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
+		if (!this.canPutSomewhere(BLACK))
+			this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
+	}
+
+	public get blackCount() {
+		return this.board.filter(x => x === BLACK).length;
+	}
+
+	public get whiteCount() {
+		return this.board.filter(x => x === WHITE).length;
+	}
+
+	public posToXy(pos: number): number[] {
+		const x = pos % this.mapWidth;
+		const y = Math.floor(pos / this.mapWidth);
+		return [x, y];
+	}
+
+	public xyToPos(x: number, y: number): number {
+		return x + (y * this.mapWidth);
+	}
+
+	public put(color: Color, pos: number) {
+		this.prevPos = pos;
+		this.prevColor = color;
+
+		this.board[pos] = color;
+
+		// 反転させられる石を取得
+		const effects = this.effects(color, pos);
+
+		// 反転させる
+		for (const pos of effects) {
+			this.board[pos] = color;
+		}
+
+		const turn = this.turn;
+
+		this.logs.push({
+			color,
+			pos,
+			effects,
+			turn
+		});
+
+		this.calcTurn();
+	}
+
+	private calcTurn() {
+		// ターン計算
+		this.turn =
+			this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
+			this.canPutSomewhere(this.prevColor!) ? this.prevColor :
+			null;
+	}
+
+	public undo() {
+		const undo = this.logs.pop()!;
+		this.prevColor = undo.color;
+		this.prevPos = undo.pos;
+		this.board[undo.pos] = null;
+		for (const pos of undo.effects) {
+			const color = this.board[pos];
+			this.board[pos] = !color;
+		}
+		this.turn = undo.turn;
+	}
+
+	public mapDataGet(pos: number): MapCell {
+		const [x, y] = this.posToXy(pos);
+		return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
+	}
+
+	public getPuttablePlaces(color: Color): number[] {
+		return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
+	}
+
+	public canPutSomewhere(color: Color): boolean {
+		return this.getPuttablePlaces(color).length > 0;
+	}
+
+	public canPut(color: Color, pos: number): boolean {
+		return (
+			this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
+			this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
+			this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
+	}
+
+	/**
+	 * 指定のマスに石を置いた時の、反転させられる石を取得します
+	 * @param color 自分の色
+	 * @param initPos 位置
+	 */
+	public effects(color: Color, initPos: number): number[] {
+		const enemyColor = !color;
+
+		const diffVectors: [number, number][] = [
+			[ 0, -1], // 上
+			[+1, -1], // 右上
+			[+1,  0], // 右
+			[+1, +1], // 右下
+			[ 0, +1], // 下
+			[-1, +1], // 左下
+			[-1,  0], // å·¦
+			[-1, -1]  // 左上
+		];
+
+		const effectsInLine = ([dx, dy]: [number, number]): number[] => {
+			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
+
+			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
+			let [x, y] = this.posToXy(initPos);
+			while (true) {
+				[x, y] = nextPos(x, y);
+
+				// 座標が指し示す位置がボード外に出たとき
+				if (this.opts.loopedBoard && this.xyToPos(
+					(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
+					(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos)
+						// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
+					return found;
+				else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight)
+					return []; // 挟めないことが確定 (盤面外に到達)
+
+				const pos = this.xyToPos(x, y);
+				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
+				const stone = this.board[pos];
+				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
+				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
+				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
+			}
+		};
+
+		return ([] as number[]).concat(...diffVectors.map(effectsInLine));
+	}
+
+	public get isEnded(): boolean {
+		return this.turn === null;
+	}
+
+	public get winner(): Color | null {
+		return this.isEnded ?
+			this.blackCount == this.whiteCount ? null :
+			this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
+			undefined as never;
+	}
+}
diff --git a/packages/misskey-reversi/src/index.ts b/packages/misskey-reversi/src/index.ts
new file mode 100644
index 0000000000..20ed36f208
--- /dev/null
+++ b/packages/misskey-reversi/src/index.ts
@@ -0,0 +1,7 @@
+import { Game } from './game.js';
+
+export {
+	Game,
+};
+
+export * as maps from './maps.js';
diff --git a/packages/misskey-reversi/src/maps.ts b/packages/misskey-reversi/src/maps.ts
new file mode 100644
index 0000000000..85cf1a0485
--- /dev/null
+++ b/packages/misskey-reversi/src/maps.ts
@@ -0,0 +1,715 @@
+/**
+ * 組み込みマップ定義
+ *
+ * データ値:
+ * (スペース) ... マス無し
+ * - ... マス
+ * b ... 初期配置される黒石
+ * w ... 初期配置される白石
+ */
+
+export type Map = {
+	name?: string;
+	category?: string;
+	author?: string;
+	data: string[];
+};
+
+export const fourfour: Map = {
+	name: '4x4',
+	category: '4x4',
+	data: [
+		'----',
+		'-wb-',
+		'-bw-',
+		'----'
+	]
+};
+
+export const sixsix: Map = {
+	name: '6x6',
+	category: '6x6',
+	data: [
+		'------',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		'------'
+	]
+};
+
+export const roundedSixsix: Map = {
+	name: '6x6 rounded',
+	category: '6x6',
+	author: 'syuilo',
+	data: [
+		' ---- ',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		' ---- '
+	]
+};
+
+export const roundedSixsix2: Map = {
+	name: '6x6 rounded 2',
+	category: '6x6',
+	author: 'syuilo',
+	data: [
+		'  --  ',
+		' ---- ',
+		'--wb--',
+		'--bw--',
+		' ---- ',
+		'  --  '
+	]
+};
+
+export const eighteight: Map = {
+	name: '8x8',
+	category: '8x8',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------'
+	]
+};
+
+export const eighteightH28: Map = {
+	name: '8x8 handicap 28',
+	category: '8x8',
+	data: [
+		'bbbbbbbb',
+		'b------b',
+		'b------b',
+		'b--wb--b',
+		'b--bw--b',
+		'b------b',
+		'b------b',
+		'bbbbbbbb'
+	]
+};
+
+export const roundedEighteight: Map = {
+	name: '8x8 rounded',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		' ------ ',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		' ------ '
+	]
+};
+
+export const roundedEighteight2: Map = {
+	name: '8x8 rounded 2',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'  ----  ',
+		' ------ ',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		' ------ ',
+		'  ----  '
+	]
+};
+
+export const roundedEighteight3: Map = {
+	name: '8x8 rounded 3',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'   --   ',
+		'  ----  ',
+		' ------ ',
+		'---wb---',
+		'---bw---',
+		' ------ ',
+		'  ----  ',
+		'   --   '
+	]
+};
+
+export const eighteightWithNotch: Map = {
+	name: '8x8 with notch',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'---  ---',
+		'--------',
+		'--------',
+		' --wb-- ',
+		' --bw-- ',
+		'--------',
+		'--------',
+		'---  ---'
+	]
+};
+
+export const eighteightWithSomeHoles: Map = {
+	name: '8x8 with some holes',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--- ----',
+		'----- --',
+		'-- -----',
+		'---wb---',
+		'---bw- -',
+		' -------',
+		'--- ----',
+		'--------'
+	]
+};
+
+export const circle: Map = {
+	name: 'Circle',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'   --   ',
+		' ------ ',
+		' ------ ',
+		'---wb---',
+		'---bw---',
+		' ------ ',
+		' ------ ',
+		'   --   '
+	]
+};
+
+export const smile: Map = {
+	name: 'Smile',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		' ------ ',
+		'--------',
+		'-- -- --',
+		'---wb---',
+		'-- bw --',
+		'---  ---',
+		'--------',
+		' ------ '
+	]
+};
+
+export const window: Map = {
+	name: 'Window',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--------',
+		'-  --  -',
+		'-  --  -',
+		'---wb---',
+		'---bw---',
+		'-  --  -',
+		'-  --  -',
+		'--------'
+	]
+};
+
+export const reserved: Map = {
+	name: 'Reserved',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'w------b',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'b------w'
+	]
+};
+
+export const x: Map = {
+	name: 'X',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'w------b',
+		'-w----b-',
+		'--w--b--',
+		'---wb---',
+		'---bw---',
+		'--b--w--',
+		'-b----w-',
+		'b------w'
+	]
+};
+
+export const parallel: Map = {
+	name: 'Parallel',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---bb---',
+		'---ww---',
+		'--------',
+		'--------',
+		'--------'
+	]
+};
+
+export const lackOfBlack: Map = {
+	name: 'Lack of Black',
+	category: '8x8',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---w----',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------'
+	]
+};
+
+export const squareParty: Map = {
+	name: 'Square Party',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--------',
+		'-wwwbbb-',
+		'-w-wb-b-',
+		'-wwwbbb-',
+		'-bbbwww-',
+		'-b-bw-w-',
+		'-bbbwww-',
+		'--------'
+	]
+};
+
+export const minesweeper: Map = {
+	name: 'Minesweeper',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'b-b--w-w',
+		'-w-wb-b-',
+		'w-b--w-b',
+		'-b-wb-w-',
+		'-w-bw-b-',
+		'b-w--b-w',
+		'-b-bw-w-',
+		'w-w--b-b'
+	]
+};
+
+export const tenthtenth: Map = {
+	name: '10x10',
+	category: '10x10',
+	data: [
+		'----------',
+		'----------',
+		'----------',
+		'----------',
+		'----wb----',
+		'----bw----',
+		'----------',
+		'----------',
+		'----------',
+		'----------'
+	]
+};
+
+export const hole: Map = {
+	name: 'The Hole',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'----------',
+		'----------',
+		'--wb--wb--',
+		'--bw--bw--',
+		'----  ----',
+		'----  ----',
+		'--wb--wb--',
+		'--bw--bw--',
+		'----------',
+		'----------'
+	]
+};
+
+export const grid: Map = {
+	name: 'Grid',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'----------',
+		'- - -- - -',
+		'----------',
+		'- - -- - -',
+		'----wb----',
+		'----bw----',
+		'- - -- - -',
+		'----------',
+		'- - -- - -',
+		'----------'
+	]
+};
+
+export const cross: Map = {
+	name: 'Cross',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		'   ----   ',
+		'   ----   ',
+		'   ----   ',
+		'----------',
+		'----wb----',
+		'----bw----',
+		'----------',
+		'   ----   ',
+		'   ----   ',
+		'   ----   '
+	]
+};
+
+export const charX: Map = {
+	name: 'Char X',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'---    ---',
+		'----  ----',
+		'----------',
+		' -------- ',
+		'  --wb--  ',
+		'  --bw--  ',
+		' -------- ',
+		'----------',
+		'----  ----',
+		'---    ---'
+	]
+};
+
+export const charY: Map = {
+	name: 'Char Y',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'---    ---',
+		'----  ----',
+		'----------',
+		' -------- ',
+		'  --wb--  ',
+		'  --bw--  ',
+		'  ------  ',
+		'  ------  ',
+		'  ------  ',
+		'  ------  '
+	]
+};
+
+export const walls: Map = {
+	name: 'Walls',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		' bbbbbbbb ',
+		'w--------w',
+		'w--------w',
+		'w--------w',
+		'w---wb---w',
+		'w---bw---w',
+		'w--------w',
+		'w--------w',
+		'w--------w',
+		' bbbbbbbb '
+	]
+};
+
+export const cpu: Map = {
+	name: 'CPU',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		' b b  b b ',
+		'w--------w',
+		' -------- ',
+		'w--------w',
+		' ---wb--- ',
+		' ---bw--- ',
+		'w--------w',
+		' -------- ',
+		'w--------w',
+		' b b  b b '
+	]
+};
+
+export const checker: Map = {
+	name: 'Checker',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		'----------',
+		'----------',
+		'----------',
+		'---wbwb---',
+		'---bwbw---',
+		'---wbwb---',
+		'---bwbw---',
+		'----------',
+		'----------',
+		'----------'
+	]
+};
+
+export const japaneseCurry: Map = {
+	name: 'Japanese curry',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'w-b-b-b-b-',
+		'-w-b-b-b-b',
+		'w-w-b-b-b-',
+		'-w-w-b-b-b',
+		'w-w-wwb-b-',
+		'-w-wbb-b-b',
+		'w-w-w-b-b-',
+		'-w-w-w-b-b',
+		'w-w-w-w-b-',
+		'-w-w-w-w-b'
+	]
+};
+
+export const mosaic: Map = {
+	name: 'Mosaic',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'- - - - - ',
+		' - - - - -',
+		'- - - - - ',
+		' - w w - -',
+		'- - b b - ',
+		' - w w - -',
+		'- - b b - ',
+		' - - - - -',
+		'- - - - - ',
+		' - - - - -',
+	]
+};
+
+export const arena: Map = {
+	name: 'Arena',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'- - -- - -',
+		' - -  - - ',
+		'- ------ -',
+		' -------- ',
+		'- --wb-- -',
+		'- --bw-- -',
+		' -------- ',
+		'- ------ -',
+		' - -  - - ',
+		'- - -- - -'
+	]
+};
+
+export const reactor: Map = {
+	name: 'Reactor',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'-w------b-',
+		'b- -  - -w',
+		'- --wb-- -',
+		'---b  w---',
+		'- b wb w -',
+		'- w bw b -',
+		'---w  b---',
+		'- --bw-- -',
+		'w- -  - -b',
+		'-b------w-'
+	]
+};
+
+export const sixeight: Map = {
+	name: '6x8',
+	category: 'Special',
+	data: [
+		'------',
+		'------',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		'------',
+		'------'
+	]
+};
+
+export const spark: Map = {
+	name: 'Spark',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		' -      - ',
+		'----------',
+		' -------- ',
+		' -------- ',
+		' ---wb--- ',
+		' ---bw--- ',
+		' -------- ',
+		' -------- ',
+		'----------',
+		' -      - '
+	]
+};
+
+export const islands: Map = {
+	name: 'Islands',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'--------  ',
+		'---wb---  ',
+		'---bw---  ',
+		'--------  ',
+		'  -    -  ',
+		'  -    -  ',
+		'  --------',
+		'  --------',
+		'  --------',
+		'  --------'
+	]
+};
+
+export const galaxy: Map = {
+	name: 'Galaxy',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'   ------   ',
+		'  --www---  ',
+		' ------w--- ',
+		'---bbb--w---',
+		'--b---b-w-b-',
+		'-b--wwb-w-b-',
+		'-b-w-bww--b-',
+		'-b-w-b---b--',
+		'---w--bbb---',
+		' ---w------ ',
+		'  ---www--  ',
+		'   ------   '
+	]
+};
+
+export const triangle: Map = {
+	name: 'Triangle',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'    --    ',
+		'    --    ',
+		'   ----   ',
+		'   ----   ',
+		'  --wb--  ',
+		'  --bw--  ',
+		' -------- ',
+		' -------- ',
+		'----------',
+		'----------'
+	]
+};
+
+export const iphonex: Map = {
+	name: 'iPhone X',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		' --  -- ',
+		'--------',
+		'--------',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------',
+		'--------',
+		' ------ '
+	]
+};
+
+export const dealWithIt: Map = {
+	name: 'Deal with it!',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'------------',
+		'--w-b-------',
+		' --b-w------',
+		'  --w-b---- ',
+		'   -------  '
+	]
+};
+
+export const bigBoard: Map = {
+	name: 'Big board',
+	category: 'Special',
+	data: [
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'-------wb-------',
+		'-------bw-------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------',
+		'----------------'
+	]
+};
+
+export const twoBoard: Map = {
+	name: 'Two board',
+	category: 'Special',
+	author: 'Aya',
+	data: [
+		'-------- --------',
+		'-------- --------',
+		'-------- --------',
+		'---wb--- ---wb---',
+		'---bw--- ---bw---',
+		'-------- --------',
+		'-------- --------',
+		'-------- --------'
+	]
+};
diff --git a/packages/misskey-reversi/tsconfig.json b/packages/misskey-reversi/tsconfig.json
new file mode 100644
index 0000000000..f56b65e868
--- /dev/null
+++ b/packages/misskey-reversi/tsconfig.json
@@ -0,0 +1,33 @@
+{
+	"$schema": "https://json.schemastore.org/tsconfig",
+	"compilerOptions": {
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"declaration": true,
+		"declarationMap": true,
+		"sourceMap": true,
+		"outDir": "./built/",
+		"removeComments": true,
+		"strict": true,
+		"strictFunctionTypes": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"noImplicitReturns": true,
+		"esModuleInterop": true,
+		"typeRoots": [
+			"./node_modules/@types"
+		],
+		"lib": [
+			"esnext",
+			"dom"
+		]
+	},
+	"include": [
+		"src/**/*"
+	],
+	"exclude": [
+		"node_modules",
+		"test/**/*"
+	]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 825a7ab860..31394eb081 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -185,6 +185,9 @@ importers:
       content-disposition:
         specifier: 0.5.4
         version: 0.5.4
+      crc-32:
+        specifier: ^1.2.2
+        version: 1.2.2
       date-fns:
         specifier: 2.30.0
         version: 2.30.0
@@ -263,6 +266,9 @@ importers:
       misskey-js:
         specifier: workspace:*
         version: link:../misskey-js
+      misskey-reversi:
+        specifier: workspace:*
+        version: link:../misskey-reversi
       ms:
         specifier: 3.0.0-canary.1
         version: 3.0.0-canary.1
@@ -736,6 +742,9 @@ importers:
       compare-versions:
         specifier: 6.1.0
         version: 6.1.0
+      crc-32:
+        specifier: ^1.2.2
+        version: 1.2.2
       cropperjs:
         specifier: 2.0.0-beta.4
         version: 2.0.0-beta.4
@@ -772,6 +781,9 @@ importers:
       misskey-js:
         specifier: workspace:*
         version: link:../misskey-js
+      misskey-reversi:
+        specifier: workspace:*
+        version: link:../misskey-reversi
       photoswipe:
         specifier: 5.4.3
         version: 5.4.3
@@ -1114,6 +1126,27 @@ importers:
         specifier: 5.3.3
         version: 5.3.3
 
+  packages/misskey-reversi:
+    devDependencies:
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
+      '@types/node':
+        specifier: 20.11.5
+        version: 20.11.5
+      '@typescript-eslint/eslint-plugin':
+        specifier: 6.19.0
+        version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser':
+        specifier: 6.19.0
+        version: 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint:
+        specifier: 8.56.0
+        version: 8.56.0
+      typescript:
+        specifier: 5.3.3
+        version: 5.3.3
+
   packages/sw:
     dependencies:
       esbuild:
@@ -1128,7 +1161,7 @@ importers:
     devDependencies:
       '@misskey-dev/eslint-plugin':
         specifier: ^1.0.0
-        version: 1.0.0(@typescript-eslint/eslint-plugin@6.14.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
+        version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
       '@typescript-eslint/parser':
         specifier: 6.14.0
         version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
@@ -1812,7 +1845,7 @@ packages:
       '@babel/traverse': 7.22.11
       '@babel/types': 7.22.17
       convert-source-map: 1.9.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -1835,7 +1868,7 @@ packages:
       '@babel/traverse': 7.23.5
       '@babel/types': 7.23.5
       convert-source-map: 2.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -1937,7 +1970,7 @@ packages:
       '@babel/core': 7.23.5
       '@babel/helper-compilation-targets': 7.22.15
       '@babel/helper-plugin-utils': 7.22.5
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       lodash.debounce: 4.0.8
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -3336,7 +3369,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.5
       '@babel/types': 7.22.17
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -3354,7 +3387,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.5
       '@babel/types': 7.23.5
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -4233,7 +4266,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4250,7 +4283,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4515,7 +4548,7 @@ packages:
     engines: {node: '>=10.10.0'}
     dependencies:
       '@humanwhocodes/object-schema': 2.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -4571,7 +4604,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
@@ -4592,14 +4625,14 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.7.1
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.10.5)
+      jest-config: 29.7.0(@types/node@20.11.5)
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -4634,7 +4667,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       jest-mock: 29.7.0
     dev: true
 
@@ -4661,7 +4694,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -4694,7 +4727,7 @@ packages:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.18
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
       exit: 0.1.2
@@ -4788,7 +4821,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4800,7 +4833,7 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       '@types/yargs': 17.0.19
       chalk: 4.1.2
     dev: true
@@ -4992,6 +5025,34 @@ packages:
       eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)
     dev: true
 
+  /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0):
+    resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': '>= 6'
+      '@typescript-eslint/parser': '>= 6'
+      eslint: '>= 3'
+      eslint-plugin-import: '>= 2'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint: 8.56.0
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)
+    dev: true
+
+  /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0):
+    resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': '>= 6'
+      '@typescript-eslint/parser': '>= 6'
+      eslint: '>= 3'
+      eslint-plugin-import: '>= 2'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      eslint: 8.56.0
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)
+    dev: true
+
   /@misskey-dev/sharp-read-bmp@1.1.1:
     resolution: {integrity: sha512-X52BQYL/I9mafypQ+wBhst+BUlYiPWnHhKGcF6ybcYSLl+zhcV0q5mezIXHozhM0Sv0A7xCdrWmR7TCNxHLrtQ==}
     dependencies:
@@ -5089,7 +5150,7 @@ packages:
       '@open-draft/until': 1.0.3
       '@types/debug': 4.1.7
       '@xmldom/xmldom': 0.8.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       headers-polyfill: 3.2.5
       outvariant: 1.4.0
       strict-event-emitter: 0.2.8
@@ -7992,7 +8053,7 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.1
       '@types/keyv': 3.1.4
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       '@types/responselike': 1.0.0
     dev: false
 
@@ -8025,7 +8086,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/content-disposition@0.5.8:
@@ -8039,7 +8100,7 @@ packages:
   /@types/cross-spawn@6.0.2:
     resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/debug@4.1.7:
@@ -8097,7 +8158,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -8125,13 +8186,13 @@ packages:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/http-cache-semantics@4.0.1:
@@ -8212,7 +8273,7 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: false
 
   /@types/lodash@4.14.191:
@@ -8261,7 +8322,7 @@ packages:
   /@types/node-fetch@2.6.4:
     resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       form-data: 3.0.1
 
   /@types/node-fetch@3.0.3:
@@ -8279,6 +8340,11 @@ packages:
     dependencies:
       undici-types: 5.26.5
 
+  /@types/node@20.11.5:
+    resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==}
+    dependencies:
+      undici-types: 5.26.5
+
   /@types/node@20.9.1:
     resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==}
     dependencies:
@@ -8381,7 +8447,7 @@ packages:
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/rename@1.0.7:
@@ -8395,7 +8461,7 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: false
 
   /@types/sanitize-html@2.9.5:
@@ -8421,7 +8487,7 @@ packages:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/serviceworker@0.0.67:
@@ -8431,7 +8497,7 @@ packages:
   /@types/set-cookie-parser@2.4.3:
     resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /@types/sharp@0.32.0:
@@ -8534,7 +8600,7 @@ packages:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
     optional: true
 
@@ -8555,7 +8621,7 @@ packages:
       '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8584,7 +8650,65 @@ packages:
       '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.56.0
+      graphemer: 1.4.0
+      ignore: 5.2.4
+      natural-compare: 1.4.0
+      semver: 7.5.4
+      ts-api-utils: 1.0.1(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
+      eslint: ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@eslint-community/regexpp': 4.6.2
+      '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.0
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.56.0
+      graphemer: 1.4.0
+      ignore: 5.2.4
+      natural-compare: 1.4.0
+      semver: 7.5.4
+      ts-api-utils: 1.0.1(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
+      eslint: ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@eslint-community/regexpp': 4.6.2
+      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.0
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8610,7 +8734,7 @@ packages:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8631,7 +8755,28 @@ packages:
       '@typescript-eslint/types': 6.14.0
       '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.56.0
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/parser@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.19.0
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8654,6 +8799,14 @@ packages:
       '@typescript-eslint/visitor-keys': 6.14.0
     dev: true
 
+  /@typescript-eslint/scope-manager@6.19.0:
+    resolution: {integrity: sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/visitor-keys': 6.19.0
+    dev: true
+
   /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.3):
     resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -8666,7 +8819,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8686,7 +8839,27 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
+      eslint: 8.56.0
+      ts-api-utils: 1.0.1(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/type-utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8704,6 +8877,11 @@ packages:
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
+  /@typescript-eslint/types@6.19.0:
+    resolution: {integrity: sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dev: true
+
   /@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3):
     resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -8715,7 +8893,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8736,7 +8914,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.14.0
       '@typescript-eslint/visitor-keys': 6.14.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8746,6 +8924,28 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/typescript-estree@6.19.0(typescript@5.3.3):
+    resolution: {integrity: sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/visitor-keys': 6.19.0
+      debug: 4.3.4(supports-color@5.5.0)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      minimatch: 9.0.3
+      semver: 7.5.4
+      ts-api-utils: 1.0.1(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.3.3):
     resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -8784,6 +8984,25 @@ packages:
       - typescript
     dev: true
 
+  /@typescript-eslint/utils@6.19.0(eslint@8.56.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^7.0.0 || ^8.0.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+      '@types/json-schema': 7.0.12
+      '@types/semver': 7.5.6
+      '@typescript-eslint/scope-manager': 6.19.0
+      '@typescript-eslint/types': 6.19.0
+      '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3)
+      eslint: 8.56.0
+      semver: 7.5.4
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /@typescript-eslint/visitor-keys@6.11.0:
     resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -8800,6 +9019,14 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: true
 
+  /@typescript-eslint/visitor-keys@6.19.0:
+    resolution: {integrity: sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    dependencies:
+      '@typescript-eslint/types': 6.19.0
+      eslint-visitor-keys: 3.4.3
+    dev: true
+
   /@ungap/structured-clone@1.2.0:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: true
@@ -9193,7 +9420,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     requiresBuild: true
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -9201,7 +9428,7 @@ packages:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
     engines: {node: '>= 14'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -9587,7 +9814,7 @@ packages:
     resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==}
     dependencies:
       archy: 1.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       fastq: 1.15.0
     transitivePeerDependencies:
       - supports-color
@@ -11036,7 +11263,6 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 5.5.0
-    dev: true
 
   /debug@4.3.4(supports-color@8.1.1):
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -11049,6 +11275,7 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 8.1.1
+    dev: true
 
   /decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@@ -11265,7 +11492,7 @@ packages:
     hasBin: true
     dependencies:
       address: 1.2.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -11589,7 +11816,7 @@ packages:
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       esbuild: 0.18.20
     transitivePeerDependencies:
       - supports-color
@@ -11806,6 +12033,35 @@ packages:
       - supports-color
     dev: true
 
+  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0):
+    resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint:
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      debug: 3.2.7(supports-color@8.1.1)
+      eslint: 8.56.0
+      eslint-import-resolver-node: 0.3.9
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.11.0)(eslint@8.53.0):
     resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
     engines: {node: '>=4'}
@@ -11876,6 +12132,41 @@ packages:
       - supports-color
     dev: true
 
+  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0):
+    resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3)
+      array-includes: 3.1.7
+      array.prototype.findlastindex: 1.2.3
+      array.prototype.flat: 1.3.2
+      array.prototype.flatmap: 1.3.2
+      debug: 3.2.7(supports-color@8.1.1)
+      doctrine: 2.1.0
+      eslint: 8.56.0
+      eslint-import-resolver-node: 0.3.9
+      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0)
+      hasown: 2.0.0
+      is-core-module: 2.13.1
+      is-glob: 4.0.3
+      minimatch: 3.1.2
+      object.fromentries: 2.0.7
+      object.groupby: 1.0.1
+      object.values: 1.1.7
+      semver: 6.3.1
+      tsconfig-paths: 3.15.0
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+    dev: true
+
   /eslint-plugin-vue@9.19.2(eslint@8.56.0):
     resolution: {integrity: sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==}
     engines: {node: ^14.17.0 || >=16.0.0}
@@ -11927,7 +12218,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -11974,7 +12265,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -12604,7 +12895,7 @@ packages:
       debug:
         optional: true
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
 
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -13160,7 +13451,6 @@ packages:
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
-    dev: true
 
   /has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
@@ -13298,7 +13588,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13360,7 +13650,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     dependencies:
       agent-base: 5.1.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13370,7 +13660,7 @@ packages:
     engines: {node: '>= 6'}
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -13379,7 +13669,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13389,7 +13679,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13549,7 +13839,7 @@ packages:
     dependencies:
       '@ioredis/commands': 1.2.0
       cluster-key-slot: 1.1.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       denque: 2.1.0
       lodash.defaults: 4.2.0
       lodash.isarguments: 3.1.0
@@ -13990,7 +14280,7 @@ packages:
     resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       istanbul-lib-coverage: 3.2.0
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -14044,7 +14334,7 @@ packages:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.3.0
@@ -14133,6 +14423,46 @@ packages:
       - supports-color
     dev: true
 
+  /jest-config@29.7.0(@types/node@20.11.5):
+    resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+    peerDependencies:
+      '@types/node': '*'
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      ts-node:
+        optional: true
+    dependencies:
+      '@babel/core': 7.22.11
+      '@jest/test-sequencer': 29.7.0
+      '@jest/types': 29.6.3
+      '@types/node': 20.11.5
+      babel-jest: 29.7.0(@babel/core@7.22.11)
+      chalk: 4.1.2
+      ci-info: 3.7.1
+      deepmerge: 4.2.2
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      jest-circus: 29.7.0
+      jest-environment-node: 29.7.0
+      jest-get-type: 29.6.3
+      jest-regex-util: 29.6.3
+      jest-resolve: 29.7.0
+      jest-runner: 29.7.0
+      jest-util: 29.7.0
+      jest-validate: 29.7.0
+      micromatch: 4.0.5
+      parse-json: 5.2.0
+      pretty-format: 29.7.0
+      slash: 3.0.0
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - babel-plugin-macros
+      - supports-color
+    dev: true
+
   /jest-diff@28.1.3:
     resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==}
     engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
@@ -14188,7 +14518,7 @@ packages:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       jest-mock: 29.7.0
       jest-util: 29.7.0
     dev: true
@@ -14218,7 +14548,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.6
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -14279,7 +14609,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
     dev: true
 
   /jest-mock@29.7.0:
@@ -14342,7 +14672,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -14373,7 +14703,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -14425,7 +14755,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.11
@@ -14450,7 +14780,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -14469,7 +14799,7 @@ packages:
     resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.5
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
@@ -14667,7 +14997,7 @@ packages:
     resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       rfdc: 1.3.0
       uri-js: 4.4.1
     transitivePeerDependencies:
@@ -17278,7 +17608,7 @@ packages:
     engines: {node: '>=8.16.0'}
     dependencies:
       '@types/mime-types': 2.1.4
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       extract-zip: 1.7.0
       https-proxy-agent: 4.0.0
       mime: 2.6.0
@@ -18275,7 +18605,7 @@ packages:
     dependencies:
       '@hapi/hoek': 10.0.1
       '@hapi/wreck': 18.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       joi: 17.7.0
     transitivePeerDependencies:
       - supports-color
@@ -18475,7 +18805,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color
@@ -18628,7 +18958,7 @@ packages:
       arg: 5.0.2
       bluebird: 3.7.2
       check-more-types: 2.24.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       execa: 5.1.1
       lazy-ass: 1.6.0
       ps-tree: 1.2.0
@@ -18892,7 +19222,6 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       has-flag: 3.0.0
-    dev: true
 
   /supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
@@ -19515,7 +19844,7 @@ packages:
       chalk: 4.1.2
       cli-highlight: 2.1.11
       date-fns: 2.30.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       dotenv: 16.0.3
       glob: 8.1.0
       ioredis: 5.3.2
@@ -19880,7 +20209,7 @@ packages:
     hasBin: true
     dependencies:
       cac: 6.7.14
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       mlly: 1.4.0
       pathe: 1.1.1
       picocolors: 1.0.0
@@ -19992,7 +20321,7 @@ packages:
       acorn-walk: 8.2.0
       cac: 6.7.14
       chai: 4.3.10
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       happy-dom: 10.0.3
       local-pkg: 0.4.3
       magic-string: 0.30.3
@@ -20074,7 +20403,7 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 3b2ecec7fd..3a03a58253 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -4,3 +4,4 @@ packages:
  - 'packages/sw'
  - 'packages/misskey-js'
  - 'packages/misskey-js/generator'
+ - 'packages/misskey-reversi'
-- 
GitLab