This commit is contained in:
syuilo 2018-03-07 11:40:40 +09:00
parent 06eabcbc63
commit 6c495268ae
15 changed files with 230 additions and 95 deletions

View file

@ -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
});
}
});

View file

@ -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));
});

View file

@ -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));
});

View file

@ -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);

View file

@ -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;
}

View file

@ -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);
});

View file

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

View file

@ -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);
});
}

View file

@ -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);
});
}

View file

@ -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',

View file

@ -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',

View file

@ -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) {

View file

@ -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) {

View file

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

View file

@ -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';
export default Vue.extend({
methods: {
createSession() {
(this as any).api('othello/sessions/create');
import XGame from './othello.game.vue';
export default Vue.extend({
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);
}
}
});