From 50e40ed61e8226ecbb7f1d7d53ba028fad1a9a01 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Wed, 7 Mar 2018 21:57:06 +0900
Subject: [PATCH] :v:

---
 src/api/stream/othello-game.ts                |  9 +-
 src/common/othello.ts                         | 40 +++++++-
 .../common/views/components/othello.game.vue  | 97 +++++++++++++++++--
 3 files changed, 126 insertions(+), 20 deletions(-)

diff --git a/src/api/stream/othello-game.ts b/src/api/stream/othello-game.ts
index a5fb379e80..d086478159 100644
--- a/src/api/stream/othello-game.ts
+++ b/src/api/stream/othello-game.ts
@@ -55,9 +55,7 @@ export default function(request: websocket.request, connection: websocket.connec
 
 		let winner;
 		if (isEnded) {
-			const blackCount = o.board.filter(s => s == 'black').length;
-			const whiteCount = o.board.filter(s => s == 'white').length;
-			winner = blackCount == whiteCount ? null : blackCount > whiteCount ? game.black_user_id : game.white_user_id;
+			winner = o.blackCount == o.whiteCount ? null : o.blackCount > o.whiteCount ? game.black_user_id : game.white_user_id;
 		}
 
 		const log = {
@@ -79,9 +77,6 @@ export default function(request: websocket.request, connection: websocket.connec
 			}
 		});
 
-		publishOthelloGameStream(gameId, 'set', {
-			color: myColor,
-			pos
-		});
+		publishOthelloGameStream(gameId, 'set', log);
 	}
 }
diff --git a/src/common/othello.ts b/src/common/othello.ts
index fc27d72dcd..1057e6b9a4 100644
--- a/src/common/othello.ts
+++ b/src/common/othello.ts
@@ -3,6 +3,11 @@ const BOARD_SIZE = 8;
 export default class Othello {
 	public board: Array<'black' | 'white'>;
 
+	public stats: Array<{
+		b: number;
+		w: number;
+	}> = [];
+
 	/**
 	 * ゲームを初期化します
 	 */
@@ -17,6 +22,27 @@ export default class Othello {
 			null, null, null, null, null, null, null, null,
 			null, null, null, null, null, null, null, null
 		];
+
+		this.stats.push({
+			b: 0.5,
+			w: 0.5
+		});
+	}
+
+	public get blackCount() {
+		return this.board.filter(s => s == 'black').length;
+	}
+
+	public get whiteCount() {
+		return this.board.filter(s => s == 'white').length;
+	}
+
+	public get blackP() {
+		return this.blackCount / (this.blackCount + this.whiteCount);
+	}
+
+	public get whiteP() {
+		return this.whiteCount / (this.blackCount + this.whiteCount);
 	}
 
 	public setByNumber(color, n) {
@@ -88,6 +114,11 @@ export default class Othello {
 					break;
 				}
 		});
