From: Benjamin Auder 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/%7B%7B%20asset%28%27mixstore/images/doc/html/mini-custom.min.css?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 @@ - + + -
- - - - - - - - -
+ + + Rock Paper Scissors Lizard Spock + + + + +
+
+
+
+
+ + +
+ + + + +
+
+
+
+ +

{{ symbols[humanMove] + " " + messages[humanMove][compMove] + " " + symbols[compMove] }}

+

+ Score: + {{ gameState }} +

+
+
+ + + + + - + 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"; }, }, });