Merge remote-branch 'misskey/develop'

This commit is contained in:
NoriDev 2024-01-25 16:08:25 +09:00
commit a375e81ba9
18 changed files with 170 additions and 80 deletions

8
locales/index.d.ts vendored
View file

@ -10849,6 +10849,14 @@ export interface Locale extends ILocale {
*
*/
"opponentHasSettingsChanged": string;
/**
* ()
*/
"allowIrregularRules": string;
/**
*
*/
"disallowIrregularRules": string;
};
"_offlineScreen": {
/**

View file

@ -2886,6 +2886,8 @@ _reversi:
shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿"
iStartedAGame: "対局を開始しました! #MisskeyReversi"
opponentHasSettingsChanged: "相手が設定を変更しました"
allowIrregularRules: "変則許可 (完全フリー)"
disallowIrregularRules: "変則なし"
_offlineScreen:
title: "オフライン - サーバーに接続できません"

View file

@ -2804,6 +2804,8 @@ _reversi:
shareToTlTheGameWhenStart: "대국 시작 시 타임라인에 대국 게시"
iStartedAGame: "대국이 시작되었어요! #MisskeyReversi"
opponentHasSettingsChanged: "상대방이 게임 설정을 변경했어요"
allowIrregularRules: "변칙 허용(완전 자유)"
disallowIrregularRules: "변칙 없음"
_offlineScreen:
title: "오프라인 - 서버에 연결할 수 없음"
header: "서버에 연결할 수 없어요"

View file

@ -2572,6 +2572,11 @@ _reversi:
freeMatch: "自由對戰"
lookingForPlayer: "正在搜尋對手"
gameCanceled: "對弈已被取消"
shareToTlTheGameWhenStart: "在遊戲開始時將對弈資訊發布到時間軸"
iStartedAGame: "對弈開始了! #MisskeyReversi"
opponentHasSettingsChanged: "對手更改了設定"
allowIrregularRules: "允許異常規則(完全自由)"
disallowIrregularRules: "不允許異常規則"
_offlineScreen:
title: "離線-無法連接伺服器"
header: "無法連接伺服器"

View file

@ -1,7 +1,7 @@
{
"name": "cherrypick",
"version": "4.7.0-beta.1",
"basedMisskeyVersion": "2024.2.0-beta.6",
"basedMisskeyVersion": "2024.2.0-beta.7",
"codename": "nasubi",
"repository": {
"type": "git",

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Reversi61706081514499 {
name = 'Reversi61706081514499'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" ADD "noIrregularRules" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "noIrregularRules"`);
}
}

View file

@ -85,6 +85,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
map: game.map,
bw: game.bw,
crc32: game.crc32,
noIrregularRules: game.noIrregularRules,
} satisfies Partial<MiReversiGame>;
}
@ -119,7 +120,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
if (invitations.includes(targetUser.id)) {
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
const game = await this.matched(targetUser.id, me.id);
const game = await this.matched(targetUser.id, me.id, {
noIrregularRules: false,
});
return game;
}
@ -138,7 +141,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
}
@bindThis
public async matchAnyUser(me: MiUser, multiple = false): Promise<MiReversiGame | null> {
public async matchAnyUser(me: MiUser, options: { noIrregularRules: boolean }, multiple = false): Promise<MiReversiGame | null> {
if (!multiple) {
// 既にマッチしている対局が無いか探す(3分以内)
const games = await this.reversiGamesRepository.find({
@ -165,7 +168,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
const game = await this.matched(invitorId, me.id);
const game = await this.matched(invitorId, me.id, {
noIrregularRules: false,
});
return game;
}
@ -177,19 +182,29 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
2, // 自分自身のIDが入っている場合もあるので2つ取得
'REV');
const userIds = matchings.filter(id => id !== me.id);
const items = matchings.filter(id => !id.startsWith(me.id));
if (userIds.length > 0) {
const matchedUserId = userIds[0];
if (items.length > 0) {
const [matchedUserId, option] = items[0].split(':');
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
await this.redisClient.zrem('reversi:matchAny',
me.id,
matchedUserId,
me.id + ':noIrregularRules',
matchedUserId + ':noIrregularRules');
const game = await this.matched(matchedUserId, me.id);
const game = await this.matched(matchedUserId, me.id, {
noIrregularRules: options.noIrregularRules || option === 'noIrregularRules',
});
return game;
} else {
const redisPipeline = this.redisClient.pipeline();
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id);
if (options.noIrregularRules) {
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules');
} else {
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id);
}
redisPipeline.expire('reversi:matchAny', 15, 'NX');
await redisPipeline.exec();
return null;
@ -203,7 +218,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
@bindThis
public async matchAnyUserCancel(user: MiUser) {
await this.redisClient.zrem('reversi:matchAny', user.id);
await this.redisClient.zrem('reversi:matchAny', user.id, user.id + ':noIrregularRules');
}
@bindThis
@ -265,7 +280,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
}
@bindThis
private async matched(parentId: MiUser['id'], childId: MiUser['id']): Promise<MiReversiGame> {
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
const game = await this.reversiGamesRepository.insert({
id: this.idService.gen(),
user1Id: parentId,
@ -278,6 +293,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
map: Reversi.maps.eighteight.data,
bw: 'random',
isLlotheo: false,
noIrregularRules: options.noIrregularRules,
}).then(x => this.reversiGamesRepository.findOneOrFail({
where: { id: x.identifiers[0].id },
relations: ['user1', 'user2'],

View file

@ -61,6 +61,7 @@ export class ReversiGameEntityService {
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
timeLimitForEachTurn: game.timeLimitForEachTurn,
noIrregularRules: game.noIrregularRules,
logs: game.logs,
map: game.map,
});
@ -105,6 +106,7 @@ export class ReversiGameEntityService {
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
timeLimitForEachTurn: game.timeLimitForEachTurn,
noIrregularRules: game.noIrregularRules,
});
}

View file

@ -106,6 +106,11 @@ export class MiReversiGame {
})
public bw: string;
@Column('boolean', {
default: false,
})
public noIrregularRules: boolean;
@Column('boolean', {
default: false,
})

View file

@ -82,6 +82,10 @@ export const packedReversiGameLiteSchema = {
type: 'string',
optional: false, nullable: false,
},
noIrregularRules: {
type: 'boolean',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,
@ -196,6 +200,10 @@ export const packedReversiGameDetailedSchema = {
type: 'string',
optional: false, nullable: false,
},
noIrregularRules: {
type: 'boolean',
optional: false, nullable: false,
},
isLlotheo: {
type: 'boolean',
optional: false, nullable: false,

View file

@ -37,6 +37,7 @@ export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id', nullable: true },
noIrregularRules: { type: 'boolean', default: false },
multiple: { type: 'boolean', default: false },
},
required: [],
@ -57,7 +58,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw err;
}) : null;
const game = target ? await this.reversiService.matchSpecificUser(me, target, ps.multiple) : await this.reversiService.matchAnyUser(me, ps.multiple);
const game = target
? await this.reversiService.matchSpecificUser(me, target, ps.multiple)
: await this.reversiService.matchAnyUser(me, { noIrregularRules: ps.noIrregularRules }, ps.multiple);
if (game == null) return;

View file

@ -1,7 +1,7 @@
/*
* version: 4.7.0-beta.1
* basedMisskeyVersion: 2024.2.0-beta.6
* generatedAt: 2024-01-24T08:25:14.507Z
* basedMisskeyVersion: 2024.2.0-beta.7
* generatedAt: 2024-01-25T07:07:45.745Z
*/
import type { SwitchCaseResponseType } from '../api.js';

View file

@ -1,7 +1,7 @@
/*
* version: 4.7.0-beta.1
* basedMisskeyVersion: 2024.2.0-beta.6
* generatedAt: 2024-01-24T08:25:14.505Z
* basedMisskeyVersion: 2024.2.0-beta.7
* generatedAt: 2024-01-25T07:07:45.742Z
*/
import type {

View file

@ -1,7 +1,7 @@
/*
* version: 4.7.0-beta.1
* basedMisskeyVersion: 2024.2.0-beta.6
* generatedAt: 2024-01-24T08:25:14.504Z
* basedMisskeyVersion: 2024.2.0-beta.7
* generatedAt: 2024-01-25T07:07:45.740Z
*/
import { operations } from './types.js';

View file

@ -1,7 +1,7 @@
/*
* version: 4.7.0-beta.1
* basedMisskeyVersion: 2024.2.0-beta.6
* generatedAt: 2024-01-24T08:25:14.503Z
* basedMisskeyVersion: 2024.2.0-beta.7
* generatedAt: 2024-01-25T07:07:45.738Z
*/
import { components } from './types.js';

View file

@ -3,8 +3,8 @@
/*
* version: 4.7.0-beta.1
* basedMisskeyVersion: 2024.2.0-beta.6
* generatedAt: 2024-01-24T08:25:14.421Z
* basedMisskeyVersion: 2024.2.0-beta.7
* generatedAt: 2024-01-25T07:07:45.651Z
*/
/**
@ -4826,6 +4826,7 @@ export type components = {
timeoutUserId: string | null;
black: number | null;
bw: string;
noIrregularRules: boolean;
isLlotheo: boolean;
canPutEverywhere: boolean;
loopedBoard: boolean;
@ -4861,6 +4862,7 @@ export type components = {
timeoutUserId: string | null;
black: number | null;
bw: string;
noIrregularRules: boolean;
isLlotheo: boolean;
canPutEverywhere: boolean;
loopedBoard: boolean;
@ -27992,6 +27994,8 @@ export type operations = {
/** Format: misskey:id */
userId?: string | null;
/** @default false */
noIrregularRules?: boolean;
/** @default false */
multiple?: boolean;
};
};

