commit
						a227ef30e8
					
				
					 47 changed files with 1091 additions and 177 deletions
				
			
		| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -233,6 +233,26 @@ const endpoints: Endpoint[] = [
 | 
			
		|||
		kind: 'notification-read'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								src/api/endpoints/othello/games.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/api/endpoints/othello/games.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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))));
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										15
									
								
								src/api/endpoints/othello/invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/api/endpoints/othello/invitations.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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))));
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										77
									
								
								src/api/endpoints/othello/match.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/api/endpoints/othello/match.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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));
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										9
									
								
								src/api/endpoints/othello/match/cancel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/api/endpoints/othello/match/cancel.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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();
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										53
									
								
								src/api/models/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/api/models/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										42
									
								
								src/api/models/othello-matching.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/api/models/othello-matching.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										86
									
								
								src/api/stream/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/api/stream/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/api/stream/othello.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/api/stream/othello.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,38 @@
 | 
			
		|||
const BOARD_SIZE = 8;
 | 
			
		||||
 | 
			
		||||
export default class Othello {
 | 
			
		||||
	public board: Array<Array<'black' | 'white'>>;
 | 
			
		||||
	public board: Array<'black' | 'white'>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * ゲームを初期化します
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.board = [
 | 
			
		||||
			[null, null, null, null, null, null, null, null],
 | 
			
		||||
			[null, null, null, null, null, null, null, null],
 | 
			
		||||
			[null, null, null, null, null, null, null, null],
 | 
			
		||||
			[null, null, null, 'black', 'white', null, null, null],
 | 
			
		||||
			[null, null, null, 'white', 'black', null, null, null],
 | 
			
		||||
			[null, null, null, null, null, null, null, null],
 | 
			
		||||
			[null, null, null, null, null, null, null, null],
 | 
			
		||||
			[null, null, null, null, null, null, null, null]
 | 
			
		||||
			null, null, null, null, null, null, null, null,
 | 
			
		||||
			null, null, null, null, null, null, null, null,
 | 
			
		||||
			null, null, null, null, null, null, null, null,
 | 
			
		||||
			null, null, null, 'white', 'black', null, null, null,
 | 
			
		||||
			null, null, null, 'black', 'white', null, null, null,
 | 
			
		||||
			null, null, null, null, null, null, null, null,
 | 
			
		||||
			null, null, null, null, null, null, null, null,
 | 
			
		||||
			null, null, null, null, null, null, null, null
 | 
			
		||||
		];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public setByNumber(color, n) {
 | 
			
		||||
		const ps = this.getPattern(color);
 | 
			
		||||
		this.set(color, ps[n][0], ps[n][1]);
 | 
			
		||||
		this.set2(color, ps[n][0], ps[n][1]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private write(color, x, y) {
 | 
			
		||||
		this.board[y][x] = color;
 | 
			
		||||
		const pos = x + (y * 8);
 | 
			
		||||
		this.board[pos] = color;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 石を配置します
 | 
			
		||||
	 */
 | 
			
		||||
	public set(color, x, y) {
 | 
			
		||||
	public set2(color, x, y) {
 | 
			
		||||
		this.write(color, x, y);
 | 
			
		||||
 | 
			
		||||
		const reverses = this.getReverse(color, x, y);
 | 
			
		||||
| 
						 | 
				
			
			@ -89,24 +90,42 @@ export default class Othello {
 | 
			
		|||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public set(color, pos) {
 | 
			
		||||
		const x = pos % BOARD_SIZE;
 | 
			
		||||
		const y = Math.floor(pos / BOARD_SIZE);
 | 
			
		||||
		this.set2(color, x, y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public get(x, y) {
 | 
			
		||||
		const pos = x + (y * 8);
 | 
			
		||||
		return this.board[pos];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 打つことができる場所を取得します
 | 
			
		||||
	 */
 | 
			
		||||
	public getPattern(myColor): number[][] {
 | 
			
		||||
		const result = [];
 | 
			
		||||
		this.board.forEach((stones, y) => stones.forEach((stone, x) => {
 | 
			
		||||
		this.board.forEach((stone, i) => {
 | 
			
		||||
			if (stone != null) return;
 | 
			
		||||
			if (this.canReverse(myColor, x, y)) result.push([x, y]);
 | 
			
		||||
		}));
 | 
			
		||||
			const x = i % BOARD_SIZE;
 | 
			
		||||
			const y = Math.floor(i / BOARD_SIZE);
 | 
			
		||||
			if (this.canReverse2(myColor, x, y)) result.push([x, y]);
 | 
			
		||||
		});
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 指定の位置に石を打つことができるかどうか(相手の石を1つでも反転させられるか)を取得します
 | 
			
		||||
	 */
 | 
			
		||||
	public canReverse(myColor, targetx, targety): boolean {
 | 
			
		||||
	public canReverse2(myColor, targetx, targety): boolean {
 | 
			
		||||
		return this.getReverse(myColor, targetx, targety) !== null;
 | 
			
		||||
	}
 | 
			
		||||
	public canReverse(myColor, pos): boolean {
 | 
			
		||||
		const x = pos % BOARD_SIZE;
 | 
			
		||||
		const y = Math.floor(pos / BOARD_SIZE);
 | 
			
		||||
		return this.getReverse(myColor, x, y) !== null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private getReverse(myColor, targetx, targety): number[] {
 | 
			
		||||
		const opponentColor = myColor == 'black' ? 'white' : 'black';
 | 
			
		||||
| 
						 | 
				
			
			@ -117,11 +136,11 @@ export default class Othello {
 | 
			
		|||
			return (x, y): any => {
 | 
			
		||||
				if (breaked) {
 | 
			
		||||
					return;
 | 
			
		||||
				} else if (this.board[y][x] == myColor && opponentStoneFound) {
 | 
			
		||||
				} else if (this.get(x, y) == myColor && opponentStoneFound) {
 | 
			
		||||
					return true;
 | 
			
		||||
				} else if (this.board[y][x] == myColor && !opponentStoneFound) {
 | 
			
		||||
				} else if (this.get(x, y) == myColor && !opponentStoneFound) {
 | 
			
		||||
					breaked = true;
 | 
			
		||||
				} else if (this.board[y][x] == opponentColor) {
 | 
			
		||||
				} else if (this.get(x, y) == opponentColor) {
 | 
			
		||||
					opponentStoneFound = true;
 | 
			
		||||
				} else {
 | 
			
		||||
					breaked = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -210,12 +229,13 @@ export default class Othello {
 | 
			
		|||
 | 
			
		||||
	public toString(): string {
 | 
			
		||||
		//return this.board.map(row => row.map(state => state === 'black' ? '●' : state === 'white' ? '○' : '┼').join('')).join('\n');
 | 
			
		||||
		return this.board.map(row => row.map(state => state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : '🔹').join('')).join('\n');
 | 
			
		||||
		//return this.board.map(row => row.map(state => state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : '🔹').join('')).join('\n');
 | 
			
		||||
		return 'wip';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public toPatternString(color): string {
 | 
			
		||||
		//const num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
 | 
			
		||||
		const num = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🍍'];
 | 
			
		||||
		/*const num = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🍍'];
 | 
			
		||||
 | 
			
		||||
		const pattern = this.getPattern(color);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +243,9 @@ export default class Othello {
 | 
			
		|||
			const i = pattern.findIndex(p => p[0] == x && p[1] == y);
 | 
			
		||||
			//return state === 'black' ? '●' : state === 'white' ? '○' : i != -1 ? num[i] : '┼';
 | 
			
		||||
			return state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : i != -1 ? num[i] : '🔹';
 | 
			
		||||
		}).join('')).join('\n');
 | 
			
		||||
		}).join('')).join('\n');*/
 | 
			
		||||
 | 
			
		||||
		return 'wip';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,11 +4,12 @@ import * as merge from 'object-assign-deep';
 | 
			
		|||
 | 
			
		||||
import { host, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config';
 | 
			
		||||
import Progress from './scripts/loading';
 | 
			
		||||
import HomeStreamManager from './scripts/streaming/home-stream-manager';
 | 
			
		||||
import DriveStreamManager from './scripts/streaming/drive-stream-manager';
 | 
			
		||||
import ServerStreamManager from './scripts/streaming/server-stream-manager';
 | 
			
		||||
import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
 | 
			
		||||
import MessagingIndexStreamManager from './scripts/streaming/messaging-index-stream-manager';
 | 
			
		||||
import { HomeStreamManager } from './scripts/streaming/home';
 | 
			
		||||
import { DriveStreamManager } from './scripts/streaming/drive';
 | 
			
		||||
import { ServerStreamManager } from './scripts/streaming/server';
 | 
			
		||||
import { RequestsStreamManager } from './scripts/streaming/requests';
 | 
			
		||||
import { MessagingIndexStreamManager } from './scripts/streaming/messaging-index';
 | 
			
		||||
import { OthelloStreamManager } from './scripts/streaming/othello';
 | 
			
		||||
 | 
			
		||||
import Err from '../common/views/components/connect-failed.vue';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,11 +118,13 @@ export default class MiOS extends EventEmitter {
 | 
			
		|||
		serverStream: ServerStreamManager;
 | 
			
		||||
		requestsStream: RequestsStreamManager;
 | 
			
		||||
		messagingIndexStream: MessagingIndexStreamManager;
 | 
			
		||||
		othelloStream: OthelloStreamManager;
 | 
			
		||||
	} = {
 | 
			
		||||
		driveStream: null,
 | 
			
		||||
		serverStream: null,
 | 
			
		||||
		requestsStream: null,
 | 
			
		||||
		messagingIndexStream: null
 | 
			
		||||
		messagingIndexStream: null,
 | 
			
		||||
		othelloStream: null
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			@ -169,6 +172,7 @@ export default class MiOS extends EventEmitter {
 | 
			
		|||
			// Init other stream manager
 | 
			
		||||
			this.streams.driveStream = new DriveStreamManager(this.i);
 | 
			
		||||
			this.streams.messagingIndexStream = new MessagingIndexStreamManager(this.i);
 | 
			
		||||
			this.streams.othelloStream = new OthelloStreamManager(this.i);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (this.debug) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,20 +0,0 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Connection from './drive-stream';
 | 
			
		||||
 | 
			
		||||
export default class DriveStreamManager extends StreamManager<Connection> {
 | 
			
		||||
	private me;
 | 
			
		||||
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new Connection(this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Drive stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super('drive', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/web/app/common/scripts/streaming/drive.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/web/app/common/scripts/streaming/drive.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
import StreamManager from './stream-manager';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Drive stream connection
 | 
			
		||||
 */
 | 
			
		||||
export class DriveStream extends Stream {
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super('drive', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DriveStreamManager extends StreamManager<DriveStream> {
 | 
			
		||||
	private me;
 | 
			
		||||
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new DriveStream(this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Connection from './home-stream';
 | 
			
		||||
import MiOS from '../../mios';
 | 
			
		||||
 | 
			
		||||
export default class HomeStreamManager extends StreamManager<Connection> {
 | 
			
		||||
	private me;
 | 
			
		||||
	private os: MiOS;
 | 
			
		||||
 | 
			
		||||
	constructor(os: MiOS, me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
		this.os = os;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new Connection(this.os, this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,13 @@
 | 
			
		|||
import * as merge from 'object-assign-deep';
 | 
			
		||||
 | 
			
		||||
import Stream from './stream';
 | 
			
		||||
import StreamManager from './stream-manager';
 | 
			
		||||
import MiOS from '../../mios';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Home stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
export class HomeStream extends Stream {
 | 
			
		||||
	constructor(os: MiOS, me) {
 | 
			
		||||
		super('', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
| 
						 | 
				
			
			@ -34,3 +35,23 @@ export default class Connection extends Stream {
 | 
			
		|||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class HomeStreamManager extends StreamManager<HomeStream> {
 | 
			
		||||
	private me;
 | 
			
		||||
	private os: MiOS;
 | 
			
		||||
 | 
			
		||||
	constructor(os: MiOS, me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
		this.os = os;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new HomeStream(this.os, this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,20 +0,0 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Connection from './messaging-index-stream';
 | 
			
		||||
 | 
			
		||||
export default class MessagingIndexStreamManager extends StreamManager<Connection> {
 | 
			
		||||
	private me;
 | 
			
		||||
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new Connection(this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Messaging index stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super('messaging-index', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/web/app/common/scripts/streaming/messaging-index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/web/app/common/scripts/streaming/messaging-index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
import StreamManager from './stream-manager';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Messaging index stream connection
 | 
			
		||||
 */
 | 
			
		||||
export class MessagingIndexStream extends Stream {
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super('messaging-index', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class MessagingIndexStreamManager extends StreamManager<MessagingIndexStream> {
 | 
			
		||||
	private me;
 | 
			
		||||
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new MessagingIndexStream(this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import Stream from './stream';
 | 
			
		|||
/**
 | 
			
		||||
 * Messaging stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
export class MessagingStream extends Stream {
 | 
			
		||||
	constructor(me, otherparty) {
 | 
			
		||||
		super('messaging', {
 | 
			
		||||
			i: me.token,
 | 
			
		||||
							
								
								
									
										10
									
								
								src/web/app/common/scripts/streaming/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/web/app/common/scripts/streaming/othello-game.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
export class OthelloGameStream extends Stream {
 | 
			
		||||
	constructor(me, game) {
 | 
			
		||||
		super('othello-game', {
 | 
			
		||||
			i: me.token,
 | 
			
		||||
			game: game.id
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/web/app/common/scripts/streaming/othello.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/web/app/common/scripts/streaming/othello.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
export class OthelloStream extends Stream {
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super('othello', {
 | 
			
		||||
			i: me.token
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class OthelloStreamManager extends StreamManager<OthelloStream> {
 | 
			
		||||
	private me;
 | 
			
		||||
 | 
			
		||||
	constructor(me) {
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.me = me;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new OthelloStream(this.me);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Connection from './requests-stream';
 | 
			
		||||
 | 
			
		||||
export default class RequestsStreamManager extends StreamManager<Connection> {
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new Connection();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Requests stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super('requests');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/web/app/common/scripts/streaming/requests.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/web/app/common/scripts/streaming/requests.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
import StreamManager from './stream-manager';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Requests stream connection
 | 
			
		||||
 */
 | 
			
		||||
export class RequestsStream extends Stream {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super('requests');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class RequestsStreamManager extends StreamManager<RequestsStream> {
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new RequestsStream();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import StreamManager from './stream-manager';
 | 
			
		||||
import Connection from './server-stream';
 | 
			
		||||
 | 
			
		||||
export default class ServerStreamManager extends StreamManager<Connection> {
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new Connection();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Server stream connection
 | 
			
		||||
 */
 | 
			
		||||
export default class Connection extends Stream {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super('server');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/web/app/common/scripts/streaming/server.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/web/app/common/scripts/streaming/server.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
import Stream from './stream';
 | 
			
		||||
import StreamManager from './stream-manager';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Server stream connection
 | 
			
		||||
 */
 | 
			
		||||
export class ServerStream extends Stream {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super('server');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ServerStreamManager extends StreamManager<ServerStream> {
 | 
			
		||||
	public getConnection() {
 | 
			
		||||
		if (this.connection == null) {
 | 
			
		||||
			this.connection = new ServerStream();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.connection;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import urlPreview from './url-preview.vue';
 | 
			
		|||
import twitterSetting from './twitter-setting.vue';
 | 
			
		||||
import fileTypeIcon from './file-type-icon.vue';
 | 
			
		||||
import Switch from './switch.vue';
 | 
			
		||||
import Othello from './othello.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-signin', signin);
 | 
			
		||||
Vue.component('mk-signup', signup);
 | 
			
		||||
| 
						 | 
				
			
			@ -43,3 +44,4 @@ Vue.component('mk-url-preview', urlPreview);
 | 
			
		|||
Vue.component('mk-twitter-setting', twitterSetting);
 | 
			
		||||
Vue.component('mk-file-type-icon', fileTypeIcon);
 | 
			
		||||
Vue.component('mk-switch', Switch);
 | 
			
		||||
Vue.component('mk-othello', Othello);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import MessagingStreamConnection from '../../scripts/streaming/messaging-stream';
 | 
			
		||||
import { MessagingStream } from '../../scripts/streaming/messaging';
 | 
			
		||||
import XMessage from './messaging-room.message.vue';
 | 
			
		||||
import XForm from './messaging-room.form.vue';
 | 
			
		||||
import { url } from '../../../config';
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +66,7 @@ export default Vue.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = new MessagingStreamConnection((this as any).os.i, this.user.id);
 | 
			
		||||
		this.connection = new MessagingStream((this as any).os.i, this.user.id);
 | 
			
		||||
 | 
			
		||||
		this.connection.on('message', this.onMessage);
 | 
			
		||||
		this.connection.on('read', this.onRead);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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).os.streams.messagingIndexStream.dispose(this.connectionId);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		isMe(message) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										171
									
								
								src/web/app/common/views/components/othello.game.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/web/app/common/views/components/othello.game.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div class="root">
 | 
			
		||||
	<header><b>{{ game.black_user.name }}</b>(黒) vs <b>{{ game.white_user.name }}</b>(白)</header>
 | 
			
		||||
	<p class="turn" v-if="!iAmPlayer && !isEnded">{{ turn.name }}のターンです<mk-ellipsis/></p>
 | 
			
		||||
	<p class="turn" v-if="iAmPlayer && !isEnded">{{ isMyTurn ? 'あなたのターンです' : '相手のターンです' }}<mk-ellipsis v-if="!isMyTurn"/></p>
 | 
			
		||||
	<p class="result" v-if="isEnded">
 | 
			
		||||
		<template v-if="winner"><b>{{ winner.name }}</b>の勝ち</template>
 | 
			
		||||
		<template v-else>引き分け</template>
 | 
			
		||||
	</p>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div v-for="(stone, i) in o.board"
 | 
			
		||||
			:class="{ empty: stone == null, myTurn: isMyTurn, can: o.canReverse(turn.id == game.black_user.id ? 'black' : 'white', i) }"
 | 
			
		||||
			@click="set(i)"
 | 
			
		||||
		>
 | 
			
		||||
			<img v-if="stone == 'black'" :src="`${game.black_user.avatar_url}?thumbnail&size=64`" alt="">
 | 
			
		||||
			<img v-if="stone == 'white'" :src="`${game.white_user.avatar_url}?thumbnail&size=64`" alt="">
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { OthelloGameStream } from '../../scripts/streaming/othello-game';
 | 
			
		||||
import Othello from '../../../../../common/othello';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['game'],
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			o: new Othello(),
 | 
			
		||||
			turn: null,
 | 
			
		||||
			isMyTurn: null,
 | 
			
		||||
			isEnded: false,
 | 
			
		||||
			winner: null,
 | 
			
		||||
			connection: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		iAmPlayer(): boolean {
 | 
			
		||||
			return this.game.black_user_id == (this as any).os.i.id || this.game.white_user_id == (this as any).os.i.id;
 | 
			
		||||
		},
 | 
			
		||||
		myColor(): string {
 | 
			
		||||
			return this.game.black_user_id == (this as any).os.i.id ? 'black' : 'white';
 | 
			
		||||
		},
 | 
			
		||||
		opColor(): string {
 | 
			
		||||
			return this.myColor == 'black' ? 'white' : 'black';
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		this.game.logs.forEach(log => {
 | 
			
		||||
			this.o.set(log.color, log.pos);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.turn = this.game.turn_user_id == this.game.black_user_id ? this.game.black_user : this.game.white_user;
 | 
			
		||||
		this.isMyTurn = this.game.turn_user_id == (this as any).os.i.id;
 | 
			
		||||
		this.isEnded = this.game.is_ended;
 | 
			
		||||
		this.winner = this.game.winner;
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = new OthelloGameStream((this as any).os.i, this.game);
 | 
			
		||||
		this.connection.on('set', this.onSet);
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('set', this.onSet);
 | 
			
		||||
		this.connection.close();
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		set(pos) {
 | 
			
		||||
			if (!this.isMyTurn) return;
 | 
			
		||||
			if (!this.o.canReverse(this.myColor, pos)) return;
 | 
			
		||||
			this.o.set(this.myColor, pos);
 | 
			
		||||
			if (this.o.getPattern(this.opColor).length > 0) {
 | 
			
		||||
				this.isMyTurn = !this.isMyTurn;
 | 
			
		||||
				this.turn = this.myColor == 'black' ? this.game.white_user : this.game.black_user;
 | 
			
		||||
			} else if (this.o.getPattern(this.myColor).length == 0) {
 | 
			
		||||
				this.isEnded = true;
 | 
			
		||||
				const blackCount = this.o.board.filter(s => s == 'black').length;
 | 
			
		||||
				const whiteCount = this.o.board.filter(s => s == 'white').length;
 | 
			
		||||
				this.winner = blackCount == whiteCount ? null : blackCount > whiteCount ? this.game.black_user : this.game.white_user;
 | 
			
		||||
			}
 | 
			
		||||
			this.connection.send({
 | 
			
		||||
				type: 'set',
 | 
			
		||||
				pos
 | 
			
		||||
			});
 | 
			
		||||
			this.$forceUpdate();
 | 
			
		||||
		},
 | 
			
		||||
		onSet(x) {
 | 
			
		||||
			this.o.set(x.color, x.pos);
 | 
			
		||||
			if (this.o.getPattern('black').length == 0 && this.o.getPattern('white').length == 0) {
 | 
			
		||||
				this.isEnded = true;
 | 
			
		||||
				const blackCount = this.o.board.filter(s => s == 'black').length;
 | 
			
		||||
				const whiteCount = this.o.board.filter(s => s == 'white').length;
 | 
			
		||||
				this.winner = blackCount == whiteCount ? null : blackCount > whiteCount ? this.game.black_user : this.game.white_user;
 | 
			
		||||
			} else {
 | 
			
		||||
				if (this.iAmPlayer && this.o.getPattern(this.myColor).length > 0) {
 | 
			
		||||
					this.isMyTurn = true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (x.color == 'black' && this.o.getPattern('white').length > 0) {
 | 
			
		||||
					this.turn = this.game.white_user;
 | 
			
		||||
				}
 | 
			
		||||
				if (x.color == 'black' && this.o.getPattern('white').length == 0) {
 | 
			
		||||
					this.turn = this.game.black_user;
 | 
			
		||||
				}
 | 
			
		||||
				if (x.color == 'white' && this.o.getPattern('black').length > 0) {
 | 
			
		||||
					this.turn = this.game.black_user;
 | 
			
		||||
				}
 | 
			
		||||
				if (x.color == 'white' && this.o.getPattern('black').length == 0) {
 | 
			
		||||
					this.turn = this.game.white_user;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			this.$forceUpdate();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.root
 | 
			
		||||
	text-align center
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		padding 8px
 | 
			
		||||
		border-bottom dashed 1px #c4cdd4
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		display grid
 | 
			
		||||
		grid-template-rows repeat(8, 1fr)
 | 
			
		||||
		grid-template-columns repeat(8, 1fr)
 | 
			
		||||
		grid-gap 4px
 | 
			
		||||
		width 300px
 | 
			
		||||
		height 300px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
 | 
			
		||||
		> div
 | 
			
		||||
			background transparent
 | 
			
		||||
			border-radius 6px
 | 
			
		||||
			overflow hidden
 | 
			
		||||
 | 
			
		||||
			*
 | 
			
		||||
				pointer-events none
 | 
			
		||||
				user-select none
 | 
			
		||||
 | 
			
		||||
			&.empty
 | 
			
		||||
				border solid 2px #f5f5f5
 | 
			
		||||
 | 
			
		||||
			&.empty.can
 | 
			
		||||
				background #f5f5f5
 | 
			
		||||
 | 
			
		||||
			&.empty.myTurn
 | 
			
		||||
				border-color #eee
 | 
			
		||||
 | 
			
		||||
				&.can
 | 
			
		||||
					background #eee
 | 
			
		||||
					cursor pointer
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						border-color darken($theme-color, 10%)
 | 
			
		||||
						background $theme-color
 | 
			
		||||
 | 
			
		||||
					&:active
 | 
			
		||||
						background darken($theme-color, 10%)
 | 
			
		||||
 | 
			
		||||
			> img
 | 
			
		||||
				display block
 | 
			
		||||
				width 100%
 | 
			
		||||
				height 100%
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										275
									
								
								src/web/app/common/views/components/othello.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/web/app/common/views/components/othello.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,275 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div class="mk-othello">
 | 
			
		||||
	<div v-if="game">
 | 
			
		||||
		<x-game :game="game"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="matching" v-else-if="matching">
 | 
			
		||||
		<h1><b>{{ matching.name }}</b>を待っています<mk-ellipsis/></h1>
 | 
			
		||||
		<div class="cancel">
 | 
			
		||||
			<el-button round @click="cancel">キャンセル</el-button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="index" v-else>
 | 
			
		||||
		<h1>Misskey %fa:circle%thell%fa:circle R%</h1>
 | 
			
		||||
		<p>他のMisskeyユーザーとオセロで対戦しよう</p>
 | 
			
		||||
		<div class="play">
 | 
			
		||||
			<el-button round>フリーマッチ(準備中)</el-button>
 | 
			
		||||
			<el-button type="primary" round @click="match">指名</el-button>
 | 
			
		||||
			<details>
 | 
			
		||||
				<summary>遊び方</summary>
 | 
			
		||||
				<div>
 | 
			
		||||
					<p>オセロは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
 | 
			
		||||
					<dl>
 | 
			
		||||
						<dt><b>フリーマッチ</b></dt>
 | 
			
		||||
						<dd>ランダムなユーザーと対戦するモードです。</dd>
 | 
			
		||||
						<dt><b>指名</b></dt>
 | 
			
		||||
						<dd>指定したユーザーと対戦するモードです。</dd>
 | 
			
		||||
					</dl>
 | 
			
		||||
				</div>
 | 
			
		||||
			</details>
 | 
			
		||||
		</div>
 | 
			
		||||
		<section v-if="invitations.length > 0">
 | 
			
		||||
			<h2>対局の招待があります!:</h2>
 | 
			
		||||
			<div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
 | 
			
		||||
				<img :src="`${i.parent.avatar_url}?thumbnail&size=32`" alt="">
 | 
			
		||||
				<span class="name"><b>{{ i.parent.name }}</b></span>
 | 
			
		||||
				<span class="username">@{{ i.parent.username }}</span>
 | 
			
		||||
				<mk-time :time="i.created_at"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section v-if="myGames.length > 0">
 | 
			
		||||
			<h2>自分の対局</h2>
 | 
			
		||||
			<div class="game" v-for="g in myGames" tabindex="-1" @click="game = g">
 | 
			
		||||
				<img :src="`${g.black_user.avatar_url}?thumbnail&size=32`" alt="">
 | 
			
		||||
				<img :src="`${g.white_user.avatar_url}?thumbnail&size=32`" alt="">
 | 
			
		||||
				<span><b>{{ g.black_user.name }}</b> vs <b>{{ g.white_user.name }}</b></span>
 | 
			
		||||
				<span class="state">{{ g.is_ended ? '終了' : '進行中' }}</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</section>
 | 
			
		||||
		<section v-if="games.length > 0">
 | 
			
		||||
			<h2>みんなの対局</h2>
 | 
			
		||||
			<div class="game" v-for="g in games" tabindex="-1" @click="game = g">
 | 
			
		||||
				<img :src="`${g.black_user.avatar_url}?thumbnail&size=32`" alt="">
 | 
			
		||||
				<img :src="`${g.white_user.avatar_url}?thumbnail&size=32`" alt="">
 | 
			
		||||
				<span><b>{{ g.black_user.name }}</b> vs <b>{{ g.white_user.name }}</b></span>
 | 
			
		||||
				<span class="state">{{ g.is_ended ? '終了' : '進行中' }}</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</section>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import XGame from './othello.game.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XGame
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			game: null,
 | 
			
		||||
			games: [],
 | 
			
		||||
			gamesFetching: true,
 | 
			
		||||
			gamesMoreFetching: false,
 | 
			
		||||
			myGames: [],
 | 
			
		||||
			matching: null,
 | 
			
		||||
			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('matched', this.onMatched);
 | 
			
		||||
		this.connection.on('invited', this.onInvited);
 | 
			
		||||
 | 
			
		||||
		(this as any).api('othello/games', {
 | 
			
		||||
			my: true
 | 
			
		||||
		}).then(games => {
 | 
			
		||||
			this.myGames = games;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		(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('matched', this.onMatched);
 | 
			
		||||
		this.connection.off('invited', this.onInvited);
 | 
			
		||||
		(this as any).os.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;
 | 
			
		||||
						}
 | 
			
		||||
					});
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		cancel() {
 | 
			
		||||
			this.matching = null;
 | 
			
		||||
			(this as any).api('othello/match/cancel');
 | 
			
		||||
		},
 | 
			
		||||
		accept(invitation) {
 | 
			
		||||
			(this as any).api('othello/match', {
 | 
			
		||||
				user_id: invitation.parent.id
 | 
			
		||||
			}).then(game => {
 | 
			
		||||
				if (game) {
 | 
			
		||||
					this.game = game;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		onMatched(game) {
 | 
			
		||||
			this.game = game;
 | 
			
		||||
		},
 | 
			
		||||
		onInvited(invite) {
 | 
			
		||||
			this.invitations.unshift(invite);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.mk-othello
 | 
			
		||||
	color #677f84
 | 
			
		||||
 | 
			
		||||
	> .matching
 | 
			
		||||
		> h1
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 24px
 | 
			
		||||
			font-size 20px
 | 
			
		||||
			text-align center
 | 
			
		||||
			font-weight normal
 | 
			
		||||
 | 
			
		||||
		> .cancel
 | 
			
		||||
			margin 0 auto
 | 
			
		||||
			padding 24px 0 0 0
 | 
			
		||||
			max-width 200px
 | 
			
		||||
			text-align center
 | 
			
		||||
			border-top dashed 1px #c4cdd4
 | 
			
		||||
 | 
			
		||||
	> .index
 | 
			
		||||
		> h1
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 24px
 | 
			
		||||
			font-size 24px
 | 
			
		||||
			text-align center
 | 
			
		||||
			font-weight normal
 | 
			
		||||
			color #fff
 | 
			
		||||
			background linear-gradient(to bottom, #8bca3e, #d6cf31)
 | 
			
		||||
 | 
			
		||||
			& + p
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 12px
 | 
			
		||||
				margin-bottom 12px
 | 
			
		||||
				text-align center
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				border-bottom solid 1px #d3d9dc
 | 
			
		||||
 | 
			
		||||
		> .play
 | 
			
		||||
			margin 0 auto
 | 
			
		||||
			padding 0 16px
 | 
			
		||||
			max-width 500px
 | 
			
		||||
			text-align center
 | 
			
		||||
 | 
			
		||||
			> details
 | 
			
		||||
				margin 8px 0
 | 
			
		||||
 | 
			
		||||
				> div
 | 
			
		||||
					padding 16px
 | 
			
		||||
					font-size 14px
 | 
			
		||||
					text-align left
 | 
			
		||||
					background #f5f5f5
 | 
			
		||||
					border-radius 8px
 | 
			
		||||
 | 
			
		||||
		> section
 | 
			
		||||
			margin 0 auto
 | 
			
		||||
			padding 0 16px 16px 16px
 | 
			
		||||
			max-width 500px
 | 
			
		||||
			border-top solid 1px #d3d9dc
 | 
			
		||||
 | 
			
		||||
			> h2
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 16px 0 8px 0
 | 
			
		||||
				font-size 16px
 | 
			
		||||
				font-weight bold
 | 
			
		||||
 | 
			
		||||
	.invitation
 | 
			
		||||
		margin 8px 0
 | 
			
		||||
		padding 8px
 | 
			
		||||
		border solid 1px #e1e5e8
 | 
			
		||||
		border-radius 6px
 | 
			
		||||
		cursor pointer
 | 
			
		||||
 | 
			
		||||
		*
 | 
			
		||||
			pointer-events none
 | 
			
		||||
			user-select none
 | 
			
		||||
 | 
			
		||||
		&:focus
 | 
			
		||||
			border-color $theme-color
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			background #f5f5f5
 | 
			
		||||
 | 
			
		||||
		&:active
 | 
			
		||||
			background #eee
 | 
			
		||||
 | 
			
		||||
		> img
 | 
			
		||||
			vertical-align bottom
 | 
			
		||||
			border-radius 100%
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			margin 0 8px
 | 
			
		||||
			line-height 32px
 | 
			
		||||
 | 
			
		||||
	.game
 | 
			
		||||
		margin 8px 0
 | 
			
		||||
		padding 8px
 | 
			
		||||
		border solid 1px #e1e5e8
 | 
			
		||||
		border-radius 6px
 | 
			
		||||
		cursor pointer
 | 
			
		||||
 | 
			
		||||
		*
 | 
			
		||||
			pointer-events none
 | 
			
		||||
			user-select none
 | 
			
		||||
 | 
			
		||||
		&:focus
 | 
			
		||||
			border-color $theme-color
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			background #f5f5f5
 | 
			
		||||
 | 
			
		||||
		&:active
 | 
			
		||||
			background #eee
 | 
			
		||||
 | 
			
		||||
		> img
 | 
			
		||||
			vertical-align bottom
 | 
			
		||||
			border-radius 100%
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			margin 0 8px
 | 
			
		||||
			line-height 32px
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										24
									
								
								src/web/app/desktop/views/components/game-window.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/web/app/desktop/views/components/game-window.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
<template>
 | 
			
		||||
<mk-window ref="window" width="500px" height="560px" @closed="$destroy">
 | 
			
		||||
	<span slot="header" :class="$style.header">%fa:gamepad%オセロ</span>
 | 
			
		||||
	<mk-othello :class="$style.content"/>
 | 
			
		||||
</mk-window>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.header
 | 
			
		||||
	> [data-fa]
 | 
			
		||||
		margin-right 4px
 | 
			
		||||
 | 
			
		||||
.content
 | 
			
		||||
	height 100%
 | 
			
		||||
	overflow auto
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,13 @@
 | 
			
		|||
					<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li class="game">
 | 
			
		||||
				<a @click="game">
 | 
			
		||||
					%fa:gamepad%
 | 
			
		||||
					<p>ゲーム</p>
 | 
			
		||||
					<template v-if="hasGameInvitations">%fa:circle%</template>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
		</template>
 | 
			
		||||
		<li class="ch">
 | 
			
		||||
			<a :href="chUrl" target="_blank">
 | 
			
		||||
| 
						 | 
				
			
			@ -22,12 +29,6 @@
 | 
			
		|||
				<p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p>
 | 
			
		||||
			</a>
 | 
			
		||||
		</li>
 | 
			
		||||
		<li class="info">
 | 
			
		||||
			<a href="https://twitter.com/misskey_xyz" target="_blank">
 | 
			
		||||
				%fa:info%
 | 
			
		||||
				<p>%i18n:desktop.tags.mk-ui-header-nav.info%</p>
 | 
			
		||||
			</a>
 | 
			
		||||
		</li>
 | 
			
		||||
	</ul>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -36,11 +37,13 @@
 | 
			
		|||
import Vue from 'vue';
 | 
			
		||||
import { chUrl } from '../../../config';
 | 
			
		||||
import MkMessagingWindow from './messaging-window.vue';
 | 
			
		||||
import MkGameWindow from './game-window.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			hasUnreadMessagingMessages: false,
 | 
			
		||||
			hasGameInvitations: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			chUrl
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +83,10 @@ export default Vue.extend({
 | 
			
		|||
 | 
			
		||||
		messaging() {
 | 
			
		||||
			(this as any).os.new(MkMessagingWindow);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		game() {
 | 
			
		||||
			(this as any).os.new(MkGameWindow);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import ChannelStream from '../../../common/scripts/streaming/channel-stream';
 | 
			
		||||
import ChannelStream from '../../../common/scripts/streaming/channel';
 | 
			
		||||
import XForm from './channel.channel.form.vue';
 | 
			
		||||
import XPost from './channel.channel.post.vue';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
export default function(opts) {
 | 
			
		||||
	return new Promise<string>((res, rej) => {
 | 
			
		||||
		alert('input not implemented yet');
 | 
			
		||||
		const x = window.prompt(opts.title);
 | 
			
		||||
		if (x) {
 | 
			
		||||
			res(x);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
 | 
			
		||||
// Style
 | 
			
		||||
import './style.styl';
 | 
			
		||||
import '../../element.scss';
 | 
			
		||||
 | 
			
		||||
import init from '../init';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +29,7 @@ import MkFollowers from './views/pages/followers.vue';
 | 
			
		|||
import MkFollowing from './views/pages/following.vue';
 | 
			
		||||
import MkSettings from './views/pages/settings.vue';
 | 
			
		||||
import MkProfileSetting from './views/pages/profile-setting.vue';
 | 
			
		||||
import MkOthello from './views/pages/othello.vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * init
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +69,7 @@ init((launch) => {
 | 
			
		|||
		{ path: '/i/drive/file/:file', component: MkDrive },
 | 
			
		||||
		{ path: '/selectdrive', component: MkSelectDrive },
 | 
			
		||||
		{ path: '/search', component: MkSearch },
 | 
			
		||||
		{ path: '/game/othello', component: MkOthello },
 | 
			
		||||
		{ path: '/:user', component: MkUser },
 | 
			
		||||
		{ path: '/:user/followers', component: MkFollowers },
 | 
			
		||||
		{ path: '/:user/following', component: MkFollowing },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@
 | 
			
		|||
					<li><router-link to="/">%fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%</router-link></li>
 | 
			
		||||
					<li><router-link to="/i/notifications">%fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%<template v-if="hasUnreadNotifications">%fa:circle%</template>%fa:angle-right%</router-link></li>
 | 
			
		||||
					<li><router-link to="/i/messaging">%fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>%fa:angle-right%</router-link></li>
 | 
			
		||||
					<li><router-link to="/game/othello">%fa:gamepad%ゲーム%fa:angle-right%</router-link></li>
 | 
			
		||||
				</ul>
 | 
			
		||||
				<ul>
 | 
			
		||||
					<li><a :href="chUrl" target="_blank">%fa:tv%%i18n:mobile.tags.mk-ui-nav.ch%%fa:angle-right%</a></li>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										16
									
								
								src/web/app/mobile/views/pages/othello.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/web/app/mobile/views/pages/othello.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:gamepad%オセロ</span>
 | 
			
		||||
	<mk-othello/>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = 'Misskey オセロ';
 | 
			
		||||
		document.documentElement.style.background = '#fff';
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue