summaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/bot/core.ts4
-rw-r--r--src/api/endpoints.ts20
-rw-r--r--src/api/endpoints/othello/games.ts26
-rw-r--r--src/api/endpoints/othello/invitations.ts15
-rw-r--r--src/api/endpoints/othello/match.ts77
-rw-r--r--src/api/endpoints/othello/match/cancel.ts9
-rw-r--r--src/api/event.ts12
-rw-r--r--src/api/models/othello-game.ts53
-rw-r--r--src/api/models/othello-matching.ts42
-rw-r--r--src/api/stream/messaging.ts2
-rw-r--r--src/api/stream/othello-game.ts86
-rw-r--r--src/api/stream/othello.ts10
-rw-r--r--src/api/stream/requests.ts2
-rw-r--r--src/api/stream/server.ts2
-rw-r--r--src/api/streaming.ts4
15 files changed, 359 insertions, 5 deletions
diff --git a/src/api/bot/core.ts b/src/api/bot/core.ts
index 0a073a3127..75564b81e8 100644
--- a/src/api/bot/core.ts
+++ b/src/api/bot/core.ts
@@ -475,8 +475,8 @@ class OthelloContext extends Context {
othelloAi('white', this.othello);
if (this.othello.getPattern('black').length === 0) {
this.bot.clearContext();
- const blackCount = this.othello.board.map(row => row.filter(s => s == 'black').length).reduce((a, b) => a + b);
- const whiteCount = this.othello.board.map(row => row.filter(s => s == 'white').length).reduce((a, b) => a + b);
+ const blackCount = this.othello.board.filter(s => s == 'black').length;
+ const whiteCount = this.othello.board.filter(s => s == 'white').length;
const winner = blackCount == whiteCount ? '引き分け' : blackCount > whiteCount ? '黒の勝ち' : '白の勝ち';
return this.othello.toString() + `\n\n~終了~\n\n黒${blackCount}、白${whiteCount}で${winner}です。`;
} else {
diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts
index cbc016f20f..fad6667111 100644
--- a/src/api/endpoints.ts
+++ b/src/api/endpoints.ts
@@ -234,6 +234,26 @@ const endpoints: Endpoint[] = [
},
{
+ name: 'othello/match',
+ withCredential: true
+ },
+
+ {
+ name: 'othello/match/cancel',
+ withCredential: true
+ },
+
+ {
+ name: 'othello/invitations',
+ withCredential: true
+ },
+
+ {
+ name: 'othello/games',
+ withCredential: true
+ },
+
+ {
name: 'mute/create',
withCredential: true,
kind: 'account/write'
diff --git a/src/api/endpoints/othello/games.ts b/src/api/endpoints/othello/games.ts
new file mode 100644
index 0000000000..39963fcd29
--- /dev/null
+++ b/src/api/endpoints/othello/games.ts
@@ -0,0 +1,26 @@
+import $ from 'cafy';
+import Game, { pack } from '../../models/othello-game';
+
+module.exports = (params, user) => new Promise(async (res, rej) => {
+ // Get 'my' parameter
+ const [my = false, myErr] = $(params.my).optional.boolean().$;
+ if (myErr) return rej('invalid my param');
+
+ const q = my ? {
+ $or: [{
+ black_user_id: user._id
+ }, {
+ white_user_id: user._id
+ }]
+ } : {};
+
+ // Fetch games
+ const games = await Game.find(q, {
+ sort: {
+ _id: -1
+ }
+ });
+
+ // Reponse
+ res(Promise.all(games.map(async (g) => await pack(g, user))));
+});
diff --git a/src/api/endpoints/othello/invitations.ts b/src/api/endpoints/othello/invitations.ts
new file mode 100644
index 0000000000..02fb421fbc
--- /dev/null
+++ b/src/api/endpoints/othello/invitations.ts
@@ -0,0 +1,15 @@
+import Matching, { pack as packMatching } from '../../models/othello-matching';
+
+module.exports = (params, user) => new Promise(async (res, rej) => {
+ // Find session
+ const invitations = await Matching.find({
+ child_id: user._id
+ }, {
+ sort: {
+ _id: -1
+ }
+ });
+
+ // Reponse
+ res(Promise.all(invitations.map(async (i) => await packMatching(i, user))));
+});
diff --git a/src/api/endpoints/othello/match.ts b/src/api/endpoints/othello/match.ts
new file mode 100644
index 0000000000..cb094bbc65
--- /dev/null
+++ b/src/api/endpoints/othello/match.ts
@@ -0,0 +1,77 @@
+import $ from 'cafy';
+import Matching, { pack as packMatching } from '../../models/othello-matching';
+import Game, { pack as packGame } 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,
+ turn_user_id: parentIsBlack ? exist.parent_id : user._id,
+ is_ended: false,
+ logs: []
+ });
+
+ // Reponse
+ res(await packGame(game, user));
+
+ publishOthelloStream(exist.parent_id, 'matched', await packGame(game, exist.parent_id));
+ } 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
+ });
+
+ // セッションを作成
+ const matching = await Matching.insert({
+ created_at: new Date(),
+ parent_id: user._id,
+ child_id: child._id
+ });
+
+ // Reponse
+ res();
+
+ // 招待
+ publishOthelloStream(child._id, 'invited', await packMatching(matching, child));
+ }
+});
diff --git a/src/api/endpoints/othello/match/cancel.ts b/src/api/endpoints/othello/match/cancel.ts
new file mode 100644
index 0000000000..6f751ef835
--- /dev/null
+++ b/src/api/endpoints/othello/match/cancel.ts
@@ -0,0 +1,9 @@
+import Matching from '../../../models/othello-matching';
+
+module.exports = (params, user) => new Promise(async (res, rej) => {
+ await Matching.remove({
+ parent_id: user._id
+ });
+
+ res();
+});
diff --git a/src/api/event.ts b/src/api/event.ts
index 4a2e4e453d..4c9cc18e4d 100644
--- a/src/api/event.ts
+++ b/src/api/event.ts
@@ -38,6 +38,14 @@ 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 publishOthelloGameStream(gameId: ID, type: string, value?: any): void {
+ this.publish(`othello-game-stream:${gameId}`, 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 +73,8 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
+export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
+
+export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
+
export const publishChannelStream = ev.publishChannelStream.bind(ev);
diff --git a/src/api/models/othello-game.ts b/src/api/models/othello-game.ts
new file mode 100644
index 0000000000..73a5c94b21
--- /dev/null
+++ b/src/api/models/othello-game.ts
@@ -0,0 +1,53 @@
+import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
+import db from '../../db/mongodb';
+import { IUser, pack as packUser } from './user';
+
+const Game = db.get<IGame>('othello_games');
+export default Game;
+
+export interface IGame {
+ _id: mongo.ObjectID;
+ created_at: Date;
+ black_user_id: mongo.ObjectID;
+ white_user_id: mongo.ObjectID;
+ turn_user_id: mongo.ObjectID;
+ is_ended: boolean;
+ winner_id: mongo.ObjectID;
+ logs: any[];
+}
+
+/**
+ * Pack an othello game for API response
+ */
+export const pack = (
+ game: any,
+ me?: string | mongo.ObjectID | IUser
+) => new Promise<any>(async (resolve, reject) => {
+
+ // Me
+ const meId: mongo.ObjectID = me
+ ? mongo.ObjectID.prototype.isPrototypeOf(me)
+ ? me as mongo.ObjectID
+ : typeof me === 'string'
+ ? new mongo.ObjectID(me)
+ : (me as IUser)._id
+ : null;
+
+ const _game = deepcopy(game);
+
+ // Rename _id to id
+ _game.id = _game._id;
+ delete _game._id;
+
+ // Populate user
+ _game.black_user = await packUser(_game.black_user_id, meId);
+ _game.white_user = await packUser(_game.white_user_id, meId);
+ if (_game.winner_id) {
+ _game.winner = await packUser(_game.winner_id, meId);
+ } else {
+ _game.winner = null;
+ }
+
+ resolve(_game);
+});
diff --git a/src/api/models/othello-matching.ts b/src/api/models/othello-matching.ts
new file mode 100644
index 0000000000..89fcd6df6a
--- /dev/null
+++ b/src/api/models/othello-matching.ts
@@ -0,0 +1,42 @@
+import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
+import db from '../../db/mongodb';
+import { IUser, pack as packUser } from './user';
+
+const Matching = db.get<IMatching>('othello_matchings');
+export default Matching;
+
+export interface IMatching {
+ _id: mongo.ObjectID;
+ created_at: Date;
+ parent_id: mongo.ObjectID;
+ child_id: mongo.ObjectID;
+}
+
+/**
+ * Pack an othello matching for API response
+ */
+export const pack = (
+ matching: any,
+ me?: string | mongo.ObjectID | IUser
+) => new Promise<any>(async (resolve, reject) => {
+
+ // Me
+ const meId: mongo.ObjectID = me
+ ? mongo.ObjectID.prototype.isPrototypeOf(me)
+ ? me as mongo.ObjectID
+ : typeof me === 'string'
+ ? new mongo.ObjectID(me)
+ : (me as IUser)._id
+ : null;
+
+ const _matching = deepcopy(matching);
+
+ delete _matching._id;
+
+ // Populate user
+ _matching.parent = await packUser(_matching.parent_id, meId);
+ _matching.child = await packUser(_matching.child_id, meId);
+
+ resolve(_matching);
+});
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..59d964777d
--- /dev/null
+++ b/src/api/stream/othello-game.ts
@@ -0,0 +1,86 @@
+import * as websocket from 'websocket';
+import * as redis from 'redis';
+import Game from '../models/othello-game';
+import { publishOthelloGameStream } from '../event';
+import Othello from '../../common/othello';
+
+export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
+ const gameId = request.resourceURL.query.game;
+
+ // Subscribe game stream
+ subscriber.subscribe(`misskey:othello-game-stream:${gameId}`);
+ subscriber.on('message', (_, data) => {
+ connection.send(data);
+ });
+
+ connection.on('message', async (data) => {
+ const msg = JSON.parse(data.utf8Data);
+
+ switch (msg.type) {
+ case 'set':
+ if (msg.pos == null) return;
+ set(msg.pos);
+ break;
+ }
+ });
+
+ async function set(pos) {
+ const game = await Game.findOne({ _id: gameId });
+
+ if (game.is_ended) return;
+
+ const o = new Othello();
+
+ game.logs.forEach(log => {
+ o.set(log.color, log.pos);
+ });
+
+ const myColor = game.black_user_id.equals(user._id) ? 'black' : 'white';
+ const opColor = myColor == 'black' ? 'white' : 'black';
+
+ if (!o.canReverse(myColor, pos)) return;
+ o.set(myColor, pos);
+
+ let turn;
+ if (o.getPattern(opColor).length > 0) {
+ turn = myColor == 'black' ? game.white_user_id : game.black_user_id;
+ } else if (o.getPattern(myColor).length > 0) {
+ turn = myColor == 'black' ? game.black_user_id : game.white_user_id;
+ } else {
+ turn = null;
+ }
+
+ const isEnded = turn === null;
+
+ let winner;
+ if (isEnded) {
+ const blackCount = o.board.filter(s => s == 'black').length;
+ const whiteCount = o.board.filter(s => s == 'white').length;
+ winner = blackCount == whiteCount ? null : blackCount > whiteCount ? game.black_user_id : game.white_user_id;
+ }
+
+ const log = {
+ at: new Date(),
+ color: myColor,
+ pos
+ };
+
+ await Game.update({
+ _id: gameId
+ }, {
+ $set: {
+ turn_user_id: turn,
+ is_ended: isEnded,
+ winner_id: winner
+ },
+ $push: {
+ logs: log
+ }
+ });
+
+ publishOthelloGameStream(gameId, 'set', {
+ color: myColor,
+ pos
+ });
+ }
+}
diff --git a/src/api/stream/othello.ts b/src/api/stream/othello.ts
new file mode 100644
index 0000000000..5056eb535c
--- /dev/null
+++ b/src/api/stream/othello.ts
@@ -0,0 +1,10 @@
+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 {
+ // Subscribe othello stream
+ subscriber.subscribe(`misskey:othello-stream:${user._id}`);
+ 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..7d67ba9574 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 othelloStream from './stream/othello';
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' ? othelloStream :
null;
if (channel !== null) {