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