View file

@ -12,69 +12,74 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps" :class="{ [$style.disallowInner]: isReady }">
<div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
<div class="_panel">
<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
<div>{{ mapName }}</div>
<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
</div>
<template v-if="game.noIrregularRules">
<div>{{ i18n.ts._reversi.disallowIrregularRules }}</div>
</template>
<template v-else>
<div class="_panel">
<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
<div>{{ mapName }}</div>
<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
</div>
<div style="padding: 16px;">
<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
<div style="padding: 16px;">
<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
</div>
</div>
</div>
</div>
</div>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
<MkRadios v-model="game.bw">
<option value="random">{{ i18n.ts.random }}</option>
<option :value="'1'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
</I18n>
</option>
<option :value="'2'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
</I18n>
</option>
</MkRadios>
</MkFolder>
<MkRadios v-model="game.bw">
<option value="random">{{ i18n.ts.random }}</option>
<option :value="'1'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
</I18n>
</option>
<option :value="'2'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
</I18n>
</option>
</MkRadios>
</MkFolder>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
<MkRadios v-model="game.timeLimitForEachTurn">
<option :value="5">5{{ i18n.ts._time.second }}</option>
<option :value="10">10{{ i18n.ts._time.second }}</option>
<option :value="30">30{{ i18n.ts._time.second }}</option>
<option :value="60">60{{ i18n.ts._time.second }}</option>
<option :value="90">90{{ i18n.ts._time.second }}</option>
<option :value="120">120{{ i18n.ts._time.second }}</option>
<option :value="180">180{{ i18n.ts._time.second }}</option>
<option :value="3600">3600{{ i18n.ts._time.second }}</option>
</MkRadios>
</MkFolder>
<MkRadios v-model="game.timeLimitForEachTurn">
<option :value="5">5{{ i18n.ts._time.second }}</option>
<option :value="10">10{{ i18n.ts._time.second }}</option>
<option :value="30">30{{ i18n.ts._time.second }}</option>
<option :value="60">60{{ i18n.ts._time.second }}</option>
<option :value="90">90{{ i18n.ts._time.second }}</option>
<option :value="120">120{{ i18n.ts._time.second }}</option>
<option :value="180">180{{ i18n.ts._time.second }}</option>
<option :value="3600">3600{{ i18n.ts._time.second }}</option>
</MkRadios>
</MkFolder>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.rules }}</template>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.rules }}</template>
<div class="_gaps_s">
<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
</div>
</MkFolder>
<div class="_gaps_s">
<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
</div>
</MkFolder>
</template>
</div>
</div>
</MkSpacer>

