From 643479f8d7c3622b57fc49c4f10d9950793ebf4f Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 18 Dec 2018 19:08:14 +0100
Subject: [PATCH] Play computer move in webworker to not freeze interface

---
 TODO                                      |  2 --
 db/create.sql                             |  1 +
 public/javascripts/components/game.js     | 40 +++++++++++++++--------
 public/javascripts/playCompMove.js        | 26 +++++++++++++++
 public/javascripts/variant.js             |  8 ++++-
 public/javascripts/variants/Alice.js      |  2 ++
 public/javascripts/variants/Antiking.js   |  2 ++
 public/javascripts/variants/Atomic.js     |  2 ++
 public/javascripts/variants/Checkered.js  |  2 ++
 public/javascripts/variants/Chess960.js   |  2 ++
 public/javascripts/variants/Crazyhouse.js |  2 ++
 public/javascripts/variants/Extinction.js |  2 ++
 public/javascripts/variants/Grand.js      |  2 ++
 public/javascripts/variants/Loser.js      |  2 ++
 public/javascripts/variants/Magnetic.js   |  2 ++
 public/javascripts/variants/Switching.js  |  2 ++
 public/javascripts/variants/Ultima.js     |  6 ++--
 public/javascripts/variants/Wildebeest.js |  2 ++
 public/javascripts/variants/Zen.js        |  2 ++
 public/stylesheets/variant.sass           |  3 ++
 views/rules/Ultima.pug                    |  8 ++---
 views/variant.pug                         |  1 -
 22 files changed, 98 insertions(+), 23 deletions(-)
 delete mode 100644 TODO
 create mode 100644 public/javascripts/playCompMove.js

