summaryrefslogtreecommitdiff
path: root/packages/client/src/scripts/games
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-11-12 02:02:25 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-11-12 02:02:25 +0900
commit0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch)
tree40874799472fa07416f17b50a398ac33b7771905 /packages/client/src/scripts/games
parentupdate deps (diff)
downloadmisskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz
misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2
misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip
refactoring
Resolve #7779
Diffstat (limited to 'packages/client/src/scripts/games')
-rw-r--r--packages/client/src/scripts/games/reversi/core.ts263
-rw-r--r--packages/client/src/scripts/games/reversi/maps.ts896
-rw-r--r--packages/client/src/scripts/games/reversi/package.json18
-rw-r--r--packages/client/src/scripts/games/reversi/tsconfig.json21
4 files changed, 1198 insertions, 0 deletions
diff --git a/packages/client/src/scripts/games/reversi/core.ts b/packages/client/src/scripts/games/reversi/core.ts
new file mode 100644
index 0000000000..0cb8922e19
--- /dev/null
+++ b/packages/client/src/scripts/games/reversi/core.ts
@@ -0,0 +1,263 @@
+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;
+ }
+}
diff --git a/packages/client/src/scripts/games/reversi/maps.ts b/packages/client/src/scripts/games/reversi/maps.ts
new file mode 100644
index 0000000000..dc0d1bf9d0
--- /dev/null
+++ b/packages/client/src/scripts/games/reversi/maps.ts
@@ -0,0 +1,896 @@
+/**
+ * 組み込みマップ定義
+ *
+ * データ値:
+ * (スペース) ... マス無し
+ * - ... マス
+ * 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----',
+ ]
+};
diff --git a/packages/client/src/scripts/games/reversi/package.json b/packages/client/src/scripts/games/reversi/package.json
new file mode 100644
index 0000000000..a4415ad141
--- /dev/null
+++ b/packages/client/src/scripts/games/reversi/package.json
@@ -0,0 +1,18 @@
+{
+ "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": {}
+}
diff --git a/packages/client/src/scripts/games/reversi/tsconfig.json b/packages/client/src/scripts/games/reversi/tsconfig.json
new file mode 100644
index 0000000000..851fb6b7e4
--- /dev/null
+++ b/packages/client/src/scripts/games/reversi/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "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"
+ ]
+}