wip
This commit is contained in:
parent
2133d0552c
commit
67e6184a75
56 changed files with 3035 additions and 92 deletions
|
@ -26,6 +26,7 @@ COPY --link ["packages/sw/package.json", "./packages/sw/"]
|
|||
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
|
||||
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
|
||||
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
|
||||
COPY --link ["packages/misskey-mahjong/package.json", "./packages/misskey-mahjong/"]
|
||||
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm i --frozen-lockfile --aggregate-output
|
||||
|
@ -56,6 +57,7 @@ COPY --link ["packages/backend/package.json", "./packages/backend/"]
|
|||
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
|
||||
COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"]
|
||||
COPY --link ["packages/misskey-bubble-game/package.json", "./packages/misskey-bubble-game/"]
|
||||
COPY --link ["packages/misskey-mahjong/package.json", "./packages/misskey-mahjong/"]
|
||||
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm i --frozen-lockfile --aggregate-output
|
||||
|
@ -85,10 +87,12 @@ COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/nod
|
|||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-reversi/node_modules ./packages/misskey-reversi/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-bubble-game/node_modules ./packages/misskey-bubble-game/node_modules
|
||||
COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-mahjong/node_modules ./packages/misskey-mahjong/node_modules
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/built ./built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-js/built ./packages/misskey-js/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-reversi/built ./packages/misskey-reversi/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/misskey-mahjong/built ./packages/misskey-mahjong/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/built ./packages/backend/built
|
||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||
COPY --chown=misskey:misskey . ./
|
||||
|
|
26
locales/index.d.ts
vendored
26
locales/index.d.ts
vendored
|
@ -9604,6 +9604,32 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"disallowIrregularRules": string;
|
||||
};
|
||||
"_mahjong": {
|
||||
/**
|
||||
* 麻雀
|
||||
*/
|
||||
"mahjong": string;
|
||||
/**
|
||||
* ルームに参加
|
||||
*/
|
||||
"joinRoom": string;
|
||||
/**
|
||||
* ルームを作成
|
||||
*/
|
||||
"createRoom": string;
|
||||
/**
|
||||
* 準備完了
|
||||
*/
|
||||
"ready": string;
|
||||
/**
|
||||
* 準備を再開
|
||||
*/
|
||||
"cancelReady": string;
|
||||
/**
|
||||
* 退室
|
||||
*/
|
||||
"leave": string;
|
||||
};
|
||||
"_offlineScreen": {
|
||||
/**
|
||||
* オフライン - サーバーに接続できません
|
||||
|
|
|
@ -2559,6 +2559,14 @@ _reversi:
|
|||
allowIrregularRules: "変則許可 (完全フリー)"
|
||||
disallowIrregularRules: "変則なし"
|
||||
|
||||
_mahjong:
|
||||
mahjong: "麻雀"
|
||||
joinRoom: "ルームに参加"
|
||||
createRoom: "ルームを作成"
|
||||
ready: "準備完了"
|
||||
cancelReady: "準備を再開"
|
||||
leave: "退室"
|
||||
|
||||
_offlineScreen:
|
||||
title: "オフライン - サーバーに接続できません"
|
||||
header: "サーバーに接続できません"
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"packages/sw",
|
||||
"packages/misskey-js",
|
||||
"packages/misskey-reversi",
|
||||
"packages/misskey-bubble-game"
|
||||
"packages/misskey-bubble-game",
|
||||
"packages/misskey-mahjong"
|
||||
],
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
24
packages/backend/migration/1706234054207-mahjong.js
Normal file
24
packages/backend/migration/1706234054207-mahjong.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class Mahjong1706234054207 {
|
||||
name = 'Mahjong1706234054207'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "mahjong_game" ("id" character varying(32) NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "endedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32), "user2Id" character varying(32), "user3Id" character varying(32), "user4Id" character varying(32), "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "timeLimitForEachTurn" smallint NOT NULL DEFAULT '90', "logs" jsonb NOT NULL DEFAULT '[]', CONSTRAINT "PK_77db54c0a9785d387e3fbbdd2f0" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_b98c78761a845b69e6540401264" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_f17b0ba519ae28f188a7915ad6f" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_64314ffd3cb59475b0d06330058" FOREIGN KEY ("user3Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" ADD CONSTRAINT "FK_58a75f1ea2a810ae3986f72a0e3" FOREIGN KEY ("user4Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_58a75f1ea2a810ae3986f72a0e3"`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_64314ffd3cb59475b0d06330058"`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_f17b0ba519ae28f188a7915ad6f"`);
|
||||
await queryRunner.query(`ALTER TABLE "mahjong_game" DROP CONSTRAINT "FK_b98c78761a845b69e6540401264"`);
|
||||
await queryRunner.query(`DROP TABLE "mahjong_game"`);
|
||||
}
|
||||
}
|
|
@ -134,6 +134,7 @@
|
|||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"misskey-mahjong": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.0.4",
|
||||
"nested-property": "4.0.0",
|
||||
|
|
|
@ -67,6 +67,7 @@ import { FanoutTimelineService } from './FanoutTimelineService.js';
|
|||
import { ChannelFollowingService } from './ChannelFollowingService.js';
|
||||
import { RegistryApiService } from './RegistryApiService.js';
|
||||
import { ReversiService } from './ReversiService.js';
|
||||
import { MahjongService } from './MahjongService.js';
|
||||
|
||||
import { ChartLoggerService } from './chart/ChartLoggerService.js';
|
||||
import FederationChart from './chart/charts/federation.js';
|
||||
|
@ -205,6 +206,7 @@ const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpo
|
|||
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
|
||||
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
|
||||
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
|
||||
const $MahjongService: Provider = { provide: 'MahjongService', useExisting: MahjongService };
|
||||
|
||||
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
|
||||
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
|
||||
|
@ -344,6 +346,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
ReversiService,
|
||||
MahjongService,
|
||||
|
||||
ChartLoggerService,
|
||||
FederationChart,
|
||||
|
@ -479,6 +482,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$ReversiService,
|
||||
$MahjongService,
|
||||
|
||||
$ChartLoggerService,
|
||||
$FederationChart,
|
||||
|
@ -615,6 +619,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
ChannelFollowingService,
|
||||
RegistryApiService,
|
||||
ReversiService,
|
||||
MahjongService,
|
||||
|
||||
FederationChart,
|
||||
NotesChart,
|
||||
|
@ -749,6 +754,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$ChannelFollowingService,
|
||||
$RegistryApiService,
|
||||
$ReversiService,
|
||||
$MahjongService,
|
||||
|
||||
$FederationChart,
|
||||
$NotesChart,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import * as Reversi from 'misskey-reversi';
|
||||
import * as Mahjong from 'misskey-mahjong';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||
|
@ -192,6 +193,28 @@ export interface ReversiGameEventTypes {
|
|||
userId: MiUser['id'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface MahjongRoomEventTypes {
|
||||
changeReadyStates: {
|
||||
user1: boolean;
|
||||
user2: boolean;
|
||||
user3: boolean;
|
||||
user4: boolean;
|
||||
};
|
||||
tsumo: {
|
||||
house: Mahjong.Engine.House;
|
||||
tile: Mahjong.Engine.Tile;
|
||||
};
|
||||
dahai: {
|
||||
house: Mahjong.Engine.House;
|
||||
tile: Mahjong.Engine.Tile;
|
||||
};
|
||||
dahaiAndTsumo: {
|
||||
house: Mahjong.Engine.House;
|
||||
dahaiTile: Mahjong.Engine.Tile;
|
||||
tsumoTile: Mahjong.Engine.Tile;
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// 辞書(interface or type)から{ type, body }ユニオンを定義
|
||||
|
@ -290,6 +313,10 @@ export type GlobalEvents = {
|
|||
name: `reversiGameStream:${MiReversiGame['id']}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
|
||||
};
|
||||
mahjongRoom: {
|
||||
name: `mahjongRoomStream:${string}`;
|
||||
payload: EventUnionFromDictionary<SerializedAll<MahjongRoomEventTypes>>;
|
||||
};
|
||||
};
|
||||
|
||||
// API event definitions
|
||||
|
@ -389,4 +416,9 @@ export class GlobalEventService {
|
|||
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);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public publishMahjongRoomStream<K extends keyof MahjongRoomEventTypes>(roomId: string, type: K, value?: MahjongRoomEventTypes[K]): void {
|
||||
this.publish(`mahjongRoomStream:${roomId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
}
|
||||
|
|
350
packages/backend/src/core/MahjongService.ts
Normal file
350
packages/backend/src/core/MahjongService.ts
Normal file
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* 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 { ModuleRef } from '@nestjs/core';
|
||||
import { IsNull, LessThan, MoreThan } from 'typeorm';
|
||||
import * as Mahjong from 'misskey-mahjong';
|
||||
import type {
|
||||
MiMahjongGame,
|
||||
MahjongGamesRepository,
|
||||
} from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { Serialized } from '@/types.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
|
||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
|
||||
const PON_TIMEOUT_MS = 1000 * 10; // 10sec
|
||||
const DAHAI_TIMEOUT_MS = 1000 * 30; // 30sec
|
||||
|
||||
type Room = {
|
||||
id: string;
|
||||
user1Id: MiUser['id'];
|
||||
user2Id: MiUser['id'] | null;
|
||||
user3Id: MiUser['id'] | null;
|
||||
user4Id: MiUser['id'] | null;
|
||||
user1: Packed<'UserLite'> | null;
|
||||
user2: Packed<'UserLite'> | null;
|
||||
user3: Packed<'UserLite'> | null;
|
||||
user4: Packed<'UserLite'> | null;
|
||||
user1Ai?: boolean;
|
||||
user2Ai?: boolean;
|
||||
user3Ai?: boolean;
|
||||
user4Ai?: boolean;
|
||||
user1Ready: boolean;
|
||||
user2Ready: boolean;
|
||||
user3Ready: boolean;
|
||||
user4Ready: boolean;
|
||||
user1Offline?: boolean;
|
||||
user2Offline?: boolean;
|
||||
user3Offline?: boolean;
|
||||
user4Offline?: boolean;
|
||||
isStarted?: boolean;
|
||||
|
||||
gameState?: Mahjong.Engine.MasterState;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||
private notificationService: NotificationService;
|
||||
|
||||
constructor(
|
||||
private moduleRef: ModuleRef,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
//@Inject(DI.mahjongGamesRepository)
|
||||
//private mahjongGamesRepository: MahjongGamesRepository,
|
||||
|
||||
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
|
||||
private async saveRoom(room: Room) {
|
||||
await this.redisClient.set(`mahjong:room:${room.id}`, JSON.stringify(room), 'EX', 60 * 30);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async createRoom(user: MiUser): Promise<Room> {
|
||||
const room: Room = {
|
||||
id: this.idService.gen(),
|
||||
user1Id: user.id,
|
||||
user2Id: null,
|
||||
user3Id: null,
|
||||
user4Id: null,
|
||||
user1: await this.userEntityService.pack(user),
|
||||
user1Ready: false,
|
||||
user2Ready: false,
|
||||
user3Ready: false,
|
||||
user4Ready: false,
|
||||
};
|
||||
await this.saveRoom(room);
|
||||
return room;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getRoom(id: Room['id']): Promise<Room | null> {
|
||||
const room = await this.redisClient.get(`mahjong:room:${id}`);
|
||||
if (!room) return null;
|
||||
const parsed = JSON.parse(room);
|
||||
return {
|
||||
...parsed,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async joinRoom(roomId: Room['id'], user: MiUser): Promise<Room | null> {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (!room) return null;
|
||||
if (room.user1Id === user.id) return room;
|
||||
if (room.user2Id === user.id) return room;
|
||||
if (room.user3Id === user.id) return room;
|
||||
if (room.user4Id === user.id) return room;
|
||||
if (room.user2Id === null) {
|
||||
room.user2Id = user.id;
|
||||
room.user2 = await this.userEntityService.pack(user);
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 2, user: room.user2 });
|
||||
return room;
|
||||
}
|
||||
if (room.user3Id === null) {
|
||||
room.user3Id = user.id;
|
||||
room.user3 = await this.userEntityService.pack(user);
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 3, user: room.user3 });
|
||||
return room;
|
||||
}
|
||||
if (room.user4Id === null) {
|
||||
room.user4Id = user.id;
|
||||
room.user4 = await this.userEntityService.pack(user);
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 4, user: room.user4 });
|
||||
return room;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async addAi(roomId: Room['id'], user: MiUser): Promise<Room | null> {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (!room) return null;
|
||||
if (room.user1Id !== user.id) throw new Error('access denied');
|
||||
|
||||
if (room.user2Id == null) {
|
||||
room.user2Ai = true;
|
||||
room.user2Ready = true;
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 2, user: null });
|
||||
return room;
|
||||
}
|
||||
if (room.user3Id == null) {
|
||||
room.user3Ai = true;
|
||||
room.user3Ready = true;
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 3, user: null });
|
||||
return room;
|
||||
}
|
||||
if (room.user4Id == null) {
|
||||
room.user4Ai = true;
|
||||
room.user4Ready = true;
|
||||
await this.saveRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'joined', { index: 4, user: null });
|
||||
return room;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async leaveRoom(roomId: Room['id'], user: MiUser): Promise<Room | null> {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (!room) return null;
|
||||
if (room.user1Id === user.id) {
|
||||
room.user1Id = null;
|
||||
room.user1 = null;
|
||||
await this.saveRoom(room);
|
||||
return room;
|
||||
}
|
||||
if (room.user2Id === user.id) {
|
||||
room.user2Id = null;
|
||||
room.user2 = null;
|
||||
await this.saveRoom(room);
|
||||
return room;
|
||||
}
|
||||
if (room.user3Id === user.id) {
|
||||
room.user3Id = null;
|
||||
room.user3 = null;
|
||||
await this.saveRoom(room);
|
||||
return room;
|
||||
}
|
||||
if (room.user4Id === user.id) {
|
||||
room.user4Id = null;
|
||||
room.user4 = null;
|
||||
await this.saveRoom(room);
|
||||
return room;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async changeReadyState(roomId: Room['id'], user: MiUser, ready: boolean): Promise<void> {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
if (room.user1Id === user.id) {
|
||||
room.user1Ready = ready;
|
||||
await this.saveRoom(room);
|
||||
}
|
||||
if (room.user2Id === user.id) {
|
||||
room.user2Ready = ready;
|
||||
await this.saveRoom(room);
|
||||
}
|
||||
if (room.user3Id === user.id) {
|
||||
room.user3Ready = ready;
|
||||
await this.saveRoom(room);
|
||||
}
|
||||
if (room.user4Id === user.id) {
|
||||
room.user4Ready = ready;
|
||||
await this.saveRoom(room);
|
||||
}
|
||||
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'changeReadyStates', {
|
||||
user1: room.user1Ready,
|
||||
user2: room.user2Ready,
|
||||
user3: room.user3Ready,
|
||||
user4: room.user4Ready,
|
||||
});
|
||||
|
||||
if (room.user1Ready && room.user2Ready && room.user3Ready && room.user4Ready) {
|
||||
await this.startGame(room);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async startGame(room: Room) {
|
||||
if (!room.user1Ready || !room.user2Ready || !room.user3Ready || !room.user4Ready) {
|
||||
throw new Error('Not ready');
|
||||
}
|
||||
|
||||
room.gameState = Mahjong.Engine.MasterGameEngine.createInitialState();
|
||||
room.isStarted = true;
|
||||
|
||||
await this.saveRoom(room);
|
||||
|
||||
const packed = await this.packRoom(room);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'started', { room: packed });
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packRoom(room: Room, me: MiUser) {
|
||||
return {
|
||||
...room,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async dahai(room: Room, engine: Mahjong.Engine.MasterGameEngine, user: MiUser, tile: Mahjong.Engine.Tile) {
|
||||
const myHouse = user.id === room.user1Id ? engine.state.user1House : user.id === room.user2Id ? engine.state.user2House : user.id === room.user3Id ? engine.state.user3House : engine.state.user4House;
|
||||
const res = engine.op_dahai(myHouse, tile);
|
||||
if (res.canPonHouse) {
|
||||
// TODO: 家がCPUだった場合の処理
|
||||
this.redisClient.set(`mahjong:gamePonAsking:${room.id}`, '');
|
||||
const waitingStartedAt = Date.now();
|
||||
const interval = setInterval(async () => {
|
||||
const waiting = await this.redisClient.get(`mahjong:gamePonAsking:${room.id}`);
|
||||
if (waiting == null) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - waitingStartedAt > PON_TIMEOUT_MS) {
|
||||
await this.redisClient.del(`mahjong:gamePonAsking:${room.id}`);
|
||||
clearInterval(interval);
|
||||
const res = engine.op_noOnePon();
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile });
|
||||
return;
|
||||
}
|
||||
}, 2000);
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'dahai', { house: myHouse, tile });
|
||||
} else {
|
||||
this.globalEventService.publishMahjongRoomStream(room.id, 'dahaiAndTsumo', { house: myHouse, dahaiTile: tile, tsumoTile: res.tsumoTile });
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async op_dahai(roomId: MiMahjongGame['id'], user: MiUser, tile: string) {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (room == null) return;
|
||||
if (room.gameState == null) return;
|
||||
|
||||
await this.redisClient.del(`mahjong:gameDahaiWaiting:${room.id}`);
|
||||
|
||||
const engine = new Mahjong.Engine.MasterGameEngine(room.gameState);
|
||||
await this.dahai(room, engine, user, tile);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async op_pon(roomId: MiMahjongGame['id'], user: MiUser) {
|
||||
const room = await this.getRoom(roomId);
|
||||
if (room == null) return;
|
||||
if (room.gameState == null) return;
|
||||
|
||||
const engine = new Mahjong.Engine.MasterGameEngine(room.gameState);
|
||||
const myHouse = user.id === room.user1Id ? engine.state.user1House : user.id === room.user2Id ? engine.state.user2House : user.id === room.user3Id ? engine.state.user3House : engine.state.user4House;
|
||||
const res = engine.op_pon(myHouse);
|
||||
this.waitForDahai(room, user, engine);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async waitForDahai(game: Room, user: MiUser, engine: Mahjong.Engine.MasterGameEngine) {
|
||||
this.redisClient.set(`mahjong:gameDahaiWaiting:${game.id}`, '');
|
||||
const waitingStartedAt = Date.now();
|
||||
const interval = setInterval(async () => {
|
||||
const waiting = await this.redisClient.get(`mahjong:gameDahaiWaiting:${game.id}`);
|
||||
if (waiting == null) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - waitingStartedAt > DAHAI_TIMEOUT_MS) {
|
||||
await this.redisClient.del(`mahjong:gameDahaiWaiting:${game.id}`);
|
||||
clearInterval(interval);
|
||||
const house = game.user1Id === user.id ? engine.state.user1House : game.user2Id === user.id ? engine.state.user2House : game.user3Id === user.id ? engine.state.user3House : engine.state.user4House;
|
||||
const handTiles = house === 'e' ? engine.state.eHandTiles : house === 's' ? engine.state.sHandTiles : house === 'w' ? engine.state.wHandTiles : engine.state.nHandTiles;
|
||||
await this.dahai(game, engine, user, handTiles.at(-1));
|
||||
return;
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -80,5 +80,6 @@ export const DI = {
|
|||
userMemosRepository: Symbol('userMemosRepository'),
|
||||
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
|
||||
reversiGamesRepository: Symbol('reversiGamesRepository'),
|
||||
mahjongGamesRepository: Symbol('mahjongGamesRepository'),
|
||||
//#endregion
|
||||
};
|
||||
|
|
|
@ -40,6 +40,7 @@ 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';
|
||||
import { packedMahjongRoomDetailedSchema } from '@/models/json-schema/mahjong-room.js';
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
@ -81,6 +82,7 @@ export const refs = {
|
|||
Role: packedRoleSchema,
|
||||
ReversiGameLite: packedReversiGameLiteSchema,
|
||||
ReversiGameDetailed: packedReversiGameDetailedSchema,
|
||||
MahjongRoomDetailed: packedMahjongRoomDetailedSchema,
|
||||
};
|
||||
|
||||
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
|
||||
|
|
89
packages/backend/src/models/MahjongGame.ts
Normal file
89
packages/backend/src/models/MahjongGame.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
@Entity('mahjong_game')
|
||||
export class MiMahjongGame {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public startedAt: Date | null;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public endedAt: Date | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public user1Id: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user1: MiUser | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public user2Id: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user2: MiUser | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public user3Id: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user3: MiUser | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public user4Id: MiUser['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user4: MiUser | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isEnded: boolean;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public winnerId: MiUser['id'] | null;
|
||||
|
||||
// in sec
|
||||
@Column('smallint', {
|
||||
default: 90,
|
||||
})
|
||||
public timeLimitForEachTurn: number;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public logs: number[][];
|
||||
}
|
|
@ -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, MiReversiGame } 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, MiMahjongGame } from './_.js';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
|
@ -411,6 +411,12 @@ const $reversiGamesRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $mahjongGamesRepository: Provider = {
|
||||
provide: DI.mahjongGamesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiMahjongGame),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
],
|
||||
|
@ -482,6 +488,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$userMemosRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
$mahjongGamesRepository,
|
||||
],
|
||||
exports: [
|
||||
$usersRepository,
|
||||
|
@ -551,6 +558,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$userMemosRepository,
|
||||
$bubbleGameRecordsRepository,
|
||||
$reversiGamesRepository,
|
||||
$mahjongGamesRepository,
|
||||
],
|
||||
})
|
||||
export class RepositoryModule {}
|
||||
|
|
|
@ -70,6 +70,7 @@ 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 { MiMahjongGame } from '@/models/MahjongGame.js';
|
||||
|
||||
import type { Repository } from 'typeorm';
|
||||
|
||||
|
@ -141,6 +142,7 @@ export {
|
|||
MiUserMemo,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
MiMahjongGame,
|
||||
};
|
||||
|
||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
|
||||
|
@ -210,3 +212,4 @@ export type FlashLikesRepository = Repository<MiFlashLike>;
|
|||
export type UserMemoRepository = Repository<MiUserMemo>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame>;
|
||||
export type MahjongGamesRepository = Repository<MiMahjongGame>;
|
||||
|
|
110
packages/backend/src/models/json-schema/mahjong-room.ts
Normal file
110
packages/backend/src/models/json-schema/mahjong-room.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedMahjongRoomDetailedSchema = {
|
||||
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',
|
||||
},
|
||||
endedAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
isStarted: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isEnded: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user1Id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: null,
|
||||
format: 'id',
|
||||
},
|
||||
user2Id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: null,
|
||||
format: 'id',
|
||||
},
|
||||
user3Id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: null,
|
||||
format: 'id',
|
||||
},
|
||||
user4Id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: null,
|
||||
format: 'id',
|
||||
},
|
||||
user1: {
|
||||
type: 'object',
|
||||
optional: false, nullable: null,
|
||||
ref: 'User',
|
||||
},
|
||||
user2: {
|
||||
type: 'object',
|
||||
optional: false, nullable: null,
|
||||
ref: 'User',
|
||||
},
|
||||
user3: {
|
||||
type: 'object',
|
||||
optional: false, nullable: null,
|
||||
ref: 'User',
|
||||
},
|
||||
user4: {
|
||||
type: 'object',
|
||||
optional: false, nullable: null,
|
||||
ref: 'User',
|
||||
},
|
||||
user1Ai: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user2Ai: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user3Ai: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user4Ai: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user1Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user2Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user3Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
user4Ready: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -78,6 +78,7 @@ 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 { MiMahjongGame } from '@/models/MahjongGame.js';
|
||||
|
||||
import { Config } from '@/config.js';
|
||||
import MisskeyLogger from '@/logger.js';
|
||||
|
@ -194,6 +195,7 @@ export const entities = [
|
|||
MiUserMemo,
|
||||
MiBubbleGameRecord,
|
||||
MiReversiGame,
|
||||
MiMahjongGame,
|
||||
...charts,
|
||||
];
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
|
|||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
||||
import { MahjongRoomChannelService } from './api/stream/channels/mahjong-room.js';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -82,6 +83,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js
|
|||
RoleTimelineChannelService,
|
||||
ReversiChannelService,
|
||||
ReversiGameChannelService,
|
||||
MahjongRoomChannelService,
|
||||
HomeTimelineChannelService,
|
||||
HybridTimelineChannelService,
|
||||
LocalTimelineChannelService,
|
||||
|
|
|
@ -373,6 +373,9 @@ 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 * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||
import * as ep___mahjong_createRoom from './endpoints/mahjong/create-room.js';
|
||||
import * as ep___mahjong_joinRoom from './endpoints/mahjong/join-room.js';
|
||||
import * as ep___mahjong_showRoom from './endpoints/mahjong/show-room.js';
|
||||
import { GetterService } from './GetterService.js';
|
||||
import { ApiLoggerService } from './ApiLoggerService.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
@ -744,6 +747,9 @@ const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useC
|
|||
const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
|
||||
const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
|
||||
const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
|
||||
const $mahjong_createRoom: Provider = { provide: 'ep:mahjong/create-room', useClass: ep___mahjong_createRoom.default };
|
||||
const $mahjong_joinRoom: Provider = { provide: 'ep:mahjong/join-room', useClass: ep___mahjong_joinRoom.default };
|
||||
const $mahjong_showRoom: Provider = { provide: 'ep:mahjong/show-room', useClass: ep___mahjong_showRoom.default };
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -1119,6 +1125,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$reversi_showGame,
|
||||
$reversi_surrender,
|
||||
$reversi_verify,
|
||||
$mahjong_createRoom,
|
||||
$mahjong_joinRoom,
|
||||
$mahjong_showRoom,
|
||||
],
|
||||
exports: [
|
||||
$admin_meta,
|
||||
|
@ -1485,6 +1494,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$reversi_showGame,
|
||||
$reversi_surrender,
|
||||
$reversi_verify,
|
||||
$mahjong_createRoom,
|
||||
$mahjong_joinRoom,
|
||||
$mahjong_showRoom,
|
||||
],
|
||||
})
|
||||
export class EndpointsModule {}
|
||||
|
|
|
@ -374,6 +374,9 @@ 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 * as ep___reversi_verify from './endpoints/reversi/verify.js';
|
||||
import * as ep___mahjong_createRoom from './endpoints/mahjong/create-room.js';
|
||||
import * as ep___mahjong_joinRoom from './endpoints/mahjong/join-room.js';
|
||||
import * as ep___mahjong_showRoom from './endpoints/mahjong/show-room.js';
|
||||
|
||||
const eps = [
|
||||
['admin/meta', ep___admin_meta],
|
||||
|
@ -743,6 +746,9 @@ const eps = [
|
|||
['reversi/show-game', ep___reversi_showGame],
|
||||
['reversi/surrender', ep___reversi_surrender],
|
||||
['reversi/verify', ep___reversi_verify],
|
||||
['mahjong/create-room', ep___mahjong_createRoom],
|
||||
['mahjong/join-room', ep___mahjong_joinRoom],
|
||||
['mahjong/show-room', ep___mahjong_showRoom],
|
||||
];
|
||||
|
||||
interface IEndpointMetaBase {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 { MahjongService } from '@/core/MahjongService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'MahjongRoomDetailed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private mahjongService: MahjongService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.mahjongService.createRoom(me);
|
||||
return await this.mahjongService.packRoom(room, me);
|
||||
});
|
||||
}
|
||||
}
|
64
packages/backend/src/server/api/endpoints/mahjong/games.ts
Normal file
64
packages/backend/src/server/api/endpoints/mahjong/games.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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)
|
||||
.innerJoinAndSelect('game.user1', 'user1')
|
||||
.innerJoinAndSelect('game.user2', 'user2');
|
||||
|
||||
if (ps.my && me) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('game.user1Id = :userId', { userId: me.id })
|
||||
.orWhere('game.user2Id = :userId', { userId: me.id });
|
||||
}));
|
||||
} else {
|
||||
query.andWhere('game.isStarted = TRUE');
|
||||
}
|
||||
|
||||
const games = await query.take(ps.limit).getMany();
|
||||
|
||||
return await this.reversiGameEntityService.packLiteMany(games);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { MahjongService } from '@/core/MahjongService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '370e42b0-2a67-4306-9328-51c5f568f110',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'MahjongRoomDetailed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private mahjongService: MahjongService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.mahjongService.getRoom(ps.roomId);
|
||||
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
await this.mahjongService.joinRoom(room.id, me);
|
||||
|
||||
return await this.mahjongService.packRoom(room, me);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 { MahjongService } from '@/core/MahjongService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:account',
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'd77df68f-06f3-492b-9078-e6f72f4acf23',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'MahjongRoomDetailed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private mahjongService: MahjongService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.mahjongService.getRoom(ps.roomId);
|
||||
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
return await this.mahjongService.packRoom(room, me);
|
||||
});
|
||||
}
|
||||
}
|
64
packages/backend/src/server/api/endpoints/mahjong/verify.ts
Normal file
64
packages/backend/src/server/api/endpoints/mahjong/verify.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ReversiService } from '@/core/ReversiService.js';
|
||||
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
errors: {
|
||||
noSuchGame: {
|
||||
message: 'No such game.',
|
||||
code: 'NO_SUCH_GAME',
|
||||
id: '8fb05624-b525-43dd-90f7-511852bdfeee',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
desynced: { type: 'boolean' },
|
||||
game: {
|
||||
type: 'object',
|
||||
optional: true, nullable: true,
|
||||
ref: 'ReversiGameDetailed',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
gameId: { type: 'string', format: 'misskey:id' },
|
||||
crc32: { type: 'string' },
|
||||
},
|
||||
required: ['gameId', 'crc32'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private reversiService: ReversiService,
|
||||
private reversiGameEntityService: ReversiGameEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const game = await this.reversiService.checkCrc(ps.gameId, ps.crc32);
|
||||
if (game) {
|
||||
return {
|
||||
desynced: true,
|
||||
game: await this.reversiGameEntityService.packDetail(game),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
desynced: false,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ 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 { MahjongRoomChannelService } from './channels/mahjong-room.js';
|
||||
import { type MiChannelService } from './channel.js';
|
||||
|
||||
@Injectable()
|
||||
|
@ -42,6 +43,7 @@ export class ChannelsService {
|
|||
private adminChannelService: AdminChannelService,
|
||||
private reversiChannelService: ReversiChannelService,
|
||||
private reversiGameChannelService: ReversiGameChannelService,
|
||||
private mahjongRoomChannelService: MahjongRoomChannelService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -64,6 +66,7 @@ export class ChannelsService {
|
|||
case 'admin': return this.adminChannelService;
|
||||
case 'reversi': return this.reversiChannelService;
|
||||
case 'reversiGame': return this.reversiGameChannelService;
|
||||
case 'mahjongRoom': return this.mahjongRoomChannelService;
|
||||
|
||||
default:
|
||||
throw new Error(`no such channel: ${name}`);
|
||||
|
|
107
packages/backend/src/server/api/stream/channels/mahjong-room.ts
Normal file
107
packages/backend/src/server/api/stream/channels/mahjong-room.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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 { bindThis } from '@/decorators.js';
|
||||
import { MahjongService } from '@/core/MahjongService.js';
|
||||
import Channel, { type MiChannelService } from '../channel.js';
|
||||
|
||||
class MahjongRoomChannel extends Channel {
|
||||
public readonly chName = 'mahjongRoom';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true as const;
|
||||
public static kind = 'read:account';
|
||||
private roomId: string | null = null;
|
||||
|
||||
constructor(
|
||||
private mahjongService: MahjongService,
|
||||
|
||||
id: string,
|
||||
connection: Channel['connection'],
|
||||
) {
|
||||
super(id, connection);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: any) {
|
||||
this.roomId = params.roomId as string;
|
||||
|
||||
this.subscriber.on(`mahjongRoomStream:${this.roomId}`, 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 'addAi': this.addAi(); break;
|
||||
case 'putStone': this.putStone(body.pos, body.id); break;
|
||||
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async updateSettings(key: string, value: any) {
|
||||
if (this.user == null) return;
|
||||
|
||||
this.mahjongService.updateSettings(this.roomId!, this.user, key, value);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async ready(ready: boolean) {
|
||||
if (this.user == null) return;
|
||||
|
||||
this.mahjongService.changeReadyState(this.roomId!, this.user, ready);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async addAi() {
|
||||
if (this.user == null) return;
|
||||
|
||||
this.mahjongService.addAi(this.roomId!, this.user);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async putStone(pos: number, id: string) {
|
||||
if (this.user == null) return;
|
||||
|
||||
this.mahjongService.putStoneToRoom(this.roomId!, this.user, pos, id);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async claimTimeIsUp() {
|
||||
if (this.user == null) return;
|
||||
|
||||
this.mahjongService.checkTimeout(this.roomId!);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose() {
|
||||
// Unsubscribe events
|
||||
this.subscriber.off(`mahjongRoomStream:${this.roomId}`, this.send);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MahjongRoomChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = MahjongRoomChannel.shouldShare;
|
||||
public readonly requireCredential = MahjongRoomChannel.requireCredential;
|
||||
public readonly kind = MahjongRoomChannel.kind;
|
||||
|
||||
constructor(
|
||||
private mahjongService: MahjongService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(id: string, connection: Channel['connection']): MahjongRoomChannel {
|
||||
return new MahjongRoomChannel(
|
||||
this.mahjongService,
|
||||
id,
|
||||
connection,
|
||||
);
|
||||
}
|
||||
}
|
BIN
packages/frontend/assets/mahjong/dahai.mp3
Normal file
BIN
packages/frontend/assets/mahjong/dahai.mp3
Normal file
Binary file not shown.
BIN
packages/frontend/assets/mahjong/logo.png
Normal file
BIN
packages/frontend/assets/mahjong/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
|
@ -53,6 +53,7 @@
|
|||
"matter-js": "0.19.0",
|
||||
"mfm-js": "0.24.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-mahjong": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.3",
|
||||
|
|
|
@ -549,6 +549,14 @@ const routes = [{
|
|||
path: '/reversi/g/:gameId',
|
||||
component: page(() => import('@/pages/reversi/game.vue')),
|
||||
loginRequired: false,
|
||||
}, {
|
||||
path: '/mahjong',
|
||||
component: page(() => import('@/pages/mahjong/index.vue')),
|
||||
loginRequired: false,
|
||||
}, {
|
||||
path: '/mahjong/g/:roomId',
|
||||
component: page(() => import('@/pages/mahjong/room.vue')),
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/timeline',
|
||||
component: page(() => import('@/pages/timeline.vue')),
|
||||
|
|
|
@ -18,6 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
|
||||
</MkA>
|
||||
</div>
|
||||
<div class="_panel">
|
||||
<MkA to="/mahjong">
|
||||
<img src="/client-assets/mahjong/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
|
|
166
packages/frontend/src/pages/mahjong/index.vue
Normal file
166
packages/frontend/src/pages/mahjong/index.vue
Normal file
|
@ -0,0 +1,166 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkSpacer :contentMax="600">
|
||||
<div class="_gaps">
|
||||
<div>
|
||||
<img src="/client-assets/mahjong/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
|
||||
</div>
|
||||
|
||||
<div class="_panel _gaps" style="padding: 16px;">
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton primary gradate rounded @click="joinRoom">{{ i18n.ts._mahjong.joinRoom }}</MkButton>
|
||||
<MkButton primary gradate rounded @click="createRoom">{{ i18n.ts._mahjong.createRoom }}</MkButton>
|
||||
</div>
|
||||
<div style="font-size: 90%; opacity: 0.7; text-align: center;"><i class="ti ti-music"></i> {{ i18n.ts.soundWillBePlayed }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onDeactivated, 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';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
|
||||
const myGamesPagination = {
|
||||
endpoint: 'mahjong/games' as const,
|
||||
limit: 10,
|
||||
params: {
|
||||
my: true,
|
||||
},
|
||||
};
|
||||
|
||||
const gamesPagination = {
|
||||
endpoint: 'mahjong/games' as const,
|
||||
limit: 10,
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const invitations = ref<Misskey.entities.UserLite[]>([]);
|
||||
const matchingUser = ref<Misskey.entities.UserLite | null>(null);
|
||||
const matchingAny = ref<boolean>(false);
|
||||
const noIrregularRules = ref<boolean>(false);
|
||||
|
||||
async function joinRoom() {
|
||||
const { canceled, result } = await os.inputText({
|
||||
title: 'roomId',
|
||||
});
|
||||
if (canceled) return;
|
||||
const room = await misskeyApi('mahjong/join-room', {
|
||||
roomId: result,
|
||||
});
|
||||
router.push(`/mahjong/g/${room.id}`);
|
||||
}
|
||||
|
||||
async function createRoom(ev: MouseEvent) {
|
||||
const room = await misskeyApi('mahjong/create-room', {
|
||||
});
|
||||
router.push(`/mahjong/g/${room.id}`);
|
||||
}
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: i18n.ts._mahjong.mahjong,
|
||||
icon: 'ti ti-device-gamepad',
|
||||
})));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
@keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.gamePreviewActive {
|
||||
box-shadow: inset 0 0 8px 0px var(--accent);
|
||||
}
|
||||
|
||||
.gamePreviewWaiting {
|
||||
box-shadow: inset 0 0 8px 0px var(--warn);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.gamePreviewStatusActive {
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
animation: blink 2s infinite;
|
||||
}
|
||||
|
||||
.gamePreviewStatusWaiting {
|
||||
color: var(--warn);
|
||||
font-weight: bold;
|
||||
animation: blink 2s infinite;
|
||||
}
|
||||
|
||||
.waitingScreen {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.waitingScreenTitle {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
</style>
|
307
packages/frontend/src/pages/mahjong/room.game.vue
Normal file
307
packages/frontend/src/pages/mahjong/room.game.vue
Normal file
|
@ -0,0 +1,307 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkSpacer :contentMax="500">
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as Mahjong from 'misskey-mahjong';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.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';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import * as os from '@/os.js';
|
||||
import { confetti } from '@/scripts/confetti.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const props = defineProps<{
|
||||
room: Misskey.entities.ReversiRoomDetailed;
|
||||
connection?: Misskey.ChannelConnection | null;
|
||||
}>();
|
||||
|
||||
const room = ref<Misskey.entities.ReversiRoomDetailed>(deepClone(props.room));
|
||||
const myUserNumber = computed(() => room.value.user1Id === $i.id ? 1 : room.value.user2Id === $i.id ? 2 : room.value.user3Id === $i.id ? 3 : 4);
|
||||
const engine = shallowRef(new Mahjong.Engine.PlayerGameEngine(myUserNumber, room.value.gameState));
|
||||
|
||||
const isMyTurn = computed(() => {
|
||||
return engine.value.state.turn === engine.value.myHouse;
|
||||
});
|
||||
|
||||
/*
|
||||
if (room.value.isStarted && !room.value.isEnded) {
|
||||
useInterval(() => {
|
||||
if (room.value.isEnded) return;
|
||||
const crc32 = engine.value.calcCrc32();
|
||||
if (_DEV_) console.log('crc32', crc32);
|
||||
misskeyApi('reversi/verify', {
|
||||
roomId: room.value.id,
|
||||
crc32: crc32.toString(),
|
||||
}).then((res) => {
|
||||
if (res.desynced) {
|
||||
console.log('resynced');
|
||||
restoreRoom(res.room!);
|
||||
}
|
||||
});
|
||||
}, 10000, { immediate: false, afterMounted: true });
|
||||
}
|
||||
*/
|
||||
|
||||
const appliedOps: string[] = [];
|
||||
|
||||
const myTurnTimerRmain = ref<number>(room.value.timeLimitForEachTurn);
|
||||
const opTurnTimerRmain = ref<number>(room.value.timeLimitForEachTurn);
|
||||
|
||||
/*
|
||||
const TIMER_INTERVAL_SEC = 3;
|
||||
if (!props.room.isEnded) {
|
||||
useInterval(() => {
|
||||
if (myTurnTimerRmain.value > 0) {
|
||||
myTurnTimerRmain.value = Math.max(0, myTurnTimerRmain.value - TIMER_INTERVAL_SEC);
|
||||
}
|
||||
if (opTurnTimerRmain.value > 0) {
|
||||
opTurnTimerRmain.value = Math.max(0, opTurnTimerRmain.value - TIMER_INTERVAL_SEC);
|
||||
}
|
||||
|
||||
if (iAmPlayer.value) {
|
||||
if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) {
|
||||
props.connection!.send('claimTimeIsUp', {});
|
||||
}
|
||||
}
|
||||
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
|
||||
}
|
||||
*/
|
||||
|
||||
async function onStreamLog(log) {
|
||||
if (log.id == null || !appliedOps.includes(log.id)) {
|
||||
switch (log.operation) {
|
||||
case 'put': {
|
||||
sound.playUrl('/client-assets/mahjong/dahai.mp3', {
|
||||
volume: 1,
|
||||
playbackRate: 1,
|
||||
});
|
||||
|
||||
if (log.house !== engine.value.turn) { // = desyncが発生している
|
||||
const _room = await misskeyApi('reversi/show-room', {
|
||||
roomId: props.room.id,
|
||||
});
|
||||
restoreRoom(_room);
|
||||
return;
|
||||
}
|
||||
|
||||
engine.value.op_dahai(log.house, log.tile);
|
||||
triggerRef(engine);
|
||||
|
||||
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||
opTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restoreRoom(_room) {
|
||||
room.value = deepClone(_room);
|
||||
|
||||
engine.value = new Mahjong.Engine.PlayerGameEngine(myUserNumber, room.value.gameState);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.connection != null) {
|
||||
props.connection.on('log', onStreamLog);
|
||||
props.connection.on('ended', onStreamEnded);
|
||||
}
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (props.connection != null) {
|
||||
props.connection.on('log', onStreamLog);
|
||||
props.connection.on('ended', onStreamEnded);
|
||||
}
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
if (props.connection != null) {
|
||||
props.connection.off('log', onStreamLog);
|
||||
props.connection.off('ended', onStreamEnded);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (props.connection != null) {
|
||||
props.connection.off('log', onStreamLog);
|
||||
props.connection.off('ended', onStreamEnded);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
@use "sass:math";
|
||||
|
||||
.transition_flip_enterActive,
|
||||
.transition_flip_leaveActive {
|
||||
backface-visibility: hidden;
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
.transition_flip_enterFrom {
|
||||
transform: rotateY(-180deg);
|
||||
opacity: 0;
|
||||
}
|
||||
.transition_flip_leaveTo {
|
||||
transform: rotateY(180deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
$label-size: 16px;
|
||||
$gap: 4px;
|
||||
|
||||
.root {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.board {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
|
||||
padding: 7px;
|
||||
background: #8C4F26;
|
||||
box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.boardInner {
|
||||
padding: 32px;
|
||||
|
||||
background: var(--panel);
|
||||
box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@container (max-width: 400px) {
|
||||
.boardInner {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.labelsX {
|
||||
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: 100%;
|
||||
aspect-ratio: 1;
|
||||
transform-style: preserve-3d;
|
||||
perspective: 150px;
|
||||
transition: border 0.25s ease, opacity 0.25s ease;
|
||||
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
|
||||
.boardCellStone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
}
|
||||
</style>
|
128
packages/frontend/src/pages/mahjong/room.setting.vue
Normal file
128
packages/frontend/src/pages/mahjong/room.setting.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<MkSpacer :contentMax="600">
|
||||
<div class="_gaps">
|
||||
<div class="_panel">
|
||||
<MkAvatar v-if="room.user1" :user="room.user1" :class="$style.userAvatar"/>
|
||||
<div v-else-if="room.user1Ai">AI</div>
|
||||
<div v-if="room.user1Ready">OK</div>
|
||||
</div>
|
||||
<div class="_panel">
|
||||
<MkAvatar v-if="room.user2" :user="room.user2" :class="$style.userAvatar"/>
|
||||
<div v-else-if="room.user2Ai">AI</div>
|
||||
<div v-if="room.user2Ready">OK</div>
|
||||
</div>
|
||||
<div class="_panel">
|
||||
<MkAvatar v-if="room.user3" :user="room.user3" :class="$style.userAvatar"/>
|
||||
<div v-else-if="room.user3Ai">AI</div>
|
||||
<div v-if="room.user3Ready">OK</div>
|
||||
</div>
|
||||
<div class="_panel">
|
||||
<MkAvatar v-if="room.user4" :user="room.user4" :class="$style.userAvatar"/>
|
||||
<div v-else-if="room.user4Ai">AI</div>
|
||||
<div v-if="room.user4Ready">OK</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<template #footer>
|
||||
<div :class="$style.footer">
|
||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
||||
<div style="text-align: center;" class="_gaps_s">
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton rounded danger @click="leave">{{ i18n.ts._mahjong.leave }}</MkButton>
|
||||
<MkButton v-if="!isReady" rounded primary @click="ready">{{ i18n.ts._mahjong.ready }}</MkButton>
|
||||
<MkButton v-if="isReady" rounded @click="unready">{{ i18n.ts._mahjong.cancelReady }}</MkButton>
|
||||
</div>
|
||||
</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 Mahjong from 'misskey-mahjong';
|
||||
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';
|
||||
import { useRouter } from '@/global/router/supplier.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps<{
|
||||
room: Misskey.entities.MahjongRoomDetailed;
|
||||
connection: Misskey.ChannelConnection;
|
||||
}>();
|
||||
|
||||
const room = ref<Misskey.entities.MahjongRoomDetailed>(deepClone(props.room));
|
||||
|
||||
const isReady = computed(() => {
|
||||
if (room.value.user1Id === $i.id && room.value.user1Ready) return true;
|
||||
if (room.value.user2Id === $i.id && room.value.user2Ready) return true;
|
||||
if (room.value.user3Id === $i.id && room.value.user3Ready) return true;
|
||||
if (room.value.user4Id === $i.id && room.value.user4Ready) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
async function leave() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.areYouSure,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
props.connection.send('leave', {});
|
||||
|
||||
router.push('/mahjong');
|
||||
}
|
||||
|
||||
function ready() {
|
||||
props.connection.send('ready', true);
|
||||
}
|
||||
|
||||
function unready() {
|
||||
props.connection.send('ready', false);
|
||||
}
|
||||
|
||||
function onChangeReadyStates(states) {
|
||||
room.value.user1Ready = states.user1;
|
||||
room.value.user2Ready = states.user2;
|
||||
room.value.user3Ready = states.user3;
|
||||
room.value.user4Ready = states.user4;
|
||||
}
|
||||
|
||||
props.connection.on('changeReadyStates', onChangeReadyStates);
|
||||
|
||||
onUnmounted(() => {
|
||||
props.connection.off('changeReadyStates', onChangeReadyStates);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.userAvatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.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>
|
113
packages/frontend/src/pages/mahjong/room.vue
Normal file
113
packages/frontend/src/pages/mahjong/room.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="room == null || (!room.isEnded && connection == null)"><MkLoading/></div>
|
||||
<RoomSetting v-else-if="!room.isStarted" :room="room" :connection="connection!"/>
|
||||
<RoomGame v-else :room="room" :connection="connection"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import RoomSetting from './room.setting.vue';
|
||||
import RoomGame from './room.game.vue';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import { useRouter } from '@/global/router/supplier.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useInterval } from '@/scripts/use-interval.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps<{
|
||||
roomId: string;
|
||||
}>();
|
||||
|
||||
const room = shallowRef<Misskey.entities.MahjongRoomDetailed | null>(null);
|
||||
const connection = shallowRef<Misskey.ChannelConnection | null>(null);
|
||||
const shareWhenStart = ref(false);
|
||||
|
||||
watch(() => props.roomId, () => {
|
||||
fetchGame();
|
||||
});
|
||||
|
||||
function start(_room: Misskey.entities.MahjongRoomDetailed) {
|
||||
if (room.value?.isStarted) return;
|
||||
|
||||
room.value = _room;
|
||||
}
|
||||
|
||||
async function fetchGame() {
|
||||
const _room = await misskeyApi('mahjong/show-room', {
|
||||
roomId: props.roomId,
|
||||
});
|
||||
|
||||
room.value = _room;
|
||||
shareWhenStart.value = false;
|
||||
|
||||
if (connection.value) {
|
||||
connection.value.dispose();
|
||||
}
|
||||
if (!room.value.isEnded) {
|
||||
connection.value = useStream().useChannel('mahjongRoom', {
|
||||
roomId: room.value.id,
|
||||
});
|
||||
connection.value.on('started', x => {
|
||||
start(x.room);
|
||||
});
|
||||
connection.value.on('canceled', x => {
|
||||
connection.value?.dispose();
|
||||
|
||||
if (x.userId !== $i.id) {
|
||||
os.alert({
|
||||
type: 'warning',
|
||||
text: i18n.ts._mahjong.roomCanceled,
|
||||
});
|
||||
router.push('/mahjong');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 通信を取りこぼした場合の救済
|
||||
useInterval(async () => {
|
||||
if (room.value == null) return;
|
||||
if (room.value.isStarted) return;
|
||||
|
||||
const _room = await misskeyApi('mahjong/show-room', {
|
||||
roomId: props.roomId,
|
||||
});
|
||||
|
||||
if (_room.isStarted) {
|
||||
start(_room);
|
||||
} else {
|
||||
room.value = _room;
|
||||
}
|
||||
}, 1000 * 10, {
|
||||
immediate: false,
|
||||
afterMounted: true,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
fetchGame();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (connection.value) {
|
||||
connection.value.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: i18n.ts._mahjong.mahjong,
|
||||
icon: 'ti ti-device-roompad',
|
||||
})));
|
||||
</script>
|
|
@ -130,7 +130,7 @@ export function getConfig(): UserConfig {
|
|||
|
||||
// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
|
||||
commonjsOptions: {
|
||||
include: [/misskey-js/, /misskey-reversi/, /misskey-bubble-game/, /node_modules/],
|
||||
include: [/misskey-js/, /misskey-reversi/, /misskey-bubble-game/, /misskey-mahjong/, /node_modules/],
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -1635,6 +1635,11 @@ declare namespace entities {
|
|||
ReversiSurrenderRequest,
|
||||
ReversiVerifyRequest,
|
||||
ReversiVerifyResponse,
|
||||
MahjongCreateRoomResponse,
|
||||
MahjongJoinRoomRequest,
|
||||
MahjongJoinRoomResponse,
|
||||
MahjongShowRoomRequest,
|
||||
MahjongShowRoomResponse,
|
||||
Error_2 as Error,
|
||||
UserLite,
|
||||
UserDetailedNotMeOnly,
|
||||
|
@ -1673,7 +1678,8 @@ declare namespace entities {
|
|||
RoleLite,
|
||||
Role,
|
||||
ReversiGameLite,
|
||||
ReversiGameDetailed
|
||||
ReversiGameDetailed,
|
||||
MahjongRoomDetailed
|
||||
}
|
||||
}
|
||||
export { entities }
|
||||
|
@ -2169,6 +2175,24 @@ type IWebhooksShowResponse = operations['i/webhooks/show']['responses']['200']['
|
|||
// @public (undocumented)
|
||||
type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongCreateRoomResponse = operations['mahjong/create-room']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongJoinRoomRequest = operations['mahjong/join-room']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongJoinRoomResponse = operations['mahjong/join-room']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongRoomDetailed = components['schemas']['MahjongRoomDetailed'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongShowRoomRequest = operations['mahjong/show-room']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MahjongShowRoomResponse = operations['mahjong/show-room']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type MeDetailed = components['schemas']['MeDetailed'];
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* version: 2024.2.0-beta.6
|
||||
* generatedAt: 2024-01-24T07:32:10.455Z
|
||||
* version: 2024.2.0-beta.7
|
||||
* generatedAt: 2024-01-26T05:23:04.911Z
|
||||
*/
|
||||
|
||||
import type { SwitchCaseResponseType } from '../api.js';
|
||||
|
@ -4084,5 +4084,38 @@ 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 'mahjong/create-room', 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 'mahjong/join-room', 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 'mahjong/show-room', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* version: 2024.2.0-beta.6
|
||||
* generatedAt: 2024-01-24T07:32:10.453Z
|
||||
* version: 2024.2.0-beta.7
|
||||
* generatedAt: 2024-01-26T05:23:04.909Z
|
||||
*/
|
||||
|
||||
import type {
|
||||
|
@ -556,6 +556,11 @@ import type {
|
|||
ReversiSurrenderRequest,
|
||||
ReversiVerifyRequest,
|
||||
ReversiVerifyResponse,
|
||||
MahjongCreateRoomResponse,
|
||||
MahjongJoinRoomRequest,
|
||||
MahjongJoinRoomResponse,
|
||||
MahjongShowRoomRequest,
|
||||
MahjongShowRoomResponse,
|
||||
} from './entities.js';
|
||||
|
||||
export type Endpoints = {
|
||||
|
@ -926,4 +931,7 @@ export type Endpoints = {
|
|||
'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse };
|
||||
'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse };
|
||||
'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse };
|
||||
'mahjong/create-room': { req: EmptyRequest; res: MahjongCreateRoomResponse };
|
||||
'mahjong/join-room': { req: MahjongJoinRoomRequest; res: MahjongJoinRoomResponse };
|
||||
'mahjong/show-room': { req: MahjongShowRoomRequest; res: MahjongShowRoomResponse };
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* version: 2024.2.0-beta.6
|
||||
* generatedAt: 2024-01-24T07:32:10.452Z
|
||||
* version: 2024.2.0-beta.7
|
||||
* generatedAt: 2024-01-26T05:23:04.908Z
|
||||
*/
|
||||
|
||||
import { operations } from './types.js';
|
||||
|
@ -558,3 +558,8 @@ export type ReversiShowGameResponse = operations['reversi/show-game']['responses
|
|||
export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
|
||||
export type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json'];
|
||||
export type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json'];
|
||||
export type MahjongCreateRoomResponse = operations['mahjong/create-room']['responses']['200']['content']['application/json'];
|
||||
export type MahjongJoinRoomRequest = operations['mahjong/join-room']['requestBody']['content']['application/json'];
|
||||
export type MahjongJoinRoomResponse = operations['mahjong/join-room']['responses']['200']['content']['application/json'];
|
||||
export type MahjongShowRoomRequest = operations['mahjong/show-room']['requestBody']['content']['application/json'];
|
||||
export type MahjongShowRoomResponse = operations['mahjong/show-room']['responses']['200']['content']['application/json'];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* version: 2024.2.0-beta.6
|
||||
* generatedAt: 2024-01-24T07:32:10.450Z
|
||||
* version: 2024.2.0-beta.7
|
||||
* generatedAt: 2024-01-26T05:23:04.907Z
|
||||
*/
|
||||
|
||||
import { components } from './types.js';
|
||||
|
@ -43,3 +43,4 @@ export type RoleLite = components['schemas']['RoleLite'];
|
|||
export type Role = components['schemas']['Role'];
|
||||
export type ReversiGameLite = components['schemas']['ReversiGameLite'];
|
||||
export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
|
||||
export type MahjongRoomDetailed = components['schemas']['MahjongRoomDetailed'];
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||
|
||||
/*
|
||||
* version: 2024.2.0-beta.6
|
||||
* generatedAt: 2024-01-24T07:32:10.370Z
|
||||
* version: 2024.2.0-beta.7
|
||||
* generatedAt: 2024-01-26T05:23:04.825Z
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -3535,6 +3535,33 @@ export type paths = {
|
|||
*/
|
||||
post: operations['reversi/verify'];
|
||||
};
|
||||
'/mahjong/create-room': {
|
||||
/**
|
||||
* mahjong/create-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:account*
|
||||
*/
|
||||
post: operations['mahjong/create-room'];
|
||||
};
|
||||
'/mahjong/join-room': {
|
||||
/**
|
||||
* mahjong/join-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:account*
|
||||
*/
|
||||
post: operations['mahjong/join-room'];
|
||||
};
|
||||
'/mahjong/show-room': {
|
||||
/**
|
||||
* mahjong/show-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||
*/
|
||||
post: operations['mahjong/show-room'];
|
||||
};
|
||||
};
|
||||
|
||||
export type webhooks = Record<string, never>;
|
||||
|
@ -4537,6 +4564,38 @@ export type components = {
|
|||
logs: unknown[][];
|
||||
map: string[];
|
||||
};
|
||||
MahjongRoomDetailed: {
|
||||
/** Format: id */
|
||||
id: string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
startedAt: string | null;
|
||||
/** Format: date-time */
|
||||
endedAt: string | null;
|
||||
isStarted: boolean;
|
||||
isEnded: boolean;
|
||||
/** Format: id */
|
||||
user1Id: string;
|
||||
/** Format: id */
|
||||
user2Id: string;
|
||||
/** Format: id */
|
||||
user3Id: string;
|
||||
/** Format: id */
|
||||
user4Id: string;
|
||||
user1: components['schemas']['User'];
|
||||
user2: components['schemas']['User'];
|
||||
user3: components['schemas']['User'];
|
||||
user4: components['schemas']['User'];
|
||||
user1Ai: boolean;
|
||||
user2Ai: boolean;
|
||||
user3Ai: boolean;
|
||||
user4Ai: boolean;
|
||||
user1Ready: boolean;
|
||||
user2Ready: boolean;
|
||||
user3Ready: boolean;
|
||||
user4Ready: boolean;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
|
@ -26057,5 +26116,159 @@ export type operations = {
|
|||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* mahjong/create-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:account*
|
||||
*/
|
||||
'mahjong/create-room': {
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['MahjongRoomDetailed'];
|
||||
};
|
||||
};
|
||||
/** @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'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* mahjong/join-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:account*
|
||||
*/
|
||||
'mahjong/join-room': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
roomId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['MahjongRoomDetailed'];
|
||||
};
|
||||
};
|
||||
/** @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'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* mahjong/show-room
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:account*
|
||||
*/
|
||||
'mahjong/show-room': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
roomId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['MahjongRoomDetailed'];
|
||||
};
|
||||
};
|
||||
/** @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'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
7
packages/misskey-mahjong/.eslintignore
Normal file
7
packages/misskey-mahjong/.eslintignore
Normal file
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
/built
|
||||
/coverage
|
||||
/.eslintrc.js
|
||||
/jest.config.ts
|
||||
/test
|
||||
/test-d
|
10
packages/misskey-mahjong/.eslintrc.cjs
Normal file
10
packages/misskey-mahjong/.eslintrc.cjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
};
|
31
packages/misskey-mahjong/build.js
Normal file
31
packages/misskey-mahjong/build.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { build } from "esbuild";
|
||||
import { globSync } from "glob";
|
||||
|
||||
const entryPoints = globSync("./src/**/**.{ts,tsx}");
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints,
|
||||
minify: true,
|
||||
outdir: "./built/esm",
|
||||
target: "es2022",
|
||||
platform: "browser",
|
||||
format: "esm",
|
||||
};
|
||||
|
||||
if (process.env.WATCH === "true") {
|
||||
options.watch = {
|
||||
onRebuild(error, result) {
|
||||
if (error) {
|
||||
console.error("watch build failed:", error);
|
||||
} else {
|
||||
console.log("watch build succeeded:", result);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
build(options).catch((err) => {
|
||||
process.stderr.write(err.stderr);
|
||||
process.exit(1);
|
||||
});
|
43
packages/misskey-mahjong/package.json
Normal file
43
packages/misskey-mahjong/package.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "misskey-mahjong",
|
||||
"version": "0.0.1",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./built/esm/index.js",
|
||||
"types": "./built/dts/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./built/esm/*",
|
||||
"types": "./built/dts/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./build.js",
|
||||
"build:tsc": "npm run tsc",
|
||||
"tsc": "npm run tsc-esm && npm run tsc-dts",
|
||||
"tsc-esm": "tsc --outDir built/esm",
|
||||
"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
|
||||
"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
|
||||
"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.18.1",
|
||||
"@typescript-eslint/parser": "6.18.1",
|
||||
"eslint": "8.56.0",
|
||||
"nodemon": "3.0.2",
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"files": [
|
||||
"built"
|
||||
],
|
||||
"dependencies": {
|
||||
"crc-32": "1.2.2",
|
||||
"esbuild": "0.19.11",
|
||||
"glob": "10.3.10"
|
||||
}
|
||||
}
|
523
packages/misskey-mahjong/src/engine.ts
Normal file
523
packages/misskey-mahjong/src/engine.ts
Normal file
|
@ -0,0 +1,523 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import CRC32 from 'crc-32';
|
||||
|
||||
export const TILE_TYPES = [
|
||||
'bamboo1',
|
||||
'bamboo2',
|
||||
'bamboo3',
|
||||
'bamboo4',
|
||||
'bamboo5',
|
||||
'bamboo6',
|
||||
'bamboo7',
|
||||
'bamboo8',
|
||||
'bamboo9',
|
||||
'character1',
|
||||
'character2',
|
||||
'character3',
|
||||
'character4',
|
||||
'character5',
|
||||
'character6',
|
||||
'character7',
|
||||
'character8',
|
||||
'character9',
|
||||
'circle1',
|
||||
'circle2',
|
||||
'circle3',
|
||||
'circle4',
|
||||
'circle5',
|
||||
'circle6',
|
||||
'circle7',
|
||||
'circle8',
|
||||
'circle9',
|
||||
'wind-east',
|
||||
'wind-south',
|
||||
'wind-west',
|
||||
'wind-north',
|
||||
'dragon-red',
|
||||
'dragon-green',
|
||||
'dragon-white',
|
||||
] as const;
|
||||
|
||||
export type Tile = typeof TILE_TYPES[number];
|
||||
|
||||
export function isTile(tile: string): tile is Tile {
|
||||
return TILE_TYPES.includes(tile as Tile);
|
||||
}
|
||||
|
||||
export type House = 'e' | 's' | 'w' | 'n';
|
||||
|
||||
export type MasterState = {
|
||||
user1House: House;
|
||||
user2House: House;
|
||||
user3House: House;
|
||||
user4House: House;
|
||||
tiles: Tile[];
|
||||
eHandTiles: Tile[];
|
||||
sHandTiles: Tile[];
|
||||
wHandTiles: Tile[];
|
||||
nHandTiles: Tile[];
|
||||
eHoTiles: Tile[];
|
||||
sHoTiles: Tile[];
|
||||
wHoTiles: Tile[];
|
||||
nHoTiles: Tile[];
|
||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
eRiichi: boolean;
|
||||
sRiichi: boolean;
|
||||
wRiichi: boolean;
|
||||
nRiichi: boolean;
|
||||
ePoints: number;
|
||||
sPoints: number;
|
||||
wPoints: number;
|
||||
nPoints: number;
|
||||
turn: House | null;
|
||||
ponAsking: {
|
||||
source: House;
|
||||
target: House;
|
||||
} | null;
|
||||
ciiAsking: {
|
||||
source: House;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export class Common {
|
||||
public static nextHouse(house: House): House {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
return 's';
|
||||
case 's':
|
||||
return 'w';
|
||||
case 'w':
|
||||
return 'n';
|
||||
case 'n':
|
||||
return 'e';
|
||||
}
|
||||
}
|
||||
|
||||
public static checkYaku(tiles: Tile[]) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class MasterGameEngine {
|
||||
public state: MasterState;
|
||||
|
||||
constructor(state: MasterState) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public static createInitialState(): MasterState {
|
||||
const tiles = TILE_TYPES.slice();
|
||||
tiles.sort(() => Math.random() - 0.5);
|
||||
|
||||
const eHandTiles = tiles.splice(0, 14);
|
||||
const sHandTiles = tiles.splice(0, 13);
|
||||
const wHandTiles = tiles.splice(0, 13);
|
||||
const nHandTiles = tiles.splice(0, 13);
|
||||
|
||||
return {
|
||||
user1House: 'e',
|
||||
user2House: 's',
|
||||
user3House: 'w',
|
||||
user4House: 'n',
|
||||
tiles,
|
||||
eHandTiles,
|
||||
sHandTiles,
|
||||
wHandTiles,
|
||||
nHandTiles,
|
||||
eHoTiles: [],
|
||||
sHoTiles: [],
|
||||
wHoTiles: [],
|
||||
nHoTiles: [],
|
||||
ePonnedTiles: [],
|
||||
sPonnedTiles: [],
|
||||
wPonnedTiles: [],
|
||||
nPonnedTiles: [],
|
||||
eCiiedTiles: [],
|
||||
sCiiedTiles: [],
|
||||
wCiiedTiles: [],
|
||||
nCiiedTiles: [],
|
||||
eRiichi: false,
|
||||
sRiichi: false,
|
||||
wRiichi: false,
|
||||
nRiichi: false,
|
||||
ePoints: 25000,
|
||||
sPoints: 25000,
|
||||
wPoints: 25000,
|
||||
nPoints: 25000,
|
||||
turn: 'e',
|
||||
ponAsking: null,
|
||||
ciiAsking: null,
|
||||
};
|
||||
}
|
||||
|
||||
private ツモ(): Tile {
|
||||
const tile = this.state.tiles.pop();
|
||||
switch (this.state.turn) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
public op_dahai(house: House, tile: Tile) {
|
||||
if (this.state.turn !== house) throw new Error('Not your turn');
|
||||
|
||||
switch (house) {
|
||||
case 'e':
|
||||
if (!this.state.eHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.eHandTiles.splice(this.state.eHandTiles.indexOf(tile), 1);
|
||||
this.state.eHoTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
if (!this.state.sHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.sHandTiles.splice(this.state.sHandTiles.indexOf(tile), 1);
|
||||
this.state.sHoTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
if (!this.state.wHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.wHandTiles.splice(this.state.wHandTiles.indexOf(tile), 1);
|
||||
this.state.wHoTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
if (!this.state.nHandTiles.includes(tile)) throw new Error('Invalid tile');
|
||||
this.state.nHandTiles.splice(this.state.nHandTiles.indexOf(tile), 1);
|
||||
this.state.nHoTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
|
||||
let canPonHouse: House | null = null;
|
||||
if (house === 'e') {
|
||||
canPonHouse = this.canPon('s', tile) ? 's' : this.canPon('w', tile) ? 'w' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 's') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('w', tile) ? 'w' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 'w') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('s', tile) ? 's' : this.canPon('n', tile) ? 'n' : null;
|
||||
} else if (house === 'n') {
|
||||
canPonHouse = this.canPon('e', tile) ? 'e' : this.canPon('s', tile) ? 's' : this.canPon('w', tile) ? 'w' : null;
|
||||
}
|
||||
|
||||
// TODO
|
||||
//let canCii: boolean = false;
|
||||
//if (house === 'e') {
|
||||
// canCii = this.state.sHandTiles...
|
||||
//} else if (house === 's') {
|
||||
// canCii = this.state.wHandTiles...
|
||||
//} else if (house === 'w') {
|
||||
// canCii = this.state.nHandTiles...
|
||||
//} else if (house === 'n') {
|
||||
// canCii = this.state.eHandTiles...
|
||||
//}
|
||||
|
||||
if (canPonHouse) {
|
||||
this.state.ponAsking = {
|
||||
source: house,
|
||||
target: canPonHouse,
|
||||
};
|
||||
return {
|
||||
canPonHouse: canPonHouse,
|
||||
};
|
||||
}
|
||||
|
||||
this.state.turn = Common.nextHouse(house);
|
||||
|
||||
const tsumoTile = this.ツモ();
|
||||
|
||||
return {
|
||||
tsumo: tsumoTile,
|
||||
};
|
||||
}
|
||||
|
||||
public op_pon(house: House) {
|
||||
if (this.state.ponAsking == null) throw new Error('No one is asking for pon');
|
||||
if (this.state.ponAsking.target !== house) throw new Error('Not you');
|
||||
|
||||
const source = this.state.ponAsking.source;
|
||||
const target = this.state.ponAsking.target;
|
||||
this.state.ponAsking = null;
|
||||
|
||||
let tile: Tile;
|
||||
|
||||
switch (source) {
|
||||
case 'e':
|
||||
tile = this.state.eHoTiles.pop();
|
||||
break;
|
||||
case 's':
|
||||
tile = this.state.sHoTiles.pop();
|
||||
break;
|
||||
case 'w':
|
||||
tile = this.state.wHoTiles.pop();
|
||||
break;
|
||||
case 'n':
|
||||
tile = this.state.nHoTiles.pop();
|
||||
break;
|
||||
default: throw new Error('Invalid source');
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case 'e':
|
||||
this.state.ePonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 's':
|
||||
this.state.sPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
}
|
||||
|
||||
this.state.turn = target;
|
||||
}
|
||||
|
||||
public op_noOnePon() {
|
||||
if (this.state.ponAsking == null) throw new Error('No one is asking for pon');
|
||||
|
||||
this.state.ponAsking = null;
|
||||
this.state.turn = Common.nextHouse(this.state.turn);
|
||||
|
||||
const tile = this.ツモ();
|
||||
|
||||
return {
|
||||
house: this.state.turn,
|
||||
tile,
|
||||
};
|
||||
}
|
||||
|
||||
private canPon(house: House, tile: Tile): boolean {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
return this.state.eHandTiles.filter(t => t === tile).length === 2;
|
||||
case 's':
|
||||
return this.state.sHandTiles.filter(t => t === tile).length === 2;
|
||||
case 'w':
|
||||
return this.state.wHandTiles.filter(t => t === tile).length === 2;
|
||||
case 'n':
|
||||
return this.state.nHandTiles.filter(t => t === tile).length === 2;
|
||||
}
|
||||
}
|
||||
|
||||
public calcCrc32ForUser1(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser2(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser3(): number {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public calcCrc32ForUser4(): number {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
export type PlayerState = {
|
||||
user1House: House;
|
||||
user2House: House;
|
||||
user3House: House;
|
||||
user4House: House;
|
||||
tilesCount: number;
|
||||
eHandTiles: Tile[] | null[];
|
||||
sHandTiles: Tile[] | null[];
|
||||
wHandTiles: Tile[] | null[];
|
||||
nHandTiles: Tile[] | null[];
|
||||
eHoTiles: Tile[];
|
||||
sHoTiles: Tile[];
|
||||
wHoTiles: Tile[];
|
||||
nHoTiles: Tile[];
|
||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
||||
eRiichi: boolean;
|
||||
sRiichi: boolean;
|
||||
wRiichi: boolean;
|
||||
nRiichi: boolean;
|
||||
ePoints: number;
|
||||
sPoints: number;
|
||||
wPoints: number;
|
||||
nPoints: number;
|
||||
latestDahaiedTile: Tile | null;
|
||||
turn: House | null;
|
||||
};
|
||||
|
||||
export class PlayerGameEngine {
|
||||
/**
|
||||
* このエラーが発生したときはdesyncが疑われる
|
||||
*/
|
||||
public static InvalidOperationError = class extends Error {};
|
||||
|
||||
private myUserNumber: 1 | 2 | 3 | 4;
|
||||
public state: PlayerState;
|
||||
|
||||
constructor(myUserNumber: PlayerGameEngine['myUserNumber'], state: PlayerState) {
|
||||
this.myUserNumber = myUserNumber;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public get myHouse(): House {
|
||||
switch (this.myUserNumber) {
|
||||
case 1: return this.state.user1House;
|
||||
case 2: return this.state.user2House;
|
||||
case 3: return this.state.user3House;
|
||||
case 4: return this.state.user4House;
|
||||
}
|
||||
}
|
||||
|
||||
public get myHandTiles(): Tile[] {
|
||||
switch (this.myHouse) {
|
||||
case 'e': return this.state.eHandTiles as Tile[];
|
||||
case 's': return this.state.sHandTiles as Tile[];
|
||||
case 'w': return this.state.wHandTiles as Tile[];
|
||||
case 'n': return this.state.nHandTiles as Tile[];
|
||||
}
|
||||
}
|
||||
|
||||
public get myHoTiles(): Tile[] {
|
||||
switch (this.myHouse) {
|
||||
case 'e': return this.state.eHoTiles;
|
||||
case 's': return this.state.sHoTiles;
|
||||
case 'w': return this.state.wHoTiles;
|
||||
case 'n': return this.state.nHoTiles;
|
||||
}
|
||||
}
|
||||
|
||||
public op_tsumo(house: House, tile: Tile) {
|
||||
if (house === this.myHouse) {
|
||||
this.myHandTiles.push(tile);
|
||||
} else {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.push(null);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.push(null);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.push(null);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.push(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public op_dahai(house: House, tile: Tile) {
|
||||
if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError();
|
||||
|
||||
if (house === this.myHouse) {
|
||||
this.myHandTiles.splice(this.myHandTiles.indexOf(tile), 1);
|
||||
this.myHoTiles.push(tile);
|
||||
} else {
|
||||
switch (house) {
|
||||
case 'e':
|
||||
this.state.eHandTiles.pop();
|
||||
this.state.eHoTiles.push(tile);
|
||||
break;
|
||||
case 's':
|
||||
this.state.sHandTiles.pop();
|
||||
this.state.sHoTiles.push(tile);
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wHandTiles.pop();
|
||||
this.state.wHoTiles.push(tile);
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nHandTiles.pop();
|
||||
this.state.nHoTiles.push(tile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (house === this.myHouse) {
|
||||
this.state.turn = null;
|
||||
} else {
|
||||
const canPon = this.myHandTiles.filter(t => t === tile).length === 2;
|
||||
|
||||
// TODO: canCii
|
||||
|
||||
return {
|
||||
canPon,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public op_pon(source: House, target: House) {
|
||||
let tile: Tile;
|
||||
|
||||
switch (source) {
|
||||
case 'e': {
|
||||
const lastTile = this.state.eHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
const lastTile = this.state.sHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 'w': {
|
||||
const lastTile = this.state.wHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
const lastTile = this.state.nHoTiles.pop();
|
||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||
tile = lastTile;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Invalid source');
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case 'e':
|
||||
this.state.ePonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 's':
|
||||
this.state.sPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'w':
|
||||
this.state.wPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
case 'n':
|
||||
this.state.nPonnedTiles.push({ tile, from: source });
|
||||
break;
|
||||
}
|
||||
|
||||
this.state.turn = target;
|
||||
}
|
||||
}
|
7
packages/misskey-mahjong/src/index.ts
Normal file
7
packages/misskey-mahjong/src/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export * as Engine from './engine.js';
|
||||
export * as Serializer from './serializer.js';
|
114
packages/misskey-mahjong/src/serializer.ts
Normal file
114
packages/misskey-mahjong/src/serializer.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Tile } from './engine.js';
|
||||
|
||||
export type Log = {
|
||||
time: number;
|
||||
player: 1 | 2 | 3 | 4;
|
||||
operation: 'dahai';
|
||||
tile: string;
|
||||
};
|
||||
|
||||
export type SerializedLog = number[];
|
||||
|
||||
export const TILE_MAP: Record<Tile, number> = {
|
||||
'bamboo1': 1,
|
||||
'bamboo2': 2,
|
||||
'bamboo3': 3,
|
||||
'bamboo4': 4,
|
||||
'bamboo5': 5,
|
||||
'bamboo6': 6,
|
||||
'bamboo7': 7,
|
||||
'bamboo8': 8,
|
||||
'bamboo9': 9,
|
||||
'character1': 10,
|
||||
'character2': 11,
|
||||
'character3': 12,
|
||||
'character4': 13,
|
||||
'character5': 14,
|
||||
'character6': 15,
|
||||
'character7': 16,
|
||||
'character8': 17,
|
||||
'character9': 18,
|
||||
'circle1': 19,
|
||||
'circle2': 20,
|
||||
'circle3': 21,
|
||||
'circle4': 22,
|
||||
'circle5': 23,
|
||||
'circle6': 24,
|
||||
'circle7': 25,
|
||||
'circle8': 26,
|
||||
'circle9': 27,
|
||||
'wind-east': 28,
|
||||
'wind-south': 29,
|
||||
'wind-west': 30,
|
||||
'wind-north': 31,
|
||||
'dragon-red': 32,
|
||||
'dragon-green': 33,
|
||||
'dragon-white': 34,
|
||||
};
|
||||
|
||||
export function serializeTile(tile: Tile): number {
|
||||
return TILE_MAP[tile];
|
||||
}
|
||||
|
||||
export function deserializeTile(tile: number): Tile {
|
||||
return Object.keys(TILE_MAP).find(key => TILE_MAP[key as Tile] === tile) as Tile;
|
||||
}
|
||||
|
||||
export function serializeLogs(logs: Log[]) {
|
||||
const _logs: number[][] = [];
|
||||
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
const log = logs[i];
|
||||
const timeDelta = i === 0 ? log.time : log.time - logs[i - 1].time;
|
||||
|
||||
switch (log.operation) {
|
||||
case 'dahai':
|
||||
_logs.push([timeDelta, log.player, 1, serializeTile(log.tile)]);
|
||||
break;
|
||||
//case 'surrender':
|
||||
// _logs.push([timeDelta, log.player, 1]);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
return _logs;
|
||||
}
|
||||
|
||||
export function deserializeLogs(logs: SerializedLog[]) {
|
||||
const _logs: Log[] = [];
|
||||
|
||||
let time = 0;
|
||||
|
||||
for (const log of logs) {
|
||||
const timeDelta = log[0];
|
||||
time += timeDelta;
|
||||
|
||||
const player = log[1];
|
||||
const operation = log[2];
|
||||
|
||||
switch (operation) {
|
||||
case 1:
|
||||
_logs.push({
|
||||
time,
|
||||
player: player,
|
||||
operation: 'dahai',
|
||||
tile: log[3],
|
||||
});
|
||||
break;
|
||||
//case 1:
|
||||
// _logs.push({
|
||||
// time,
|
||||
// player: player === 1,
|
||||
// operation: 'surrender',
|
||||
// });
|
||||
// break;
|
||||
}
|
||||
}
|
||||
|
||||
return _logs;
|
||||
}
|
33
packages/misskey-mahjong/tsconfig.json
Normal file
33
packages/misskey-mahjong/tsconfig.json
Normal file
|
@ -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/**/*"
|
||||
]
|
||||
}
|
179
pnpm-lock.yaml
179
pnpm-lock.yaml
|
@ -263,6 +263,9 @@ importers:
|
|||
misskey-js:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-js
|
||||
misskey-mahjong:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-mahjong
|
||||
misskey-reversi:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-reversi
|
||||
|
@ -778,6 +781,9 @@ importers:
|
|||
misskey-js:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-js
|
||||
misskey-mahjong:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-mahjong
|
||||
misskey-reversi:
|
||||
specifier: workspace:*
|
||||
version: link:../misskey-reversi
|
||||
|
@ -831,7 +837,7 @@ importers:
|
|||
version: 1.7.2(vue@3.4.15)
|
||||
vite:
|
||||
specifier: 5.0.12
|
||||
version: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
version: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
vue:
|
||||
specifier: 3.4.15
|
||||
version: 3.4.15(typescript@5.3.3)
|
||||
|
@ -1009,7 +1015,7 @@ importers:
|
|||
version: 1.0.3
|
||||
vitest:
|
||||
specifier: 0.34.6
|
||||
version: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)
|
||||
version: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0)
|
||||
vitest-fetch-mock:
|
||||
specifier: 0.2.2
|
||||
version: 0.2.2(vitest@0.34.6)
|
||||
|
@ -1166,6 +1172,40 @@ importers:
|
|||
specifier: 5.3.3
|
||||
version: 5.3.3
|
||||
|
||||
packages/misskey-mahjong:
|
||||
dependencies:
|
||||
crc-32:
|
||||
specifier: 1.2.2
|
||||
version: 1.2.2
|
||||
esbuild:
|
||||
specifier: 0.19.11
|
||||
version: 0.19.11
|
||||
glob:
|
||||
specifier: 10.3.10
|
||||
version: 10.3.10
|
||||
devDependencies:
|
||||
'@misskey-dev/eslint-plugin':
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0(@typescript-eslint/eslint-plugin@6.18.1)(@typescript-eslint/parser@6.18.1)(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.18.1
|
||||
version: 6.18.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 6.18.1
|
||||
version: 6.18.1(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint:
|
||||
specifier: 8.56.0
|
||||
version: 8.56.0
|
||||
nodemon:
|
||||
specifier: 3.0.2
|
||||
version: 3.0.2
|
||||
typescript:
|
||||
specifier: 5.3.3
|
||||
version: 5.3.3
|
||||
|
||||
packages/misskey-reversi:
|
||||
dependencies:
|
||||
crc-32:
|
||||
|
@ -1906,7 +1946,7 @@ packages:
|
|||
'@babel/traverse': 7.22.11
|
||||
'@babel/types': 7.22.17
|
||||
convert-source-map: 1.9.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
|
@ -1929,7 +1969,7 @@ packages:
|
|||
'@babel/traverse': 7.23.5
|
||||
'@babel/types': 7.23.5
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
|
@ -2031,7 +2071,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@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
|
@ -3430,7 +3470,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@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -3448,7 +3488,7 @@ packages:
|
|||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.5
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -4155,7 +4195,7 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
espree: 9.6.1
|
||||
globals: 13.19.0
|
||||
ignore: 5.2.4
|
||||
|
@ -4172,7 +4212,7 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
espree: 9.6.1
|
||||
globals: 13.19.0
|
||||
ignore: 5.2.4
|
||||
|
@ -4407,7 +4447,7 @@ packages:
|
|||
engines: {node: '>=10.10.0'}
|
||||
dependencies:
|
||||
'@humanwhocodes/object-schema': 2.0.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
minimatch: 3.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -4711,7 +4751,7 @@ packages:
|
|||
magic-string: 0.27.0
|
||||
react-docgen-typescript: 2.2.2(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
dev: true
|
||||
|
||||
/@jridgewell/gen-mapping@0.3.2:
|
||||
|
@ -4735,7 +4775,6 @@ packages:
|
|||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.2
|
||||
'@jridgewell/trace-mapping': 0.3.18
|
||||
dev: false
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.14:
|
||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||
|
@ -6772,7 +6811,7 @@ packages:
|
|||
magic-string: 0.30.5
|
||||
rollup: 3.29.4
|
||||
typescript: 5.3.3
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
@ -6977,7 +7016,7 @@ packages:
|
|||
util: 0.12.5
|
||||
util-deprecate: 1.0.2
|
||||
watchpack: 2.4.0
|
||||
ws: 8.16.0
|
||||
ws: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
|
@ -7146,7 +7185,7 @@ packages:
|
|||
react: 18.2.0
|
||||
react-docgen: 7.0.1
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- '@preact/preset-vite'
|
||||
- encoding
|
||||
|
@ -7272,7 +7311,7 @@ packages:
|
|||
'@storybook/vue3': 7.6.10(vue@3.4.15)
|
||||
'@vitejs/plugin-vue': 4.5.2(vite@5.0.12)(vue@3.4.15)
|
||||
magic-string: 0.30.5
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
vue-docgen-api: 4.64.1(vue@3.4.15)
|
||||
transitivePeerDependencies:
|
||||
- '@preact/preset-vite'
|
||||
|
@ -7772,7 +7811,7 @@ packages:
|
|||
dom-accessibility-api: 0.5.16
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0)
|
||||
dev: true
|
||||
|
||||
/@testing-library/user-event@14.4.3(@testing-library/dom@9.2.0):
|
||||
|
@ -8446,7 +8485,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@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
graphemer: 1.4.0
|
||||
ignore: 5.2.4
|
||||
|
@ -8475,7 +8514,7 @@ packages:
|
|||
'@typescript-eslint/type-utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/visitor-keys': 6.18.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.56.0
|
||||
graphemer: 1.4.0
|
||||
ignore: 5.2.4
|
||||
|
@ -8501,7 +8540,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@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
|
@ -8522,7 +8561,7 @@ packages:
|
|||
'@typescript-eslint/types': 6.18.1
|
||||
'@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
|
||||
'@typescript-eslint/visitor-keys': 6.18.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.56.0
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
|
@ -8557,7 +8596,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@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.53.0
|
||||
ts-api-utils: 1.0.1(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
|
@ -8577,7 +8616,7 @@ packages:
|
|||
dependencies:
|
||||
'@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
|
||||
'@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.56.0
|
||||
ts-api-utils: 1.0.1(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
|
@ -8606,7 +8645,7 @@ packages:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 6.11.0
|
||||
'@typescript-eslint/visitor-keys': 6.11.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.4
|
||||
|
@ -8627,7 +8666,7 @@ packages:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 6.18.1
|
||||
'@typescript-eslint/visitor-keys': 6.18.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.3
|
||||
|
@ -8707,7 +8746,7 @@ packages:
|
|||
'@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.5)
|
||||
magic-string: 0.27.0
|
||||
react-refresh: 0.14.0
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -8719,7 +8758,7 @@ packages:
|
|||
vite: ^4.0.0 || ^5.0.0
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
vue: 3.4.15(typescript@5.3.3)
|
||||
dev: true
|
||||
|
||||
|
@ -8730,7 +8769,7 @@ packages:
|
|||
vite: ^5.0.0
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
vue: 3.4.15(typescript@5.3.3)
|
||||
dev: false
|
||||
|
||||
|
@ -8750,7 +8789,7 @@ packages:
|
|||
std-env: 3.7.0
|
||||
test-exclude: 6.0.0
|
||||
v8-to-istanbul: 9.2.0
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -9091,7 +9130,7 @@ packages:
|
|||
engines: {node: '>= 6.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
@ -9099,7 +9138,7 @@ packages:
|
|||
resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -9485,7 +9524,7 @@ packages:
|
|||
resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==}
|
||||
dependencies:
|
||||
archy: 1.0.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
fastq: 1.15.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -9889,7 +9928,6 @@ packages:
|
|||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.6.0
|
||||
dev: false
|
||||
|
||||
/bullmq@5.1.4:
|
||||
resolution: {integrity: sha512-j/AjaPc8BhyrH7b2MyZpi4cUtGH8TJTxonZUmXEefmKU8z5DcldzmlXPief0P4+qvN0A7qwWZH3n0F+GsWgQkg==}
|
||||
|
@ -10434,7 +10472,6 @@ packages:
|
|||
|
||||
/commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
dev: false
|
||||
|
||||
/commander@6.2.1:
|
||||
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
|
||||
|
@ -10931,6 +10968,7 @@ 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==}
|
||||
|
@ -10943,7 +10981,6 @@ 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==}
|
||||
|
@ -11160,7 +11197,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
address: 1.2.2
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -11484,7 +11521,7 @@ packages:
|
|||
peerDependencies:
|
||||
esbuild: '>=0.12 <1'
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
esbuild: 0.18.20
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -11793,7 +11830,7 @@ packages:
|
|||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.3
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.2.2
|
||||
|
@ -11840,7 +11877,7 @@ packages:
|
|||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.3
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.2.2
|
||||
|
@ -12471,7 +12508,7 @@ packages:
|
|||
debug:
|
||||
optional: true
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
|
||||
/for-each@0.3.3:
|
||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||
|
@ -13027,6 +13064,7 @@ 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==}
|
||||
|
@ -13164,7 +13202,7 @@ packages:
|
|||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -13224,7 +13262,7 @@ packages:
|
|||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
agent-base: 5.1.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -13234,7 +13272,7 @@ packages:
|
|||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
@ -13243,7 +13281,7 @@ packages:
|
|||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -13403,7 +13441,7 @@ packages:
|
|||
dependencies:
|
||||
'@ioredis/commands': 1.2.0
|
||||
cluster-key-slot: 1.1.2
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
denque: 2.1.0
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.isarguments: 3.1.0
|
||||
|
@ -13849,7 +13887,7 @@ packages:
|
|||
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
|
@ -15624,7 +15662,6 @@ packages:
|
|||
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/node-gyp@10.0.1:
|
||||
resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==}
|
||||
|
@ -17159,7 +17196,7 @@ packages:
|
|||
engines: {node: '>=8.16.0'}
|
||||
dependencies:
|
||||
'@types/mime-types': 2.1.4
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
extract-zip: 1.7.0
|
||||
https-proxy-agent: 4.0.0
|
||||
mime: 2.6.0
|
||||
|
@ -18159,7 +18196,7 @@ packages:
|
|||
dependencies:
|
||||
'@hapi/hoek': 10.0.1
|
||||
'@hapi/wreck': 18.0.1
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
joi: 17.7.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -18359,7 +18396,7 @@ packages:
|
|||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
socks: 2.7.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -18512,7 +18549,7 @@ packages:
|
|||
arg: 5.0.2
|
||||
bluebird: 3.7.2
|
||||
check-more-types: 2.24.0
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
execa: 5.1.1
|
||||
lazy-ass: 1.6.0
|
||||
ps-tree: 1.2.0
|
||||
|
@ -18770,6 +18807,7 @@ 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==}
|
||||
|
@ -18932,7 +18970,6 @@ packages:
|
|||
acorn: 8.11.3
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: false
|
||||
|
||||
/test-exclude@6.0.0:
|
||||
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
|
||||
|
@ -19391,7 +19428,7 @@ packages:
|
|||
chalk: 4.1.2
|
||||
cli-highlight: 2.1.11
|
||||
dayjs: 1.11.10
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
dotenv: 16.0.3
|
||||
glob: 10.3.10
|
||||
ioredis: 5.3.2
|
||||
|
@ -19654,7 +19691,6 @@ packages:
|
|||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.6.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
@ -19746,17 +19782,17 @@ packages:
|
|||
core-util-is: 1.0.2
|
||||
extsprintf: 1.3.0
|
||||
|
||||
/vite-node@0.34.6(@types/node@20.11.5)(sass@1.70.0):
|
||||
/vite-node@0.34.6(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0):
|
||||
resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
|
||||
engines: {node: '>=v14.18.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
mlly: 1.5.0
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.0.0
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
|
@ -19772,7 +19808,7 @@ packages:
|
|||
resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
|
||||
dev: true
|
||||
|
||||
/vite@5.0.12(@types/node@20.11.5)(sass@1.70.0):
|
||||
/vite@5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0):
|
||||
resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
@ -19805,6 +19841,7 @@ packages:
|
|||
postcss: 8.4.33
|
||||
rollup: 4.9.6
|
||||
sass: 1.70.0
|
||||
terser: 5.27.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
|
@ -19815,12 +19852,12 @@ packages:
|
|||
vitest: '>=0.16.0'
|
||||
dependencies:
|
||||
cross-fetch: 3.1.5
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)
|
||||
vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/vitest@0.34.6(happy-dom@10.0.3)(sass@1.70.0):
|
||||
/vitest@0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0):
|
||||
resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==}
|
||||
engines: {node: '>=v14.18.0'}
|
||||
hasBin: true
|
||||
|
@ -19863,7 +19900,7 @@ packages:
|
|||
acorn-walk: 8.3.2
|
||||
cac: 6.7.14
|
||||
chai: 4.3.10
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
happy-dom: 10.0.3
|
||||
local-pkg: 0.4.3
|
||||
magic-string: 0.30.5
|
||||
|
@ -19873,8 +19910,8 @@ packages:
|
|||
strip-literal: 1.3.0
|
||||
tinybench: 2.6.0
|
||||
tinypool: 0.7.0
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite-node: 0.34.6(@types/node@20.11.5)(sass@1.70.0)
|
||||
vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
vite-node: 0.34.6(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0)
|
||||
why-is-node-running: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
|
@ -19945,7 +19982,7 @@ packages:
|
|||
peerDependencies:
|
||||
eslint: '>=6.0.0'
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.56.0
|
||||
eslint-scope: 7.2.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
@ -20275,19 +20312,6 @@ packages:
|
|||
async-limiter: 1.0.1
|
||||
dev: true
|
||||
|
||||
/ws@8.16.0:
|
||||
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/ws@8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
|
||||
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
@ -20302,7 +20326,6 @@ packages:
|
|||
dependencies:
|
||||
bufferutil: 4.0.7
|
||||
utf-8-validate: 6.0.3
|
||||
dev: false
|
||||
|
||||
/xev@3.0.2:
|
||||
resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==}
|
||||
|
|
|
@ -6,3 +6,4 @@ packages:
|
|||
- 'packages/misskey-js/generator'
|
||||
- 'packages/misskey-reversi'
|
||||
- 'packages/misskey-bubble-game'
|
||||
- 'packages/misskey-mahjong'
|
||||
|
|
|
@ -25,6 +25,9 @@ const fs = require('fs');
|
|||
fs.rmSync(__dirname + '/../packages/misskey-bubble-game/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../packages/misskey-bubble-game/node_modules', { recursive: true, force: true });
|
||||
|
||||
fs.rmSync(__dirname + '/../packages/misskey-mahjong/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../packages/misskey-mahjong/node_modules', { recursive: true, force: true });
|
||||
|
||||
fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
|
||||
|
||||
|
|
|
@ -12,5 +12,6 @@ const fs = require('fs');
|
|||
fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../packages/misskey-bubble-game/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../packages/misskey-mahjong/built', { recursive: true, force: true });
|
||||
fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
|
||||
})();
|
||||
|
|
|
@ -46,6 +46,12 @@ await execa('pnpm', ['--filter', 'misskey-bubble-game', 'build'], {
|
|||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
await execa('pnpm', ['--filter', 'misskey-mahjong', 'build'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['build-pre', '--watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
|
@ -87,3 +93,9 @@ execa('pnpm', ['--filter', 'misskey-bubble-game', 'watch'], {
|
|||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
||||
execa('pnpm', ['--filter', 'misskey-mahjong', 'watch'], {
|
||||
cwd: _dirname + '/../',
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue