bye reversi
This commit is contained in:
		
							parent
							
								
									45211e14b3
								
							
						
					
					
						commit
						b267a504ca
					
				
					 39 changed files with 3 additions and 5226 deletions
				
			
		|  | @ -12,6 +12,8 @@ | ||||||
| ### Changes | ### Changes | ||||||
| - Room機能が削除されました | - Room機能が削除されました | ||||||
|   - 後日別リポジトリとして復活予定です |   - 後日別リポジトリとして復活予定です | ||||||
|  | - リバーシ機能が削除されました | ||||||
|  |   - 後日別リポジトリとして復活予定です | ||||||
| - Chat UIが削除されました | - Chat UIが削除されました | ||||||
| 
 | 
 | ||||||
| ### Improvements | ### Improvements | ||||||
|  |  | ||||||
|  | @ -242,7 +242,6 @@ uploadFromUrlDescription: "アップロードしたいファイルのURL" | ||||||
| uploadFromUrlRequested: "アップロードをリクエストしました" | uploadFromUrlRequested: "アップロードをリクエストしました" | ||||||
| uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。" | uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。" | ||||||
| explore: "みつける" | explore: "みつける" | ||||||
| games: "Misskey Games" |  | ||||||
| messageRead: "既読" | messageRead: "既読" | ||||||
| noMoreHistory: "これより過去の履歴はありません" | noMoreHistory: "これより過去の履歴はありません" | ||||||
| startMessaging: "チャットを開始" | startMessaging: "チャットを開始" | ||||||
|  | @ -669,7 +668,6 @@ emailVerified: "メールアドレスが確認されました" | ||||||
| noteFavoritesCount: "お気に入りノートの数" | noteFavoritesCount: "お気に入りノートの数" | ||||||
| pageLikesCount: "Pageにいいねした数" | pageLikesCount: "Pageにいいねした数" | ||||||
| pageLikedCount: "Pageにいいねされた数" | pageLikedCount: "Pageにいいねされた数" | ||||||
| reversiCount: "リバーシの対局数" |  | ||||||
| contact: "連絡先" | contact: "連絡先" | ||||||
| useSystemFont: "システムのデフォルトのフォントを使う" | useSystemFont: "システムのデフォルトのフォントを使う" | ||||||
| clips: "クリップ" | clips: "クリップ" | ||||||
|  | @ -957,40 +955,6 @@ _mfm: | ||||||
|   rotate: "回転" |   rotate: "回転" | ||||||
|   rotateDescription: "指定した角度で回転させます。" |   rotateDescription: "指定した角度で回転させます。" | ||||||
| 
 | 
 | ||||||
| _reversi: |  | ||||||
|   reversi: "リバーシ" |  | ||||||
|   gameSettings: "対局の設定" |  | ||||||
|   chooseBoard: "ボードを選択" |  | ||||||
|   blackOrWhite: "先行/後攻" |  | ||||||
|   blackIs: "{name}が黒(先行)" |  | ||||||
|   rules: "ルール" |  | ||||||
|   botSettings: "Botのオプション" |  | ||||||
|   thisGameIsStartedSoon: "対局は数秒後に開始されます" |  | ||||||
|   waitingForOther: "相手の準備が完了するのを待っています" |  | ||||||
|   waitingForMe: "あなたの準備が完了するのを待っています" |  | ||||||
|   waitingBoth: "準備してください" |  | ||||||
|   ready: "準備完了" |  | ||||||
|   cancelReady: "準備を再開" |  | ||||||
|   opponentTurn: "相手のターンです" |  | ||||||
|   myTurn: "あなたのターンです" |  | ||||||
|   turnOf: "{name}のターンです" |  | ||||||
|   pastTurnOf: "{name}のターン" |  | ||||||
|   surrender: "投了" |  | ||||||
|   surrendered: "投了により" |  | ||||||
|   drawn: "引き分け" |  | ||||||
|   won: "{name}の勝ち" |  | ||||||
|   black: "黒" |  | ||||||
|   white: "白" |  | ||||||
|   total: "合計" |  | ||||||
|   turnCount: "{count}ターン目" |  | ||||||
|   myGames: "自分の対局" |  | ||||||
|   allGames: "みんなの対局" |  | ||||||
|   ended: "終了" |  | ||||||
|   playing: "対局中" |  | ||||||
|   isLlotheo: "石の少ない方が勝ち(ロセオ)" |  | ||||||
|   loopedMap: "ループマップ" |  | ||||||
|   canPutEverywhere: "どこでも置けるモード" |  | ||||||
| 
 |  | ||||||
| _instanceTicker: | _instanceTicker: | ||||||
|   none: "表示しない" |   none: "表示しない" | ||||||
|   remote: "リモートユーザーに表示" |   remote: "リモートユーザーに表示" | ||||||
|  | @ -1118,8 +1082,6 @@ _sfx: | ||||||
|   chatBg: "チャット(バックグラウンド)" |   chatBg: "チャット(バックグラウンド)" | ||||||
|   antenna: "アンテナ受信" |   antenna: "アンテナ受信" | ||||||
|   channel: "チャンネル通知" |   channel: "チャンネル通知" | ||||||
|   reversiPutBlack: "リバーシ: 黒が打ったとき" |  | ||||||
|   reversiPutWhite: "リバーシ: 白が打ったとき" |  | ||||||
| 
 | 
 | ||||||
