Skip to content

Commit

Permalink
enhance(reversi): 変則なしマッチングを可能に
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Jan 24, 2024
1 parent 2b6bf07 commit 5719a92
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 82 deletions.
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9595,6 +9595,14 @@ export interface Locale extends ILocale {
* 相手が設定を変更しました
*/
"opponentHasSettingsChanged": string;
/**
* 変則許可 (完全フリー)
*/
"allowIrregularRules": string;
/**
* 変則なし
*/
"disallowIrregularRules": string;
};
"_offlineScreen": {
/**
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2556,6 +2556,8 @@ _reversi:
shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿"
iStartedAGame: "対局を開始しました! #MisskeyReversi"
opponentHasSettingsChanged: "相手が設定を変更しました"
allowIrregularRules: "変則許可 (完全フリー)"
disallowIrregularRules: "変則なし"

_offlineScreen:
title: "オフライン - サーバーに接続できません"
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/migration/1706081514499-reversi-6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey 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"`);
}
}
33 changes: 24 additions & 9 deletions packages/backend/src/core/ReversiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}

Expand Down Expand Up @@ -138,7 +139,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({
Expand Down Expand Up @@ -177,19 +178,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;
Expand All @@ -203,7 +214,10 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {

@bindThis
public async matchAnyUserCancel(user: MiUser) {
await this.redisClient.zrem('reversi:matchAny', user.id);
const redisPipeline = this.redisClient.pipeline();
redisPipeline.zrem('reversi:matchAny', user.id);
redisPipeline.zrem('reversi:matchAny', user.id + ':noIrregularRules');
await redisPipeline.exec();
}

@bindThis
Expand Down Expand Up @@ -265,7 +279,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,
Expand All @@ -278,6 +292,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'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -105,6 +106,7 @@ export class ReversiGameEntityService {
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
timeLimitForEachTurn: game.timeLimitForEachTurn,
noIrregularRules: game.noIrregularRules,
});
}

Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/ReversiGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export class MiReversiGame {
})
public bw: string;

@Column('boolean', {
default: false,
})
public noIrregularRules: boolean;

@Column('boolean', {
default: false,
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/models/json-schema/reversi-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/server/api/endpoints/reversi/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand All @@ -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;

Expand Down
121 changes: 63 additions & 58 deletions packages/frontend/src/pages/reversi/game.setting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

<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>

<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>

<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>
<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>

<MkRadios v-model="game.bw">
<option value="random">{{ i18n.ts.random }}</option>
<option :value="'1'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user1"/></b>
</template>
</I18n>
</option>
<option :value="'2'">
<I18n :src="i18n.ts._reversi.blackIs" tag="span">
<template #name>
<b><MkUserName :user="game.user2"/></b>
</template>
</I18n>
</option>
</MkRadios>
</MkFolder>

<MkFolder :defaultOpen="true">
<template #label>{{ i18n.ts._reversi.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>

<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>
</template>
</div>
</div>
</MkSpacer>
Expand Down
22 changes: 18 additions & 4 deletions packages/frontend/src/pages/reversi/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/apiClientJSDoc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2024.2.0-beta.4
* generatedAt: 2024-01-24T01:14:40.901Z
* version: 2024.2.0-beta.6
* generatedAt: 2024-01-24T07:32:10.455Z
*/

import type { SwitchCaseResponseType } from '../api.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2024.2.0-beta.4
* generatedAt: 2024-01-24T01:14:40.899Z
* version: 2024.2.0-beta.6
* generatedAt: 2024-01-24T07:32:10.453Z
*/

import type {
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2024.2.0-beta.4
* generatedAt: 2024-01-24T01:14:40.897Z
* version: 2024.2.0-beta.6
* generatedAt: 2024-01-24T07:32:10.452Z
*/

import { operations } from './types.js';
Expand Down
Loading

0 comments on commit 5719a92

Please sign in to comment.