diff --git a/TODO b/TODO
deleted file mode 100644
index 4b311b04..00000000
--- a/TODO
+++ /dev/null
@@ -1,2 +0,0 @@
-global lang cookie, + display (remember in each variant what is shown...)
-translations (how ? switch on index page only, then find ideas...)
diff --git a/db/create.sql b/db/create.sql
index 36bda8aa..033df4be 100644
--- a/db/create.sql
+++ b/db/create.sql
@@ -21,6 +21,7 @@ insert into Variants values
 	('Ultima', 'Exotic captures');
 
 create table Problems (
+	num integer primary key,
 	added datetime,
 	variant varchar,
 	fen varchar,
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 186c027c..b242ee27 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -22,6 +22,9 @@ Vue.component('my-game', {
 			color: getCookie("color", "lichess"), //lichess, chesscom or chesstempo
 			// sound level: 0 = no sound, 1 = sound only on newgame, 2 = always
 			sound: parseInt(getCookie("sound", "2")),
+			// Web worker to play computer moves without freezing interface:
+			compWorker: new Worker('/javascripts/playCompMove.js'),
+			timeStart: undefined, //time when computer starts thinking
 		};
 	},
 	watch: {
@@ -146,7 +149,7 @@ Vue.component('my-game', {
 					'class': {
 						"tooltip": true,
 						"topindicator": true,
-						"indic-right": true,
+						"indic-left": true,
 						"settings-btn": !smallScreen,
 						"settings-btn-small": smallScreen,
 					},
@@ -965,6 +968,19 @@ Vue.component('my-game', {
 					this.play();
 			}
 		};
+		// Computer moves web worker logic:
+		this.compWorker.postMessage(["scripts",variant]);
+		const self = this;
+		this.compWorker.onmessage = function(e) {
+			const compMove = e.data;
+			// (first move) HACK: small delay to avoid selecting elements
+			// before they appear on page:
+			const delay = Math.max(500-(Date.now()-self.timeStart), 0);
+			setTimeout(() => {
+				if (self.mode == "computer") //Warning: mode could have changed!
+					self.play(compMove, "animate")
+			}, delay);
+		}
 	},
 	methods: {
 		toggleShowSolution: function() {
@@ -1161,23 +1177,16 @@ Vue.component('my-game', {
 			}
 			else if (mode == "computer")
 			{
+				this.compWorker.postMessage(["init",this.vr.getFen()]);
 				this.mycolor = Math.random() < 0.5 ? 'w' : 'b';
 				if (this.mycolor == 'b')
-					setTimeout(this.playComputerMove, 100); //small delay for drawing board
+					this.playComputerMove();
 			}
 			//else: against a (IRL) friend or problem solving: nothing more to do
 		},
 		playComputerMove: function() {
-			const timeStart = Date.now();
-			// TODO: next call asynchronous (avoid freezing interface while computer "think").
-			// This would also allow to remove some artificial setTimeouts
-			const compMove = this.vr.getComputerMove();
-			// (first move) HACK: avoid selecting elements before they appear on page:
-			const delay = Math.max(250-(Date.now()-timeStart), 0);
-			setTimeout(() => {
-				if (this.mode == "computer") //Warning: mode could have changed!
-					this.play(compMove, "animate")
-			}, delay);
+			this.timeStart = Date.now();
+			this.compWorker.postMessage(["askmove"]);
 		},
 		// Get the identifier of a HTML table cell from its numeric coordinates o.x,o.y.
 		getSquareId: function(o) {
@@ -1339,6 +1348,11 @@ Vue.component('my-game', {
 			{
 				this.incheck = this.vr.getCheckSquares(move); //is opponent in check?
 				this.vr.play(move, "ingame");
+				if (this.mode == "computer")
+				{
+					// Send the move to web worker
+					this.compWorker.postMessage(["newmove",move]);
+				}
 			}
 			else
 			{
@@ -1363,7 +1377,7 @@ Vue.component('my-game', {
 				}
 			}
 			if (this.mode == "computer" && this.vr.turn != this.mycolor)
-				setTimeout(this.playComputerMove, 250); //small delay for animation
+				this.playComputerMove();
 		},
 		undo: function() {
 			// Navigate after game is over
diff --git a/public/javascripts/playCompMove.js b/public/javascripts/playCompMove.js
new file mode 100644
index 00000000..fc69ce24
--- /dev/null
+++ b/public/javascripts/playCompMove.js
@@ -0,0 +1,26 @@
+// For asynchronous computer move search
+onmessage = function(e)
+{
+	switch (e.data[0])
+	{
+		case "scripts":
+			self.importScripts(
+				'//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js',
+				'/javascripts/base_rules.js',
+				'/javascripts/utils/array.js',
+				'/javascripts/variants/' + e.data[1] + '.js');
+			self.V = VariantRules;
+			break;
+		case "init":
+			const fen = e.data[1];
+			self.vr = new VariantRules(fen, []);
+			break;
+		case "newmove":
+			self.vr.play(e.data[1]);
+			break;
+		case "askmove":
+			const compMove = self.vr.getComputerMove();
+			postMessage(compMove);
+			break;
+	}
+}
diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js
index 3aa2f686..e9e956ca 100644
--- a/public/javascripts/variant.js
+++ b/public/javascripts/variant.js
@@ -1,15 +1,21 @@
 new Vue({
 	el: "#variantPage",
 	data: {
-		display: "", //do not show anything...
+		display: getCookie("display-"+variant,""), //default: do not show anything...
 		problem: undefined, //current problem in view
 	},
 	methods: {
 		toggleDisplay: function(elt) {
 			if (this.display == elt)
+			{
 				this.display = ""; //hide
+				setCookie("display-"+variant, "");
+			}
 			else
+			{
 				this.display = elt; //show
+				setCookie("display-"+variant, elt);
+			}
 		},
 		showProblem: function(problemTxt) {
 			this.problem = JSON.parse(problemTxt);
diff --git a/public/javascripts/variants/Alice.js b/public/javascripts/variants/Alice.js
index 00103a7b..f7d2fa13 100644
--- a/public/javascripts/variants/Alice.js
+++ b/public/javascripts/variants/Alice.js
@@ -336,3 +336,5 @@ class AliceRules extends ChessRules
 		return notation;
 	}
 }
+
+const VariantRules = AliceRules;
diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js
index 38cadf22..0d342713 100644
--- a/public/javascripts/variants/Antiking.js
+++ b/public/javascripts/variants/Antiking.js
@@ -204,3 +204,5 @@ class AntikingRules extends ChessRules
 			" w 1111";
 	}
 }
+
+const VariantRules = AntikingRules;
diff --git a/public/javascripts/variants/Atomic.js b/public/javascripts/variants/Atomic.js
index e2f8ea33..0275805f 100644
--- a/public/javascripts/variants/Atomic.js
+++ b/public/javascripts/variants/Atomic.js
@@ -147,3 +147,5 @@ class AtomicRules extends ChessRules
 		return color == "w" ? "0-1" : "1-0"; //checkmate
 	}
 }
+
+const VariantRules = AtomicRules;
diff --git a/public/javascripts/variants/Checkered.js b/public/javascripts/variants/Checkered.js
index 314cd8e4..d73cef41 100644
--- a/public/javascripts/variants/Checkered.js
+++ b/public/javascripts/variants/Checkered.js
@@ -303,3 +303,5 @@ class CheckeredRules extends ChessRules
 		}
 	}
 }
+
+const VariantRules = CheckeredRules;
diff --git a/public/javascripts/variants/Chess960.js b/public/javascripts/variants/Chess960.js
index 1c8292ce..529ad997 100644
--- a/public/javascripts/variants/Chess960.js
+++ b/public/javascripts/variants/Chess960.js
@@ -2,3 +2,5 @@ class Chess960Rules extends ChessRules
 {
 	// Standard rules
 }
+
+const VariantRules = Chess960Rules;
diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js
index 00452cbd..afebcc35 100644
--- a/public/javascripts/variants/Crazyhouse.js
+++ b/public/javascripts/variants/Crazyhouse.js
@@ -266,3 +266,5 @@ class CrazyhouseRules extends ChessRules
 		return "@" + V.CoordsToSquare(move.end);
 	}
 }
+
+const VariantRules = CrazyhouseRules;
diff --git a/public/javascripts/variants/Extinction.js b/public/javascripts/variants/Extinction.js
index 49d61b2d..bc061629 100644
--- a/public/javascripts/variants/Extinction.js
+++ b/public/javascripts/variants/Extinction.js
@@ -135,3 +135,5 @@ class ExtinctionRules extends ChessRules
 		return super.evalPosition();
 	}
 }
