Debug, add README and images, ready for publish
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 24 Jan 2018 14:56:14 +0000 (15:56 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 24 Jan 2018 14:56:14 +0000 (15:56 +0100)
README.md [new file with mode: 0644]
css/rpsls.css [new file with mode: 0644]
img/Lizard.png [new file with mode: 0644]
img/Paper.png [new file with mode: 0644]
img/README [new file with mode: 0644]
img/Rock.png [new file with mode: 0644]
img/Scissors.png [new file with mode: 0644]
img/Spock.png [new file with mode: 0644]
index.html
js/rpsls.js [moved from rpsls.js with 61% similarity]

diff --git a/README.md b/README.md
new file mode 100644 (file)
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 (file)
index 0000000..4b0bbbe
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..abf8ab7
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..433943c
Binary files /dev/null and b/img/Spock.png differ
index 79786e8..7b2639b 100644 (file)
@@ -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>
similarity index 61%
rename from rpsls.js
rename to js/rpsls.js
index 661e31c..cff3001 100644 (file)
--- a/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";
                },
   },
 });