From fd373b27c15a98f891e1158639abc50e19466449 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Fri, 11 Jan 2019 17:12:36 +0100
Subject: [PATCH] COntinue thinking about code arrangement for game+board+...

---
 public/javascripts/components/board.js |  77 +------
 public/javascripts/components/game.js  | 280 +++++++++++++------------
 public/javascripts/layout.js           |   2 +
 public/javascripts/utils/storage.js    |  66 ++----
 4 files changed, 177 insertions(+), 248 deletions(-)

diff --git a/public/javascripts/components/board.js b/public/javascripts/components/board.js
index 6110cfb8..cec6049e 100644
--- a/public/javascripts/components/board.js
+++ b/public/javascripts/components/board.js
@@ -1,10 +1,7 @@
 Vue.component('my-board', {
 	// Last move cannot be guessed from here, and is required to highlight squares
-	// gotoMove : juste set FEN depuis FEN stocké dans le coup (TODO)
-	// send event after each move (or undo), to notify what was played
-	// also notify end of game (which returns here later through prop...)
-	props: ["fen","moveToPlay","moveToUndo",
-		"analyze","lastMove","orientation","userColor","gameOver"],
+	// vr: object to check moves, print board...
+	props: ["vr","lastMove","mode","orientation","userColor","gameOver"],
 	data: function () {
 		return {
 			hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"),
@@ -14,24 +11,8 @@ Vue.component('my-board', {
 			selectedPiece: null, //moving piece (or clicked piece)
 			incheck: [],
 			start: {}, //pixels coordinates + id of starting square (click or drag)
-			vr: null, //object to check moves, store them, FEN..
 		};
 	},
-	watch: {
-		// NOTE: maybe next 3 should be encapsulated in object to be watched (?)
-		fen: function(newFen) {
-			this.vr = new VariantRules(newFen);
-		},
-		moveToPlay: function(move) {
-			this.play(move, "animate");
-		},
-		moveToUndo: function(move) {
-			this.undo(move);
-		},
-	},
-	created: function() {
-		this.vr = new VariantRules(this.fen);
-	},
 	render(h) {
 		const [sizeX,sizeY] = [V.size.x,V.size.y];
 		// Precompute hints squares to facilitate rendering
@@ -191,7 +172,7 @@ Vue.component('my-board', {
 				]));
 			}
 			let oppReservePiecesArray = [];
-			const oppCol = this.vr.getOppCol(this.userColor);
+			const oppCol = V.GetOppCol(this.userColor);
 			for (let i=0; i<V.RESERVE_PIECES.length; i++)
 			{
 				oppReservePiecesArray.push(h('div',
@@ -305,7 +286,7 @@ Vue.component('my-board', {
 				this.selectedPiece.style.zIndex = 3000;
 				const startSquare = this.getSquareFromId(e.target.parentNode.id);
 				this.possibleMoves = [];
-				const color = this.analyze || this.gameOver
+				const color = this.mode=="analyze" || this.gameOver
 					? this.vr.turn
 					: this.userColor;
 				if (this.vr.canIplay(color,startSquare))
@@ -370,54 +351,8 @@ Vue.component('my-board', {
 			});
 			return moves;
 		},
-		animateMove: function(move) {
-			let startSquare = document.getElementById(this.getSquareId(move.start));
-			let endSquare = document.getElementById(this.getSquareId(move.end));
-			let rectStart = startSquare.getBoundingClientRect();
-			let rectEnd = endSquare.getBoundingClientRect();
-			let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y};
-			let movingPiece =
-				document.querySelector("#" + this.getSquareId(move.start) + " > img.piece");
-			// HACK for animation (with positive translate, image slides "under background")
-			// Possible improvement: just alter squares on the piece's way...
-			squares = document.getElementsByClassName("board");
-			for (let i=0; i<squares.length; i++)
-			{
-				let square = squares.item(i);
-				if (square.id != this.getSquareId(move.start))
-					square.style.zIndex = "-1";
-			}
-			movingPiece.style.transform = "translate(" + translation.x + "px," +
-				translation.y + "px)";
-			movingPiece.style.transitionDuration = "0.2s";
-			movingPiece.style.zIndex = "3000";
-			setTimeout( () => {
-				for (let i=0; i<squares.length; i++)
-					squares.item(i).style.zIndex = "auto";
-				movingPiece.style = {}; //required e.g. for 0-0 with KR swap
-				this.play(move);
-			}, 250);
-		},
-		play: function(move, programmatic) {
-			if (!!programmatic) //computer or human opponent
-				return this.animateMove(move);
-			// Not programmatic, or animation is over
-			this.vr.play(move);
-			if (this.sound == 2)
-				new Audio("/sounds/move.mp3").play().catch(err => {});
-			// Is opponent in check?
-			this.incheck = this.vr.getCheckSquares(this.vr.turn);
-			const eog = this.vr.getCurrentScore();
-			if (eog != "*")
-			{
-				// TODO: notify end of game (give score)
-			}
-		},
-		undo: function(move) {
-			this.vr.undo(move);
-			if (this.sound == 2)
-				new Audio("/sounds/undo.mp3").play().catch(err => {});
-			this.incheck = this.vr.getCheckSquares(this.vr.turn);
+		play: function(move) {
+			this.$emit('play-move', move);
 		},
 	},
 })
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 2b527d93..bbef6f85 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -1,114 +1,83 @@
-// Game logic on a variant page
+// TODO: envoyer juste "light move", sans FEN ni notation ...etc
+
+// Game logic on a variant page: 3 modes, analyze, computer or human
 Vue.component('my-game', {
-	props: ["gameId"], //to find the game in storage (assumption: it exists)
+	// gameId: to find the game in storage (assumption: it exists)
+	props: ["gameId","mode","allowChat","allowMovelist"],
 	data: function() {
 		return {
-			
-			// TODO: merge next variables into "game"
 			// if oppid == "computer" then mode = "computer" (otherwise human)
 			myid: "", //our ID, always set
-		//this.myid = localStorage.getItem("myid")
+			//this.myid = localStorage.getItem("myid")
 			oppid: "", //opponent ID in case of HH game
 			score: "*", //'*' means 'unfinished'
 			mycolor: "w",
-			fromChallenge: false, //if true, show chat during game
-			
-			conn: null, //socket connection
-			oppConnected: false,
-			seek: false,
-			fenStart: "",
+			conn: null, //socket connection (et WebRTC connection ?!)
+			oppConnected: false, //TODO?
 			pgnTxt: "",
 			// sound level: 0 = no sound, 1 = sound only on newgame, 2 = always
 			sound: parseInt(localStorage["sound"] || "2"),
 			// Web worker to play computer moves without freezing interface:
 			compWorker: new Worker('/javascripts/playCompMove.js'),
 			timeStart: undefined, //time when computer starts thinking
+			vr: null, //VariantRules object, describing the game state + rules
 		};
 	},
 	computed: {
-		mode: function() {
-			return (this.game.oppid == "computer" ? "computer" ? "human");
-		},
 		showChat: function() {
-			return this.mode=='human' &&
-				(this.game.score != '*' || this.game.fromChallenge);
+			return this.allowChat && this.mode=='human' && this.score != '*';
 		},
 		showMoves: function() {
-			return window.innerWidth >= 768;
+			return this.allowMovelist && window.innerWidth >= 768;
+		},
+		showFen: function() {
+			return variant.name != "Dark" || this.score != "*";
 		},
 	},
 	// Modal end of game, and then sub-components
+	// TODO: provide chat parameters (connection, players ID...)
+	// and alwo moveList parameters (just moves ?)
+	// TODO: connection + turn indicators en haut à droite (superposé au menu)
+	// TODO: controls: abort, clear, resign, draw (avec confirm box)
+	// et si partie terminée : (mode analyse) just clear, back / play
+	// + flip button toujours disponible
+	// gotoMove : vr = new VariantRules(fen stocké dans le coup [TODO])
 	template: `
 		<div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
 			<input id="modal-eog" type="checkbox" class="modal"/>
 			<div role="dialog" aria-labelledby="eogMessage">
 				<div class="card smallpad small-modal text-center">
-					<label for="modal-eog" class="modal-close"></label>
-					<h3 id="eogMessage" class="section">{{ endgameMessage }}</h3>
+					<label for="modal-eog" class="modal-close">
+					</label>
+					<h3 id="eogMessage" class="section">
+						{{ endgameMessage }}
+					</h3>
 				</div>
 			</div>
-
-			<my-chat v-if="showChat"></my-chat>
-			//TODO: connection + turn indicators en haut à droite (superposé au menu)
-			<my-board></my-board>
-			// TODO: controls: abort, clear, resign, draw (avec confirm box)
-			// et si partie terminée : (mode analyse) just clear, back / play
-			// + flip button toujours disponible
-				// Show current FEN (just below board, lower right corner)
-// (if mode != Dark ...)
-				elementArray.push(
-					h('div',
-						{
-							attrs: { id: "fen-div" },
-							"class": { "section-content": true },
-						},
-						[
-							h('p',
-								{
-									attrs: { id: "fen-string" },
-									domProps: { innerHTML: this.vr.getBaseFen() },
-									"class": { "text-center": true },
-								}
-							)
-						]
-					)
-				);
-			
+			<my-chat v-if="showChat">
+			</my-chat>
+			<my-board v-bind:vr="vr">
+			</my-board>
+			<div v-show="showFen" id="fen-div" class="section-content">
+				<p id="fen-string" class="text-center">
+					{{ vr.getFen() }}
+				</p>
+			</div>
 			<div id="pgn-div" class="section-content">
-				<a id="download" href: "#"></a>
+				<a id="download" href: "#">
+				</a>
 				<button id="downloadBtn" @click="download">
 					{{ translations["Download PGN"] }}
 				</button>
-			
-			<my-move-list v-if="showMoves"></my-move-list>
+			</div>
+			<my-move-list v-if="showMoves">
+			</my-move-list>
 		</div>
 	`,
-	computed: {
-		endgameMessage: function() {
-			let eogMessage = "Unfinished";
-			switch (this.game.score)
-			{
-				case "1-0":
-					eogMessage = translations["White win"];
-					break;
-				case "0-1":
-					eogMessage = translations["Black win"];
-					break;
-				case "1/2":
-					eogMessage = translations["Draw"];
-					break;
-			}
-			return eogMessage;
-		},
-	},
 	created: function() {
 		const url = socketUrl;
 		this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant._id);
-//		const socketOpenListener = () => {
-//		};
-
-// TODO: after game, archive in indexedDB
-
+		// TODO: after game, archive in indexedDB
 		// TODO: this events listener is central. Refactor ? How ?
 		const socketMessageListener = msg => {
 			const data = JSON.parse(msg.data);
@@ -183,29 +152,11 @@ Vue.component('my-game', {
 
 		const socketCloseListener = () => {
 			this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant._id);
-			//this.conn.addEventListener('open', socketOpenListener);
 			this.conn.addEventListener('message', socketMessageListener);
 			this.conn.addEventListener('close', socketCloseListener);
 		};
-		//this.conn.onopen = socketOpenListener;
 		this.conn.onmessage = socketMessageListener;
 		this.conn.onclose = socketCloseListener;
-		
-		
-		// Listen to keyboard left/right to navigate in game
-		// TODO: also mouse wheel !
-		document.onkeydown = event => {
-			if (["human","computer"].includes(this.mode) &&
-				!!this.vr && this.vr.moves.length > 0 && [37,39].includes(event.keyCode))
-			{
-				event.preventDefault();
-				if (event.keyCode == 37) //Back
-					this.undo();
-				else //Forward (39)
-					this.play();
-			}
-		};
-
 
 		// Computer moves web worker logic: (TODO: also for observers in HH games)
 		this.compWorker.postMessage(["scripts",variant.name]);
@@ -233,19 +184,41 @@ Vue.component('my-game', {
 			}, delay);
 		}
 	},
-
-
+	//TODO: conn pourrait être une prop, donnée depuis variant.js
+	//dans variant.js (plutôt room.js) conn gère aussi les challenges
+	// Puis en webRTC, repenser tout ça.
 	methods: {
+		setEndgameMessage: function(score) {
+			let eogMessage = "Undefined";
+			switch (score)
+			{
+				case "1-0":
+					eogMessage = translations["White win"];
+					break;
+				case "0-1":
+					eogMessage = translations["Black win"];
+					break;
+				case "1/2":
+					eogMessage = translations["Draw"];
+					break;
+				case "?":
+					eogMessage = "Unfinished";
+					break;
+			}
+			this.endgameMessage = eogMessage;
+		},
 		download: function() {
 			// Variants may have special PGN structure (so next function isn't defined here)
-			const content = V.GetPGN(this.moves, this.mycolor, this.score, this.fenStart, this.mode);
+			// TODO: get fenStart from local game (using gameid)
+			const content = V.GetPGN(this.moves, this.mycolor, this.score, fenStart, this.mode);
 			// Prepare and trigger download link
 			let downloadAnchor = document.getElementById("download");
 			downloadAnchor.setAttribute("download", "game.pgn");
 			downloadAnchor.href = "data:text/plain;charset=utf-8," + encodeURIComponent(content);
 			downloadAnchor.click();
 		},
-		showScoreMsg: function() {
+		showScoreMsg: function(score) {
+			this.setEndgameMessage(score);
 			let modalBox = document.getElementById("modal-eog");
 			modalBox.checked = true;
 			setTimeout(() => { modalBox.checked = false; }, 2000);
@@ -257,14 +230,15 @@ Vue.component('my-game', {
 				const prefix = (this.mode=="computer" ? "comp-" : "");
 				localStorage.setItem(prefix+"score", score);
 			}
-			this.showScoreMsg();
+			this.showScoreMsg(score);
 			if (this.mode == "human" && this.oppConnected)
 			{
 				// Send our nickname to opponent
 				this.conn.send(JSON.stringify({
 					code:"myname", name:this.myname, oppid:this.oppid}));
 			}
-			this.cursor = this.vr.moves.length; //to navigate in finished game
+			// TODO: what about cursor ?
+			//this.cursor = this.vr.moves.length; //to navigate in finished game
 		},
 		resign: function(e) {
 			this.getRidOfTooltip(e.currentTarget);
@@ -282,42 +256,88 @@ Vue.component('my-game', {
 			this.timeStart = Date.now();
 			this.compWorker.postMessage(["askmove"]);
 		},
-		// OK, these last functions can stay here (?!)
+		animateMove: function(move) {
+			let startSquare = document.getElementById(this.getSquareId(move.start));
+			let endSquare = document.getElementById(this.getSquareId(move.end));
+			let rectStart = startSquare.getBoundingClientRect();
+			let rectEnd = endSquare.getBoundingClientRect();
+			let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y};
+			let movingPiece =
+				document.querySelector("#" + this.getSquareId(move.start) + " > img.piece");
+			// HACK for animation (with positive translate, image slides "under background")
+			// Possible improvement: just alter squares on the piece's way...
+			squares = document.getElementsByClassName("board");
+			for (let i=0; i<squares.length; i++)
+			{
+				let square = squares.item(i);
+				if (square.id != this.getSquareId(move.start))
+					square.style.zIndex = "-1";
+			}
+			movingPiece.style.transform = "translate(" + translation.x + "px," +
+				translation.y + "px)";
+			movingPiece.style.transitionDuration = "0.2s";
+			movingPiece.style.zIndex = "3000";
+			setTimeout( () => {
+				for (let i=0; i<squares.length; i++)
+					squares.item(i).style.zIndex = "auto";
+				movingPiece.style = {}; //required e.g. for 0-0 with KR swap
+				this.play(move); //TODO: plutôt envoyer message "please play"
+			}, 250);
+		},
+		play: function(move, programmatic) {
+			if (!!programmatic) //computer or human opponent
+				return this.animateMove(move);
+			// Not programmatic, or animation is over
+			if (!move.notation)
+				move.notation = this.vr.getNotation(move);
+			this.vr.play(move);
+			if (!move.fen)
+				move.fen = this.vr.getFen();
+			if (this.sound == 2)
+				new Audio("/sounds/move.mp3").play().catch(err => {});
+			if (this.mode == "human")
+			{
+				updateStorage(move); //after our moves and opponent moves
+				if (this.vr.turn == this.userColor)
+					this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
+			}
+			else if (this.mode == "computer")
+			{
+				// Send the move to web worker (including his own moves)
+				this.compWorker.postMessage(["newmove",move]);
+			}
+			if (this.score == "*" || this.mode == "analyze")
+			{
+				// Stack move on movesList
+				this.moves.push(move);
+			}
+			// Is opponent in check?
+			this.incheck = this.vr.getCheckSquares(this.vr.turn);
+			const score = this.vr.getCurrentScore();
+			if (score != "*")
+			{
+				if (["human","computer"].includes(this.mode))
+					this.endGame(score);
+				else //just show score on screen (allow undo)
+					this.showScoreMsg(score);
+				// TODO: notify end of game (give score)
+			}
+			else if (this.mode == "computer" && this.vr.turn != this.userColor)
+				this.playComputerMove();
+		},
+		undo: function(move) {
+			this.vr.undo(move);
+			if (this.sound == 2)
+				new Audio("/sounds/undo.mp3").play().catch(err => {});
+			this.incheck = this.vr.getCheckSquares(this.vr.turn);
+			if (this.mode == "analyze")
+				this.moves.pop();
+		},
 	},
 })
-
-//// TODO: keep moves list here
-//get lastMove()
-//	{
-//		const L = this.moves.length;
-//		return (L>0 ? this.moves[L-1] : null);
-//	}
-//
-//// here too:
-//			move.notation = this.getNotation(move);
+// cursor + ........
 //TODO: confirm dialog with "opponent offers draw", avec possible bouton "prevent future offers" + bouton "proposer nulle"
 //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions,
 //comme sur lichess
-			
-// send move from here:
-//if (this.mode == "human" && this.vr.turn == this.mycolor)
-			//this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
-			// TODO: play move, and stack it on this.moves (if a move was provided; otherwise just navigate)
-			
-//			if (["human","computer","friend"].includes(this.mode))
-//				this.updateStorage(); //after our moves and opponent moves
-//			if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*")
-//				this.playComputerMove();
-//			if (this.mode == "computer")
-//			{
-//				// Send the move to web worker (TODO: including his own moves?!)
-//				this.compWorker.postMessage(["newmove",move]);
-//			}
-//				if (["human","computer"].includes(this.mode))
-//					this.endGame(eog);
-//				else
-//				{
-//					// Just show score on screen (allow undo)
-//					this.score = eog;
-//					this.showScoreMsg();
-//				}
+//
+//TODO: quand partie terminée (ci-dessus) passer partie dans indexedDB
diff --git a/public/javascripts/layout.js b/public/javascripts/layout.js
index 89f1f476..6d26b921 100644
--- a/public/javascripts/layout.js
+++ b/public/javascripts/layout.js
@@ -1,2 +1,4 @@
 // TODO:
 //à l'arrivée sur le site : set peerID (un identifiant unique en tout cas...) si pas trouvé
+//
+//TODO: si une partie en cours dans storage, rediriger vers cette partie
diff --git a/public/javascripts/utils/storage.js b/public/javascripts/utils/storage.js
index 8bd43f0f..5fc62a87 100644
--- a/public/javascripts/utils/storage.js
+++ b/public/javascripts/utils/storage.js
@@ -1,55 +1,27 @@
 		// TODO: general methods to access/retrieve from storage, to be generalized
 		// https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB
 		// https://dexie.org/
-		getStoragePrefix: function(mode) {
-			let prefix = "";
-			if (mode == "computer")
-				prefix = "comp-";
-			else if (mode == "friend")
-				prefix = "anlz-";
-			return prefix;
+		setStorage: function(myid, oppid, gameId, variant, mycolor, fenStart) {
+			localStorage.setItem("myid", myid);
+			localStorage.setItem("oppid", oppid);
+			localStorage.setItem("gameId", gameId);
+			localStorage.setItem("variant", variant);
+			localStorage.setItem("mycolor", mycolor);
+			localStorage.setItem("fenStart", fenStart);
+			localStorage.setItem("moves", []);
 		},
-		setStorage: function() {
-			if (this.mode=="human")
-			{
-				localStorage.setItem("myid", this.myid);
-				localStorage.setItem("oppid", this.oppid);
-				localStorage.setItem("gameId", this.gameId);
-			}
-			const prefix = this.getStoragePrefix(this.mode);
-			localStorage.setItem(prefix+"variant", variant);
-			localStorage.setItem(prefix+"mycolor", this.mycolor);
-			localStorage.setItem(prefix+"fenStart", this.fenStart);
-			localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
-			localStorage.setItem(prefix+"fen", this.vr.getFen());
-			localStorage.setItem(prefix+"score", "*");
-		},
-		updateStorage: function() {
-			const prefix = this.getStoragePrefix(this.mode);
-			localStorage.setItem(prefix+"moves", JSON.stringify(this.vr.moves));
-			localStorage.setItem(prefix+"fen", this.vr.getFen());
-			if (this.score != "*")
-				localStorage.setItem(prefix+"score", this.score);
+		updateStorage: function(move) {
+			let moves = JSON.parse(localStorage.getItem("moves"));
+			moves.push(move);
+			localStorage.setItem("moves", JSON.stringify(moves));
 		},
 		// "computer mode" clearing is done through the menu
 		clearStorage: function() {
-			if (this.mode == "human")
-			{
-				delete localStorage["myid"];
-				delete localStorage["oppid"];
-				delete localStorage["gameId"];
-			}
-			const prefix = this.getStoragePrefix(this.mode);
-			delete localStorage[prefix+"variant"];
-			delete localStorage[prefix+"mycolor"];
-			delete localStorage[prefix+"fenStart"];
-			delete localStorage[prefix+"moves"];
-			delete localStorage[prefix+"fen"];
-			delete localStorage[prefix+"score"];
-		},
-		clearCurrentGame: function(e) {
-			this.getRidOfTooltip(e.currentTarget);
-			this.clearStorage();
-			location.reload(); //to see clearing effects
+			delete localStorage["myid"];
+			delete localStorage["oppid"];
+			delete localStorage["gameId"];
+			delete localStorage["variant"];
+			delete localStorage["mycolor"];
+			delete localStorage["fenStart"];
+			delete localStorage["moves"];
 		},
-
-- 
2.44.0