+
+const VariantRules = ExtinctionRules;
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
index b598f2d2..ec2afd81 100644
--- a/public/javascripts/variants/Grand.js
+++ b/public/javascripts/variants/Grand.js
@@ -358,3 +358,5 @@ class GrandRules extends ChessRules
 			" w 1111 -";
 	}
 }
+
+const VariantRules = GrandRules;
diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js
index 5f156129..6731d322 100644
--- a/public/javascripts/variants/Loser.js
+++ b/public/javascripts/variants/Loser.js
@@ -193,3 +193,5 @@ class LoserRules extends ChessRules
 			" w -"; //no en-passant
 	}
 }
+
+const VariantRules = LoserRules;
diff --git a/public/javascripts/variants/Magnetic.js b/public/javascripts/variants/Magnetic.js
index 225489ec..17efeaa9 100644
--- a/public/javascripts/variants/Magnetic.js
+++ b/public/javascripts/variants/Magnetic.js
@@ -281,3 +281,5 @@ class MagneticRules extends ChessRules
 		return 500; //checkmates evals may be slightly below 1000
 	}
 }
+
+const VariantRules = MagneticRules;
diff --git a/public/javascripts/variants/Switching.js b/public/javascripts/variants/Switching.js
index ee84e4ca..e53ab692 100644
--- a/public/javascripts/variants/Switching.js
+++ b/public/javascripts/variants/Switching.js
@@ -137,3 +137,5 @@ class SwitchingRules extends ChessRules
 		return "S" + startSquare + finalSquare;
 	}
 }
