First draft
[rpsls-bot.git] / rpsls.js
... / ...
CommitLineData
1const nChoice = 5,; //fixed for this game\r
2\r
3// Rewards matrix. Order: rock, lizard, Spock, scissors, paper\r
4const rewards = Array.from(Array(nChoice)).map( i => { //lines\r
5 return Array.from(Array(nChoice)).map( j => { //columns\r
6 // i against j: gain from i viewpoint\r
7 if (j == (i+1) % nChoice || j == (i+3) % nChoice)\r
8 return 1; //I win :)\r
9 else if ((i+1) % nChoice == j || j == (i+2) % nChoice)\r
10 return -1; //I lose :(\r
11 else\r
12 return 0;\r
13 });\r
14});\r
15\r
16const symbols = [ "Rock", "Lizard", "Spock", "Scissors", "Paper" ];\r
17\r
18new Vue({\r
19 el: "#rpsls",\r
20 data: {\r
21 humanMove: -1, //integer in 0...nChoice-1\r
22 nInput: 5, //default\r
23 humanHistory: [ ], //your nInput last moves, stored (oldest first)\r
24 gameState: 0, //total points of the computer\r
25 drawIsLost: false, //normal (or true: draw is considered loss)\r
26 rewards: [ ], //initialized at first human move with new nInput\r
27 weights: [ ], //same as above\r
28 },\r
29 methods: {\r
30 // Called on nInput change\r
31 reinitialize: function() {\r
32 // weights[i][j][k]: output i -- input j/choice k\r
33 this.weights = Array.from(Array(nChoice)).map( i => {\r
34 return Array.from(Array(this.nInput)).map( j => {\r
35 return Array.from(Array(nChoice)).map( k => {\r
36 return 0;\r
37 });\r
38 })\r
39 });\r
40 if (this.humanHistory.length > this.nInput)\r
41 this.humanHistory.splice(this.nInput);\r
42 },\r
43 // Play a move given current data (*after* human move: trigger onChange)\r
44 play: function() {\r
45 let candidates = [ ];\r
46 Array.from(Array(nChoice)).forEach( i => {\r
47 // Sum all weights from an activated input to this output\r
48 let sumWeights = this.weights[i].reduce( (acc,input,j) => {\r
49 if (this.humanHistory.length <= j)\r
50 return 0;\r
51 return input[ this.humanHistory[j] ];\r
52 }, 0 );\r
53 let currentValue = {\r
54 val: sumWeights,\r
55 index: i\r
56 };\r
57 if (candidates.length == 0 || sumWeights > candidates[0].val)\r
58 candidates = [ currentValue ];\r
59 else if (sumWeights == candidates[0].val)\r
60 candidates.push(currentValue);\r
61 });\r
62 // Pick a choice at random in maxValue (total random for first move)\r
63 let randIdx = Math.floor((Math.random() * candidates.length) + 1);\r
64 this.updateGameState(candidates[randIdx]);\r
65 },\r
66 updateGameState: function(move) {\r
67 let reward = rewards[move.val][this.humanMove]; //viewpoint of computer\r
68 this.gameState += reward;\r
69 this.updateWeights(reward, move.index);\r
70 },\r
71 updateWeights: function(reward, index) {\r
72 let delta = Math.sign(reward);\r
73 if (this.drawIsLost && reward == 0)\r
74 delta = -1;\r
75 this.weights[index].forEach( (input,i) => {\r
76 if (i < this.humanHistory.length)\r
77 input[ this.humanHistory[i] ] += delta;\r
78 });\r
79 this.postUpdate();\r
80 },\r
81 // Finalize weights update\r
82 postUpdate: function() {\r
83 // Re-center the weights\r
84 let sumAllWeights = this.weights.reduce( (a,output) => {\r
85 return a + output.reduce( (b,input) => {\r
86 return b + input.reduce( (c,choiceWeight) => {\r
87 return c + choiceWeight;\r
88 }, 0);\r
89 }, 0);\r
90 }, 0);\r
91 let meanWeight = sumAllWeights / (this.nInput * nChoice * nChoice);\r
92 this.weights.forEach( output => {\r
93 output.forEach( input => {\r
94 for (let i=0; i<input.length; i++)\r
95 input[i] -= meanWeight;\r
96 });\r
97 });\r
98 // Update human moves history\r
99 this.humanHistory.push(coup_adversaire);\r
100 this.humanHistory.shift();\r
101 },\r
102 },\r
103};\r