View file

@ -157,6 +157,7 @@ if ($i) {
const invitations = ref<Misskey.entities.UserLite[]>([]);
const matchingUser = ref<Misskey.entities.UserLite | null>(null);
const matchingAny = ref<boolean>(false);
const noIrregularRules = ref<boolean>(false);
function startGame(game: Misskey.entities.ReversiGameDetailed) {
matchingUser.value = null;
@ -182,6 +183,7 @@ async function matchHeatbeat() {
} else if (matchingAny.value) {
const res = await misskeyApi('reversi/match', {
userId: null,
noIrregularRules: noIrregularRules.value,
});
if (res != null) {
@ -199,10 +201,22 @@ async function matchUser() {
matchHeatbeat();
}
async function matchAny() {
matchingAny.value = true;
matchHeatbeat();
function matchAny(ev: MouseEvent) {
os.popupMenu([{
text: i18n.ts._reversi.allowIrregularRules,
action: () => {
noIrregularRules.value = false;
matchingAny.value = true;
matchHeatbeat();
},
}, {
text: i18n.ts._reversi.disallowIrregularRules,
action: () => {
noIrregularRules.value = true;
matchingAny.value = true;
matchHeatbeat();
},
}], ev.currentTarget ?? ev.target);
}
function cancelMatching() {