+
+const VariantRules = SwitchingRules;
diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js
index 7f4a7ebe..04d62b7d 100644
--- a/public/javascripts/variants/Ultima.js
+++ b/public/javascripts/variants/Ultima.js
@@ -1,8 +1,8 @@
 class UltimaRules extends ChessRules
 {
-	static get HasFlags { return false; }
+	static get HasFlags() { return false; }
 
-	static get HasEnpassant { return false; }
+	static get HasEnpassant() { return false; }
 
 	static getPpath(b)
 	{
@@ -626,3 +626,5 @@ class UltimaRules extends ChessRules
 		return notation;
 	}
 }
+
+const VariantRules = UltimaRules;
diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js
index 4194521e..783eb440 100644
--- a/public/javascripts/variants/Wildebeest.js
+++ b/public/javascripts/variants/Wildebeest.js
@@ -281,3 +281,5 @@ class WildebeestRules extends ChessRules
 			" w 1111 -";
 	}
 }
+
+const VariantRules = WildebeestRules;
diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js
index da4dd7af..b090a292 100644
--- a/public/javascripts/variants/Zen.js
+++ b/public/javascripts/variants/Zen.js
@@ -226,3 +226,5 @@ class ZenRules extends ChessRules
 		}
 	}
 }
+
+const VariantRules = ZenRules;
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index 856f22b6..a0d4f2a2 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -280,3 +280,6 @@ ul:not(.browser-default) > li
 
 #solution-div h3
   cursor: pointer
+
+.newproblem-form, .newproblem-preview
+  max-width: 90%
diff --git a/views/rules/Ultima.pug b/views/rules/Ultima.pug
index cf5da24f..8255a5e9 100644
--- a/views/rules/Ultima.pug
+++ b/views/rules/Ultima.pug
@@ -103,7 +103,7 @@ p.
 
 figure.diagram-container
 	.diagram
-		| fen:2n4k/3r4/5b2/3p4/1m6/3b4/3N4/K7 w d4,d6,d8,a5:
+		| fen:2n4k/3r4/5b2/3p4/1m6/3b4/3N4/K7 d4,d6,d8,a5:
 	figcaption All marked squares captures are playable from d2.
 
 h4 Withdrawer (queen)
@@ -114,7 +114,7 @@ p.
 
 figure.diagram-container
 	.diagram
-		| fen:7k/8/8/3Qr3/8/8/8/K7 w a5,b5,c5:
+		| fen:7k/8/8/3Qr3/8/8/8/K7 a5,b5,c5:
 	figcaption 1.Qa5, 1.Qb5 or 1.Qc5 captures the black rook.
 
 h4 Chameleon (bishop)
@@ -129,7 +129,7 @@ p ...and these captures can be combined.
 
 figure.diagram-container
 	.diagram
-		| fen:7k/8/8/r3pP2/2n5/8/B7/K7 w a5,c4,e5:
+		| fen:7k/8/8/r3pP2/2n5/8/B7/K7 a5,c4,e5:
 	figcaption 1.Bd5 captures all marked pieces.
 
 p.
@@ -150,7 +150,7 @@ p.
 
 figure.diagram-container
 	.diagram
-		| fen:7k/8/8/p4r/4K3/8/8/8 w e5:
+		| fen:7k/8/8/p4r/4K3/8/8/8 e5:
 	figcaption 1.Ke5 is impossible
 
 h3 Credits
diff --git a/views/variant.pug b/views/variant.pug
index b1caabdb..d3581ae3 100644
--- a/views/variant.pug
+++ b/views/variant.pug
@@ -32,7 +32,6 @@ block javascripts
 	script(src="/javascripts/base_rules.js")
 	script(src="/javascripts/variants/" + variant + ".js")
 	script.
-		const VariantRules = #{variant}Rules;
 		const V = VariantRules; //because this variable is often used
 		const variant = "#{variant}";
 		const problemArray = !{JSON.stringify(problemArray)};
-- 
2.44.0