From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 24 Jan 2018 14:56:14 +0000 (+0100)
Subject: Debug, add README and images, ready for publish
X-Git-Url: https://git.auder.net/js/pieces/doc/html/%7B%7B%20pkg.url%20%7D%7D?a=commitdiff_plain;h=d666e77138bb5e3e397d68dbbddc28845c81068b;p=rpsls-bot.git

Debug, add README and images, ready for publish
---

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9994d94
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# Rock-Paper-Scissors-Lizard-Spock
+
+A simple bot to play this game, following ideas from [this article](https://www.his.se/PageFiles/8158/Henrik_Engstrom.pdf).
+
+The rules are given by Sheldon in episode 8 of season 2 of TBBT (The Big Bang Theory).
+
+---
+
+Try to win ;-) ...and if it's too easy, check "draw is lost".
+
+But it should not be too easy, because it's hard to play at random.
diff --git a/css/rpsls.css b/css/rpsls.css
new file mode 100644
index 0000000..4b0bbbe
--- /dev/null
+++ b/css/rpsls.css
@@ -0,0 +1,49 @@
+#rpsls {
+	margin-top: 50px;
+	margin-bottom: 50px;
+}
+
+.topSpacing {
+	margin-top: 40px;
+}
+
+.leftSpacing {
+	padding-left: 40px;
+}
+
+.compChoice {
+	border: 3px solid red;
+}
+
+.humanChoice {
+	border: 3px solid green;
+}
+
+.compChoice.humanChoice {
+	border: 3px solid black;
+}
+
+.image {
+	width: 20%;
+	cursor: pointer;
+}
+
+.message {
+	width: 100%;
+	font-weight: bold;
+	font-size: 1.2rem;
+}
+
+.scoreMsg {
+	font-size: 1.5rem;
+}
+
+.score {
+	font-weight: bold;
+}
+
+footer {
+	position: absolute;
+	bottom: 0;
+	width: 100%;
+}
diff --git a/img/Lizard.png b/img/Lizard.png
new file mode 100644
index 0000000..bb03530
Binary files /dev/null and b/img/Lizard.png differ
diff --git a/img/Paper.png b/img/Paper.png
new file mode 100644
index 0000000..e07b3d6
Binary files /dev/null and b/img/Paper.png differ
diff --git a/img/README b/img/README
new file mode 100644
index 0000000..abf8ab7
--- /dev/null
+++ b/img/README
@@ -0,0 +1,6 @@
+Rock, Paper and Scissors images are from https://www.iconfinder.com/
+(they are still striped, as printed on the website, since I didn't pay for them)
+
+Spock image was found here http://all-free-download.com/free-vector/download/spock-star-trek-cartoon_52494.html
+
+Lizard image was found here http://www.symbols-n-emoticons.com/2014/07/likable-lizard.html
diff --git a/img/Rock.png b/img/Rock.png
new file mode 100644
index 0000000..198c0ce
Binary files /dev/null and b/img/Rock.png differ
diff --git a/img/Scissors.png b/img/Scissors.png
new file mode 100644
index 0000000..c70f9e5
Binary files /dev/null and b/img/Scissors.png differ
diff --git a/img/Spock.png b/img/Spock.png
new file mode 100644
index 0000000..433943c
Binary files /dev/null and b/img/Spock.png differ
diff --git a/index.html b/index.html
index 79786e8..7b2639b 100644
--- a/index.html
+++ b/index.html
@@ -1,15 +1,44 @@
-<!-- TODO: click : on image (show computer move on same image) + show network-matrix on the right (top-bottom, 5 per row) -->
+<!doctype html>
+<html lang="en">
 
-<div id="rpsls">
-	<input id="drawIsLost" type="checkbox" v-model="drawIsLost">
-	<label for="drawIsLost"> Draw count as loss ?</label>
-	<input id="nInput" type="number" v-model="nInput" @change="reinitialize()">
-	<label for="nInput">Number of inputs</label>
-	<input id="humanMove" type="number" v-model="humanMove" @change="play()">
-	<label for="humanMove">Move ?</label>
-	<input id="gameState" type="number" v-model="gameState" disabled>
-	<label for="gameState">Score of computer</label>
-</div>
+	<head>
+		<meta charset="utf-8">
+		<title>Rock Paper Scissors Lizard Spock</title>
+		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.min.css">
+		<link rel="stylesheet" href="css/rpsls.css">
+	</head>
 
+	<body>
+		<div class="container" id="rpsls">
+			<div class="row">
+				<div class="col s12">
+					<form class="center">
+						<div class="input-field inline">
+							<input id="nInput" type="number" v-model="nInput" @change="reinitialize()">
+							<label for="nInput">Number of inputs</label>
+						</div>
+						<span class="leftSpacing">
+							<input id="drawIsLost" type="checkbox" v-model="drawIsLost">
+							<label for="drawIsLost"> Draw count as loss ?</label>
+						</span>
+					</form>
+				</div>
+			</div>
+			<div class="row topSpacing">
+				<img v-for="(symb,i) in symbols" class="image" :class="{compChoice:compMove==i,humanChoice:humanMove==i}" :src="imgSource(symb)" @click="play(i)">
+				<p class="blue-text center message topSpacing" v-if="humanMove>=0 && compMove>=0">{{ symbols[humanMove] + " " + messages[humanMove][compMove] + " " + symbols[compMove] }}</p>
+				<p class="center scoreMsg topSpacing">
+					<span>Score: </span>
+					<span class="score">{{ gameState }}</span>
+				</p>
+			</div>
+		</div>
+		<footer class="center grey-text">
+			<p>Images were found on the web; if they should be removed, let me know: yagu0 on github</p>
+		</footer>
+	</body>
+
+<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
-<script src="./rpsls.js"></script>
+<script src="js/rpsls.js"></script>
diff --git a/rpsls.js b/js/rpsls.js
similarity index 61%
rename from rpsls.js
rename to js/rpsls.js
index 661e31c..cff3001 100644
--- a/rpsls.js
+++ b/js/rpsls.js
@@ -1,30 +1,41 @@
-const nChoice = 3; //fixed for this game
+const nChoice = 5; //fixed for this game
 
-// Rewards matrix. Order: rock, lizard, Spock, scissors, paper
+const symbols = [ "Rock", "Lizard", "Spock", "Scissors", "Paper" ];
+
+// Same order as in symbols
+const messages = [
+	[ "=",             "crushes",        "is vaporized by", "cruches",           "is covered by"   ],
+	[ "is crushed by", "=",              "poisons",         "is decapitated by", "eats"            ],
+	[ "vaporizes",     "is poisoned by", "=",               "smashes",           "is disproved by" ],
+	[ "is crushed by", "decapitates",    "is smashed by",   "=",                 "cuts"            ],
+	[ "covers",        "is eaten by",    "disproves",       "is cut by",         "="               ],
+];
+
+// Rewards matrix, order as in symbols
 const rewards = Array.from(Array(nChoice)).map( (e,i) => { //lines
 	return Array.from(Array(nChoice)).map( (f,j) => { //columns
 		// i against j: gain from i viewpoint
-		if (j == (i+1) % nChoice)// || j == (i+3) % nChoice)
+		if (j == (i+1) % nChoice || j == (i+3) % nChoice)
 			return 1; //I win :)
 		else if (i != j)
 			return -1; //I lose :(
-		else //i == j
-			return 0;
+		return 0; //i==j
 	});
 });
 
-const symbols = [ "Rock", "Lizard", "Spock", "Scissors", "Paper" ];
-
 new Vue({
 	el: "#rpsls",
 	data: {
-		humanMove: -1, //integer in 0...nChoice-1
 		nInput: 5, //default
 		humanHistory: [ ], //your nInput last moves, stored (oldest first)
-		gameState: 0, //total points of the computer
+		humanMove: -1,
+		gameState: 0, //total points of the human
+		compMove: -1, //last move played by computer
 		drawIsLost: false, //normal (or true: draw is considered loss)
 		rewards: [ ], //initialized at first human move with new nInput
 		weights: [ ], //same as above
+		symbols: symbols,
+		messages: messages,
 	},
 	created: function() {
 		this.reinitialize();
@@ -43,8 +54,9 @@ new Vue({
 			if (this.humanHistory.length > this.nInput)
 				this.humanHistory.splice(this.nInput);
 		},
-		// Play a move given current data (*after* human move: trigger onChange)
-		play: function() {
+		// Play a move given current data (*after* human move)
+		play: function(humanMove) {
+			this.humanMove = humanMove;
 			let candidates = [ ];
 			Array.from(Array(nChoice)).forEach( (e,i) => {
 				// Sum all weights from an activated input to this output
@@ -53,36 +65,35 @@ new Vue({
 						return 0;
 					return input[ this.humanHistory[j] ];
 				}, 0 );
-				let currentValue = {
+				let move = {
 					val: sumWeights,
-					index: i
+					index: i,
 				};
 				if (candidates.length == 0 || sumWeights > candidates[0].val)
-					candidates = [ currentValue ];
+					candidates = [move];
 				else if (sumWeights == candidates[0].val)
-					candidates.push(currentValue);
+					candidates.push(move);
 			});
 			// Pick a choice at random in maxValue (total random for first move)
-			let randIdx = Math.floor((Math.random() * candidates.length) + 1);
-			this.updateGameState(candidates[randIdx].index);
-		},
-		updateGameState: function(index) {
-			let reward = rewards[index][this.humanMove]; //viewpoint of computer
-			this.gameState += reward;
-			this.updateWeights(reward, index);
+			let randIdx = Math.floor(Math.random() * candidates.length);
+			this.compMove = candidates[randIdx].index;
+			// Update game state
+			let reward = rewards[this.compMove][humanMove]; //viewpoint of computer
+			this.gameState -= reward;
+			this.updateWeights(reward);
+			// Update human moves history
+			this.humanHistory.push(humanMove);
+			if (this.humanHistory.length > this.nInput)
+				this.humanHistory.shift();
 		},
-		updateWeights: function(reward, index) {
+		updateWeights: function(reward) {
 			let delta = Math.sign(reward);
 			if (this.drawIsLost && reward == 0)
 				delta = -1;
-			this.weights[index].forEach( (input,i) => {
+			this.weights[this.compMove].forEach( (input,i) => {
 				if (i < this.humanHistory.length)
 					input[ this.humanHistory[i] ] += delta;
 			});
-			this.postUpdate();
-		},
-		// Finalize weights update
-		postUpdate: function() {
 			// Re-center the weights
 			let sumAllWeights = this.weights.reduce( (a,output) => {
 				return a + output.reduce( (b,input) => {
@@ -98,10 +109,9 @@ new Vue({
 						input[i] -= meanWeight;
 				});
 			});
-			// Update human moves history
-			this.humanHistory.push(this.humanMove);
-			if (this.humanHistory.length > this.nInput)
-				this.humanHistory.shift();
+		},
+		imgSource: function(symbol) {
+			return "img/" + symbol + ".png";
 		},
   },
 });