commit
						a227ef30e8
					
				
					 47 changed files with 1091 additions and 177 deletions
				
			
		| 
						 | 
					@ -475,8 +475,8 @@ class OthelloContext extends Context {
 | 
				
			||||||
		othelloAi('white', this.othello);
 | 
							othelloAi('white', this.othello);
 | 
				
			||||||
		if (this.othello.getPattern('black').length === 0) {
 | 
							if (this.othello.getPattern('black').length === 0) {
 | 
				
			||||||
			this.bot.clearContext();
 | 
								this.bot.clearContext();
 | 
				
			||||||
			const blackCount = this.othello.board.map(row => row.filter(s => s == 'black').length).reduce((a, b) => a + b);
 | 
								const blackCount = this.othello.board.filter(s => s == 'black').length;
 | 
				
			||||||
			const whiteCount = this.othello.board.map(row => row.filter(s => s == 'white').length).reduce((a, b) => a + b);
 | 
								const whiteCount = this.othello.board.filter(s => s == 'white').length;
 | 
				
			||||||
			const winner = blackCount == whiteCount ? '引き分け' : blackCount > whiteCount ? '黒の勝ち' : '白の勝ち';
 | 
								const winner = blackCount == whiteCount ? '引き分け' : blackCount > whiteCount ? '黒の勝ち' : '白の勝ち';
 | 
				
			||||||
			return this.othello.toString() + `\n\n~終了~\n\n黒${blackCount}、白${whiteCount}で${winner}です。`;
 | 
								return this.othello.toString() + `\n\n~終了~\n\n黒${blackCount}、白${whiteCount}で${winner}です。`;
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,6 +233,26 @@ const endpoints: Endpoint[] = [
 | 
				
			||||||
		kind: 'notification-read'
 | 
							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',
 | 
							name: 'mute/create',
 | 
				
			||||||
		withCredential: true,
 | 
							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);
 | 
							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 {
 | 
						public publishChannelStream(channelId: ID, type: string, value?: any): void {
 | 
				
			||||||
		this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value);
 | 
							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 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);
 | 
					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 * as redis from 'redis';
 | 
				
			||||||
import read from '../common/read-messaging-message';
 | 
					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;
 | 
						const otherparty = request.resourceURL.query.otherparty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Subscribe messaging stream
 | 
						// 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();
 | 
					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 => {
 | 
						const onRequest = request => {
 | 
				
			||||||
		connection.send(JSON.stringify({
 | 
							connection.send(JSON.stringify({
 | 
				
			||||||
			type: 'request',
 | 
								type: 'request',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import Xev from 'xev';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ev = new 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 => {
 | 
						const onStats = stats => {
 | 
				
			||||||
		connection.send(JSON.stringify({
 | 
							connection.send(JSON.stringify({
 | 
				
			||||||
			type: 'stats',
 | 
								type: 'stats',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,8 @@ import homeStream from './stream/home';
 | 
				
			||||||
import driveStream from './stream/drive';
 | 
					import driveStream from './stream/drive';
 | 
				
			||||||
import messagingStream from './stream/messaging';
 | 
					import messagingStream from './stream/messaging';
 | 
				
			||||||
import messagingIndexStream from './stream/messaging-index';
 | 
					import messagingIndexStream from './stream/messaging-index';
 | 
				
			||||||
 | 
					import othelloGameStream from './stream/othello-game';
 | 
				
			||||||
 | 
					import othelloStream from './stream/othello';
 | 
				
			||||||
import serverStream from './stream/server';
 | 
					import serverStream from './stream/server';
 | 
				
			||||||
import requestsStream from './stream/requests';
 | 
					import requestsStream from './stream/requests';
 | 
				
			||||||
import channelStream from './stream/channel';
 | 
					import channelStream from './stream/channel';
 | 
				
			||||||
| 
						 | 
					@ -62,6 +64,8 @@ module.exports = (server: http.Server) => {
 | 
				
			||||||
			request.resourceURL.pathname === '/drive' ? driveStream :
 | 
								request.resourceURL.pathname === '/drive' ? driveStream :
 | 
				
			||||||
			request.resourceURL.pathname === '/messaging' ? messagingStream :
 | 
								request.resourceURL.pathname === '/messaging' ? messagingStream :
 | 
				
			||||||
			request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream :
 | 
								request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream :
 | 
				
			||||||
 | 
								request.resourceURL.pathname === '/othello-game' ? othelloGameStream :
 | 
				
			||||||
 | 
								request.resourceURL.pathname === '/othello' ? othelloStream :
 | 
				
			||||||
			null;
 | 
								null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (channel !== null) {
 | 
							if (channel !== null) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,37 +1,38 @@
 | 
				
			||||||
const BOARD_SIZE = 8;
 | 
					const BOARD_SIZE = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Othello {
 | 
					export default class Othello {
 | 
				
			||||||
	public board: Array<Array<'black' | 'white'>>;
 | 
						public board: Array<'black' | 'white'>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * ゲームを初期化します
 | 
						 * ゲームを初期化します
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	constructor() {
 | 
						constructor() {
 | 
				
			||||||
		this.board = [
 | 
							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, 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, '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],
 | 
								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) {
 | 
						public setByNumber(color, n) {
 | 
				
			||||||
		const ps = this.getPattern(color);
 | 
							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) {
 | 
						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);
 | 
							this.write(color, x, y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const reverses = this.getReverse(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[][] {
 | 
						public getPattern(myColor): number[][] {
 | 
				
			||||||
		const result = [];
 | 
							const result = [];
 | 
				
			||||||
		this.board.forEach((stones, y) => stones.forEach((stone, x) => {
 | 
							this.board.forEach((stone, i) => {
 | 
				
			||||||
			if (stone != null) return;
 | 
								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;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * 指定の位置に石を打つことができるかどうか(相手の石を1つでも反転させられるか)を取得します
 | 
						 * 指定の位置に石を打つことができるかどうか(相手の石を1つでも反転させられるか)を取得します
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public canReverse(myColor, targetx, targety): boolean {
 | 
						public canReverse2(myColor, targetx, targety): boolean {
 | 
				
			||||||
		return this.getReverse(myColor, targetx, targety) !== null;
 | 
							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[] {
 | 
						private getReverse(myColor, targetx, targety): number[] {
 | 
				
			||||||
		const opponentColor = myColor == 'black' ? 'white' : 'black';
 | 
							const opponentColor = myColor == 'black' ? 'white' : 'black';
 | 
				
			||||||
| 
						 | 
					@ -117,11 +136,11 @@ export default class Othello {
 | 
				
			||||||
			return (x, y): any => {
 | 
								return (x, y): any => {
 | 
				
			||||||
				if (breaked) {
 | 
									if (breaked) {
 | 
				
			||||||
					return;
 | 
										return;
 | 
				
			||||||
				} else if (this.board[y][x] == myColor && opponentStoneFound) {
 | 
									} else if (this.get(x, y) == myColor && opponentStoneFound) {
 | 
				
			||||||
					return true;
 | 
										return true;
 | 
				
			||||||
				} else if (this.board[y][x] == myColor && !opponentStoneFound) {
 | 
									} else if (this.get(x, y) == myColor && !opponentStoneFound) {
 | 
				
			||||||
					breaked = true;
 | 
										breaked = true;
 | 
				
			||||||
				} else if (this.board[y][x] == opponentColor) {
 | 
									} else if (this.get(x, y) == opponentColor) {
 | 
				
			||||||
					opponentStoneFound = true;
 | 
										opponentStoneFound = true;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					breaked = true;
 | 
										breaked = true;
 | 
				
			||||||
| 
						 | 
					@ -210,12 +229,13 @@ export default class Othello {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public toString(): string {
 | 
						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 this.board.map(row => row.map(state => state === 'black' ? '⚫️' : state === 'white' ? '⚪️' : '🔹').join('')).join('\n');
 | 
				
			||||||
 | 
							return 'wip';
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public toPatternString(color): string {
 | 
						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 num = ['0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟', '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🍈', '🍒', '🍑', '🍍'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const pattern = this.getPattern(color);
 | 
							const pattern = this.getPattern(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,7 +243,9 @@ export default class Othello {
 | 
				
			||||||
			const i = pattern.findIndex(p => p[0] == x && p[1] == y);
 | 
								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] : '┼';
 | 
				
			||||||
			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 { host, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config';
 | 
				
			||||||
import Progress from './scripts/loading';
 | 
					import Progress from './scripts/loading';
 | 
				
			||||||
import HomeStreamManager from './scripts/streaming/home-stream-manager';
 | 
					import { HomeStreamManager } from './scripts/streaming/home';
 | 
				
			||||||
import DriveStreamManager from './scripts/streaming/drive-stream-manager';
 | 
					import { DriveStreamManager } from './scripts/streaming/drive';
 | 
				
			||||||
import ServerStreamManager from './scripts/streaming/server-stream-manager';
 | 
					import { ServerStreamManager } from './scripts/streaming/server';
 | 
				
			||||||
import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
 | 
					import { RequestsStreamManager } from './scripts/streaming/requests';
 | 
				
			||||||
import MessagingIndexStreamManager from './scripts/streaming/messaging-index-stream-manager';
 | 
					import { MessagingIndexStreamManager } from './scripts/streaming/messaging-index';
 | 
				
			||||||
 | 
					import { OthelloStreamManager } from './scripts/streaming/othello';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Err from '../common/views/components/connect-failed.vue';
 | 
					import Err from '../common/views/components/connect-failed.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,11 +118,13 @@ export default class MiOS extends EventEmitter {
 | 
				
			||||||
		serverStream: ServerStreamManager;
 | 
							serverStream: ServerStreamManager;
 | 
				
			||||||
		requestsStream: RequestsStreamManager;
 | 
							requestsStream: RequestsStreamManager;
 | 
				
			||||||
		messagingIndexStream: MessagingIndexStreamManager;
 | 
							messagingIndexStream: MessagingIndexStreamManager;
 | 
				
			||||||
 | 
							othelloStream: OthelloStreamManager;
 | 
				
			||||||
	} = {
 | 
						} = {
 | 
				
			||||||
		driveStream: null,
 | 
							driveStream: null,
 | 
				
			||||||
		serverStream: null,
 | 
							serverStream: null,
 | 
				
			||||||
		requestsStream: null,
 | 
							requestsStream: null,
 | 
				
			||||||
		messagingIndexStream: null
 | 
							messagingIndexStream: null,
 | 
				
			||||||
 | 
							othelloStream: null
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -169,6 +172,7 @@ export default class MiOS extends EventEmitter {
 | 
				
			||||||
			// Init other stream manager
 | 
								// Init other stream manager
 | 
				
			||||||
			this.streams.driveStream = new DriveStreamManager(this.i);
 | 
								this.streams.driveStream = new DriveStreamManager(this.i);
 | 
				
			||||||
			this.streams.messagingIndexStream = new MessagingIndexStreamManager(this.i);
 | 
								this.streams.messagingIndexStream = new MessagingIndexStreamManager(this.i);
 | 
				
			||||||
 | 
								this.streams.othelloStream = new OthelloStreamManager(this.i);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this.debug) {
 | 
							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 * as merge from 'object-assign-deep';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Stream from './stream';
 | 
					import Stream from './stream';
 | 
				
			||||||
 | 
					import StreamManager from './stream-manager';
 | 
				
			||||||
import MiOS from '../../mios';
 | 
					import MiOS from '../../mios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Home stream connection
 | 
					 * Home stream connection
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default class Connection extends Stream {
 | 
					export class HomeStream extends Stream {
 | 
				
			||||||
	constructor(os: MiOS, me) {
 | 
						constructor(os: MiOS, me) {
 | 
				
			||||||
		super('', {
 | 
							super('', {
 | 
				
			||||||
			i: me.token
 | 
								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
 | 
					 * Messaging stream connection
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default class Connection extends Stream {
 | 
					export class MessagingStream extends Stream {
 | 
				
			||||||
	constructor(me, otherparty) {
 | 
						constructor(me, otherparty) {
 | 
				
			||||||
		super('messaging', {
 | 
							super('messaging', {
 | 
				
			||||||
			i: me.token,
 | 
								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 twitterSetting from './twitter-setting.vue';
 | 
				
			||||||
import fileTypeIcon from './file-type-icon.vue';
 | 
					import fileTypeIcon from './file-type-icon.vue';
 | 
				
			||||||
import Switch from './switch.vue';
 | 
					import Switch from './switch.vue';
 | 
				
			||||||
 | 
					import Othello from './othello.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.component('mk-signin', signin);
 | 
					Vue.component('mk-signin', signin);
 | 
				
			||||||
Vue.component('mk-signup', signup);
 | 
					Vue.component('mk-signup', signup);
 | 
				
			||||||
| 
						 | 
					@ -43,3 +44,4 @@ Vue.component('mk-url-preview', urlPreview);
 | 
				
			||||||
Vue.component('mk-twitter-setting', twitterSetting);
 | 
					Vue.component('mk-twitter-setting', twitterSetting);
 | 
				
			||||||
Vue.component('mk-file-type-icon', fileTypeIcon);
 | 
					Vue.component('mk-file-type-icon', fileTypeIcon);
 | 
				
			||||||
Vue.component('mk-switch', Switch);
 | 
					Vue.component('mk-switch', Switch);
 | 
				
			||||||
 | 
					Vue.component('mk-othello', Othello);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					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 XMessage from './messaging-room.message.vue';
 | 
				
			||||||
import XForm from './messaging-room.form.vue';
 | 
					import XForm from './messaging-room.form.vue';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						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('message', this.onMessage);
 | 
				
			||||||
		this.connection.on('read', this.onRead);
 | 
							this.connection.on('read', this.onRead);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ export default Vue.extend({
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		this.connection.off('message', this.onMessage);
 | 
							this.connection.off('message', this.onMessage);
 | 
				
			||||||
		this.connection.off('read', this.onRead);
 | 
							this.connection.off('read', this.onRead);
 | 
				
			||||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
							(this as any).os.streams.messagingIndexStream.dispose(this.connectionId);
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		isMe(message) {
 | 
							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>
 | 
										<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>
 | 
				
			||||||
				</a>
 | 
									</a>
 | 
				
			||||||
			</li>
 | 
								</li>
 | 
				
			||||||
 | 
								<li class="game">
 | 
				
			||||||
 | 
									<a @click="game">
 | 
				
			||||||
 | 
										%fa:gamepad%
 | 
				
			||||||
 | 
										<p>ゲーム</p>
 | 
				
			||||||
 | 
										<template v-if="hasGameInvitations">%fa:circle%</template>
 | 
				
			||||||
 | 
									</a>
 | 
				
			||||||
 | 
								</li>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
		<li class="ch">
 | 
							<li class="ch">
 | 
				
			||||||
			<a :href="chUrl" target="_blank">
 | 
								<a :href="chUrl" target="_blank">
 | 
				
			||||||
| 
						 | 
					@ -22,12 +29,6 @@
 | 
				
			||||||
				<p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p>
 | 
									<p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p>
 | 
				
			||||||
			</a>
 | 
								</a>
 | 
				
			||||||
		</li>
 | 
							</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>
 | 
						</ul>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -36,11 +37,13 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { chUrl } from '../../../config';
 | 
					import { chUrl } from '../../../config';
 | 
				
			||||||
import MkMessagingWindow from './messaging-window.vue';
 | 
					import MkMessagingWindow from './messaging-window.vue';
 | 
				
			||||||
 | 
					import MkGameWindow from './game-window.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			hasUnreadMessagingMessages: false,
 | 
								hasUnreadMessagingMessages: false,
 | 
				
			||||||
 | 
								hasGameInvitations: false,
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			connectionId: null,
 | 
								connectionId: null,
 | 
				
			||||||
			chUrl
 | 
								chUrl
 | 
				
			||||||
| 
						 | 
					@ -80,6 +83,10 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		messaging() {
 | 
							messaging() {
 | 
				
			||||||
			(this as any).os.new(MkMessagingWindow);
 | 
								(this as any).os.new(MkMessagingWindow);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							game() {
 | 
				
			||||||
 | 
								(this as any).os.new(MkGameWindow);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					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 XForm from './channel.channel.form.vue';
 | 
				
			||||||
import XPost from './channel.channel.post.vue';
 | 
					import XPost from './channel.channel.post.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,8 @@
 | 
				
			||||||
export default function(opts) {
 | 
					export default function(opts) {
 | 
				
			||||||
	return new Promise<string>((res, rej) => {
 | 
						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
 | 
					// Style
 | 
				
			||||||
import './style.styl';
 | 
					import './style.styl';
 | 
				
			||||||
 | 
					import '../../element.scss';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import init from '../init';
 | 
					import init from '../init';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +29,7 @@ import MkFollowers from './views/pages/followers.vue';
 | 
				
			||||||
import MkFollowing from './views/pages/following.vue';
 | 
					import MkFollowing from './views/pages/following.vue';
 | 
				
			||||||
import MkSettings from './views/pages/settings.vue';
 | 
					import MkSettings from './views/pages/settings.vue';
 | 
				
			||||||
import MkProfileSetting from './views/pages/profile-setting.vue';
 | 
					import MkProfileSetting from './views/pages/profile-setting.vue';
 | 
				
			||||||
 | 
					import MkOthello from './views/pages/othello.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * init
 | 
					 * init
 | 
				
			||||||
| 
						 | 
					@ -67,6 +69,7 @@ init((launch) => {
 | 
				
			||||||
		{ path: '/i/drive/file/:file', component: MkDrive },
 | 
							{ path: '/i/drive/file/:file', component: MkDrive },
 | 
				
			||||||
		{ path: '/selectdrive', component: MkSelectDrive },
 | 
							{ path: '/selectdrive', component: MkSelectDrive },
 | 
				
			||||||
		{ path: '/search', component: MkSearch },
 | 
							{ path: '/search', component: MkSearch },
 | 
				
			||||||
 | 
							{ path: '/game/othello', component: MkOthello },
 | 
				
			||||||
		{ path: '/:user', component: MkUser },
 | 
							{ path: '/:user', component: MkUser },
 | 
				
			||||||
		{ path: '/:user/followers', component: MkFollowers },
 | 
							{ path: '/:user/followers', component: MkFollowers },
 | 
				
			||||||
		{ path: '/:user/following', component: MkFollowing },
 | 
							{ 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="/">%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/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="/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>
 | 
				
			||||||
				<ul>
 | 
									<ul>
 | 
				
			||||||
					<li><a :href="chUrl" target="_blank">%fa:tv%%i18n:mobile.tags.mk-ui-nav.ch%%fa:angle-right%</a></li>
 | 
										<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