+
+		this.stats.push({
+			b: this.blackP,
+			w: this.whiteP
+		});
 	}
 
 	public set(color, pos) {
@@ -118,10 +149,11 @@ export default class Othello {
 	/**
 	 * 指定の位置に石を打つことができるかどうか(相手の石を1つでも反転させられるか)を取得します
 	 */
-	public canReverse2(myColor, targetx, targety): boolean {
-		return this.getReverse(myColor, targetx, targety) !== null;
+	public canReverse2(myColor, x, y): boolean {
+		return this.canReverse(myColor, x + (y * 8));
 	}
 	public canReverse(myColor, pos): boolean {
+		if (this.board[pos] != null) return false;
 		const x = pos % BOARD_SIZE;
 		const y = Math.floor(pos / BOARD_SIZE);
 		return this.getReverse(myColor, x, y) !== null;
@@ -181,7 +213,7 @@ export default class Othello {
 
 		// 右下
 		iterate = createIterater();
-		for (let c = 0, i = 1; i < Math.min(BOARD_SIZE - targetx, BOARD_SIZE - targety); c++, i++) {
+		for (let c = 0, i = 1; i <= Math.min(BOARD_SIZE - targetx, BOARD_SIZE - targety); c++, i++) {
 			if (iterate(targetx + i, targety + i)) {
 				res.push([3, c]);
 				break;
@@ -199,7 +231,7 @@ export default class Othello {
 
 		// 左下
 		iterate = createIterater();
-		for (let c = 0, i = 1; i < Math.min(targetx, BOARD_SIZE - targety); c++, i++) {
+		for (let c = 0, i = 1; i <= Math.min(targetx, BOARD_SIZE - targety); c++, i++) {
 			if (iterate(targetx - i, targety + i)) {
 				res.push([5, c]);
 				break;
diff --git a/src/web/app/common/views/components/othello.game.vue b/src/web/app/common/views/components/othello.game.vue
index b7c23e704e..70c9965ee4 100644
--- a/src/web/app/common/views/components/othello.game.vue
+++ b/src/web/app/common/views/components/othello.game.vue
@@ -2,12 +2,13 @@
 <div class="root">
 	<header><b>{{ game.black_user.name }}</b>(黒) vs <b>{{ game.white_user.name }}</b>(白)</header>
 	<p class="turn" v-if="!iAmPlayer && !isEnded">{{ turn.name }}のターンです<mk-ellipsis/></p>
+	<p class="turn" v-if="logPos != logs.length">{{ turn.name }}のターン</p>
 	<p class="turn" v-if="iAmPlayer && !isEnded">{{ isMyTurn ? 'あなたのターンです' : '相手のターンです' }}<mk-ellipsis v-if="!isMyTurn"/></p>
-	<p class="result" v-if="isEnded">
+	<p class="result" v-if="isEnded && logPos == logs.length">
 		<template v-if="winner"><b>{{ winner.name }}</b>の勝ち</template>
 		<template v-else>引き分け</template>
 	</p>
-	<div>
+	<div class="board">
 		<div v-for="(stone, i) in o.board"
 			:class="{ empty: stone == null, myTurn: isMyTurn, can: o.canReverse(turn.id == game.black_user.id ? 'black' : 'white', i) }"
 			@click="set(i)"
@@ -16,6 +17,22 @@
 			<img v-if="stone == 'white'" :src="`${game.white_user.avatar_url}?thumbnail&size=64`" alt="">
 		</div>
 	</div>
+	<p>黒:{{ o.blackCount }} 白:{{ o.whiteCount }} 合計:{{ o.blackCount + o.whiteCount }}</p>
+	<div class="graph">
+		<div v-for="n in 61 - o.stats.length">
+		</div>
+		<div v-for="data in o.stats">
+			<div :style="{ height: `${ Math.floor(data.b * 100) }%` }"></div>
+			<div :style="{ height: `${ Math.floor(data.w * 100) }%` }"></div>
+		</div>
+	</div>
+	<div class="player" v-if="isEnded">
+		<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:fast-backward%</el-button>
+		<el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:backward%</el-button>
+		<span>{{ logPos }} / {{ logs.length }}</span>
+		<el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:forward%</el-button>
+		<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:fast-forward%</el-button>
+	</div>
 </div>
 </template>
 
@@ -26,9 +43,12 @@ import Othello from '../../../../../common/othello';
 
 export default Vue.extend({
 	props: ['game'],
+
 	data() {
 		return {
 			o: new Othello(),
+			logs: [],
+			logPos: 0,
 			turn: null,
 			isMyTurn: null,
 			isEnded: false,
@@ -36,6 +56,7 @@ export default Vue.extend({
 			connection: null
 		};
 	},
+
 	computed: {
 		iAmPlayer(): boolean {
 			return this.game.black_user_id == (this as any).os.i.id || this.game.white_user_id == (this as any).os.i.id;
@@ -47,24 +68,57 @@ export default Vue.extend({
 			return this.myColor == 'black' ? 'white' : 'black';
 		}
 	},
+
+	watch: {
+		logPos(v) {
+			if (!this.isEnded) return;
+			this.o = new Othello();
+			this.turn = this.game.black_user;
+			this.logs.forEach((log, i) => {
+				if (i < v) {
+					this.o.set(log.color, log.pos);
+
+					if (log.color == 'black' && this.o.getPattern('white').length > 0) {
+						this.turn = this.game.white_user;
+					}
+					if (log.color == 'black' && this.o.getPattern('white').length == 0) {
+						this.turn = this.game.black_user;
+					}
+					if (log.color == 'white' && this.o.getPattern('black').length > 0) {
+						this.turn = this.game.black_user;
+					}
+					if (log.color == 'white' && this.o.getPattern('black').length == 0) {
+						this.turn = this.game.white_user;
+					}
+				}
+			});
+			this.$forceUpdate();
+		}
+	},
+
 	created() {
 		this.game.logs.forEach(log => {
 			this.o.set(log.color, log.pos);
 		});
 
+		this.logs = this.game.logs;
+		this.logPos = this.logs.length;
 		this.turn = this.game.turn_user_id == this.game.black_user_id ? this.game.black_user : this.game.white_user;
 		this.isMyTurn = this.game.turn_user_id == (this as any).os.i.id;
 		this.isEnded = this.game.is_ended;
 		this.winner = this.game.winner;
 	},
+
 	mounted() {
 		this.connection = new OthelloGameStream((this as any).os.i, this.game);
 		this.connection.on('set', this.onSet);
 	},
+
 	beforeDestroy() {
 		this.connection.off('set', this.onSet);
 		this.connection.close();
 	},
+
 	methods: {
 		set(pos) {
 			if (!this.isMyTurn) return;
@@ -75,9 +129,7 @@ export default Vue.extend({
 				this.turn = this.myColor == 'black' ? this.game.white_user : this.game.black_user;
 			} else if (this.o.getPattern(this.myColor).length == 0) {
 				this.isEnded = true;
-				const blackCount = this.o.board.filter(s => s == 'black').length;
-				const whiteCount = this.o.board.filter(s => s == 'white').length;
-				this.winner = blackCount == whiteCount ? null : blackCount > whiteCount ? this.game.black_user : this.game.white_user;
+				this.winner = this.o.blackCount == this.o.whiteCount ? null : this.o.blackCount > this.o.whiteCount ? this.game.black_user : this.game.white_user;
 			}
 			this.connection.send({
 				type: 'set',
@@ -85,13 +137,15 @@ export default Vue.extend({
 			});
 			this.$forceUpdate();
 		},
+
 		onSet(x) {
+			this.logs.push(x);
+			this.logPos++;
 			this.o.set(x.color, x.pos);
+
 			if (this.o.getPattern('black').length == 0 && this.o.getPattern('white').length == 0) {
 				this.isEnded = true;
-				const blackCount = this.o.board.filter(s => s == 'black').length;
-				const whiteCount = this.o.board.filter(s => s == 'white').length;
-				this.winner = blackCount == whiteCount ? null : blackCount > whiteCount ? this.game.black_user : this.game.white_user;
+				this.winner = this.o.blackCount == this.o.whiteCount ? null : this.o.blackCount > this.o.whiteCount ? this.game.black_user : this.game.white_user;
 			} else {
 				if (this.iAmPlayer && this.o.getPattern(this.myColor).length > 0) {
 					this.isMyTurn = true;
@@ -126,7 +180,7 @@ export default Vue.extend({
 		padding 8px
 		border-bottom dashed 1px #c4cdd4
 
-	> div
+	> .board
 		display grid
 		grid-template-rows repeat(8, 1fr)
 		grid-template-columns repeat(8, 1fr)
@@ -168,4 +222,29 @@ export default Vue.extend({
 				display block
 				width 100%
 				height 100%
+
+	> .graph
+		display grid
+		grid-template-columns repeat(61, 1fr)
+		width 300px
+		height 38px
+		margin 0 auto 16px auto
+
+		> div
+			&:not(:empty)
+				background #ccc
+
+			> div:first-child
+				background #333
+
+			> div:last-child
+				background #ccc
+
+	> .player
+		margin-bottom 16px
+
+		> span
+			display inline-block
+			margin 0 8px
+			min-width 70px
 </style>