| _ago: | _ago: | ||||||
|   unknown: "謎" |   unknown: "謎" | ||||||
|  |  | ||||||
|  | @ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin'; | ||||||
| import { AuthSession } from '@/models/entities/auth-session'; | import { AuthSession } from '@/models/entities/auth-session'; | ||||||
| import { FollowRequest } from '@/models/entities/follow-request'; | import { FollowRequest } from '@/models/entities/follow-request'; | ||||||
| import { Emoji } from '@/models/entities/emoji'; | import { Emoji } from '@/models/entities/emoji'; | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { ReversiMatching } from '@/models/entities/games/reversi/matching'; |  | ||||||
| import { UserNotePining } from '@/models/entities/user-note-pining'; | import { UserNotePining } from '@/models/entities/user-note-pining'; | ||||||
| import { Poll } from '@/models/entities/poll'; | import { Poll } from '@/models/entities/poll'; | ||||||
| import { UserKeypair } from '@/models/entities/user-keypair'; | import { UserKeypair } from '@/models/entities/user-keypair'; | ||||||
|  | @ -166,8 +164,6 @@ export const entities = [ | ||||||
| 	AntennaNote, | 	AntennaNote, | ||||||
| 	PromoNote, | 	PromoNote, | ||||||
| 	PromoRead, | 	PromoRead, | ||||||
| 	ReversiGame, |  | ||||||
| 	ReversiMatching, |  | ||||||
| 	Relay, | 	Relay, | ||||||
| 	MutedNote, | 	MutedNote, | ||||||
| 	Channel, | 	Channel, | ||||||
|  |  | ||||||
|  | @ -1,263 +0,0 @@ | ||||||
| import { count, concat } from '@/prelude/array'; |  | ||||||
| 
 |  | ||||||
| // MISSKEY REVERSI ENGINE
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * true ... 黒 |  | ||||||
|  * false ... 白 |  | ||||||
|  */ |  | ||||||
| export type Color = boolean; |  | ||||||
| const BLACK = true; |  | ||||||
| const WHITE = false; |  | ||||||
| 
 |  | ||||||
| export type MapPixel = 'null' | 'empty'; |  | ||||||
| 
 |  | ||||||
| export type Options = { |  | ||||||
| 	isLlotheo: boolean; |  | ||||||
| 	canPutEverywhere: boolean; |  | ||||||
| 	loopedBoard: boolean; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export type Undo = { |  | ||||||
| 	/** |  | ||||||
| 	 * 色 |  | ||||||
| 	 */ |  | ||||||
| 	color: Color; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * どこに打ったか |  | ||||||
| 	 */ |  | ||||||
| 	pos: number; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 反転した石の位置の配列 |  | ||||||
| 	 */ |  | ||||||
| 	effects: number[]; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ターン |  | ||||||
| 	 */ |  | ||||||
| 	turn: Color | null; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * リバーシエンジン |  | ||||||
|  */ |  | ||||||
| export default class Reversi { |  | ||||||
| 	public map: MapPixel[]; |  | ||||||
| 	public mapWidth: number; |  | ||||||
| 	public mapHeight: number; |  | ||||||
| 	public board: (Color | null | undefined)[]; |  | ||||||
| 	public turn: Color | null = BLACK; |  | ||||||
| 	public opts: Options; |  | ||||||
| 
 |  | ||||||
| 	public prevPos = -1; |  | ||||||
| 	public prevColor: Color | null = null; |  | ||||||
| 
 |  | ||||||
| 	private logs: Undo[] = []; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームを初期化します |  | ||||||
| 	 */ |  | ||||||
| 	constructor(map: string[], opts: Options) { |  | ||||||
| 		//#region binds
 |  | ||||||
| 		this.put = this.put.bind(this); |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		//#region Options
 |  | ||||||
| 		this.opts = opts; |  | ||||||
| 		if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; |  | ||||||
| 		if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; |  | ||||||
| 		if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		//#region Parse map data
 |  | ||||||
| 		this.mapWidth = map[0].length; |  | ||||||
| 		this.mapHeight = map.length; |  | ||||||
| 		const mapData = map.join(''); |  | ||||||
| 
 |  | ||||||
| 		this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); |  | ||||||
| 
 |  | ||||||
| 		this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
 |  | ||||||
| 		if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 黒石の数 |  | ||||||
| 	 */ |  | ||||||
| 	public get blackCount() { |  | ||||||
| 		return count(BLACK, this.board); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 白石の数 |  | ||||||
| 	 */ |  | ||||||
| 	public get whiteCount() { |  | ||||||
| 		return count(WHITE, this.board); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public transformPosToXy(pos: number): number[] { |  | ||||||
| 		const x = pos % this.mapWidth; |  | ||||||
| 		const y = Math.floor(pos / this.mapWidth); |  | ||||||
| 		return [x, y]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public transformXyToPos(x: number, y: number): number { |  | ||||||
| 		return x + (y * this.mapWidth); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を打ちます |  | ||||||
| 	 * @param color 石の色 |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public put(color: Color, pos: number) { |  | ||||||
| 		this.prevPos = pos; |  | ||||||
| 		this.prevColor = color; |  | ||||||
| 
 |  | ||||||
| 		this.board[pos] = color; |  | ||||||
| 
 |  | ||||||
| 		// 反転させられる石を取得
 |  | ||||||
| 		const effects = this.effects(color, pos); |  | ||||||
| 
 |  | ||||||
| 		// 反転させる
 |  | ||||||
| 		for (const pos of effects) { |  | ||||||
| 			this.board[pos] = color; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		const turn = this.turn; |  | ||||||
| 
 |  | ||||||
| 		this.logs.push({ |  | ||||||
| 			color, |  | ||||||
| 			pos, |  | ||||||
| 			effects, |  | ||||||
| 			turn, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		this.calcTurn(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private calcTurn() { |  | ||||||
| 		// ターン計算
 |  | ||||||
| 		this.turn = |  | ||||||
| 			this.canPutSomewhere(!this.prevColor) ? !this.prevColor : |  | ||||||
| 			this.canPutSomewhere(this.prevColor!) ? this.prevColor : |  | ||||||
| 			null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public undo() { |  | ||||||
| 		const undo = this.logs.pop()!; |  | ||||||
| 		this.prevColor = undo.color; |  | ||||||
| 		this.prevPos = undo.pos; |  | ||||||
| 		this.board[undo.pos] = null; |  | ||||||
| 		for (const pos of undo.effects) { |  | ||||||
| 			const color = this.board[pos]; |  | ||||||
| 			this.board[pos] = !color; |  | ||||||
| 		} |  | ||||||
| 		this.turn = undo.turn; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定した位置のマップデータのマスを取得します |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public mapDataGet(pos: number): MapPixel { |  | ||||||
| 		const [x, y] = this.transformPosToXy(pos); |  | ||||||
| 		return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 打つことができる場所を取得します |  | ||||||
| 	 */ |  | ||||||
| 	public puttablePlaces(color: Color): number[] { |  | ||||||
| 		return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 打つことができる場所があるかどうかを取得します |  | ||||||
| 	 */ |  | ||||||
| 	public canPutSomewhere(color: Color): boolean { |  | ||||||
| 		return this.puttablePlaces(color).length > 0; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を打つことができるかどうかを取得します |  | ||||||
| 	 * @param color 自分の色 |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public canPut(color: Color, pos: number): boolean { |  | ||||||
| 		return ( |  | ||||||
| 			this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
 |  | ||||||
| 			this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
 |  | ||||||
| 			this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を置いた時の、反転させられる石を取得します |  | ||||||
| 	 * @param color 自分の色 |  | ||||||
| 	 * @param initPos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public effects(color: Color, initPos: number): number[] { |  | ||||||
| 		const enemyColor = !color; |  | ||||||
| 
 |  | ||||||
| 		const diffVectors: [number, number][] = [ |  | ||||||
| 			[  0,  -1], // 上
 |  | ||||||
| 			[ +1,  -1], // 右上
 |  | ||||||
| 			[ +1,   0], // 右
 |  | ||||||
| 			[ +1,  +1], // 右下
 |  | ||||||
| 			[  0,  +1], // 下
 |  | ||||||
| 			[ -1,  +1], // 左下
 |  | ||||||
| 			[ -1,   0], // 左
 |  | ||||||
| 			[ -1,  -1],  // 左上
 |  | ||||||
| 		]; |  | ||||||
| 
 |  | ||||||
| 		const effectsInLine = ([dx, dy]: [number, number]): number[] => { |  | ||||||
| 			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; |  | ||||||
| 
 |  | ||||||
| 			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
 |  | ||||||
| 			let [x, y] = this.transformPosToXy(initPos); |  | ||||||
| 			while (true) { |  | ||||||
| 				[x, y] = nextPos(x, y); |  | ||||||
| 
 |  | ||||||
| 				// 座標が指し示す位置がボード外に出たとき
 |  | ||||||
| 				if (this.opts.loopedBoard && this.transformXyToPos( |  | ||||||
| 					(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), |  | ||||||
| 					(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) { |  | ||||||
| 						// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
 |  | ||||||
| 					return found; |  | ||||||
| 				} else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) { |  | ||||||
| 					return []; // 挟めないことが確定 (盤面外に到達)
 |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				const pos = this.transformXyToPos(x, y); |  | ||||||
| 				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
 |  | ||||||
| 				const stone = this.board[pos]; |  | ||||||
| 				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
 |  | ||||||
| 				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
 |  | ||||||
| 				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
 |  | ||||||
| 			} |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		return concat(diffVectors.map(effectsInLine)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームが終了したか否か |  | ||||||
| 	 */ |  | ||||||
| 	public get isEnded(): boolean { |  | ||||||
| 		return this.turn === null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームの勝者 (null = 引き分け) |  | ||||||
| 	 */ |  | ||||||
| 	public get winner(): Color | null { |  | ||||||
| 		return this.isEnded ? |  | ||||||
| 			this.blackCount == this.whiteCount ? null : |  | ||||||
| 			this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : |  | ||||||
| 			undefined as never; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,896 +0,0 @@ | ||||||
| /** |  | ||||||
|  * 組み込みマップ定義 |  | ||||||
|  * |  | ||||||
|  * データ値: |  | ||||||
|  * (スペース) ... マス無し |  | ||||||
|  * - ... マス |  | ||||||
|  * b ... 初期配置される黒石 |  | ||||||
|  * w ... 初期配置される白石 |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| export type Map = { |  | ||||||
| 	name?: string; |  | ||||||
| 	category?: string; |  | ||||||
| 	author?: string; |  | ||||||
| 	data: string[]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const fourfour: Map = { |  | ||||||
| 	name: '4x4', |  | ||||||
| 	category: '4x4', |  | ||||||
| 	data: [ |  | ||||||
| 		'----', |  | ||||||
| 		'-wb-', |  | ||||||
| 		'-bw-', |  | ||||||
| 		'----', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const sixsix: Map = { |  | ||||||
| 	name: '6x6', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedSixsix: Map = { |  | ||||||
| 	name: '6x6 rounded', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ---- ', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		' ---- ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedSixsix2: Map = { |  | ||||||
| 	name: '6x6 rounded 2', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'  --  ', |  | ||||||
| 		' ---- ', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		' ---- ', |  | ||||||
| 		'  --  ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteight: Map = { |  | ||||||
| 	name: '8x8', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH1: Map = { |  | ||||||
| 	name: '8x8 handicap 1', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH2: Map = { |  | ||||||
| 	name: '8x8 handicap 2', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'-------b', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH3: Map = { |  | ||||||
| 	name: '8x8 handicap 3', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'-------b', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH4: Map = { |  | ||||||
| 	name: '8x8 handicap 4', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'b------b', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH28: Map = { |  | ||||||
| 	name: '8x8 handicap 28', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'bbbbbbbb', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b--wb--b', |  | ||||||
| 		'b--bw--b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'bbbbbbbb', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight: Map = { |  | ||||||
| 	name: '8x8 rounded', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight2: Map = { |  | ||||||
| 	name: '8x8 rounded 2', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'  ----  ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'  ----  ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight3: Map = { |  | ||||||
| 	name: '8x8 rounded 3', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   --   ', |  | ||||||
| 		'  ----  ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'  ----  ', |  | ||||||
| 		'   --   ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightWithNotch: Map = { |  | ||||||
| 	name: '8x8 with notch', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---  ---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' --wb-- ', |  | ||||||
| 		' --bw-- ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---  ---', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightWithSomeHoles: Map = { |  | ||||||
| 	name: '8x8 with some holes', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--- ----', |  | ||||||
| 		'----- --', |  | ||||||
| 		'-- -----', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw- -', |  | ||||||
| 		' -------', |  | ||||||
| 		'--- ----', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const circle: Map = { |  | ||||||
| 	name: 'Circle', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   --   ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		' ------ ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'   --   ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const smile: Map = { |  | ||||||
| 	name: 'Smile', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'-- -- --', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'-- bw --', |  | ||||||
| 		'---  ---', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const window: Map = { |  | ||||||
| 	name: 'Window', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const reserved: Map = { |  | ||||||
| 	name: 'Reserved', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'w------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'b------w', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const x: Map = { |  | ||||||
| 	name: 'X', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'w------b', |  | ||||||
| 		'-w----b-', |  | ||||||
| 		'--w--b--', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--b--w--', |  | ||||||
| 		'-b----w-', |  | ||||||
| 		'b------w', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const parallel: Map = { |  | ||||||
| 	name: 'Parallel', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---bb---', |  | ||||||
| 		'---ww---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const lackOfBlack: Map = { |  | ||||||
| 	name: 'Lack of Black', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---w----', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const squareParty: Map = { |  | ||||||
| 	name: 'Square Party', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-wwwbbb-', |  | ||||||
| 		'-w-wb-b-', |  | ||||||
| 		'-wwwbbb-', |  | ||||||
| 		'-bbbwww-', |  | ||||||
| 		'-b-bw-w-', |  | ||||||
| 		'-bbbwww-', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const minesweeper: Map = { |  | ||||||
| 	name: 'Minesweeper', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-b--w-w', |  | ||||||
| 		'-w-wb-b-', |  | ||||||
| 		'w-b--w-b', |  | ||||||
| 		'-b-wb-w-', |  | ||||||
| 		'-w-bw-b-', |  | ||||||
| 		'b-w--b-w', |  | ||||||
| 		'-b-bw-w-', |  | ||||||
| 		'w-w--b-b', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const tenthtenth: Map = { |  | ||||||
| 	name: '10x10', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const hole: Map = { |  | ||||||
| 	name: 'The Hole', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'--wb--wb--', |  | ||||||
| 		'--bw--bw--', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'--wb--wb--', |  | ||||||
| 		'--bw--bw--', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const grid: Map = { |  | ||||||
| 	name: 'Grid', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const cross: Map = { |  | ||||||
| 	name: 'Cross', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'----------', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const charX: Map = { |  | ||||||
| 	name: 'Char X', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---    ---', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'---    ---', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const charY: Map = { |  | ||||||
| 	name: 'Char Y', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---    ---', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const walls: Map = { |  | ||||||
| 	name: 'Walls', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		' bbbbbbbb ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w---wb---w', |  | ||||||
| 		'w---bw---w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' bbbbbbbb ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const cpu: Map = { |  | ||||||
| 	name: 'CPU', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' b b  b b ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' ---wb--- ', |  | ||||||
| 		' ---bw--- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' b b  b b ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const checker: Map = { |  | ||||||
| 	name: 'Checker', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'---wbwb---', |  | ||||||
| 		'---bwbw---', |  | ||||||
| 		'---wbwb---', |  | ||||||
| 		'---bwbw---', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const japaneseCurry: Map = { |  | ||||||
| 	name: 'Japanese curry', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'w-b-b-b-b-', |  | ||||||
| 		'-w-b-b-b-b', |  | ||||||
| 		'w-w-b-b-b-', |  | ||||||
| 		'-w-w-b-b-b', |  | ||||||
| 		'w-w-wwb-b-', |  | ||||||
| 		'-w-wbb-b-b', |  | ||||||
| 		'w-w-w-b-b-', |  | ||||||
| 		'-w-w-w-b-b', |  | ||||||
| 		'w-w-w-w-b-', |  | ||||||
| 		'-w-w-w-w-b', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const mosaic: Map = { |  | ||||||
| 	name: 'Mosaic', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - w w - -', |  | ||||||
| 		'- - b b - ', |  | ||||||
| 		' - w w - -', |  | ||||||
| 		'- - b b - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const arena: Map = { |  | ||||||
| 	name: 'Arena', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		' - -  - - ', |  | ||||||
| 		'- ------ -', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'- --wb-- -', |  | ||||||
| 		'- --bw-- -', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'- ------ -', |  | ||||||
| 		' - -  - - ', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const reactor: Map = { |  | ||||||
| 	name: 'Reactor', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w------b-', |  | ||||||
| 		'b- -  - -w', |  | ||||||
| 		'- --wb-- -', |  | ||||||
| 		'---b  w---', |  | ||||||
| 		'- b wb w -', |  | ||||||
| 		'- w bw b -', |  | ||||||
| 		'---w  b---', |  | ||||||
| 		'- --bw-- -', |  | ||||||
| 		'w- -  - -b', |  | ||||||
| 		'-b------w-', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const sixeight: Map = { |  | ||||||
| 	name: '6x8', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const spark: Map = { |  | ||||||
| 	name: 'Spark', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' -      - ', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' ---wb--- ', |  | ||||||
| 		' ---bw--- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		' -      - ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const islands: Map = { |  | ||||||
| 	name: 'Islands', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------  ', |  | ||||||
| 		'---wb---  ', |  | ||||||
| 		'---bw---  ', |  | ||||||
| 		'--------  ', |  | ||||||
| 		'  -    -  ', |  | ||||||
| 		'  -    -  ', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const galaxy: Map = { |  | ||||||
| 	name: 'Galaxy', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   ------   ', |  | ||||||
| 		'  --www---  ', |  | ||||||
| 		' ------w--- ', |  | ||||||
| 		'---bbb--w---', |  | ||||||
| 		'--b---b-w-b-', |  | ||||||
| 		'-b--wwb-w-b-', |  | ||||||
| 		'-b-w-bww--b-', |  | ||||||
| 		'-b-w-b---b--', |  | ||||||
| 		'---w--bbb---', |  | ||||||
| 		' ---w------ ', |  | ||||||
| 		'  ---www--  ', |  | ||||||
| 		'   ------   ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const triangle: Map = { |  | ||||||
| 	name: 'Triangle', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'    --    ', |  | ||||||
| 		'    --    ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const iphonex: Map = { |  | ||||||
| 	name: 'iPhone X', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' --  -- ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const dealWithIt: Map = { |  | ||||||
| 	name: 'Deal with it!', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'------------', |  | ||||||
| 		'--w-b-------', |  | ||||||
| 		' --b-w------', |  | ||||||
| 		'  --w-b---- ', |  | ||||||
| 		'   -------  ', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const experiment: Map = { |  | ||||||
| 	name: 'Let\'s experiment', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------------ ', |  | ||||||
| 		'------wb------', |  | ||||||
| 		'------bw------', |  | ||||||
| 		'--------------', |  | ||||||
| 		'    -    -    ', |  | ||||||
| 		'------  ------', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'wwwwww  bbbbbb', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const bigBoard: Map = { |  | ||||||
| 	name: 'Big board', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'-------wb-------', |  | ||||||
| 		'-------bw-------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const twoBoard: Map = { |  | ||||||
| 	name: 'Two board', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'---wb--- ---wb---', |  | ||||||
| 		'---bw--- ---bw---', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test1: Map = { |  | ||||||
| 	name: 'Test1', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test2: Map = { |  | ||||||
| 	name: 'Test2', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'-b--w-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test3: Map = { |  | ||||||
| 	name: 'Test3', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'---', |  | ||||||
| 		'b--', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test4: Map = { |  | ||||||
| 	name: 'Test4', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'------', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう
 |  | ||||||
| export const test6: Map = { |  | ||||||
| 	name: 'Test6', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--wwwww-', |  | ||||||
| 		'wwwwwwww', |  | ||||||
| 		'wbbbwbwb', |  | ||||||
| 		'wbbbbwbb', |  | ||||||
| 		'wbwbbwbb', |  | ||||||
| 		'wwbwbbbb', |  | ||||||
| 		'--wbbbbb', |  | ||||||
| 		'-wwwww--', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう
 |  | ||||||
| export const test7: Map = { |  | ||||||
| 	name: 'Test7', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'b--w----', |  | ||||||
| 		'b-wwww--', |  | ||||||
| 		'bwbwwwbb', |  | ||||||
| 		'wbwwwwb-', |  | ||||||
| 		'wwwwwww-', |  | ||||||
| 		'-wwbbwwb', |  | ||||||
| 		'--wwww--', |  | ||||||
| 		'--wwww--', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう
 |  | ||||||
| export const test8: Map = { |  | ||||||
| 	name: 'Test8', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-----w--', |  | ||||||
| 		'w--www--', |  | ||||||
| 		'wwwwww--', |  | ||||||
| 		'bbbbwww-', |  | ||||||
| 		'wwwwww--', |  | ||||||
| 		'--www---', |  | ||||||
| 		'--ww----', |  | ||||||
| 	], |  | ||||||
| }; |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| { |  | ||||||
|   "name": "misskey-reversi", |  | ||||||
|   "version": "0.0.5", |  | ||||||
|   "description": "Misskey reversi engine", |  | ||||||
|   "keywords": [ |  | ||||||
|     "misskey" |  | ||||||
|   ], |  | ||||||
|   "author": "syuilo <i@syuilo.com>", |  | ||||||
|   "license": "MIT", |  | ||||||
|   "repository": "https://github.com/misskey-dev/misskey.git", |  | ||||||
|   "bugs": "https://github.com/misskey-dev/misskey/issues", |  | ||||||
|   "main": "./built/core.js", |  | ||||||
|   "types": "./built/core.d.ts", |  | ||||||
|   "scripts": { |  | ||||||
|     "build": "tsc" |  | ||||||
|   }, |  | ||||||
|   "dependencies": {} |  | ||||||
| } |  | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| { |  | ||||||
| 	"compilerOptions": { |  | ||||||
| 		"noEmitOnError": false, |  | ||||||
| 		"noImplicitAny": false, |  | ||||||
| 		"noImplicitReturns": true, |  | ||||||
| 		"noFallthroughCasesInSwitch": true, |  | ||||||
| 		"experimentalDecorators": true, |  | ||||||
| 		"declaration": true, |  | ||||||
| 		"sourceMap": false, |  | ||||||
| 		"target": "es2017", |  | ||||||
| 		"module": "commonjs", |  | ||||||
| 		"removeComments": false, |  | ||||||
| 		"noLib": false, |  | ||||||
| 		"outDir": "./built", |  | ||||||
| 		"rootDir": "./" |  | ||||||
| 	}, |  | ||||||
| 	"compileOnSave": false, |  | ||||||
| 	"include": [ |  | ||||||
| 		"./core.ts" |  | ||||||
| 	] |  | ||||||
| } |  | ||||||
|  | @ -22,8 +22,6 @@ import { packedFederationInstanceSchema } from '@/models/repositories/federation | ||||||
| import { packedQueueCountSchema } from '@/models/repositories/queue'; | import { packedQueueCountSchema } from '@/models/repositories/queue'; | ||||||
| import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; | import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; | ||||||
| import { packedEmojiSchema } from '@/models/repositories/emoji'; | import { packedEmojiSchema } from '@/models/repositories/emoji'; | ||||||
| import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; |  | ||||||
| import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; |  | ||||||
| 
 | 
 | ||||||
| export const refs = { | export const refs = { | ||||||
| 	User: packedUserSchema, | 	User: packedUserSchema, | ||||||
|  | @ -49,8 +47,6 @@ export const refs = { | ||||||
| 	FederationInstance: packedFederationInstanceSchema, | 	FederationInstance: packedFederationInstanceSchema, | ||||||
| 	GalleryPost: packedGalleryPostSchema, | 	GalleryPost: packedGalleryPostSchema, | ||||||
| 	Emoji: packedEmojiSchema, | 	Emoji: packedEmojiSchema, | ||||||
| 	ReversiGame: packedReversiGameSchema, |  | ||||||
| 	ReversiMatching: packedReversiMatchingSchema, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; | export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; | ||||||
|  |  | ||||||
|  | @ -1,133 +0,0 @@ | ||||||
| import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; |  | ||||||
| import { User } from '../../user'; |  | ||||||
| import { id } from '../../../id'; |  | ||||||
| 
 |  | ||||||
| @Entity() |  | ||||||
| export class ReversiGame { |  | ||||||
| 	@PrimaryColumn(id()) |  | ||||||
| 	public id: string; |  | ||||||
| 
 |  | ||||||
| 	@Index() |  | ||||||
| 	@Column('timestamp with time zone', { |  | ||||||
| 		comment: 'The created date of the ReversiGame.', |  | ||||||
| 	}) |  | ||||||
| 	public createdAt: Date; |  | ||||||
| 
 |  | ||||||
| 	@Column('timestamp with time zone', { |  | ||||||
| 		nullable: true, |  | ||||||
| 		comment: 'The started date of the ReversiGame.', |  | ||||||
| 	}) |  | ||||||
| 	public startedAt: Date | null; |  | ||||||
| 
 |  | ||||||
| 	@Column(id()) |  | ||||||
| 	public user1Id: User['id']; |  | ||||||
| 
 |  | ||||||
| 	@ManyToOne(type => User, { |  | ||||||
| 		onDelete: 'CASCADE', |  | ||||||
| 	}) |  | ||||||
| 	@JoinColumn() |  | ||||||
| 	public user1: User | null; |  | ||||||
| 
 |  | ||||||
| 	@Column(id()) |  | ||||||
| 	public user2Id: User['id']; |  | ||||||
| 
 |  | ||||||
| 	@ManyToOne(type => User, { |  | ||||||
| 		onDelete: 'CASCADE', |  | ||||||
| 	}) |  | ||||||
| 	@JoinColumn() |  | ||||||
| 	public user2: User | null; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public user1Accepted: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public user2Accepted: boolean; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * どちらのプレイヤーが先行(黒)か |  | ||||||
| 	 * 1 ... user1 |  | ||||||
| 	 * 2 ... user2 |  | ||||||
| 	 */ |  | ||||||
| 	@Column('integer', { |  | ||||||
| 		nullable: true, |  | ||||||
| 	}) |  | ||||||
| 	public black: number | null; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public isStarted: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public isEnded: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column({ |  | ||||||
| 		...id(), |  | ||||||
| 		nullable: true, |  | ||||||
| 	}) |  | ||||||
| 	public winnerId: User['id'] | null; |  | ||||||
| 
 |  | ||||||
| 	@Column({ |  | ||||||
| 		...id(), |  | ||||||
| 		nullable: true, |  | ||||||
| 	}) |  | ||||||
| 	public surrendered: User['id'] | null; |  | ||||||
| 
 |  | ||||||
| 	@Column('jsonb', { |  | ||||||
| 		default: [], |  | ||||||
| 	}) |  | ||||||
| 	public logs: { |  | ||||||
| 		at: Date; |  | ||||||
| 		color: boolean; |  | ||||||
| 		pos: number; |  | ||||||
| 	}[]; |  | ||||||
| 
 |  | ||||||
| 	@Column('varchar', { |  | ||||||
| 		array: true, length: 64, |  | ||||||
| 	}) |  | ||||||
| 	public map: string[]; |  | ||||||
| 
 |  | ||||||
| 	@Column('varchar', { |  | ||||||
| 		length: 32, |  | ||||||
| 	}) |  | ||||||
| 	public bw: string; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public isLlotheo: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public canPutEverywhere: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column('boolean', { |  | ||||||
| 		default: false, |  | ||||||
| 	}) |  | ||||||
| 	public loopedBoard: boolean; |  | ||||||
| 
 |  | ||||||
| 	@Column('jsonb', { |  | ||||||
| 		nullable: true, default: null, |  | ||||||
| 	}) |  | ||||||
| 	public form1: any | null; |  | ||||||
| 
 |  | ||||||
| 	@Column('jsonb', { |  | ||||||
| 		nullable: true, default: null, |  | ||||||
| 	}) |  | ||||||
| 	public form2: any | null; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ログのposを文字列としてすべて連結したもののCRC32値 |  | ||||||
| 	 */ |  | ||||||
| 	@Column('varchar', { |  | ||||||
| 		length: 32, nullable: true, |  | ||||||
| 	}) |  | ||||||
| 	public crc32: string | null; |  | ||||||
| } |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; |  | ||||||
| import { User } from '../../user'; |  | ||||||
| import { id } from '../../../id'; |  | ||||||
| 
 |  | ||||||
| @Entity() |  | ||||||
| export class ReversiMatching { |  | ||||||
| 	@PrimaryColumn(id()) |  | ||||||
| 	public id: string; |  | ||||||
| 
 |  | ||||||
| 	@Index() |  | ||||||
| 	@Column('timestamp with time zone', { |  | ||||||
| 		comment: 'The created date of the ReversiMatching.', |  | ||||||
| 	}) |  | ||||||
| 	public createdAt: Date; |  | ||||||
| 
 |  | ||||||
| 	@Index() |  | ||||||
| 	@Column(id()) |  | ||||||
| 	public parentId: User['id']; |  | ||||||
| 
 |  | ||||||
| 	@ManyToOne(type => User, { |  | ||||||
| 		onDelete: 'CASCADE', |  | ||||||
| 	}) |  | ||||||
| 	@JoinColumn() |  | ||||||
| 	public parent: User | null; |  | ||||||
| 
 |  | ||||||
| 	@Index() |  | ||||||
| 	@Column(id()) |  | ||||||
| 	public childId: User['id']; |  | ||||||
| 
 |  | ||||||
| 	@ManyToOne(type => User, { |  | ||||||
| 		onDelete: 'CASCADE', |  | ||||||
| 	}) |  | ||||||
| 	@JoinColumn() |  | ||||||
| 	public child: User | null; |  | ||||||
| } |  | ||||||
|  | @ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token'; | ||||||
| import { UserNotePining } from './entities/user-note-pining'; | import { UserNotePining } from './entities/user-note-pining'; | ||||||
| import { SigninRepository } from './repositories/signin'; | import { SigninRepository } from './repositories/signin'; | ||||||
| import { MessagingMessageRepository } from './repositories/messaging-message'; | import { MessagingMessageRepository } from './repositories/messaging-message'; | ||||||
| import { ReversiGameRepository } from './repositories/games/reversi/game'; |  | ||||||
| import { UserListRepository } from './repositories/user-list'; | import { UserListRepository } from './repositories/user-list'; | ||||||
| import { UserListJoining } from './entities/user-list-joining'; | import { UserListJoining } from './entities/user-list-joining'; | ||||||
| import { UserGroupRepository } from './repositories/user-group'; | import { UserGroupRepository } from './repositories/user-group'; | ||||||
|  | @ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking'; | ||||||
| import { NoteReactionRepository } from './repositories/note-reaction'; | import { NoteReactionRepository } from './repositories/note-reaction'; | ||||||
| import { NotificationRepository } from './repositories/notification'; | import { NotificationRepository } from './repositories/notification'; | ||||||
| import { NoteFavoriteRepository } from './repositories/note-favorite'; | import { NoteFavoriteRepository } from './repositories/note-favorite'; | ||||||
| import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; |  | ||||||
| import { UserPublickey } from './entities/user-publickey'; | import { UserPublickey } from './entities/user-publickey'; | ||||||
| import { UserKeypair } from './entities/user-keypair'; | import { UserKeypair } from './entities/user-keypair'; | ||||||
| import { AppRepository } from './repositories/app'; | import { AppRepository } from './repositories/app'; | ||||||
|  | @ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository); | ||||||
| export const AccessTokens = getRepository(AccessToken); | export const AccessTokens = getRepository(AccessToken); | ||||||
| export const Signins = getCustomRepository(SigninRepository); | export const Signins = getCustomRepository(SigninRepository); | ||||||
| export const MessagingMessages = getCustomRepository(MessagingMessageRepository); | export const MessagingMessages = getCustomRepository(MessagingMessageRepository); | ||||||
| export const ReversiGames = getCustomRepository(ReversiGameRepository); |  | ||||||
| export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); |  | ||||||
| export const Pages = getCustomRepository(PageRepository); | export const Pages = getCustomRepository(PageRepository); | ||||||
| export const PageLikes = getCustomRepository(PageLikeRepository); | export const PageLikes = getCustomRepository(PageLikeRepository); | ||||||
| export const GalleryPosts = getCustomRepository(GalleryPostRepository); | export const GalleryPosts = getCustomRepository(GalleryPostRepository); | ||||||
|  |  | ||||||
|  | @ -1,191 +0,0 @@ | ||||||
| import { User } from '@/models/entities/user'; |  | ||||||
| import { EntityRepository, Repository } from 'typeorm'; |  | ||||||
| import { Users } from '../../../index'; |  | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { Packed } from '@/misc/schema'; |  | ||||||
| 
 |  | ||||||
| @EntityRepository(ReversiGame) |  | ||||||
| export class ReversiGameRepository extends Repository<ReversiGame> { |  | ||||||
| 	public async pack( |  | ||||||
| 		src: ReversiGame['id'] | ReversiGame, |  | ||||||
| 		me?: { id: User['id'] } | null | undefined, |  | ||||||
| 		options?: { |  | ||||||
| 			detail?: boolean |  | ||||||
| 		} |  | ||||||
| 	): Promise<Packed<'ReversiGame'>> { |  | ||||||
| 		const opts = Object.assign({ |  | ||||||
| 			detail: true, |  | ||||||
| 		}, options); |  | ||||||
| 
 |  | ||||||
| 		const game = typeof src === 'object' ? src : await this.findOneOrFail(src); |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			id: game.id, |  | ||||||
| 			createdAt: game.createdAt.toISOString(), |  | ||||||
| 			startedAt: game.startedAt && game.startedAt.toISOString(), |  | ||||||
| 			isStarted: game.isStarted, |  | ||||||
| 			isEnded: game.isEnded, |  | ||||||
| 			form1: game.form1, |  | ||||||
| 			form2: game.form2, |  | ||||||
| 			user1Accepted: game.user1Accepted, |  | ||||||
| 			user2Accepted: game.user2Accepted, |  | ||||||
| 			user1Id: game.user1Id, |  | ||||||
| 			user2Id: game.user2Id, |  | ||||||
| 			user1: await Users.pack(game.user1Id, me), |  | ||||||
| 			user2: await Users.pack(game.user2Id, me), |  | ||||||
| 			winnerId: game.winnerId, |  | ||||||
| 			winner: game.winnerId ? await Users.pack(game.winnerId, me) : null, |  | ||||||
| 			surrendered: game.surrendered, |  | ||||||
| 			black: game.black, |  | ||||||
| 			bw: game.bw, |  | ||||||
| 			isLlotheo: game.isLlotheo, |  | ||||||
| 			canPutEverywhere: game.canPutEverywhere, |  | ||||||
| 			loopedBoard: game.loopedBoard, |  | ||||||
| 			...(opts.detail ? { |  | ||||||
| 				logs: game.logs.map(log => ({ |  | ||||||
| 					at: log.at.toISOString(), |  | ||||||
| 					color: log.color, |  | ||||||
| 					pos: log.pos, |  | ||||||
| 				})), |  | ||||||
| 				map: game.map, |  | ||||||
| 			} : {}), |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const packedReversiGameSchema = { |  | ||||||
| 	type: 'object' as const, |  | ||||||
| 	optional: false as const, nullable: false as const, |  | ||||||
| 	properties: { |  | ||||||
| 		id: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		createdAt: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'date-time', |  | ||||||
| 		}, |  | ||||||
| 		startedAt: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 			format: 'date-time', |  | ||||||
| 		}, |  | ||||||
| 		isStarted: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		isEnded: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		form1: { |  | ||||||
| 			type: 'any' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 		}, |  | ||||||
| 		form2: { |  | ||||||
| 			type: 'any' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 		}, |  | ||||||
| 		user1Accepted: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		user2Accepted: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		user1Id: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		user2Id: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		user1: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			ref: 'User' as const, |  | ||||||
| 		}, |  | ||||||
| 		user2: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			ref: 'User' as const, |  | ||||||
| 		}, |  | ||||||
| 		winnerId: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		winner: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 			ref: 'User' as const, |  | ||||||
| 		}, |  | ||||||
| 		surrendered: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		black: { |  | ||||||
| 			type: 'number' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 		}, |  | ||||||
| 		bw: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		isLlotheo: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		canPutEverywhere: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		loopedBoard: { |  | ||||||
| 			type: 'boolean' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 		}, |  | ||||||
| 		logs: { |  | ||||||
| 			type: 'array' as const, |  | ||||||
| 			optional: true as const, nullable: false as const, |  | ||||||
| 			items: { |  | ||||||
| 				type: 'object' as const, |  | ||||||
| 				optional: true as const, nullable: false as const, |  | ||||||
| 				properties: { |  | ||||||
| 					at: { |  | ||||||
| 						type: 'string' as const, |  | ||||||
| 						optional: false as const, nullable: false as const, |  | ||||||
| 						format: 'date-time', |  | ||||||
| 					}, |  | ||||||
| 					color: { |  | ||||||
| 						type: 'boolean' as const, |  | ||||||
| 						optional: false as const, nullable: false as const, |  | ||||||
| 					}, |  | ||||||
| 					pos: { |  | ||||||
| 						type: 'number' as const, |  | ||||||
| 						optional: false as const, nullable: false as const, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		map: { |  | ||||||
| 			type: 'array' as const, |  | ||||||
| 			optional: true as const, nullable: false as const, |  | ||||||
| 			items: { |  | ||||||
| 				type: 'string' as const, |  | ||||||
| 				optional: false as const, nullable: false as const, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| import { EntityRepository, Repository } from 'typeorm'; |  | ||||||
| import { ReversiMatching } from '@/models/entities/games/reversi/matching'; |  | ||||||
| import { Users } from '../../../index'; |  | ||||||
| import { awaitAll } from '@/prelude/await-all'; |  | ||||||
| import { User } from '@/models/entities/user'; |  | ||||||
| import { Packed } from '@/misc/schema'; |  | ||||||
| 
 |  | ||||||
| @EntityRepository(ReversiMatching) |  | ||||||
| export class ReversiMatchingRepository extends Repository<ReversiMatching> { |  | ||||||
| 	public async pack( |  | ||||||
| 		src: ReversiMatching['id'] | ReversiMatching, |  | ||||||
| 		me: { id: User['id'] } |  | ||||||
| 	): Promise<Packed<'ReversiMatching'>> { |  | ||||||
| 		const matching = typeof src === 'object' ? src : await this.findOneOrFail(src); |  | ||||||
| 
 |  | ||||||
| 		return await awaitAll({ |  | ||||||
| 			id: matching.id, |  | ||||||
| 			createdAt: matching.createdAt.toISOString(), |  | ||||||
| 			parentId: matching.parentId, |  | ||||||
| 			parent: Users.pack(matching.parentId, me, { |  | ||||||
| 				detail: true, |  | ||||||
| 			}), |  | ||||||
| 			childId: matching.childId, |  | ||||||
| 			child: Users.pack(matching.childId, me, { |  | ||||||
| 				detail: true, |  | ||||||
| 			}), |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const packedReversiMatchingSchema = { |  | ||||||
| 	type: 'object' as const, |  | ||||||
| 	optional: false as const, nullable: false as const, |  | ||||||
| 	properties: { |  | ||||||
| 		id: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		createdAt: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'date-time', |  | ||||||
| 		}, |  | ||||||
| 		parentId: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		parent: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: true as const, |  | ||||||
| 			ref: 'User' as const, |  | ||||||
| 		}, |  | ||||||
| 		childId: { |  | ||||||
| 			type: 'string' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			format: 'id', |  | ||||||
| 			example: 'xxxxxxxxxx', |  | ||||||
| 		}, |  | ||||||
| 		child: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			ref: 'User' as const, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
|  | @ -1,157 +0,0 @@ | ||||||
| import $ from 'cafy'; |  | ||||||
| import { ID } from '@/misc/cafy-id'; |  | ||||||
| import define from '../../../define'; |  | ||||||
| import { ReversiGames } from '@/models/index'; |  | ||||||
| import { makePaginationQuery } from '../../../common/make-pagination-query'; |  | ||||||
| import { Brackets } from 'typeorm'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	params: { |  | ||||||
| 		limit: { |  | ||||||
| 			validator: $.optional.num.range(1, 100), |  | ||||||
| 			default: 10, |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		sinceId: { |  | ||||||
| 			validator: $.optional.type(ID), |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		untilId: { |  | ||||||
| 			validator: $.optional.type(ID), |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		my: { |  | ||||||
| 			validator: $.optional.bool, |  | ||||||
| 			default: false, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	res: { |  | ||||||
| 		type: 'array' as const, |  | ||||||
| 		optional: false as const, nullable: false as const, |  | ||||||
| 		items: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			properties: { |  | ||||||
| 				id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				createdAt: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'date-time', |  | ||||||
| 				}, |  | ||||||
| 				startedAt: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'date-time', |  | ||||||
| 				}, |  | ||||||
| 				isStarted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				isEnded: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				form1: { |  | ||||||
| 					type: 'any' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				form2: { |  | ||||||
| 					type: 'any' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				user1Accepted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					default: false, |  | ||||||
| 				}, |  | ||||||
| 				user2Accepted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					default: false, |  | ||||||
| 				}, |  | ||||||
| 				user1Id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				user2Id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				user1: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				user2: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				winnerId: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				winner: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				surrendered: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				black: { |  | ||||||
| 					type: 'number' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				bw: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				isLlotheo: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				canPutEverywhere: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				loopedBoard: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	const query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) |  | ||||||
| 		.andWhere('game.isStarted = TRUE'); |  | ||||||
| 
 |  | ||||||
| 	if (ps.my && user) { |  | ||||||
| 		query.andWhere(new Brackets(qb => { qb |  | ||||||
| 			.where('game.user1Id = :userId', { userId: user.id }) |  | ||||||
| 			.orWhere('game.user2Id = :userId', { userId: user.id }); |  | ||||||
| 		})); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Fetch games
 |  | ||||||
| 	const games = await query.take(ps.limit!).getMany(); |  | ||||||
| 
 |  | ||||||
| 	return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { |  | ||||||
| 		detail: false, |  | ||||||
| 	}))); |  | ||||||
| }); |  | ||||||
|  | @ -1,169 +0,0 @@ | ||||||
| import $ from 'cafy'; |  | ||||||
| import { ID } from '@/misc/cafy-id'; |  | ||||||
| import Reversi from '../../../../../../games/reversi/core'; |  | ||||||
| import define from '../../../../define'; |  | ||||||
| import { ApiError } from '../../../../error'; |  | ||||||
| import { ReversiGames } from '@/models/index'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	params: { |  | ||||||
| 		gameId: { |  | ||||||
| 			validator: $.type(ID), |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	errors: { |  | ||||||
| 		noSuchGame: { |  | ||||||
| 			message: 'No such game.', |  | ||||||
| 			code: 'NO_SUCH_GAME', |  | ||||||
| 			id: 'f13a03db-fae1-46c9-87f3-43c8165419e1', |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	res: { |  | ||||||
| 		type: 'array' as const, |  | ||||||
| 		optional: false as const, nullable: false as const, |  | ||||||
| 		items: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			properties: { |  | ||||||
| 				id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				createdAt: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'date-time', |  | ||||||
| 				}, |  | ||||||
| 				startedAt: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'date-time', |  | ||||||
| 				}, |  | ||||||
| 				isStarted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				isEnded: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				form1: { |  | ||||||
| 					type: 'any' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				form2: { |  | ||||||
| 					type: 'any' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				user1Accepted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					default: false, |  | ||||||
| 				}, |  | ||||||
| 				user2Accepted: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					default: false, |  | ||||||
| 				}, |  | ||||||
| 				user1Id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				user2Id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				user1: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				user2: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				winnerId: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				winner: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				surrendered: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				black: { |  | ||||||
| 					type: 'number' as const, |  | ||||||
| 					optional: false as const, nullable: true as const, |  | ||||||
| 				}, |  | ||||||
| 				bw: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				isLlotheo: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				canPutEverywhere: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				loopedBoard: { |  | ||||||
| 					type: 'boolean' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 				board: { |  | ||||||
| 					type: 'array' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					items: { |  | ||||||
| 						type: 'any' as const, |  | ||||||
| 						optional: false as const, nullable: false as const, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				turn: { |  | ||||||
| 					type: 'any' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	const game = await ReversiGames.findOne(ps.gameId); |  | ||||||
| 
 |  | ||||||
| 	if (game == null) { |  | ||||||
| 		throw new ApiError(meta.errors.noSuchGame); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const o = new Reversi(game.map, { |  | ||||||
| 		isLlotheo: game.isLlotheo, |  | ||||||
| 		canPutEverywhere: game.canPutEverywhere, |  | ||||||
| 		loopedBoard: game.loopedBoard, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	for (const log of game.logs) { |  | ||||||
| 		o.put(log.color, log.pos); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const packed = await ReversiGames.pack(game, user); |  | ||||||
| 
 |  | ||||||
| 	return Object.assign({ |  | ||||||
| 		board: o.board, |  | ||||||
| 		turn: o.turn, |  | ||||||
| 	}, packed); |  | ||||||
| }); |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| import $ from 'cafy'; |  | ||||||
| import { ID } from '@/misc/cafy-id'; |  | ||||||
| import { publishReversiGameStream } from '@/services/stream'; |  | ||||||
| import define from '../../../../define'; |  | ||||||
| import { ApiError } from '../../../../error'; |  | ||||||
| import { ReversiGames } from '@/models/index'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	requireCredential: true as const, |  | ||||||
| 
 |  | ||||||
| 	params: { |  | ||||||
| 		gameId: { |  | ||||||
| 			validator: $.type(ID), |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	errors: { |  | ||||||
| 		noSuchGame: { |  | ||||||
| 			message: 'No such game.', |  | ||||||
| 			code: 'NO_SUCH_GAME', |  | ||||||
| 			id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df', |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		alreadyEnded: { |  | ||||||
| 			message: 'That game has already ended.', |  | ||||||
| 			code: 'ALREADY_ENDED', |  | ||||||
| 			id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d', |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		accessDenied: { |  | ||||||
| 			message: 'Access denied.', |  | ||||||
| 			code: 'ACCESS_DENIED', |  | ||||||
| 			id: '6e04164b-a992-4c93-8489-2123069973e1', |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	const game = await ReversiGames.findOne(ps.gameId); |  | ||||||
| 
 |  | ||||||
| 	if (game == null) { |  | ||||||
| 		throw new ApiError(meta.errors.noSuchGame); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (game.isEnded) { |  | ||||||
| 		throw new ApiError(meta.errors.alreadyEnded); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { |  | ||||||
| 		throw new ApiError(meta.errors.accessDenied); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; |  | ||||||
| 
 |  | ||||||
| 	await ReversiGames.update(game.id, { |  | ||||||
| 		surrendered: user.id, |  | ||||||
| 		isEnded: true, |  | ||||||
| 		winnerId: winnerId, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	publishReversiGameStream(game.id, 'ended', { |  | ||||||
| 		winnerId: winnerId, |  | ||||||
| 		game: await ReversiGames.pack(game.id, user), |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
|  | @ -1,59 +0,0 @@ | ||||||
| import define from '../../../define'; |  | ||||||
| import { ReversiMatchings } from '@/models/index'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	requireCredential: true as const, |  | ||||||
| 
 |  | ||||||
| 	res: { |  | ||||||
| 		type: 'array' as const, |  | ||||||
| 		optional: false as const, nullable: false as const, |  | ||||||
| 		items: { |  | ||||||
| 			type: 'object' as const, |  | ||||||
| 			optional: false as const, nullable: false as const, |  | ||||||
| 			properties: { |  | ||||||
| 				id: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				createdAt: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'date-time', |  | ||||||
| 				}, |  | ||||||
| 				parentId: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				parent: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 				childId: { |  | ||||||
| 					type: 'string' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					format: 'id', |  | ||||||
| 				}, |  | ||||||
| 				child: { |  | ||||||
| 					type: 'object' as const, |  | ||||||
| 					optional: false as const, nullable: false as const, |  | ||||||
| 					ref: 'User', |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	// Find session
 |  | ||||||
| 	const invitations = await ReversiMatchings.find({ |  | ||||||
| 		childId: user.id, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); |  | ||||||
| }); |  | ||||||
|  | @ -1,109 +0,0 @@ | ||||||
| import $ from 'cafy'; |  | ||||||
| import { ID } from '@/misc/cafy-id'; |  | ||||||
| import { publishMainStream, publishReversiStream } from '@/services/stream'; |  | ||||||
| import { eighteight } from '../../../../../games/reversi/maps'; |  | ||||||
| import define from '../../../define'; |  | ||||||
| import { ApiError } from '../../../error'; |  | ||||||
| import { getUser } from '../../../common/getters'; |  | ||||||
| import { genId } from '@/misc/gen-id'; |  | ||||||
| import { ReversiMatchings, ReversiGames } from '@/models/index'; |  | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { ReversiMatching } from '@/models/entities/games/reversi/matching'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	requireCredential: true as const, |  | ||||||
| 
 |  | ||||||
| 	params: { |  | ||||||
| 		userId: { |  | ||||||
| 			validator: $.type(ID), |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	errors: { |  | ||||||
| 		noSuchUser: { |  | ||||||
| 			message: 'No such user.', |  | ||||||
| 			code: 'NO_SUCH_USER', |  | ||||||
| 			id: '0b4f0559-b484-4e31-9581-3f73cee89b28', |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		isYourself: { |  | ||||||
| 			message: 'Target user is yourself.', |  | ||||||
| 			code: 'TARGET_IS_YOURSELF', |  | ||||||
| 			id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e', |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	// Myself
 |  | ||||||
| 	if (ps.userId === user.id) { |  | ||||||
| 		throw new ApiError(meta.errors.isYourself); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Find session
 |  | ||||||
| 	const exist = await ReversiMatchings.findOne({ |  | ||||||
| 		parentId: ps.userId, |  | ||||||
| 		childId: user.id, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	if (exist) { |  | ||||||
| 		// Destroy session
 |  | ||||||
| 		ReversiMatchings.delete(exist.id); |  | ||||||
| 
 |  | ||||||
| 		// Create game
 |  | ||||||
| 		const game = await ReversiGames.save({ |  | ||||||
| 			id: genId(), |  | ||||||
| 			createdAt: new Date(), |  | ||||||
| 			user1Id: exist.parentId, |  | ||||||
| 			user2Id: user.id, |  | ||||||
| 			user1Accepted: false, |  | ||||||
| 			user2Accepted: false, |  | ||||||
| 			isStarted: false, |  | ||||||
| 			isEnded: false, |  | ||||||
| 			logs: [], |  | ||||||
| 			map: eighteight.data, |  | ||||||
| 			bw: 'random', |  | ||||||
| 			isLlotheo: false, |  | ||||||
| 		} as Partial<ReversiGame>); |  | ||||||
| 
 |  | ||||||
| 		publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, { id: exist.parentId })); |  | ||||||
| 
 |  | ||||||
| 		const other = await ReversiMatchings.count({ |  | ||||||
| 			childId: user.id, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		if (other == 0) { |  | ||||||
| 			publishMainStream(user.id, 'reversiNoInvites'); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return await ReversiGames.pack(game, user); |  | ||||||
| 	} else { |  | ||||||
| 		// Fetch child
 |  | ||||||
| 		const child = await getUser(ps.userId).catch(e => { |  | ||||||
| 			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); |  | ||||||
| 			throw e; |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		// 以前のセッションはすべて削除しておく
 |  | ||||||
| 		await ReversiMatchings.delete({ |  | ||||||
| 			parentId: user.id, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		// セッションを作成
 |  | ||||||
| 		const matching = await ReversiMatchings.save({ |  | ||||||
| 			id: genId(), |  | ||||||
| 			createdAt: new Date(), |  | ||||||
| 			parentId: user.id, |  | ||||||
| 			childId: child.id, |  | ||||||
| 		} as ReversiMatching); |  | ||||||
| 
 |  | ||||||
| 		const packed = await ReversiMatchings.pack(matching, child); |  | ||||||
| 		publishReversiStream(child.id, 'invited', packed); |  | ||||||
| 		publishMainStream(child.id, 'reversiInvited', packed); |  | ||||||
| 
 |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| import define from '../../../../define'; |  | ||||||
| import { ReversiMatchings } from '@/models/index'; |  | ||||||
| 
 |  | ||||||
| export const meta = { |  | ||||||
| 	tags: ['games'], |  | ||||||
| 
 |  | ||||||
| 	requireCredential: true as const, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // eslint-disable-next-line import/no-default-export
 |  | ||||||
| export default define(meta, async (ps, user) => { |  | ||||||
| 	await ReversiMatchings.delete({ |  | ||||||
| 		parentId: user.id, |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
|  | @ -2,7 +2,7 @@ import $ from 'cafy'; | ||||||
| import define from '../../define'; | import define from '../../define'; | ||||||
| import { ApiError } from '../../error'; | import { ApiError } from '../../error'; | ||||||
| import { ID } from '@/misc/cafy-id'; | import { ID } from '@/misc/cafy-id'; | ||||||
| import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, ReversiGames, Users } from '@/models/index'; | import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	tags: ['users'], | 	tags: ['users'], | ||||||
|  | @ -50,7 +50,6 @@ export default define(meta, async (ps, me) => { | ||||||
| 		pageLikedCount, | 		pageLikedCount, | ||||||
| 		driveFilesCount, | 		driveFilesCount, | ||||||
| 		driveUsage, | 		driveUsage, | ||||||
| 		reversiCount, |  | ||||||
| 	] = await Promise.all([ | 	] = await Promise.all([ | ||||||
| 		Notes.createQueryBuilder('note') | 		Notes.createQueryBuilder('note') | ||||||
| 			.where('note.userId = :userId', { userId: user.id }) | 			.where('note.userId = :userId', { userId: user.id }) | ||||||
|  | @ -113,10 +112,6 @@ export default define(meta, async (ps, me) => { | ||||||
| 			.where('file.userId = :userId', { userId: user.id }) | 			.where('file.userId = :userId', { userId: user.id }) | ||||||
| 			.getCount(), | 			.getCount(), | ||||||
| 		DriveFiles.calcDriveUsageOf(user), | 		DriveFiles.calcDriveUsageOf(user), | ||||||
| 		ReversiGames.createQueryBuilder('game') |  | ||||||
| 			.where('game.user1Id = :userId', { userId: user.id }) |  | ||||||
| 			.orWhere('game.user2Id = :userId', { userId: user.id }) |  | ||||||
| 			.getCount(), |  | ||||||
| 	]); | 	]); | ||||||
| 
 | 
 | ||||||
| 	return { | 	return { | ||||||
|  | @ -140,6 +135,5 @@ export default define(meta, async (ps, me) => { | ||||||
| 		pageLikedCount, | 		pageLikedCount, | ||||||
| 		driveFilesCount, | 		driveFilesCount, | ||||||
| 		driveUsage, | 		driveUsage, | ||||||
| 		reversiCount, |  | ||||||
| 	}; | 	}; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,372 +0,0 @@ | ||||||
| import autobind from 'autobind-decorator'; |  | ||||||
| import * as CRC32 from 'crc-32'; |  | ||||||
| import { publishReversiGameStream } from '@/services/stream'; |  | ||||||
| import Reversi from '../../../../../games/reversi/core'; |  | ||||||
| import * as maps from '../../../../../games/reversi/maps'; |  | ||||||
| import Channel from '../../channel'; |  | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { ReversiGames, Users } from '@/models/index'; |  | ||||||
| import { User } from '@/models/entities/user'; |  | ||||||
| 
 |  | ||||||
| export default class extends Channel { |  | ||||||
| 	public readonly chName = 'gamesReversiGame'; |  | ||||||
| 	public static shouldShare = false; |  | ||||||
| 	public static requireCredential = false; |  | ||||||
| 
 |  | ||||||
| 	private gameId: ReversiGame['id'] | null = null; |  | ||||||
| 	private watchers: Record<User['id'], Date> = {}; |  | ||||||
| 	private emitWatchersIntervalId: ReturnType<typeof setInterval>; |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	public async init(params: any) { |  | ||||||
| 		this.gameId = params.gameId; |  | ||||||
| 
 |  | ||||||
| 		// Subscribe game stream
 |  | ||||||
| 		this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent); |  | ||||||
| 		this.emitWatchersIntervalId = setInterval(this.emitWatchers, 5000); |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		// 観戦者イベント
 |  | ||||||
| 		this.watch(game); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private onEvent(data: any) { |  | ||||||
| 		if (data.type === 'watching') { |  | ||||||
| 			const id = data.body; |  | ||||||
| 			this.watchers[id] = new Date(); |  | ||||||
| 		} else { |  | ||||||
| 			this.send(data); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async emitWatchers() { |  | ||||||
| 		const now = new Date(); |  | ||||||
| 
 |  | ||||||
| 		// Remove not watching users
 |  | ||||||
| 		for (const [userId, date] of Object.entries(this.watchers)) { |  | ||||||
| 			if (now.getTime() - date.getTime() > 5000) delete this.watchers[userId]; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		const users = await Users.packMany(Object.keys(this.watchers), null, { detail: false }); |  | ||||||
| 
 |  | ||||||
| 		this.send({ |  | ||||||
| 			type: 'watchers', |  | ||||||
| 			body: users, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	public dispose() { |  | ||||||
| 		// Unsubscribe events
 |  | ||||||
| 		this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent); |  | ||||||
| 		clearInterval(this.emitWatchersIntervalId); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	public onMessage(type: string, body: any) { |  | ||||||
| 		switch (type) { |  | ||||||
| 			case 'accept': this.accept(true); break; |  | ||||||
| 			case 'cancelAccept': this.accept(false); break; |  | ||||||
| 			case 'updateSettings': this.updateSettings(body.key, body.value); break; |  | ||||||
| 			case 'initForm': this.initForm(body); break; |  | ||||||
| 			case 'updateForm': this.updateForm(body.id, body.value); break; |  | ||||||
| 			case 'message': this.message(body); break; |  | ||||||
| 			case 'set': this.set(body.pos); break; |  | ||||||
| 			case 'check': this.check(body.crc32); break; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async updateSettings(key: string, value: any) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (game.isStarted) return; |  | ||||||
| 		if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; |  | ||||||
| 		if ((game.user1Id === this.user.id) && game.user1Accepted) return; |  | ||||||
| 		if ((game.user2Id === this.user.id) && game.user2Accepted) return; |  | ||||||
| 
 |  | ||||||
| 		if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; |  | ||||||
| 
 |  | ||||||
| 		await ReversiGames.update(this.gameId!, { |  | ||||||
| 			[key]: value, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		publishReversiGameStream(this.gameId!, 'updateSettings', { |  | ||||||
| 			key: key, |  | ||||||
| 			value: value, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async initForm(form: any) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (game.isStarted) return; |  | ||||||
| 		if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; |  | ||||||
| 
 |  | ||||||
| 		const set = game.user1Id === this.user.id ? { |  | ||||||
| 			form1: form, |  | ||||||
| 		} : { |  | ||||||
| 			form2: form, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		await ReversiGames.update(this.gameId!, set); |  | ||||||
| 
 |  | ||||||
| 		publishReversiGameStream(this.gameId!, 'initForm', { |  | ||||||
| 			userId: this.user.id, |  | ||||||
| 			form, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async updateForm(id: string, value: any) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (game.isStarted) return; |  | ||||||
| 		if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; |  | ||||||
| 
 |  | ||||||
| 		const form = game.user1Id === this.user.id ? game.form2 : game.form1; |  | ||||||
| 
 |  | ||||||
| 		const item = form.find((i: any) => i.id == id); |  | ||||||
| 
 |  | ||||||
| 		if (item == null) return; |  | ||||||
| 
 |  | ||||||
| 		item.value = value; |  | ||||||
| 
 |  | ||||||
| 		const set = game.user1Id === this.user.id ? { |  | ||||||
| 			form2: form, |  | ||||||
| 		} : { |  | ||||||
| 				form1: form, |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 		await ReversiGames.update(this.gameId!, set); |  | ||||||
| 
 |  | ||||||
| 		publishReversiGameStream(this.gameId!, 'updateForm', { |  | ||||||
| 			userId: this.user.id, |  | ||||||
| 			id, |  | ||||||
| 			value, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async message(message: any) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		message.id = Math.random(); |  | ||||||
| 		publishReversiGameStream(this.gameId!, 'message', { |  | ||||||
| 			userId: this.user.id, |  | ||||||
| 			message, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async accept(accept: boolean) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (game.isStarted) return; |  | ||||||
| 
 |  | ||||||
| 		let bothAccepted = false; |  | ||||||
| 
 |  | ||||||
| 		if (game.user1Id === this.user.id) { |  | ||||||
| 			await ReversiGames.update(this.gameId!, { |  | ||||||
| 				user1Accepted: accept, |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			publishReversiGameStream(this.gameId!, 'changeAccepts', { |  | ||||||
| 				user1: accept, |  | ||||||
| 				user2: game.user2Accepted, |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			if (accept && game.user2Accepted) bothAccepted = true; |  | ||||||
| 		} else if (game.user2Id === this.user.id) { |  | ||||||
| 			await ReversiGames.update(this.gameId!, { |  | ||||||
| 				user2Accepted: accept, |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			publishReversiGameStream(this.gameId!, 'changeAccepts', { |  | ||||||
| 				user1: game.user1Accepted, |  | ||||||
| 				user2: accept, |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			if (accept && game.user1Accepted) bothAccepted = true; |  | ||||||
| 		} else { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (bothAccepted) { |  | ||||||
| 			// 3秒後、まだacceptされていたらゲーム開始
 |  | ||||||
| 			setTimeout(async () => { |  | ||||||
| 				const freshGame = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 				if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; |  | ||||||
| 				if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; |  | ||||||
| 
 |  | ||||||
| 				let bw: number; |  | ||||||
| 				if (freshGame.bw == 'random') { |  | ||||||
| 					bw = Math.random() > 0.5 ? 1 : 2; |  | ||||||
| 				} else { |  | ||||||
| 					bw = parseInt(freshGame.bw, 10); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				function getRandomMap() { |  | ||||||
| 					const mapCount = Object.entries(maps).length; |  | ||||||
| 					const rnd = Math.floor(Math.random() * mapCount); |  | ||||||
| 					return Object.values(maps)[rnd].data; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				const map = freshGame.map != null ? freshGame.map : getRandomMap(); |  | ||||||
| 
 |  | ||||||
| 				await ReversiGames.update(this.gameId!, { |  | ||||||
| 					startedAt: new Date(), |  | ||||||
| 					isStarted: true, |  | ||||||
| 					black: bw, |  | ||||||
| 					map: map, |  | ||||||
| 				}); |  | ||||||
| 
 |  | ||||||
| 				//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
 |  | ||||||
| 				const o = new Reversi(map, { |  | ||||||
| 					isLlotheo: freshGame.isLlotheo, |  | ||||||
| 					canPutEverywhere: freshGame.canPutEverywhere, |  | ||||||
| 					loopedBoard: freshGame.loopedBoard, |  | ||||||
| 				}); |  | ||||||
| 
 |  | ||||||
| 				if (o.isEnded) { |  | ||||||
| 					let winner; |  | ||||||
| 					if (o.winner === true) { |  | ||||||
| 						winner = freshGame.black == 1 ? freshGame.user1Id : freshGame.user2Id; |  | ||||||
| 					} else if (o.winner === false) { |  | ||||||
| 						winner = freshGame.black == 1 ? freshGame.user2Id : freshGame.user1Id; |  | ||||||
| 					} else { |  | ||||||
| 						winner = null; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					await ReversiGames.update(this.gameId!, { |  | ||||||
| 						isEnded: true, |  | ||||||
| 						winnerId: winner, |  | ||||||
| 					}); |  | ||||||
| 
 |  | ||||||
| 					publishReversiGameStream(this.gameId!, 'ended', { |  | ||||||
| 						winnerId: winner, |  | ||||||
| 						game: await ReversiGames.pack(this.gameId!, this.user), |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 				//#endregion
 |  | ||||||
| 
 |  | ||||||
| 				publishReversiGameStream(this.gameId!, 'started', |  | ||||||
| 					await ReversiGames.pack(this.gameId!, this.user)); |  | ||||||
| 			}, 3000); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// 石を打つ
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async set(pos: number) { |  | ||||||
| 		if (this.user == null) return; |  | ||||||
| 
 |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (!game.isStarted) return; |  | ||||||
| 		if (game.isEnded) return; |  | ||||||
| 		if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; |  | ||||||
| 
 |  | ||||||
| 		const myColor = |  | ||||||
| 			((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) |  | ||||||
| 				? true |  | ||||||
| 				: false; |  | ||||||
| 
 |  | ||||||
| 		const o = new Reversi(game.map, { |  | ||||||
| 			isLlotheo: game.isLlotheo, |  | ||||||
| 			canPutEverywhere: game.canPutEverywhere, |  | ||||||
| 			loopedBoard: game.loopedBoard, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		// 盤面の状態を再生
 |  | ||||||
| 		for (const log of game.logs) { |  | ||||||
| 			o.put(log.color, log.pos); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (o.turn !== myColor) return; |  | ||||||
| 
 |  | ||||||
| 		if (!o.canPut(myColor, pos)) return; |  | ||||||
| 		o.put(myColor, pos); |  | ||||||
| 
 |  | ||||||
| 		let winner; |  | ||||||
| 		if (o.isEnded) { |  | ||||||
| 			if (o.winner === true) { |  | ||||||
| 				winner = game.black == 1 ? game.user1Id : game.user2Id; |  | ||||||
| 			} else if (o.winner === false) { |  | ||||||
| 				winner = game.black == 1 ? game.user2Id : game.user1Id; |  | ||||||
| 			} else { |  | ||||||
| 				winner = null; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		const log = { |  | ||||||
| 			at: new Date(), |  | ||||||
| 			color: myColor, |  | ||||||
| 			pos, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); |  | ||||||
| 
 |  | ||||||
| 		game.logs.push(log); |  | ||||||
| 
 |  | ||||||
| 		await ReversiGames.update(this.gameId!, { |  | ||||||
| 			crc32, |  | ||||||
| 			isEnded: o.isEnded, |  | ||||||
| 			winnerId: winner, |  | ||||||
| 			logs: game.logs, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { |  | ||||||
| 			next: o.turn, |  | ||||||
| 		})); |  | ||||||
| 
 |  | ||||||
| 		if (o.isEnded) { |  | ||||||
| 			publishReversiGameStream(this.gameId!, 'ended', { |  | ||||||
| 				winnerId: winner, |  | ||||||
| 				game: await ReversiGames.pack(this.gameId!, this.user), |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private async check(crc32: string | number) { |  | ||||||
| 		const game = await ReversiGames.findOne(this.gameId!); |  | ||||||
| 		if (game == null) throw new Error('game not found'); |  | ||||||
| 
 |  | ||||||
| 		if (!game.isStarted) return; |  | ||||||
| 
 |  | ||||||
| 		if (crc32.toString() !== game.crc32) { |  | ||||||
| 			this.send('rescue', await ReversiGames.pack(game, this.user)); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// ついでに観戦者イベントを発行
 |  | ||||||
| 		this.watch(game); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	private watch(game: ReversiGame) { |  | ||||||
| 		if (this.user != null) { |  | ||||||
| 			if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) { |  | ||||||
| 				publishReversiGameStream(this.gameId!, 'watching', this.user.id); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| import autobind from 'autobind-decorator'; |  | ||||||
| import { publishMainStream } from '@/services/stream'; |  | ||||||
| import Channel from '../../channel'; |  | ||||||
| import { ReversiMatchings } from '@/models/index'; |  | ||||||
| 
 |  | ||||||
| export default class extends Channel { |  | ||||||
| 	public readonly chName = 'gamesReversi'; |  | ||||||
| 	public static shouldShare = true; |  | ||||||
| 	public static requireCredential = true; |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	public async init(params: any) { |  | ||||||
| 		// Subscribe reversi stream
 |  | ||||||
| 		this.subscriber.on(`reversiStream:${this.user!.id}`, data => { |  | ||||||
| 			this.send(data); |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	@autobind |  | ||||||
| 	public async onMessage(type: string, body: any) { |  | ||||||
| 		switch (type) { |  | ||||||
| 			case 'ping': { |  | ||||||
| 				if (body.id == null) return; |  | ||||||
| 				const matching = await ReversiMatchings.findOne({ |  | ||||||
| 					parentId: this.user!.id, |  | ||||||
| 					childId: body.id, |  | ||||||
| 				}); |  | ||||||
| 				if (matching == null) return; |  | ||||||
| 				publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, { id: matching.childId })); |  | ||||||
| 				break; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -13,8 +13,6 @@ import drive from './drive'; | ||||||
| import hashtag from './hashtag'; | import hashtag from './hashtag'; | ||||||
| import channel from './channel'; | import channel from './channel'; | ||||||
| import admin from './admin'; | import admin from './admin'; | ||||||
| import gamesReversi from './games/reversi'; |  | ||||||
| import gamesReversiGame from './games/reversi-game'; |  | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
| 	main, | 	main, | ||||||
|  | @ -32,6 +30,4 @@ export default { | ||||||
| 	hashtag, | 	hashtag, | ||||||
| 	channel, | 	channel, | ||||||
| 	admin, | 	admin, | ||||||
| 	gamesReversi, |  | ||||||
| 	gamesReversiGame, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ import { Emoji } from '@/models/entities/emoji'; | ||||||
| import { UserList } from '@/models/entities/user-list'; | import { UserList } from '@/models/entities/user-list'; | ||||||
| import { MessagingMessage } from '@/models/entities/messaging-message'; | import { MessagingMessage } from '@/models/entities/messaging-message'; | ||||||
| import { UserGroup } from '@/models/entities/user-group'; | import { UserGroup } from '@/models/entities/user-group'; | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { AbuseUserReport } from '@/models/entities/abuse-user-report'; | import { AbuseUserReport } from '@/models/entities/abuse-user-report'; | ||||||
| import { Signin } from '@/models/entities/signin'; | import { Signin } from '@/models/entities/signin'; | ||||||
| import { Page } from '@/models/entities/page'; | import { Page } from '@/models/entities/page'; | ||||||
|  | @ -77,8 +76,6 @@ export interface MainStreamTypes { | ||||||
| 	readAllChannels: undefined; | 	readAllChannels: undefined; | ||||||
| 	unreadChannel: Note['id']; | 	unreadChannel: Note['id']; | ||||||
| 	myTokenRegenerated: undefined; | 	myTokenRegenerated: undefined; | ||||||
| 	reversiNoInvites: undefined; |  | ||||||
| 	reversiInvited: Packed<'ReversiMatching'>; |  | ||||||
| 	signin: Signin; | 	signin: Signin; | ||||||
| 	registryUpdated: { | 	registryUpdated: { | ||||||
| 		scope?: string[]; | 		scope?: string[]; | ||||||
|  | @ -158,47 +155,6 @@ export interface MessagingIndexStreamTypes { | ||||||
| 	message: Packed<'MessagingMessage'>; | 	message: Packed<'MessagingMessage'>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ReversiStreamTypes { |  | ||||||
| 	matched: Packed<'ReversiGame'>; |  | ||||||
| 	invited: Packed<'ReversiMatching'>; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface ReversiGameStreamTypes { |  | ||||||
| 	started: Packed<'ReversiGame'>; |  | ||||||
| 	ended: { |  | ||||||
| 		winnerId?: User['id'] | null, |  | ||||||
| 		game: Packed<'ReversiGame'>; |  | ||||||
| 	}; |  | ||||||
| 	updateSettings: { |  | ||||||
| 		key: string; |  | ||||||
| 		value: FIXME; |  | ||||||
| 	}; |  | ||||||
| 	initForm: { |  | ||||||
| 		userId: User['id']; |  | ||||||
| 		form: FIXME; |  | ||||||
| 	}; |  | ||||||
| 	updateForm: { |  | ||||||
| 		userId: User['id']; |  | ||||||
| 		id: string; |  | ||||||
| 		value: FIXME; |  | ||||||
| 	}; |  | ||||||
| 	message: { |  | ||||||
| 		userId: User['id']; |  | ||||||
| 		message: FIXME; |  | ||||||
| 	}; |  | ||||||
| 	changeAccepts: { |  | ||||||
| 		user1: boolean; |  | ||||||
| 		user2: boolean; |  | ||||||
| 	}; |  | ||||||
| 	set: { |  | ||||||
| 		at: Date; |  | ||||||
| 		color: boolean; |  | ||||||
| 		pos: number; |  | ||||||
| 		next: boolean; |  | ||||||
| 	}; |  | ||||||
| 	watching: User['id']; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface AdminStreamTypes { | export interface AdminStreamTypes { | ||||||
| 	newAbuseUserReport: { | 	newAbuseUserReport: { | ||||||
| 		id: AbuseUserReport['id']; | 		id: AbuseUserReport['id']; | ||||||
|  | @ -268,14 +224,6 @@ export type StreamMessages = { | ||||||
| 		name: `messagingIndexStream:${User['id']}`; | 		name: `messagingIndexStream:${User['id']}`; | ||||||
| 		payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; | 		payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; | ||||||
| 	}; | 	}; | ||||||
| 	reversi: { |  | ||||||
| 		name: `reversiStream:${User['id']}`; |  | ||||||
| 		payload: EventUnionFromDictionary<ReversiStreamTypes>; |  | ||||||
| 	}; |  | ||||||
| 	reversiGame: { |  | ||||||
| 		name: `reversiGameStream:${ReversiGame['id']}`; |  | ||||||
| 		payload: EventUnionFromDictionary<ReversiGameStreamTypes>; |  | ||||||
| 	}; |  | ||||||
| 	admin: { | 	admin: { | ||||||
| 		name: `adminStream:${User['id']}`; | 		name: `adminStream:${User['id']}`; | ||||||
| 		payload: EventUnionFromDictionary<AdminStreamTypes>; | 		payload: EventUnionFromDictionary<AdminStreamTypes>; | ||||||
|  |  | ||||||
|  | @ -390,9 +390,6 @@ router.get('/cli', async ctx => { | ||||||
| const override = (source: string, target: string, depth: number = 0) => | const override = (source: string, target: string, depth: number = 0) => | ||||||
| 	[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); | 	[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); | ||||||
| 
 | 
 | ||||||
| router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1))); |  | ||||||
| router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games'))); |  | ||||||
| 
 |  | ||||||
| router.get('/flush', async ctx => { | router.get('/flush', async ctx => { | ||||||
| 	await ctx.render('flush'); | 	await ctx.render('flush'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ import { redisClient } from '../db/redis'; | ||||||
| import { User } from '@/models/entities/user'; | import { User } from '@/models/entities/user'; | ||||||
| import { Note } from '@/models/entities/note'; | import { Note } from '@/models/entities/note'; | ||||||
| import { UserList } from '@/models/entities/user-list'; | import { UserList } from '@/models/entities/user-list'; | ||||||
| import { ReversiGame } from '@/models/entities/games/reversi/game'; |  | ||||||
| import { UserGroup } from '@/models/entities/user-group'; | import { UserGroup } from '@/models/entities/user-group'; | ||||||
| import config from '@/config/index'; | import config from '@/config/index'; | ||||||
| import { Antenna } from '@/models/entities/antenna'; | import { Antenna } from '@/models/entities/antenna'; | ||||||
|  | @ -20,8 +19,6 @@ import { | ||||||
| 	MessagingIndexStreamTypes, | 	MessagingIndexStreamTypes, | ||||||
| 	MessagingStreamTypes, | 	MessagingStreamTypes, | ||||||
| 	NoteStreamTypes, | 	NoteStreamTypes, | ||||||
| 	ReversiGameStreamTypes, |  | ||||||
| 	ReversiStreamTypes, |  | ||||||
| 	UserListStreamTypes, | 	UserListStreamTypes, | ||||||
| 	UserStreamTypes, | 	UserStreamTypes, | ||||||
| } from '@/server/api/stream/types'; | } from '@/server/api/stream/types'; | ||||||
|  | @ -90,14 +87,6 @@ class Publisher { | ||||||
| 		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); | 		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { |  | ||||||
| 		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { |  | ||||||
| 		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	public publishNotesStream = (note: Packed<'Note'>): void => { | 	public publishNotesStream = (note: Packed<'Note'>): void => { | ||||||
| 		this.publish('notesStream', null, note); | 		this.publish('notesStream', null, note); | ||||||
| 	}; | 	}; | ||||||
|  | @ -124,6 +113,4 @@ export const publishAntennaStream = publisher.publishAntennaStream; | ||||||
| export const publishMessagingStream = publisher.publishMessagingStream; | export const publishMessagingStream = publisher.publishMessagingStream; | ||||||
| export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; | export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; | ||||||
| export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; | export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; | ||||||
| export const publishReversiStream = publisher.publishReversiStream; |  | ||||||
| export const publishReversiGameStream = publisher.publishReversiGameStream; |  | ||||||
| export const publishAdminStream = publisher.publishAdminStream; | export const publishAdminStream = publisher.publishAdminStream; | ||||||
|  |  | ||||||
|  | @ -163,11 +163,6 @@ export const menuDef = reactive({ | ||||||
| 		icon: 'fas fa-laugh', | 		icon: 'fas fa-laugh', | ||||||
| 		to: '/emojis', | 		to: '/emojis', | ||||||
| 	}, | 	}, | ||||||
| 	games: { |  | ||||||
| 		title: 'games', |  | ||||||
| 		icon: 'fas fa-gamepad', |  | ||||||
| 		to: '/games/reversi', |  | ||||||
| 	}, |  | ||||||
| 	scratchpad: { | 	scratchpad: { | ||||||
| 		title: 'scratchpad', | 		title: 'scratchpad', | ||||||
| 		icon: 'fas fa-terminal', | 		icon: 'fas fa-terminal', | ||||||
|  |  | ||||||
|  | @ -1,528 +0,0 @@ | ||||||
| <template> |  | ||||||
| <div class="xqnhankfuuilcwvhgsopeqncafzsquya"> |  | ||||||
| 	<header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header> |  | ||||||
| 
 |  | ||||||
| 	<div style="overflow: hidden; line-height: 28px;"> |  | ||||||
| 		<p v-if="!iAmPlayer && !game.isEnded" class="turn"> |  | ||||||
| 			<Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> |  | ||||||
| 			<MkEllipsis/> |  | ||||||
| 		</p> |  | ||||||
| 		<p v-if="logPos != logs.length" class="turn"> |  | ||||||
| 			<Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> |  | ||||||
| 		</p> |  | ||||||
| 		<p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p> |  | ||||||
| 		<p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p> |  | ||||||
| 		<p v-if="game.isEnded && logPos == logs.length" class="result"> |  | ||||||
| 			<template v-if="game.winner"> |  | ||||||
| 				<Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/> |  | ||||||
| 				<span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span> |  | ||||||
| 			</template> |  | ||||||
| 			<template v-else>{{ $ts._reversi.drawn }}</template> |  | ||||||
| 		</p> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<div class="board"> |  | ||||||
| 		<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> |  | ||||||
| 			<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> |  | ||||||
| 		</div> |  | ||||||
| 		<div class="flex"> |  | ||||||
| 			<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> |  | ||||||
| 				<div v-for="i in game.map.length">{{ i }}</div> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="cells" :style="cellsStyle"> |  | ||||||
| 				<div v-for="(stone, i) in o.board" |  | ||||||
| 					:class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }" |  | ||||||
| 					:title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`" |  | ||||||
| 					@click="set(i)" |  | ||||||
| 				> |  | ||||||
| 					<template v-if="$store.state.gamesReversiUseAvatarStones || true"> |  | ||||||
| 						<img v-if="stone === true" :src="blackUser.avatarUrl" alt="black"> |  | ||||||
| 						<img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white"> |  | ||||||
| 					</template> |  | ||||||
| 					<template v-else> |  | ||||||
| 						<i v-if="stone === true" class="fas fa-circle"></i> |  | ||||||
| 						<i v-if="stone === false" class="far fa-circle"></i> |  | ||||||
| 					</template> |  | ||||||
| 				</div> |  | ||||||
| 			</div> |  | ||||||
| 			<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> |  | ||||||
| 				<div v-for="i in game.map.length">{{ i }}</div> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 		<div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> |  | ||||||
| 			<span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p> |  | ||||||
| 
 |  | ||||||
| 	<div v-if="!game.isEnded && iAmPlayer" class="actions"> |  | ||||||
| 		<MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<div v-if="game.isEnded" class="player"> |  | ||||||
| 		<span>{{ logPos }} / {{ logs.length }}</span> |  | ||||||
| 		<div v-if="!autoplaying" class="buttons"> |  | ||||||
| 			<MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton> |  | ||||||
| 			<MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton> |  | ||||||
| 			<MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton> |  | ||||||
| 			<MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton> |  | ||||||
| 		</div> |  | ||||||
| 		<MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<div class="info"> |  | ||||||
| 		<p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p> |  | ||||||
| 		<p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p> |  | ||||||
| 		<p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<div class="watchers"> |  | ||||||
| 		<MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent } from 'vue'; |  | ||||||
| import * as CRC32 from 'crc-32'; |  | ||||||
| import Reversi, { Color } from '@/scripts/games/reversi/core'; |  | ||||||
| import { url } from '@/config'; |  | ||||||
| import MkButton from '@/components/ui/button.vue'; |  | ||||||
| import { userPage } from '@/filters/user'; |  | ||||||
| import * as os from '@/os'; |  | ||||||
| import * as sound from '@/scripts/sound'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
| 	components: { |  | ||||||
| 		MkButton |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	props: { |  | ||||||
| 		initGame: { |  | ||||||
| 			type: Object, |  | ||||||
| 			require: true |  | ||||||
| 		}, |  | ||||||
| 		connection: { |  | ||||||
| 			type: Object, |  | ||||||
| 			require: true |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			game: JSON.parse(JSON.stringify(this.initGame)), |  | ||||||
| 			o: null as Reversi, |  | ||||||
| 			logs: [], |  | ||||||
| 			logPos: 0, |  | ||||||
| 			watchers: [], |  | ||||||
| 			pollingClock: null, |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	computed: { |  | ||||||
| 		iAmPlayer(): boolean { |  | ||||||
| 			if (!this.$i) return false; |  | ||||||
| 			return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		myColor(): Color { |  | ||||||
| 			if (!this.iAmPlayer) return null; |  | ||||||
| 			if (this.game.user1Id == this.$i.id && this.game.black == 1) return true; |  | ||||||
| 			if (this.game.user2Id == this.$i.id && this.game.black == 2) return true; |  | ||||||
| 			return false; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		opColor(): Color { |  | ||||||
| 			if (!this.iAmPlayer) return null; |  | ||||||
| 			return this.myColor === true ? false : true; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		blackUser(): any { |  | ||||||
| 			return this.game.black == 1 ? this.game.user1 : this.game.user2; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		whiteUser(): any { |  | ||||||
| 			return this.game.black == 1 ? this.game.user2 : this.game.user1; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		cellsStyle(): any { |  | ||||||
| 			return { |  | ||||||
| 				'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`, |  | ||||||
| 				'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)` |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	watch: { |  | ||||||
| 		logPos(v) { |  | ||||||
| 			if (!this.game.isEnded) return; |  | ||||||
| 			const o = new Reversi(this.game.map, { |  | ||||||
| 				isLlotheo: this.game.isLlotheo, |  | ||||||
| 				canPutEverywhere: this.game.canPutEverywhere, |  | ||||||
| 				loopedBoard: this.game.loopedBoard |  | ||||||
| 			}); |  | ||||||
| 			for (const log of this.logs.slice(0, v)) { |  | ||||||
| 				o.put(log.color, log.pos); |  | ||||||
| 			} |  | ||||||
| 			this.o = o; |  | ||||||
| 			//this.$forceUpdate(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	created() { |  | ||||||
| 		this.o = new Reversi(this.game.map, { |  | ||||||
| 			isLlotheo: this.game.isLlotheo, |  | ||||||
| 			canPutEverywhere: this.game.canPutEverywhere, |  | ||||||
| 			loopedBoard: this.game.loopedBoard |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		for (const log of this.game.logs) { |  | ||||||
| 			this.o.put(log.color, log.pos); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		this.logs = this.game.logs; |  | ||||||
| 		this.logPos = this.logs.length; |  | ||||||
| 
 |  | ||||||
| 		// 通信を取りこぼしてもいいように定期的にポーリングさせる |  | ||||||
| 		if (this.game.isStarted && !this.game.isEnded) { |  | ||||||
| 			this.pollingClock = setInterval(() => { |  | ||||||
| 				if (this.game.isEnded) return; |  | ||||||
| 				const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join('')); |  | ||||||
| 				this.connection.send('check', { |  | ||||||
| 					crc32: crc32 |  | ||||||
| 				}); |  | ||||||
| 			}, 3000); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	mounted() { |  | ||||||
| 		this.connection.on('set', this.onSet); |  | ||||||
| 		this.connection.on('rescue', this.onRescue); |  | ||||||
| 		this.connection.on('ended', this.onEnded); |  | ||||||
| 		this.connection.on('watchers', this.onWatchers); |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	beforeUnmount() { |  | ||||||
| 		this.connection.off('set', this.onSet); |  | ||||||
| 		this.connection.off('rescue', this.onRescue); |  | ||||||
| 		this.connection.off('ended', this.onEnded); |  | ||||||
| 		this.connection.off('watchers', this.onWatchers); |  | ||||||
| 
 |  | ||||||
| 		clearInterval(this.pollingClock); |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	methods: { |  | ||||||
| 		userPage, |  | ||||||
| 
 |  | ||||||
| 		// this.o がリアクティブになった折にはcomputedにできる |  | ||||||
| 		turnUser(): any { |  | ||||||
| 			if (this.o.turn === true) { |  | ||||||
| 				return this.game.black == 1 ? this.game.user1 : this.game.user2; |  | ||||||
| 			} else if (this.o.turn === false) { |  | ||||||
| 				return this.game.black == 1 ? this.game.user2 : this.game.user1; |  | ||||||
| 			} else { |  | ||||||
| 				return null; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		// this.o がリアクティブになった折にはcomputedにできる |  | ||||||
| 		isMyTurn(): boolean { |  | ||||||
| 			if (!this.iAmPlayer) return false; |  | ||||||
| 			if (this.turnUser() == null) return false; |  | ||||||
| 			return this.turnUser().id == this.$i.id; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		set(pos) { |  | ||||||
| 			if (this.game.isEnded) return; |  | ||||||
| 			if (!this.iAmPlayer) return; |  | ||||||
| 			if (!this.isMyTurn()) return; |  | ||||||
| 			if (!this.o.canPut(this.myColor, pos)) return; |  | ||||||
| 
 |  | ||||||
| 			this.o.put(this.myColor, pos); |  | ||||||
| 
 |  | ||||||
| 			// サウンドを再生する |  | ||||||
| 			sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite'); |  | ||||||
| 
 |  | ||||||
| 			this.connection.send('set', { |  | ||||||
| 				pos: pos |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			this.checkEnd(); |  | ||||||
| 
 |  | ||||||
| 			this.$forceUpdate(); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onSet(x) { |  | ||||||
| 			this.logs.push(x); |  | ||||||
| 			this.logPos++; |  | ||||||
| 			this.o.put(x.color, x.pos); |  | ||||||
| 			this.checkEnd(); |  | ||||||
| 			this.$forceUpdate(); |  | ||||||
| 
 |  | ||||||
| 			// サウンドを再生する |  | ||||||
| 			if (x.color !== this.myColor) { |  | ||||||
| 				sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite'); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onEnded(x) { |  | ||||||
| 			this.game = JSON.parse(JSON.stringify(x.game)); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		checkEnd() { |  | ||||||
| 			this.game.isEnded = this.o.isEnded; |  | ||||||
| 			if (this.game.isEnded) { |  | ||||||
| 				if (this.o.winner === true) { |  | ||||||
| 					this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id; |  | ||||||
| 					this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2; |  | ||||||
| 				} else if (this.o.winner === false) { |  | ||||||
| 					this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id; |  | ||||||
| 					this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1; |  | ||||||
| 				} else { |  | ||||||
| 					this.game.winnerId = null; |  | ||||||
| 					this.game.winner = null; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		// 正しいゲーム情報が送られてきたとき |  | ||||||
| 		onRescue(game) { |  | ||||||
| 			this.game = JSON.parse(JSON.stringify(game)); |  | ||||||
| 
 |  | ||||||
| 			this.o = new Reversi(this.game.map, { |  | ||||||
| 				isLlotheo: this.game.isLlotheo, |  | ||||||
| 				canPutEverywhere: this.game.canPutEverywhere, |  | ||||||
| 				loopedBoard: this.game.loopedBoard |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			for (const log of this.game.logs) { |  | ||||||
| 				this.o.put(log.color, log.pos, true); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			this.logs = this.game.logs; |  | ||||||
| 			this.logPos = this.logs.length; |  | ||||||
| 
 |  | ||||||
| 			this.checkEnd(); |  | ||||||
| 			this.$forceUpdate(); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onWatchers(users) { |  | ||||||
| 			this.watchers = users; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		surrender() { |  | ||||||
| 			os.api('games/reversi/games/surrender', { |  | ||||||
| 				gameId: this.game.id |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		autoplay() { |  | ||||||
| 			this.autoplaying = true; |  | ||||||
| 			this.logPos = 0; |  | ||||||
| 
 |  | ||||||
| 			setTimeout(() => { |  | ||||||
| 				this.logPos = 1; |  | ||||||
| 
 |  | ||||||
| 				let i = 1; |  | ||||||
| 				let previousLog = this.game.logs[0]; |  | ||||||
| 				const tick = () => { |  | ||||||
| 					const log = this.game.logs[i]; |  | ||||||
| 					const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime() |  | ||||||
| 					setTimeout(() => { |  | ||||||
| 						i++; |  | ||||||
| 						this.logPos++; |  | ||||||
| 						previousLog = log; |  | ||||||
| 
 |  | ||||||
| 						if (i < this.game.logs.length) { |  | ||||||
| 							tick(); |  | ||||||
| 						} else { |  | ||||||
| 							this.autoplaying = false; |  | ||||||
| 						} |  | ||||||
| 					}, time); |  | ||||||
| 				}; |  | ||||||
| 
 |  | ||||||
| 				tick(); |  | ||||||
| 			}, 1000); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <style lang="scss" scoped> |  | ||||||
| 
 |  | ||||||
| @use "sass:math"; |  | ||||||
| 
 |  | ||||||
| .xqnhankfuuilcwvhgsopeqncafzsquya { |  | ||||||
| 	text-align: center; |  | ||||||
| 
 |  | ||||||
| 	> .go-index { |  | ||||||
| 		position: absolute; |  | ||||||
| 		top: 0; |  | ||||||
| 		left: 0; |  | ||||||
| 		z-index: 1; |  | ||||||
| 		width: 42px; |  | ||||||
| 		height :42px; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> header { |  | ||||||
| 		padding: 8px; |  | ||||||
| 		border-bottom: dashed 1px var(--divider); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .board { |  | ||||||
| 		width: calc(100% - 16px); |  | ||||||
| 		max-width: 500px; |  | ||||||
| 		margin: 0 auto; |  | ||||||
| 
 |  | ||||||
| 		$label-size: 16px; |  | ||||||
| 		$gap: 4px; |  | ||||||
| 
 |  | ||||||
| 		> .labels-x { |  | ||||||
| 			height: $label-size; |  | ||||||
| 			padding: 0 $label-size; |  | ||||||
| 			display: flex; |  | ||||||
| 
 |  | ||||||
| 			> * { |  | ||||||
| 				flex: 1; |  | ||||||
| 				display: flex; |  | ||||||
| 				align-items: center; |  | ||||||
| 				justify-content: center; |  | ||||||
| 				font-size: 0.8em; |  | ||||||
| 
 |  | ||||||
| 				&:first-child { |  | ||||||
| 					margin-left: -(math.div($gap, 2)); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				&:last-child { |  | ||||||
| 					margin-right: -(math.div($gap, 2)); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .flex { |  | ||||||
| 			display: flex; |  | ||||||
| 
 |  | ||||||
| 			> .labels-y { |  | ||||||
| 				width: $label-size; |  | ||||||
| 				display: flex; |  | ||||||
| 				flex-direction: column; |  | ||||||
| 
 |  | ||||||
| 				> * { |  | ||||||
| 					flex: 1; |  | ||||||
| 					display: flex; |  | ||||||
| 					align-items: center; |  | ||||||
| 					justify-content: center; |  | ||||||
| 					font-size: 12px; |  | ||||||
| 
 |  | ||||||
| 					&:first-child { |  | ||||||
| 						margin-top: -(math.div($gap, 2)); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&:last-child { |  | ||||||
| 						margin-bottom: -(math.div($gap, 2)); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			> .cells { |  | ||||||
| 				flex: 1; |  | ||||||
| 				display: grid; |  | ||||||
| 				grid-gap: $gap; |  | ||||||
| 
 |  | ||||||
| 				> div { |  | ||||||
| 					background: transparent; |  | ||||||
| 					border-radius: 6px; |  | ||||||
| 					overflow: hidden; |  | ||||||
| 
 |  | ||||||
| 					* { |  | ||||||
| 						pointer-events: none; |  | ||||||
| 						user-select: none; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.empty { |  | ||||||
| 						border: solid 2px var(--divider); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.empty.can { |  | ||||||
| 						border-color: var(--accent); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.empty.myTurn { |  | ||||||
| 						border-color: var(--divider); |  | ||||||
| 
 |  | ||||||
| 						&.can { |  | ||||||
| 							border-color: var(--accent); |  | ||||||
| 							cursor: pointer; |  | ||||||
| 
 |  | ||||||
| 							&:hover { |  | ||||||
| 								background: var(--accent); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.prev { |  | ||||||
| 						box-shadow: 0 0 0 4px var(--accent); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.isEnded { |  | ||||||
| 						border-color: var(--divider); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					&.none { |  | ||||||
| 						border-color: transparent !important; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					> svg, > img { |  | ||||||
| 						display: block; |  | ||||||
| 						width: 100%; |  | ||||||
| 						height: 100%; |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .status { |  | ||||||
| 		margin: 0; |  | ||||||
| 		padding: 16px 0; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .actions { |  | ||||||
| 		padding-bottom: 16px; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .player { |  | ||||||
| 		padding: 0 16px 32px 16px; |  | ||||||
| 		margin: 0 auto; |  | ||||||
| 		max-width: 500px; |  | ||||||
| 
 |  | ||||||
| 		> span { |  | ||||||
| 			display: inline-block; |  | ||||||
| 			margin: 0 8px; |  | ||||||
| 			min-width: 70px; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .buttons { |  | ||||||
| 			display: flex; |  | ||||||
| 
 |  | ||||||
| 			> * { |  | ||||||
| 				flex: 1; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .watchers { |  | ||||||
| 		padding: 0 0 16px 0; |  | ||||||
| 
 |  | ||||||
| 		&:empty { |  | ||||||
| 			display: none; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .avatar { |  | ||||||
| 			width: 32px; |  | ||||||
| 			height: 32px; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
|  | @ -1,390 +0,0 @@ | ||||||
| <template> |  | ||||||
| <div class="urbixznjwwuukfsckrwzwsqzsxornqij"> |  | ||||||
| 	<header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header> |  | ||||||
| 
 |  | ||||||
| 	<div> |  | ||||||
| 		<p>{{ $ts._reversi.gameSettings }}</p> |  | ||||||
| 
 |  | ||||||
| 		<div class="card map _panel"> |  | ||||||
| 			<header> |  | ||||||
| 				<select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange"> |  | ||||||
| 					<option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/> |  | ||||||
| 					<option :label="$ts.random" :value="null"/> |  | ||||||
| 					<optgroup v-for="c in mapCategories" :key="c" :label="c"> |  | ||||||
| 						<option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option> |  | ||||||
| 					</optgroup> |  | ||||||
| 				</select> |  | ||||||
| 			</header> |  | ||||||
| 
 |  | ||||||
| 			<div> |  | ||||||
| 				<div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div> |  | ||||||
| 				<div v-else class="board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> |  | ||||||
| 					<div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)"> |  | ||||||
| 						<i v-if="x === 'b'" class="fas fa-circle"></i> |  | ||||||
| 						<i v-if="x === 'w'" class="far fa-circle"></i> |  | ||||||
| 					</div> |  | ||||||
| 				</div> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 
 |  | ||||||
| 		<div class="card _panel"> |  | ||||||
| 			<header> |  | ||||||
| 				<span>{{ $ts._reversi.blackOrWhite }}</span> |  | ||||||
| 			</header> |  | ||||||
| 
 |  | ||||||
| 			<div> |  | ||||||
| 				<MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio> |  | ||||||
| 				<MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')"> |  | ||||||
| 					<I18n :src="$ts._reversi.blackIs" tag="span"> |  | ||||||
| 						<template #name> |  | ||||||
| 							<b><MkUserName :user="game.user1"/></b> |  | ||||||
| 						</template> |  | ||||||
| 					</I18n> |  | ||||||
| 				</MkRadio> |  | ||||||
| 				<MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')"> |  | ||||||
| 					<I18n :src="$ts._reversi.blackIs" tag="span"> |  | ||||||
| 						<template #name> |  | ||||||
| 							<b><MkUserName :user="game.user2"/></b> |  | ||||||
| 						</template> |  | ||||||
| 					</I18n> |  | ||||||
| 				</MkRadio> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 
 |  | ||||||
| 		<div class="card _panel"> |  | ||||||
| 			<header> |  | ||||||
| 				<span>{{ $ts._reversi.rules }}</span> |  | ||||||
| 			</header> |  | ||||||
| 
 |  | ||||||
| 			<div> |  | ||||||
| 				<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch> |  | ||||||
| 				<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch> |  | ||||||
| 				<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 
 |  | ||||||
| 		<div v-if="form" class="card form _panel"> |  | ||||||
| 			<header> |  | ||||||
| 				<span>{{ $ts._reversi.botSettings }}</span> |  | ||||||
| 			</header> |  | ||||||
| 
 |  | ||||||
| 			<div> |  | ||||||
| 				<template v-for="item in form"> |  | ||||||
| 					<MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch> |  | ||||||
| 
 |  | ||||||
| 					<div v-if="item.type == 'radio'" :key="item.id" class="card"> |  | ||||||
| 						<header> |  | ||||||
| 							<span>{{ item.label }}</span> |  | ||||||
| 						</header> |  | ||||||
| 
 |  | ||||||
| 						<div> |  | ||||||
| 							<MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
| 
 |  | ||||||
| 					<div v-if="item.type == 'slider'" :key="item.id" class="card"> |  | ||||||
| 						<header> |  | ||||||
| 							<span>{{ item.label }}</span> |  | ||||||
| 						</header> |  | ||||||
| 
 |  | ||||||
| 						<div> |  | ||||||
| 							<input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
| 
 |  | ||||||
| 					<div v-if="item.type == 'textbox'" :key="item.id" class="card"> |  | ||||||
| 						<header> |  | ||||||
| 							<span>{{ item.label }}</span> |  | ||||||
| 						</header> |  | ||||||
| 
 |  | ||||||
| 						<div> |  | ||||||
| 							<input v-model="item.value" @change="onChangeForm(item)"/> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
| 				</template> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<footer class="_acrylic"> |  | ||||||
| 		<p class="status"> |  | ||||||
| 			<template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> |  | ||||||
| 			<template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template> |  | ||||||
| 			<template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template> |  | ||||||
| 			<template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template> |  | ||||||
| 		</p> |  | ||||||
| 
 |  | ||||||
| 		<div class="actions"> |  | ||||||
| 			<MkButton inline @click="exit">{{ $ts.cancel }}</MkButton> |  | ||||||
| 			<MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton> |  | ||||||
| 			<MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton> |  | ||||||
| 		</div> |  | ||||||
| 	</footer> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent } from 'vue'; |  | ||||||
| import * as maps from '@/scripts/games/reversi/maps'; |  | ||||||
| import MkButton from '@/components/ui/button.vue'; |  | ||||||
| import MkSwitch from '@/components/form/switch.vue'; |  | ||||||
| import MkRadio from '@/components/form/radio.vue'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
| 	components: { |  | ||||||
| 		MkButton, |  | ||||||
| 		MkSwitch, |  | ||||||
| 		MkRadio, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	props: { |  | ||||||
| 		initGame: { |  | ||||||
| 			type: Object, |  | ||||||
| 			require: true |  | ||||||
| 		}, |  | ||||||
| 		connection: { |  | ||||||
| 			type: Object, |  | ||||||
| 			require: true |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			game: this.initGame, |  | ||||||
| 			o: null, |  | ||||||
| 			isLlotheo: false, |  | ||||||
| 			mapName: maps.eighteight.name, |  | ||||||
| 			maps: maps, |  | ||||||
| 			form: null, |  | ||||||
| 			messages: [], |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	computed: { |  | ||||||
| 		mapCategories(): string[] { |  | ||||||
| 			const categories = Object.values(maps).map(x => x.category); |  | ||||||
| 			return categories.filter((item, pos) => categories.indexOf(item) == pos); |  | ||||||
| 		}, |  | ||||||
| 		isAccepted(): boolean { |  | ||||||
| 			if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true; |  | ||||||
| 			if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true; |  | ||||||
| 			return false; |  | ||||||
| 		}, |  | ||||||
| 		isOpAccepted(): boolean { |  | ||||||
| 			if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true; |  | ||||||
| 			if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true; |  | ||||||
| 			return false; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	created() { |  | ||||||
| 		this.connection.on('changeAccepts', this.onChangeAccepts); |  | ||||||
| 		this.connection.on('updateSettings', this.onUpdateSettings); |  | ||||||
| 		this.connection.on('initForm', this.onInitForm); |  | ||||||
| 		this.connection.on('message', this.onMessage); |  | ||||||
| 
 |  | ||||||
| 		if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1; |  | ||||||
| 		if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	beforeUnmount() { |  | ||||||
| 		this.connection.off('changeAccepts', this.onChangeAccepts); |  | ||||||
| 		this.connection.off('updateSettings', this.onUpdateSettings); |  | ||||||
| 		this.connection.off('initForm', this.onInitForm); |  | ||||||
| 		this.connection.off('message', this.onMessage); |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	methods: { |  | ||||||
| 		exit() { |  | ||||||
| 
 |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		accept() { |  | ||||||
| 			this.connection.send('accept', {}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		cancel() { |  | ||||||
| 			this.connection.send('cancelAccept', {}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onChangeAccepts(accepts) { |  | ||||||
| 			this.game.user1Accepted = accepts.user1; |  | ||||||
| 			this.game.user2Accepted = accepts.user2; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		updateSettings(key: string) { |  | ||||||
| 			this.connection.send('updateSettings', { |  | ||||||
| 				key: key, |  | ||||||
| 				value: this.game[key] |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onUpdateSettings({ key, value }) { |  | ||||||
| 			this.game[key] = value; |  | ||||||
| 			if (this.game.map == null) { |  | ||||||
| 				this.mapName = null; |  | ||||||
| 			} else { |  | ||||||
| 				const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join('')); |  | ||||||
| 				this.mapName = found ? found.name : '-Custom-'; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onInitForm(x) { |  | ||||||
| 			if (x.userId == this.$i.id) return; |  | ||||||
| 			this.form = x.form; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onMessage(x) { |  | ||||||
| 			if (x.userId == this.$i.id) return; |  | ||||||
| 			this.messages.unshift(x.message); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onChangeForm(item) { |  | ||||||
| 			this.connection.send('updateForm', { |  | ||||||
| 				id: item.id, |  | ||||||
| 				value: item.value |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onMapChange() { |  | ||||||
| 			if (this.mapName == null) { |  | ||||||
| 				this.game.map = null; |  | ||||||
| 			} else { |  | ||||||
| 				this.game.map = Object.values(maps).find(x => x.name == this.mapName).data; |  | ||||||
| 			} |  | ||||||
| 			this.updateSettings('map'); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onPixelClick(pos, pixel) { |  | ||||||
| 			const x = pos % this.game.map[0].length; |  | ||||||
| 			const y = Math.floor(pos / this.game.map[0].length); |  | ||||||
| 			const newPixel = |  | ||||||
| 				pixel == ' ' ? '-' : |  | ||||||
| 				pixel == '-' ? 'b' : |  | ||||||
| 				pixel == 'b' ? 'w' : |  | ||||||
| 				' '; |  | ||||||
| 			const line = this.game.map[y].split(''); |  | ||||||
| 			line[x] = newPixel; |  | ||||||
| 			this.game.map[y] = line.join(''); |  | ||||||
| 			this.updateSettings('map'); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <style lang="scss" scoped> |  | ||||||
| .urbixznjwwuukfsckrwzwsqzsxornqij { |  | ||||||
| 	text-align: center; |  | ||||||
| 	background: var(--bg); |  | ||||||
| 
 |  | ||||||
| 	> header { |  | ||||||
| 		padding: 8px; |  | ||||||
| 		border-bottom: dashed 1px #c4cdd4; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> div { |  | ||||||
| 		padding: 0 16px; |  | ||||||
| 
 |  | ||||||
| 		> .card { |  | ||||||
| 			margin: 0 auto 16px auto; |  | ||||||
| 
 |  | ||||||
| 			&.map { |  | ||||||
| 				> header { |  | ||||||
| 					> select { |  | ||||||
| 						width: 100%; |  | ||||||
| 						padding: 12px 14px; |  | ||||||
| 						background: var(--face); |  | ||||||
| 						border: 1px solid var(--inputBorder); |  | ||||||
| 						border-radius: 4px; |  | ||||||
| 						color: var(--fg); |  | ||||||
| 						cursor: pointer; |  | ||||||
| 						transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); |  | ||||||
| 						-webkit-appearance: none; |  | ||||||
| 						-moz-appearance: none; |  | ||||||
| 						appearance: none; |  | ||||||
| 
 |  | ||||||
| 						&:focus-visible, |  | ||||||
| 						&:active { |  | ||||||
| 							border-color: var(--accent); |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				> div { |  | ||||||
| 					> .random { |  | ||||||
| 						padding: 32px 0; |  | ||||||
| 						font-size: 64px; |  | ||||||
| 						color: var(--fg); |  | ||||||
| 						opacity: 0.7; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					> .board { |  | ||||||
| 						display: grid; |  | ||||||
| 						grid-gap: 4px; |  | ||||||
| 						width: 300px; |  | ||||||
| 						height: 300px; |  | ||||||
| 						margin: 0 auto; |  | ||||||
| 						color: var(--fg); |  | ||||||
| 
 |  | ||||||
| 						> div { |  | ||||||
| 							background: transparent; |  | ||||||
| 							border: solid 2px var(--divider); |  | ||||||
| 							border-radius: 6px; |  | ||||||
| 							overflow: hidden; |  | ||||||
| 							cursor: pointer; |  | ||||||
| 
 |  | ||||||
| 							* { |  | ||||||
| 								pointer-events: none; |  | ||||||
| 								user-select: none; |  | ||||||
| 								width: 100%; |  | ||||||
| 								height: 100%; |  | ||||||
| 							} |  | ||||||
| 
 |  | ||||||
| 							&.none { |  | ||||||
| 								border-color: transparent; |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			&.form { |  | ||||||
| 				> div { |  | ||||||
| 					> .card + .card { |  | ||||||
| 						margin-top: 16px; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					input[type='range'] { |  | ||||||
| 						width: 100%; |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		.card { |  | ||||||
| 			max-width: 400px; |  | ||||||
| 
 |  | ||||||
| 			> header { |  | ||||||
| 				padding: 18px 20px; |  | ||||||
| 				border-bottom: 1px solid var(--divider); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			> div { |  | ||||||
| 				padding: 20px; |  | ||||||
| 				color: var(--fg); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> footer { |  | ||||||
| 		position: sticky; |  | ||||||
| 		bottom: 0; |  | ||||||
| 		padding: 16px; |  | ||||||
| 		border-top: solid 1px var(--divider); |  | ||||||
| 
 |  | ||||||
| 		> .status { |  | ||||||
| 			margin: 0 0 16px 0; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
|  | @ -1,77 +0,0 @@ | ||||||
| <template> |  | ||||||
| <div v-if="game == null"><MkLoading/></div> |  | ||||||
| <GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/> |  | ||||||
| <GameBoard v-else :init-game="game" :connection="connection"/> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent, markRaw } from 'vue'; |  | ||||||
| import GameSetting from './game.setting.vue'; |  | ||||||
| import GameBoard from './game.board.vue'; |  | ||||||
| import * as os from '@/os'; |  | ||||||
| import { stream } from '@/stream'; |  | ||||||
| import * as symbols from '@/symbols'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
| 	components: { |  | ||||||
| 		GameSetting, |  | ||||||
| 		GameBoard, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	props: { |  | ||||||
| 		gameId: { |  | ||||||
| 			type: String, |  | ||||||
| 			required: true |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			[symbols.PAGE_INFO]: { |  | ||||||
| 				title: this.$ts._reversi.reversi, |  | ||||||
| 				icon: 'fas fa-gamepad' |  | ||||||
| 			}, |  | ||||||
| 			game: null, |  | ||||||
| 			connection: null, |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	watch: { |  | ||||||
| 		gameId() { |  | ||||||
| 			this.fetch(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	mounted() { |  | ||||||
| 		this.fetch(); |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	beforeUnmount() { |  | ||||||
| 		if (this.connection) { |  | ||||||
| 			this.connection.dispose(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	methods: { |  | ||||||
| 		fetch() { |  | ||||||
| 			os.api('games/reversi/games/show', { |  | ||||||
| 				gameId: this.gameId |  | ||||||
| 			}).then(game => { |  | ||||||
| 				this.game = game; |  | ||||||
| 
 |  | ||||||
| 				if (this.connection) { |  | ||||||
| 					this.connection.dispose(); |  | ||||||
| 				} |  | ||||||
| 				this.connection = markRaw(stream.useChannel('gamesReversiGame', { |  | ||||||
| 					gameId: this.game.id |  | ||||||
| 				})); |  | ||||||
| 				this.connection.on('started', this.onStarted); |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onStarted(game) { |  | ||||||
| 			Object.assign(this.game, game); |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  | @ -1,280 +0,0 @@ | ||||||
| <template> |  | ||||||
| <div v-if="!matching" class="bgvwxkhb"> |  | ||||||
| 	<h1>Misskey {{ $ts._reversi.reversi }}</h1> |  | ||||||
| 
 |  | ||||||
| 	<div class="play"> |  | ||||||
| 		<MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton> |  | ||||||
| 	</div> |  | ||||||
| 
 |  | ||||||
| 	<div class="_section"> |  | ||||||
| 		<MkFolder v-if="invitations.length > 0"> |  | ||||||
| 			<template #header>{{ $ts.invitations }}</template> |  | ||||||
| 			<div class="nfcacttm"> |  | ||||||
| 				<button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)"> |  | ||||||
| 					<MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/> |  | ||||||
| 					<span class="name"><b><MkUserName :user="invitation.parent"/></b></span> |  | ||||||
| 					<span class="username">@{{ invitation.parent.username }}</span> |  | ||||||
| 					<MkTime :time="invitation.createdAt" class="time"/> |  | ||||||
| 				</button> |  | ||||||
| 			</div> |  | ||||||
| 		</MkFolder> |  | ||||||
| 
 |  | ||||||
| 		<MkFolder v-if="myGames.length > 0"> |  | ||||||
| 			<template #header>{{ $ts._reversi.myGames }}</template> |  | ||||||
| 			<div class="knextgwz"> |  | ||||||
| 				<MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> |  | ||||||
| 					<div class="players"> |  | ||||||
| 						<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> |  | ||||||
| 					</div> |  | ||||||
| 					<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> |  | ||||||
| 				</MkA> |  | ||||||
| 			</div> |  | ||||||
| 		</MkFolder> |  | ||||||
| 
 |  | ||||||
| 		<MkFolder v-if="games.length > 0"> |  | ||||||
| 			<template #header>{{ $ts._reversi.allGames }}</template> |  | ||||||
| 			<div class="knextgwz"> |  | ||||||
| 				<MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> |  | ||||||
| 					<div class="players"> |  | ||||||
| 						<MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> |  | ||||||
| 					</div> |  | ||||||
| 					<footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> |  | ||||||
| 				</MkA> |  | ||||||
| 			</div> |  | ||||||
| 		</MkFolder> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| <div v-else class="sazhgisb"> |  | ||||||
| 	<h1> |  | ||||||
| 		<I18n :src="$ts.waitingFor" tag="span"> |  | ||||||
| 			<template #x> |  | ||||||
| 				<b><MkUserName :user="matching"/></b> |  | ||||||
| 			</template> |  | ||||||
| 		</I18n> |  | ||||||
| 		<MkEllipsis/> |  | ||||||
| 	</h1> |  | ||||||
| 	<div class="cancel"> |  | ||||||
| 		<MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent, markRaw } from 'vue'; |  | ||||||
| import * as os from '@/os'; |  | ||||||
| import { stream } from '@/stream'; |  | ||||||
| import MkButton from '@/components/ui/button.vue'; |  | ||||||
| import MkFolder from '@/components/ui/folder.vue'; |  | ||||||
| import * as symbols from '@/symbols'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
| 	components: { |  | ||||||
| 		MkButton, MkFolder, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	inject: ['navHook'], |  | ||||||
| 
 |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			[symbols.PAGE_INFO]: { |  | ||||||
| 				title: this.$ts._reversi.reversi, |  | ||||||
| 				icon: 'fas fa-gamepad' |  | ||||||
| 			}, |  | ||||||
| 			games: [], |  | ||||||
| 			gamesFetching: true, |  | ||||||
| 			gamesMoreFetching: false, |  | ||||||
| 			myGames: [], |  | ||||||
| 			matching: null, |  | ||||||
| 			invitations: [], |  | ||||||
| 			connection: null, |  | ||||||
| 			pingClock: null, |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	mounted() { |  | ||||||
| 		if (this.$i) { |  | ||||||
| 			this.connection = markRaw(stream.useChannel('gamesReversi')); |  | ||||||
| 
 |  | ||||||
| 			this.connection.on('invited', this.onInvited); |  | ||||||
| 
 |  | ||||||
| 			this.connection.on('matched', this.onMatched); |  | ||||||
| 
 |  | ||||||
| 			this.pingClock = setInterval(() => { |  | ||||||
| 				if (this.matching) { |  | ||||||
| 					this.connection.send('ping', { |  | ||||||
| 						id: this.matching.id |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 			}, 3000); |  | ||||||
| 
 |  | ||||||
| 			os.api('games/reversi/games', { |  | ||||||
| 				my: true |  | ||||||
| 			}).then(games => { |  | ||||||
| 				this.myGames = games; |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			os.api('games/reversi/invitations').then(invitations => { |  | ||||||
| 				this.invitations = this.invitations.concat(invitations); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		os.api('games/reversi/games').then(games => { |  | ||||||
| 			this.games = games; |  | ||||||
| 			this.gamesFetching = false; |  | ||||||
| 		}); |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	beforeUnmount() { |  | ||||||
| 		if (this.connection) { |  | ||||||
| 			this.connection.dispose(); |  | ||||||
| 			clearInterval(this.pingClock); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	methods: { |  | ||||||
| 		go(game) { |  | ||||||
| 			const url = '/games/reversi/' + game.id; |  | ||||||
| 			if (this.navHook) { |  | ||||||
| 				this.navHook(url); |  | ||||||
| 			} else { |  | ||||||
| 				this.$router.push(url); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		async match() { |  | ||||||
| 			const user = await os.selectUser({ local: true }); |  | ||||||
| 			if (user == null) return; |  | ||||||
| 			os.api('games/reversi/match', { |  | ||||||
| 				userId: user.id |  | ||||||
| 			}).then(res => { |  | ||||||
| 				if (res == null) { |  | ||||||
| 					this.matching = user; |  | ||||||
| 				} else { |  | ||||||
| 					this.go(res); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		cancel() { |  | ||||||
| 			this.matching = null; |  | ||||||
| 			os.api('games/reversi/match/cancel'); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		accept(invitation) { |  | ||||||
| 			os.api('games/reversi/match', { |  | ||||||
| 				userId: invitation.parent.id |  | ||||||
| 			}).then(game => { |  | ||||||
| 				if (game) { |  | ||||||
| 					this.go(game); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onMatched(game) { |  | ||||||
| 			this.go(game); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onInvited(invite) { |  | ||||||
| 			this.invitations.unshift(invite); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <style lang="scss" scoped> |  | ||||||
| .bgvwxkhb { |  | ||||||
| 	> h1 { |  | ||||||
| 		margin: 0; |  | ||||||
| 		padding: 24px; |  | ||||||
| 		text-align: center; |  | ||||||
| 		font-size: 1.5em; |  | ||||||
| 		background: linear-gradient(0deg, #43c583, #438881); |  | ||||||
| 		color: #fff; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .play { |  | ||||||
| 		text-align: center; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .sazhgisb { |  | ||||||
| 	text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .nfcacttm { |  | ||||||
| 	> .invitation { |  | ||||||
| 		display: flex; |  | ||||||
| 		box-sizing: border-box; |  | ||||||
| 		width: 100%; |  | ||||||
| 		padding: 16px; |  | ||||||
| 		line-height: 32px; |  | ||||||
| 		text-align: left; |  | ||||||
| 
 |  | ||||||
| 		> .avatar { |  | ||||||
| 			width: 32px; |  | ||||||
| 			height: 32px; |  | ||||||
| 			margin-right: 8px; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .name { |  | ||||||
| 			margin-right: 8px; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .username { |  | ||||||
| 			margin-right: 8px; |  | ||||||
| 			opacity: 0.7; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .time { |  | ||||||
| 			margin-left: auto; |  | ||||||
| 			opacity: 0.7; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .knextgwz { |  | ||||||
| 	display: grid; |  | ||||||
| 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); |  | ||||||
| 	grid-gap: var(--margin); |  | ||||||
| 
 |  | ||||||
| 	> .game { |  | ||||||
| 		> .players { |  | ||||||
| 			text-align: center; |  | ||||||
| 			padding: 16px; |  | ||||||
| 			line-height: 32px; |  | ||||||
| 
 |  | ||||||
| 			> .avatar { |  | ||||||
| 				width: 32px; |  | ||||||
| 				height: 32px; |  | ||||||
| 
 |  | ||||||
| 				&:first-child { |  | ||||||
| 					margin-right: 8px; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				&:last-child { |  | ||||||
| 					margin-left: 8px; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> footer { |  | ||||||
| 			display: flex; |  | ||||||
| 			align-items: baseline; |  | ||||||
| 			border-top: solid 0.5px var(--divider); |  | ||||||
| 			padding: 6px 8px; |  | ||||||
| 			font-size: 0.9em; |  | ||||||
| 
 |  | ||||||
| 			> .state { |  | ||||||
| 				&.playing { |  | ||||||
| 					color: var(--accent); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			> .time { |  | ||||||
| 				margin-left: auto; |  | ||||||
| 				opacity: 0.7; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
|  | @ -94,10 +94,6 @@ | ||||||
| 			<template #key>{{ $ts.driveUsage }}</template> | 			<template #key>{{ $ts.driveUsage }}</template> | ||||||
| 			<template #value>{{ bytes(stats.driveUsage) }}</template> | 			<template #value>{{ bytes(stats.driveUsage) }}</template> | ||||||
| 		</MkKeyValue> | 		</MkKeyValue> | ||||||
| 		<MkKeyValue oneline style="margin: 1em 0;"> |  | ||||||
| 			<template #key>{{ $ts.reversiCount }}</template> |  | ||||||
| 			<template #value>{{ number(stats.reversiCount) }}</template> |  | ||||||
| 		</MkKeyValue> |  | ||||||
| 	</FormSection> | 	</FormSection> | ||||||
| 
 | 
 | ||||||
| 	<FormSection> | 	<FormSection> | ||||||
|  |  | ||||||
|  | @ -94,8 +94,6 @@ export default defineComponent({ | ||||||
| 		this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); | 		this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); | ||||||
| 		this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); | 		this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); | ||||||
| 		this.sounds.channel = ColdDeviceStorage.get('sound_channel'); | 		this.sounds.channel = ColdDeviceStorage.get('sound_channel'); | ||||||
| 		this.sounds.reversiPutBlack = ColdDeviceStorage.get('sound_reversiPutBlack'); |  | ||||||
| 		this.sounds.reversiPutWhite = ColdDeviceStorage.get('sound_reversiPutWhite'); |  | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	mounted() { | 	mounted() { | ||||||
|  |  | ||||||
|  | @ -73,8 +73,6 @@ const defaultRoutes = [ | ||||||
| 	{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, | 	{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, | ||||||
| 	{ path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, | 	{ path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, | ||||||
| 	{ path: '/instance-info/:host', component: page('instance-info'), props: route => ({ host: route.params.host }) }, | 	{ path: '/instance-info/:host', component: page('instance-info'), props: route => ({ host: route.params.host }) }, | ||||||
| 	{ path: '/games/reversi', component: page('reversi/index') }, |  | ||||||
| 	{ path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) }, |  | ||||||
| 	{ path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') }, | 	{ path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') }, | ||||||
| 	{ path: '/api-console', component: page('api-console') }, | 	{ path: '/api-console', component: page('api-console') }, | ||||||
| 	{ path: '/preview', component: page('preview') }, | 	{ path: '/preview', component: page('preview') }, | ||||||
|  |  | ||||||
|  | @ -1,263 +0,0 @@ | ||||||
| import { count, concat } from '@/scripts/array'; |  | ||||||
| 
 |  | ||||||
| // MISSKEY REVERSI ENGINE
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * true ... 黒 |  | ||||||
|  * false ... 白 |  | ||||||
|  */ |  | ||||||
| export type Color = boolean; |  | ||||||
| const BLACK = true; |  | ||||||
| const WHITE = false; |  | ||||||
| 
 |  | ||||||
| export type MapPixel = 'null' | 'empty'; |  | ||||||
| 
 |  | ||||||
| export type Options = { |  | ||||||
| 	isLlotheo: boolean; |  | ||||||
| 	canPutEverywhere: boolean; |  | ||||||
| 	loopedBoard: boolean; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export type Undo = { |  | ||||||
| 	/** |  | ||||||
| 	 * 色 |  | ||||||
| 	 */ |  | ||||||
| 	color: Color; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * どこに打ったか |  | ||||||
| 	 */ |  | ||||||
| 	pos: number; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 反転した石の位置の配列 |  | ||||||
| 	 */ |  | ||||||
| 	effects: number[]; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ターン |  | ||||||
| 	 */ |  | ||||||
| 	turn: Color | null; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * リバーシエンジン |  | ||||||
|  */ |  | ||||||
| export default class Reversi { |  | ||||||
| 	public map: MapPixel[]; |  | ||||||
| 	public mapWidth: number; |  | ||||||
| 	public mapHeight: number; |  | ||||||
| 	public board: (Color | null | undefined)[]; |  | ||||||
| 	public turn: Color | null = BLACK; |  | ||||||
| 	public opts: Options; |  | ||||||
| 
 |  | ||||||
| 	public prevPos = -1; |  | ||||||
| 	public prevColor: Color | null = null; |  | ||||||
| 
 |  | ||||||
| 	private logs: Undo[] = []; |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームを初期化します |  | ||||||
| 	 */ |  | ||||||
| 	constructor(map: string[], opts: Options) { |  | ||||||
| 		//#region binds
 |  | ||||||
| 		this.put = this.put.bind(this); |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		//#region Options
 |  | ||||||
| 		this.opts = opts; |  | ||||||
| 		if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; |  | ||||||
| 		if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; |  | ||||||
| 		if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		//#region Parse map data
 |  | ||||||
| 		this.mapWidth = map[0].length; |  | ||||||
| 		this.mapHeight = map.length; |  | ||||||
| 		const mapData = map.join(''); |  | ||||||
| 
 |  | ||||||
| 		this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); |  | ||||||
| 
 |  | ||||||
| 		this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); |  | ||||||
| 		//#endregion
 |  | ||||||
| 
 |  | ||||||
| 		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
 |  | ||||||
| 		if (!this.canPutSomewhere(BLACK)) |  | ||||||
| 			this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 黒石の数 |  | ||||||
| 	 */ |  | ||||||
| 	public get blackCount() { |  | ||||||
| 		return count(BLACK, this.board); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 白石の数 |  | ||||||
| 	 */ |  | ||||||
| 	public get whiteCount() { |  | ||||||
| 		return count(WHITE, this.board); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public transformPosToXy(pos: number): number[] { |  | ||||||
| 		const x = pos % this.mapWidth; |  | ||||||
| 		const y = Math.floor(pos / this.mapWidth); |  | ||||||
| 		return [x, y]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public transformXyToPos(x: number, y: number): number { |  | ||||||
| 		return x + (y * this.mapWidth); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を打ちます |  | ||||||
| 	 * @param color 石の色 |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public put(color: Color, pos: number) { |  | ||||||
| 		this.prevPos = pos; |  | ||||||
| 		this.prevColor = color; |  | ||||||
| 
 |  | ||||||
| 		this.board[pos] = color; |  | ||||||
| 
 |  | ||||||
| 		// 反転させられる石を取得
 |  | ||||||
| 		const effects = this.effects(color, pos); |  | ||||||
| 
 |  | ||||||
| 		// 反転させる
 |  | ||||||
| 		for (const pos of effects) { |  | ||||||
| 			this.board[pos] = color; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		const turn = this.turn; |  | ||||||
| 
 |  | ||||||
| 		this.logs.push({ |  | ||||||
| 			color, |  | ||||||
| 			pos, |  | ||||||
| 			effects, |  | ||||||
| 			turn |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		this.calcTurn(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private calcTurn() { |  | ||||||
| 		// ターン計算
 |  | ||||||
| 		this.turn = |  | ||||||
| 			this.canPutSomewhere(!this.prevColor) ? !this.prevColor : |  | ||||||
| 			this.canPutSomewhere(this.prevColor!) ? this.prevColor : |  | ||||||
| 			null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	public undo() { |  | ||||||
| 		const undo = this.logs.pop()!; |  | ||||||
| 		this.prevColor = undo.color; |  | ||||||
| 		this.prevPos = undo.pos; |  | ||||||
| 		this.board[undo.pos] = null; |  | ||||||
| 		for (const pos of undo.effects) { |  | ||||||
| 			const color = this.board[pos]; |  | ||||||
| 			this.board[pos] = !color; |  | ||||||
| 		} |  | ||||||
| 		this.turn = undo.turn; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定した位置のマップデータのマスを取得します |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public mapDataGet(pos: number): MapPixel { |  | ||||||
| 		const [x, y] = this.transformPosToXy(pos); |  | ||||||
| 		return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 打つことができる場所を取得します |  | ||||||
| 	 */ |  | ||||||
| 	public puttablePlaces(color: Color): number[] { |  | ||||||
| 		return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 打つことができる場所があるかどうかを取得します |  | ||||||
| 	 */ |  | ||||||
| 	public canPutSomewhere(color: Color): boolean { |  | ||||||
| 		return this.puttablePlaces(color).length > 0; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を打つことができるかどうかを取得します |  | ||||||
| 	 * @param color 自分の色 |  | ||||||
| 	 * @param pos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public canPut(color: Color, pos: number): boolean { |  | ||||||
| 		return ( |  | ||||||
| 			this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
 |  | ||||||
| 			this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
 |  | ||||||
| 			this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * 指定のマスに石を置いた時の、反転させられる石を取得します |  | ||||||
| 	 * @param color 自分の色 |  | ||||||
| 	 * @param initPos 位置 |  | ||||||
| 	 */ |  | ||||||
| 	public effects(color: Color, initPos: number): number[] { |  | ||||||
| 		const enemyColor = !color; |  | ||||||
| 
 |  | ||||||
| 		const diffVectors: [number, number][] = [ |  | ||||||
| 			[  0,  -1], // 上
 |  | ||||||
| 			[ +1,  -1], // 右上
 |  | ||||||
| 			[ +1,   0], // 右
 |  | ||||||
| 			[ +1,  +1], // 右下
 |  | ||||||
| 			[  0,  +1], // 下
 |  | ||||||
| 			[ -1,  +1], // 左下
 |  | ||||||
| 			[ -1,   0], // 左
 |  | ||||||
| 			[ -1,  -1]  // 左上
 |  | ||||||
| 		]; |  | ||||||
| 
 |  | ||||||
| 		const effectsInLine = ([dx, dy]: [number, number]): number[] => { |  | ||||||
| 			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; |  | ||||||
| 
 |  | ||||||
| 			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
 |  | ||||||
| 			let [x, y] = this.transformPosToXy(initPos); |  | ||||||
| 			while (true) { |  | ||||||
| 				[x, y] = nextPos(x, y); |  | ||||||
| 
 |  | ||||||
| 				// 座標が指し示す位置がボード外に出たとき
 |  | ||||||
| 				if (this.opts.loopedBoard && this.transformXyToPos( |  | ||||||
| 					(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), |  | ||||||
| 					(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) |  | ||||||
| 						// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
 |  | ||||||
| 					return found; |  | ||||||
| 				else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) |  | ||||||
| 					return []; // 挟めないことが確定 (盤面外に到達)
 |  | ||||||
| 
 |  | ||||||
| 				const pos = this.transformXyToPos(x, y); |  | ||||||
| 				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
 |  | ||||||
| 				const stone = this.board[pos]; |  | ||||||
| 				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
 |  | ||||||
| 				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
 |  | ||||||
| 				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
 |  | ||||||
| 			} |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		return concat(diffVectors.map(effectsInLine)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームが終了したか否か |  | ||||||
| 	 */ |  | ||||||
| 	public get isEnded(): boolean { |  | ||||||
| 		return this.turn === null; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * ゲームの勝者 (null = 引き分け) |  | ||||||
| 	 */ |  | ||||||
| 	public get winner(): Color | null { |  | ||||||
| 		return this.isEnded ? |  | ||||||
| 			this.blackCount == this.whiteCount ? null : |  | ||||||
| 			this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : |  | ||||||
| 			undefined as never; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,896 +0,0 @@ | ||||||
| /** |  | ||||||
|  * 組み込みマップ定義 |  | ||||||
|  * |  | ||||||
|  * データ値: |  | ||||||
|  * (スペース) ... マス無し |  | ||||||
|  * - ... マス |  | ||||||
|  * b ... 初期配置される黒石 |  | ||||||
|  * w ... 初期配置される白石 |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| export type Map = { |  | ||||||
| 	name?: string; |  | ||||||
| 	category?: string; |  | ||||||
| 	author?: string; |  | ||||||
| 	data: string[]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const fourfour: Map = { |  | ||||||
| 	name: '4x4', |  | ||||||
| 	category: '4x4', |  | ||||||
| 	data: [ |  | ||||||
| 		'----', |  | ||||||
| 		'-wb-', |  | ||||||
| 		'-bw-', |  | ||||||
| 		'----' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const sixsix: Map = { |  | ||||||
| 	name: '6x6', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		'------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedSixsix: Map = { |  | ||||||
| 	name: '6x6 rounded', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ---- ', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		' ---- ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedSixsix2: Map = { |  | ||||||
| 	name: '6x6 rounded 2', |  | ||||||
| 	category: '6x6', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'  --  ', |  | ||||||
| 		' ---- ', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		' ---- ', |  | ||||||
| 		'  --  ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteight: Map = { |  | ||||||
| 	name: '8x8', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH1: Map = { |  | ||||||
| 	name: '8x8 handicap 1', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH2: Map = { |  | ||||||
| 	name: '8x8 handicap 2', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'-------b' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH3: Map = { |  | ||||||
| 	name: '8x8 handicap 3', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'-------b' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH4: Map = { |  | ||||||
| 	name: '8x8 handicap 4', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'b------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'b------b' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightH28: Map = { |  | ||||||
| 	name: '8x8 handicap 28', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'bbbbbbbb', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b--wb--b', |  | ||||||
| 		'b--bw--b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'b------b', |  | ||||||
| 		'bbbbbbbb' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight: Map = { |  | ||||||
| 	name: '8x8 rounded', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight2: Map = { |  | ||||||
| 	name: '8x8 rounded 2', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'  ----  ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'  ----  ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const roundedEighteight3: Map = { |  | ||||||
| 	name: '8x8 rounded 3', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   --   ', |  | ||||||
| 		'  ----  ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'  ----  ', |  | ||||||
| 		'   --   ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightWithNotch: Map = { |  | ||||||
| 	name: '8x8 with notch', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---  ---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' --wb-- ', |  | ||||||
| 		' --bw-- ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---  ---' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const eighteightWithSomeHoles: Map = { |  | ||||||
| 	name: '8x8 with some holes', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--- ----', |  | ||||||
| 		'----- --', |  | ||||||
| 		'-- -----', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw- -', |  | ||||||
| 		' -------', |  | ||||||
| 		'--- ----', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const circle: Map = { |  | ||||||
| 	name: 'Circle', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   --   ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		' ------ ', |  | ||||||
| 		' ------ ', |  | ||||||
| 		'   --   ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const smile: Map = { |  | ||||||
| 	name: 'Smile', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------ ', |  | ||||||
| 		'--------', |  | ||||||
| 		'-- -- --', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'-- bw --', |  | ||||||
| 		'---  ---', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const window: Map = { |  | ||||||
| 	name: 'Window', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'-  --  -', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const reserved: Map = { |  | ||||||
| 	name: 'Reserved', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'w------b', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'b------w' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const x: Map = { |  | ||||||
| 	name: 'X', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'w------b', |  | ||||||
| 		'-w----b-', |  | ||||||
| 		'--w--b--', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--b--w--', |  | ||||||
| 		'-b----w-', |  | ||||||
| 		'b------w' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const parallel: Map = { |  | ||||||
| 	name: 'Parallel', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---bb---', |  | ||||||
| 		'---ww---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const lackOfBlack: Map = { |  | ||||||
| 	name: 'Lack of Black', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---w----', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const squareParty: Map = { |  | ||||||
| 	name: 'Square Party', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-wwwbbb-', |  | ||||||
| 		'-w-wb-b-', |  | ||||||
| 		'-wwwbbb-', |  | ||||||
| 		'-bbbwww-', |  | ||||||
| 		'-b-bw-w-', |  | ||||||
| 		'-bbbwww-', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const minesweeper: Map = { |  | ||||||
| 	name: 'Minesweeper', |  | ||||||
| 	category: '8x8', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'b-b--w-w', |  | ||||||
| 		'-w-wb-b-', |  | ||||||
| 		'w-b--w-b', |  | ||||||
| 		'-b-wb-w-', |  | ||||||
| 		'-w-bw-b-', |  | ||||||
| 		'b-w--b-w', |  | ||||||
| 		'-b-bw-w-', |  | ||||||
| 		'w-w--b-b' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const tenthtenth: Map = { |  | ||||||
| 	name: '10x10', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const hole: Map = { |  | ||||||
| 	name: 'The Hole', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'--wb--wb--', |  | ||||||
| 		'--bw--bw--', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'--wb--wb--', |  | ||||||
| 		'--bw--bw--', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const grid: Map = { |  | ||||||
| 	name: 'Grid', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------', |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		'----------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const cross: Map = { |  | ||||||
| 	name: 'Cross', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----wb----', |  | ||||||
| 		'----bw----', |  | ||||||
| 		'----------', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const charX: Map = { |  | ||||||
| 	name: 'Char X', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---    ---', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'---    ---' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const charY: Map = { |  | ||||||
| 	name: 'Char Y', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'---    ---', |  | ||||||
| 		'----  ----', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ', |  | ||||||
| 		'  ------  ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const walls: Map = { |  | ||||||
| 	name: 'Walls', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		' bbbbbbbb ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w---wb---w', |  | ||||||
| 		'w---bw---w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' bbbbbbbb ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const cpu: Map = { |  | ||||||
| 	name: 'CPU', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' b b  b b ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' ---wb--- ', |  | ||||||
| 		' ---bw--- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'w--------w', |  | ||||||
| 		' b b  b b ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const checker: Map = { |  | ||||||
| 	name: 'Checker', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'---wbwb---', |  | ||||||
| 		'---bwbw---', |  | ||||||
| 		'---wbwb---', |  | ||||||
| 		'---bwbw---', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const japaneseCurry: Map = { |  | ||||||
| 	name: 'Japanese curry', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'w-b-b-b-b-', |  | ||||||
| 		'-w-b-b-b-b', |  | ||||||
| 		'w-w-b-b-b-', |  | ||||||
| 		'-w-w-b-b-b', |  | ||||||
| 		'w-w-wwb-b-', |  | ||||||
| 		'-w-wbb-b-b', |  | ||||||
| 		'w-w-w-b-b-', |  | ||||||
| 		'-w-w-w-b-b', |  | ||||||
| 		'w-w-w-w-b-', |  | ||||||
| 		'-w-w-w-w-b' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const mosaic: Map = { |  | ||||||
| 	name: 'Mosaic', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - w w - -', |  | ||||||
| 		'- - b b - ', |  | ||||||
| 		' - w w - -', |  | ||||||
| 		'- - b b - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 		'- - - - - ', |  | ||||||
| 		' - - - - -', |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const arena: Map = { |  | ||||||
| 	name: 'Arena', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'- - -- - -', |  | ||||||
| 		' - -  - - ', |  | ||||||
| 		'- ------ -', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'- --wb-- -', |  | ||||||
| 		'- --bw-- -', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'- ------ -', |  | ||||||
| 		' - -  - - ', |  | ||||||
| 		'- - -- - -' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const reactor: Map = { |  | ||||||
| 	name: 'Reactor', |  | ||||||
| 	category: '10x10', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w------b-', |  | ||||||
| 		'b- -  - -w', |  | ||||||
| 		'- --wb-- -', |  | ||||||
| 		'---b  w---', |  | ||||||
| 		'- b wb w -', |  | ||||||
| 		'- w bw b -', |  | ||||||
| 		'---w  b---', |  | ||||||
| 		'- --bw-- -', |  | ||||||
| 		'w- -  - -b', |  | ||||||
| 		'-b------w-' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const sixeight: Map = { |  | ||||||
| 	name: '6x8', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'--wb--', |  | ||||||
| 		'--bw--', |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const spark: Map = { |  | ||||||
| 	name: 'Spark', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' -      - ', |  | ||||||
| 		'----------', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' ---wb--- ', |  | ||||||
| 		' ---bw--- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		' -      - ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const islands: Map = { |  | ||||||
| 	name: 'Islands', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------  ', |  | ||||||
| 		'---wb---  ', |  | ||||||
| 		'---bw---  ', |  | ||||||
| 		'--------  ', |  | ||||||
| 		'  -    -  ', |  | ||||||
| 		'  -    -  ', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------', |  | ||||||
| 		'  --------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const galaxy: Map = { |  | ||||||
| 	name: 'Galaxy', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'   ------   ', |  | ||||||
| 		'  --www---  ', |  | ||||||
| 		' ------w--- ', |  | ||||||
| 		'---bbb--w---', |  | ||||||
| 		'--b---b-w-b-', |  | ||||||
| 		'-b--wwb-w-b-', |  | ||||||
| 		'-b-w-bww--b-', |  | ||||||
| 		'-b-w-b---b--', |  | ||||||
| 		'---w--bbb---', |  | ||||||
| 		' ---w------ ', |  | ||||||
| 		'  ---www--  ', |  | ||||||
| 		'   ------   ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const triangle: Map = { |  | ||||||
| 	name: 'Triangle', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'    --    ', |  | ||||||
| 		'    --    ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'   ----   ', |  | ||||||
| 		'  --wb--  ', |  | ||||||
| 		'  --bw--  ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		' -------- ', |  | ||||||
| 		'----------', |  | ||||||
| 		'----------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const iphonex: Map = { |  | ||||||
| 	name: 'iPhone X', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' --  -- ', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		'--------', |  | ||||||
| 		' ------ ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const dealWithIt: Map = { |  | ||||||
| 	name: 'Deal with it!', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		'------------', |  | ||||||
| 		'--w-b-------', |  | ||||||
| 		' --b-w------', |  | ||||||
| 		'  --w-b---- ', |  | ||||||
| 		'   -------  ' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const experiment: Map = { |  | ||||||
| 	name: 'Let\'s experiment', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'syuilo', |  | ||||||
| 	data: [ |  | ||||||
| 		' ------------ ', |  | ||||||
| 		'------wb------', |  | ||||||
| 		'------bw------', |  | ||||||
| 		'--------------', |  | ||||||
| 		'    -    -    ', |  | ||||||
| 		'------  ------', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'bbbbbb  wwwwww', |  | ||||||
| 		'wwwwww  bbbbbb' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const bigBoard: Map = { |  | ||||||
| 	name: 'Big board', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	data: [ |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'-------wb-------', |  | ||||||
| 		'-------bw-------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------', |  | ||||||
| 		'----------------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const twoBoard: Map = { |  | ||||||
| 	name: 'Two board', |  | ||||||
| 	category: 'Special', |  | ||||||
| 	author: 'Aya', |  | ||||||
| 	data: [ |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'---wb--- ---wb---', |  | ||||||
| 		'---bw--- ---bw---', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------', |  | ||||||
| 		'-------- --------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test1: Map = { |  | ||||||
| 	name: 'Test1', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'---wb---', |  | ||||||
| 		'---bw---', |  | ||||||
| 		'--------' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test2: Map = { |  | ||||||
| 	name: 'Test2', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'------', |  | ||||||
| 		'------', |  | ||||||
| 		'-b--w-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test3: Map = { |  | ||||||
| 	name: 'Test3', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'--w', |  | ||||||
| 		'w--', |  | ||||||
| 		'-w-', |  | ||||||
| 		'---', |  | ||||||
| 		'b--', |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const test4: Map = { |  | ||||||
| 	name: 'Test4', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'------', |  | ||||||
| 		'-w--b-', |  | ||||||
| 		'-w--b-' |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう
 |  | ||||||
| export const test6: Map = { |  | ||||||
| 	name: 'Test6', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--wwwww-', |  | ||||||
| 		'wwwwwwww', |  | ||||||
| 		'wbbbwbwb', |  | ||||||
| 		'wbbbbwbb', |  | ||||||
| 		'wbwbbwbb', |  | ||||||
| 		'wwbwbbbb', |  | ||||||
| 		'--wbbbbb', |  | ||||||
| 		'-wwwww--', |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう
 |  | ||||||
| export const test7: Map = { |  | ||||||
| 	name: 'Test7', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'b--w----', |  | ||||||
| 		'b-wwww--', |  | ||||||
| 		'bwbwwwbb', |  | ||||||
| 		'wbwwwwb-', |  | ||||||
| 		'wwwwwww-', |  | ||||||
| 		'-wwbbwwb', |  | ||||||
| 		'--wwww--', |  | ||||||
| 		'--wwww--', |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう
 |  | ||||||
| export const test8: Map = { |  | ||||||
| 	name: 'Test8', |  | ||||||
| 	category: 'Test', |  | ||||||
| 	data: [ |  | ||||||
| 		'--------', |  | ||||||
| 		'-----w--', |  | ||||||
| 		'w--www--', |  | ||||||
| 		'wwwwww--', |  | ||||||
| 		'bbbbwww-', |  | ||||||
| 		'wwwwww--', |  | ||||||
| 		'--www---', |  | ||||||
| 		'--ww----', |  | ||||||
| 	] |  | ||||||
| }; |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| { |  | ||||||
|   "name": "misskey-reversi", |  | ||||||
|   "version": "0.0.5", |  | ||||||
|   "description": "Misskey reversi engine", |  | ||||||
|   "keywords": [ |  | ||||||
|     "misskey" |  | ||||||
|   ], |  | ||||||
|   "author": "syuilo <i@syuilo.com>", |  | ||||||
|   "license": "MIT", |  | ||||||
|   "repository": "https://github.com/misskey-dev/misskey.git", |  | ||||||
|   "bugs": "https://github.com/misskey-dev/misskey/issues", |  | ||||||
|   "main": "./built/core.js", |  | ||||||
|   "types": "./built/core.d.ts", |  | ||||||
|   "scripts": { |  | ||||||
|     "build": "tsc" |  | ||||||
|   }, |  | ||||||
|   "dependencies": {} |  | ||||||
| } |  | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| { |  | ||||||
| 	"compilerOptions": { |  | ||||||
| 		"noEmitOnError": false, |  | ||||||
| 		"noImplicitAny": false, |  | ||||||
| 		"noImplicitReturns": true, |  | ||||||
| 		"noFallthroughCasesInSwitch": true, |  | ||||||
| 		"experimentalDecorators": true, |  | ||||||
| 		"declaration": true, |  | ||||||
| 		"sourceMap": false, |  | ||||||
| 		"target": "es2017", |  | ||||||
| 		"module": "commonjs", |  | ||||||
| 		"removeComments": false, |  | ||||||
| 		"noLib": false, |  | ||||||
| 		"outDir": "./built", |  | ||||||
| 		"rootDir": "./" |  | ||||||
| 	}, |  | ||||||
| 	"compileOnSave": false, |  | ||||||
| 	"include": [ |  | ||||||
| 		"./core.ts" |  | ||||||
| 	] |  | ||||||
| } |  | ||||||
|  | @ -255,8 +255,6 @@ export class ColdDeviceStorage { | ||||||
| 		sound_chatBg: { type: 'syuilo/waon', volume: 1 }, | 		sound_chatBg: { type: 'syuilo/waon', volume: 1 }, | ||||||
| 		sound_antenna: { type: 'syuilo/triple', volume: 1 }, | 		sound_antenna: { type: 'syuilo/triple', volume: 1 }, | ||||||
| 		sound_channel: { type: 'syuilo/square-pico', volume: 1 }, | 		sound_channel: { type: 'syuilo/square-pico', volume: 1 }, | ||||||
| 		sound_reversiPutBlack: { type: 'syuilo/kick', volume: 0.3 }, |  | ||||||
| 		sound_reversiPutWhite: { type: 'syuilo/snare', volume: 0.3 }, |  | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	public static watchers = []; | 	public static watchers = []; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue