summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/endpoints/othello/match.ts80
-rw-r--r--src/api/endpoints/othello/sessions/create.ts18
-rw-r--r--src/api/endpoints/othello/sessions/in.ts34
-rw-r--r--src/api/event.ts6
-rw-r--r--src/api/models/othello-matching.ts11
-rw-r--r--src/api/models/othello-session.ts29
-rw-r--r--src/api/stream/messaging.ts2
-rw-r--r--src/api/stream/othello-game.ts12
-rw-r--r--src/api/stream/othello-matching.ts12
-rw-r--r--src/api/stream/requests.ts2
-rw-r--r--src/api/stream/server.ts2
-rw-r--r--src/api/streaming.ts4
-rw-r--r--src/web/app/common/views/components/messaging.vue2
-rw-r--r--src/web/app/common/views/components/othello.game.vue29
-rw-r--r--src/web/app/common/views/components/othello.vue80
15 files changed, 229 insertions, 94 deletions
diff --git a/src/api/endpoints/othello/match.ts b/src/api/endpoints/othello/match.ts
new file mode 100644
index 0000000000..2dc22d11f9
--- /dev/null
+++ b/src/api/endpoints/othello/match.ts
@@ -0,0 +1,80 @@
+import $ from 'cafy';
+import Matching from '../../models/othello-matchig';
+import Game, { pack } from '../../models/othello-game';
+import User from '../../models/user';
+import { publishOthelloStream } from '../../event';
+
+module.exports = (params, user) => new Promise(async (res, rej) => {
+ // Get 'user_id' parameter
+ const [childId, childIdErr] = $(params.user_id).id().$;
+ if (childIdErr) return rej('invalid user_id param');
+
+ // Myself
+ if (childId.equals(user._id)) {
+ return rej('invalid user_id param');
+ }
+
+ // Find session
+ const exist = await Matching.findOne({
+ parent_id: childId,
+ child_id: user._id
+ });
+
+ if (exist) {
+ // Destroy session
+ Matching.remove({
+ _id: exist._id
+ });
+
+ const parentIsBlack = Math.random() > 0.5;
+
+ // Start game
+ const game = await Game.insert({
+ created_at: new Date(),
+ black_user_id: parentIsBlack ? exist.parent_id : user._id,
+ white_user_id: parentIsBlack ? user._id : exist.parent_id,
+ logs: []
+ });
+
+ const packedGame = await pack(game);
+
+ // Reponse
+ res(packedGame);
+
+ publishOthelloStream(exist.parent_id, 'matched', {
+ game
+ });
+ } else {
+ // Fetch child
+ const child = await User.findOne({
+ _id: childId
+ }, {
+ fields: {
+ _id: true
+ }
+ });
+
+ if (child === null) {
+ return rej('user not found');
+ }
+
+ // 以前のセッションはすべて削除しておく
+ await Matching.remove({
+ parent_id: user._id
+ });
+
+ // セッションを作成
+ await Matching.insert({
+ parent_id: user._id,
+ child_id: child._id
+ });
+
+ // Reponse
+ res(204);
+
+ // 招待
+ publishOthelloStream(child._id, 'invited', {
+ user_id: user._id
+ });
+ }
+});
diff --git a/src/api/endpoints/othello/sessions/create.ts b/src/api/endpoints/othello/sessions/create.ts
deleted file mode 100644
index 09c3cff62b..0000000000
--- a/src/api/endpoints/othello/sessions/create.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import rndstr from 'rndstr';
-import Session, { pack } from '../../../models/othello-session';
-
-module.exports = (params, user) => new Promise(async (res, rej) => {
- // 以前のセッションはすべて削除しておく
- await Session.remove({
- user_id: user._id
- });
-
- // セッションを作成
- const session = await Session.insert({
- user_id: user._id,
- code: rndstr('a-z0-9', 3)
- });
-
- // Reponse
- res(await pack(session));
-});
diff --git a/src/api/endpoints/othello/sessions/in.ts b/src/api/endpoints/othello/sessions/in.ts
deleted file mode 100644
index d4b95bc4f9..0000000000
--- a/src/api/endpoints/othello/sessions/in.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import $ from 'cafy';
-import Session from '../../../models/othello-session';
-import Game, { pack } from '../../../models/othello-game';
-
-module.exports = (params, user) => new Promise(async (res, rej) => {
- // Get 'code' parameter
- const [code, codeErr] = $(params.code).string().$;
- if (codeErr) return rej('invalid code param');
-
- // Fetch session
- const session = await Session.findOne({ code });
-
- if (session == null) {
- return rej('session not found');
- }
-
- // Destroy session
- Session.remove({
- _id: session._id
- });
-
- const parentIsBlack = Math.random() > 0.5;
-
- // Start game
- const game = await Game.insert({
- created_at: new Date(),
- black_user_id: parentIsBlack ? session.user_id : user._id,
- white_user_id: parentIsBlack ? user._id : session.user_id,
- logs: []
- });
-
- // Reponse
- res(await pack(game));
-});
diff --git a/src/api/event.ts b/src/api/event.ts
index 4a2e4e453d..e68082f0a9 100644
--- a/src/api/event.ts
+++ b/src/api/event.ts
@@ -38,6 +38,10 @@ class MisskeyEvent {
this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
+ public publishOthelloStream(userId: ID, type: string, value?: any): void {
+ this.publish(`othello-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
+ }
+
public publishChannelStream(channelId: ID, type: string, value?: any): void {
this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value);
}
@@ -65,4 +69,6 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
+export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
+
export const publishChannelStream = ev.publishChannelStream.bind(ev);
diff --git a/src/api/models/othello-matching.ts b/src/api/models/othello-matching.ts
new file mode 100644
index 0000000000..bd7aeef3cf
--- /dev/null
+++ b/src/api/models/othello-matching.ts
@@ -0,0 +1,11 @@
+import * as mongo from 'mongodb';
+import db from '../../db/mongodb';
+
+const Matching = db.get<IMatching>('othello_matchings');
+export default Matching;
+
+export interface IMatching {
+ _id: mongo.ObjectID;
+ parent_id: mongo.ObjectID;
+ child_id: mongo.ObjectID;
+}
diff --git a/src/api/models/othello-session.ts b/src/api/models/othello-session.ts
deleted file mode 100644
index 0aa1d01e54..0000000000
--- a/src/api/models/othello-session.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import * as mongo from 'mongodb';
-import deepcopy = require('deepcopy');
-import db from '../../db/mongodb';
-
-const Session = db.get<ISession>('othello_sessions');
-export default Session;
-
-export interface ISession {
- _id: mongo.ObjectID;
- code: string;
- user_id: mongo.ObjectID;
-}
-
-/**
- * Pack an othello session for API response
- *
- * @param {any} session
- * @return {Promise<any>}
- */
-export const pack = (
- session: any
-) => new Promise<any>(async (resolve, reject) => {
-
- const _session = deepcopy(session);
-
- delete _session._id;
-
- resolve(_session);
-});
diff --git a/src/api/stream/messaging.ts b/src/api/stream/messaging.ts
index 3f505cfafa..a4a12426a3 100644
--- a/src/api/stream/messaging.ts
+++ b/src/api/stream/messaging.ts
@@ -2,7 +2,7 @@ import * as websocket from 'websocket';
import * as redis from 'redis';
import read from '../common/read-messaging-message';
-export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
+export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
const otherparty = request.resourceURL.query.otherparty;
// Subscribe messaging stream
diff --git a/src/api/stream/othello-game.ts b/src/api/stream/othello-game.ts
new file mode 100644
index 0000000000..ab91ef6422
--- /dev/null
+++ b/src/api/stream/othello-game.ts
@@ -0,0 +1,12 @@
+import * as websocket from 'websocket';
+import * as redis from 'redis';
+
+export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void {
+ const game = request.resourceURL.query.game;
+
+ // Subscribe game stream
+ subscriber.subscribe(`misskey:othello-game-stream:${game}`);
+ subscriber.on('message', (_, data) => {
+ connection.send(data);
+ });
+}
diff --git a/src/api/stream/othello-matching.ts b/src/api/stream/othello-matching.ts
new file mode 100644
index 0000000000..f30ce6eb0a
--- /dev/null
+++ b/src/api/stream/othello-matching.ts
@@ -0,0 +1,12 @@
+import * as websocket from 'websocket';
+import * as redis from 'redis';
+
+export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
+ const otherparty = request.resourceURL.query.otherparty;
+
+ // Subscribe matching stream
+ subscriber.subscribe(`misskey:othello-matching:${user._id}-${otherparty}`);
+ subscriber.on('message', (_, data) => {
+ connection.send(data);
+ });
+}
diff --git a/src/api/stream/requests.ts b/src/api/stream/requests.ts
index 2c36e58b6e..d7bb5e6c5c 100644
--- a/src/api/stream/requests.ts
+++ b/src/api/stream/requests.ts
@@ -3,7 +3,7 @@ import Xev from 'xev';
const ev = new Xev();
-export default function homeStream(request: websocket.request, connection: websocket.connection): void {
+export default function(request: websocket.request, connection: websocket.connection): void {
const onRequest = request => {
connection.send(JSON.stringify({
type: 'request',
diff --git a/src/api/stream/server.ts b/src/api/stream/server.ts
index 0db6643d40..4ca2ad1b10 100644
--- a/src/api/stream/server.ts
+++ b/src/api/stream/server.ts
@@ -3,7 +3,7 @@ import Xev from 'xev';
const ev = new Xev();
-export default function homeStream(request: websocket.request, connection: websocket.connection): void {
+export default function(request: websocket.request, connection: websocket.connection): void {
const onStats = stats => {
connection.send(JSON.stringify({
type: 'stats',
diff --git a/src/api/streaming.ts b/src/api/streaming.ts
index c06d64c245..66c2e0cec0 100644
--- a/src/api/streaming.ts
+++ b/src/api/streaming.ts
@@ -10,6 +10,8 @@ import homeStream from './stream/home';
import driveStream from './stream/drive';
import messagingStream from './stream/messaging';
import messagingIndexStream from './stream/messaging-index';
+import othelloGameStream from './stream/othello-game';
+import othelloMatchingStream from './stream/othello-matching';
import serverStream from './stream/server';
import requestsStream from './stream/requests';
import channelStream from './stream/channel';
@@ -62,6 +64,8 @@ module.exports = (server: http.Server) => {
request.resourceURL.pathname === '/drive' ? driveStream :
request.resourceURL.pathname === '/messaging' ? messagingStream :
request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream :
+ request.resourceURL.pathname === '/othello-game' ? othelloGameStream :
+ request.resourceURL.pathname === '/othello-matching' ? othelloMatchingStream :
null;
if (channel !== null) {
diff --git a/src/web/app/common/views/components/messaging.vue b/src/web/app/common/views/components/messaging.vue
index a94a996685..2ec488c247 100644
--- a/src/web/app/common/views/components/messaging.vue
+++ b/src/web/app/common/views/components/messaging.vue
@@ -89,7 +89,7 @@ export default Vue.extend({
beforeDestroy() {
this.connection.off('message', this.onMessage);
this.connection.off('read', this.onRead);
- (this as any).os.stream.dispose(this.connectionId);
+ (this as any).streams.messagingIndexStream.dispose(this.connectionId);
},
methods: {
isMe(message) {
diff --git a/src/web/app/common/views/components/othello.game.vue b/src/web/app/common/views/components/othello.game.vue
new file mode 100644
index 0000000000..3d3ffb2c07
--- /dev/null
+++ b/src/web/app/common/views/components/othello.game.vue
@@ -0,0 +1,29 @@
+<template>
+<div>
+ <header>黒:{{ game.black_user.name }} 白:{{ game.white_user.name }}</header>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: ['game'],
+ data() {
+ return {
+ game: null,
+ connection: null,
+ connectionId: null
+ };
+ },
+ mounted() {
+ this.connection = (this as any).os.streams.othelloGameStream.getConnection();
+ this.connectionId = (this as any).os.streams.othelloGameStream.use();
+
+ this.connection.on('set', this.onSet);
+ },
+ beforeDestroy() {
+ this.connection.off('set', this.onSet);
+ (this as any).streams.othelloGameStream.dispose(this.connectionId);
+ },
+});
+</script>
diff --git a/src/web/app/common/views/components/othello.vue b/src/web/app/common/views/components/othello.vue
index 136046db24..f5abcfb103 100644
--- a/src/web/app/common/views/components/othello.vue
+++ b/src/web/app/common/views/components/othello.vue
@@ -1,16 +1,19 @@
<template>
<div>
- <div v-if="session">
- <h1>相手を待っています<mk-ellipsis/></h1>
- <p>セッションID:<code>{{ session.code }}</code></p>
- <p>対戦したい相手に上記のセッションIDを伝えてください。相手が「セッションイン」でセッションIDを入力すると、対局が開始されます。</p>
+ <div v-if="game">
+ <x-game :game="game"/>
+ </div>
+ <div v-if="matching">
+ <h1><b>{{ matching.name }}</b>を待っています<mk-ellipsis/></h1>
</div>
<div v-else>
<h1>Misskey Othello</h1>
<p>他のMisskeyユーザーとオセロで対戦しよう。</p>
<button>フリーマッチ(準備中)</button>
- <button @click="inSession">セッションイン</button>
- <button @click="createSession">セッションを作成する</button>
+ <button @click="match">指名</button>
+ <section>
+ <h2>対局の招待があります:</h2>
+ </section>
<section>
<h2>過去の対局</h2>
</section>
@@ -20,11 +23,70 @@
<script lang="ts">
import Vue from 'vue';
+import XGame from './othello.game.vue';
+
export default Vue.extend({
- methods: {
- createSession() {
- (this as any).api('othello/sessions/create');
+ components: {
+ XGame
+ },
+ data() {
+ return {
+ game: null,
+ games: [],
+ gamesFetching: true,
+ gamesMoreFetching: false,
+ matching: false,
+ invitations: [],
+ connection: null,
+ connectionId: null
+ };
+ },
+ mounted() {
+ this.connection = (this as any).os.streams.othelloStream.getConnection();
+ this.connectionId = (this as any).os.streams.othelloStream.use();
+
+ this.connection.on('macthed', this.onMatched);
+ this.connection.on('invited', this.onInvited);
+ (this as any).api('othello/games').then(games => {
+ this.games = games;
+ this.gamesFetching = false;
+ });
+
+ (this as any).api('othello/invitations').then(invitations => {
+ this.invitations = this.invitations.concat(invitations);
+ });
+ },
+ beforeDestroy() {
+ this.connection.off('macthed', this.onMatched);
+ this.connection.off('invited', this.onInvited);
+ (this as any).streams.othelloStream.dispose(this.connectionId);
+ },
+ methods: {
+ match() {
+ (this as any).apis.input({
+ title: 'ユーザー名を入力してください'
+ }).then(username => {
+ (this as any).api('users/show', {
+ username
+ }).then(user => {
+ (this as any).api('othello/match', {
+ user_id: user.id
+ }).then(res => {
+ if (res == null) {
+ this.matching = user;
+ } else {
+ this.game = res;
+ }
+ });
+ });
+ });
+ },
+ onMatched(game) {
+ this.game = game;
+ },
+ onInvited(invite) {
+ this.invitations.unshift(invite);
}
}
});