From 053b07119946595c2475de063e2650ab7b0ee71a Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Wed, 24 Jan 2018 03:06:27 +0100 Subject: [PATCH 1/1] First draft --- index.html | 15 ++++++++ rpsls.js | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 index.html create mode 100644 rpsls.js diff --git a/index.html b/index.html new file mode 100644 index 0000000..31303f9 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ +<!-- TODO: click : on image (show computer move on same image) + show network-matrix on the right (top-bottom, 5 per row) --> + +<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> + +<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"> +<script src="rpsls.js"> diff --git a/rpsls.js b/rpsls.js new file mode 100644 index 0000000..42c87b6 --- /dev/null +++ b/rpsls.js @@ -0,0 +1,103 @@ +const nChoice = 5,; //fixed for this game + +// Rewards matrix. Order: rock, lizard, Spock, scissors, paper +const rewards = Array.from(Array(nChoice)).map( i => { //lines + return Array.from(Array(nChoice)).map( j => { //columns + // i against j: gain from i viewpoint + if (j == (i+1) % nChoice || j == (i+3) % nChoice) + return 1; //I win :) + else if ((i+1) % nChoice == j || j == (i+2) % nChoice) + return -1; //I lose :( + else + return 0; + }); +}); + +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 + drawIsLost: false, //normal (or true: draw is considered loss) + rewards: [ ], //initialized at first human move with new nInput + weights: [ ], //same as above + }, + methods: { + // Called on nInput change + reinitialize: function() { + // weights[i][j][k]: output i -- input j/choice k + this.weights = Array.from(Array(nChoice)).map( i => { + return Array.from(Array(this.nInput)).map( j => { + return Array.from(Array(nChoice)).map( k => { + return 0; + }); + }) + }); + if (this.humanHistory.length > this.nInput) + this.humanHistory.splice(this.nInput); + }, + // Play a move given current data (*after* human move: trigger onChange) + play: function() { + let candidates = [ ]; + Array.from(Array(nChoice)).forEach( i => { + // Sum all weights from an activated input to this output + let sumWeights = this.weights[i].reduce( (acc,input,j) => { + if (this.humanHistory.length <= j) + return 0; + return input[ this.humanHistory[j] ]; + }, 0 ); + let currentValue = { + val: sumWeights, + index: i + }; + if (candidates.length == 0 || sumWeights > candidates[0].val) + candidates = [ currentValue ]; + else if (sumWeights == candidates[0].val) + candidates.push(currentValue); + }); + // 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]); + }, + updateGameState: function(move) { + let reward = rewards[move.val][this.humanMove]; //viewpoint of computer + this.gameState += reward; + this.updateWeights(reward, move.index); + }, + updateWeights: function(reward, index) { + let delta = Math.sign(reward); + if (this.drawIsLost && reward == 0) + delta = -1; + this.weights[index].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) => { + return b + input.reduce( (c,choiceWeight) => { + return c + choiceWeight; + }, 0); + }, 0); + }, 0); + let meanWeight = sumAllWeights / (this.nInput * nChoice * nChoice); + this.weights.forEach( output => { + output.forEach( input => { + for (let i=0; i<input.length; i++) + input[i] -= meanWeight; + }); + }); + // Update human moves history + this.humanHistory.push(coup_adversaire); + this.humanHistory.shift(); + }, + }, +}; -- 2.44.0