First draft
[rpsls-bot.git] / rpsls.js
1 const nChoice = 5,; //fixed for this game
2
3 // Rewards matrix. Order: rock, lizard, Spock, scissors, paper
4 const rewards = Array.from(Array(nChoice)).map( i => { //lines
5 return Array.from(Array(nChoice)).map( 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+1) % nChoice == j || j == (i+2) % nChoice)
10 return -1; //I lose :(
11 else
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 methods: {
30 // Called on nInput change
31 reinitialize: function() {
32 // weights[i][j][k]: output i -- input j/choice k
33 this.weights = Array.from(Array(nChoice)).map( i => {
34 return Array.from(Array(this.nInput)).map( j => {
35 return Array.from(Array(nChoice)).map( k => {
36 return 0;
37 });
38 })
39 });
40 if (this.humanHistory.length > this.nInput)
41 this.humanHistory.splice(this.nInput);
42 },
43 // Play a move given current data (*after* human move: trigger onChange)
44 play: function() {
45 let candidates = [ ];
46 Array.from(Array(nChoice)).forEach( i => {
47 // Sum all weights from an activated input to this output
48 let sumWeights = this.weights[i].reduce( (acc,input,j) => {
49 if (this.humanHistory.length <= j)
50 return 0;
51 return input[ this.humanHistory[j] ];
52 }, 0 );
53 let currentValue = {
54 val: sumWeights,
55 index: i
56 };
57 if (candidates.length == 0 || sumWeights > candidates[0].val)
58 candidates = [ currentValue ];
59 else if (sumWeights == candidates[0].val)
60 candidates.push(currentValue);
61 });
62 // Pick a choice at random in maxValue (total random for first move)
63 let randIdx = Math.floor((Math.random() * candidates.length) + 1);
64 this.updateGameState(candidates[randIdx]);
65 },
66 updateGameState: function(move) {
67 let reward = rewards[move.val][this.humanMove]; //viewpoint of computer
68 this.gameState += reward;
69 this.updateWeights(reward, move.index);
70 },
71 updateWeights: function(reward, index) {
72 let delta = Math.sign(reward);
73 if (this.drawIsLost && reward == 0)
74 delta = -1;
75 this.weights[index].forEach( (input,i) => {
76 if (i < this.humanHistory.length)
77 input[ this.humanHistory[i] ] += delta;
78 });
79 this.postUpdate();
80 },
81 // Finalize weights update
82 postUpdate: function() {
83 // Re-center the weights
84 let sumAllWeights = this.weights.reduce( (a,output) => {
85 return a + output.reduce( (b,input) => {
86 return b + input.reduce( (c,choiceWeight) => {
87 return c + choiceWeight;
88 }, 0);
89 }, 0);
90 }, 0);
91 let meanWeight = sumAllWeights / (this.nInput * nChoice * nChoice);
92 this.weights.forEach( output => {
93 output.forEach( input => {
94 for (let i=0; i<input.length; i++)
95 input[i] -= meanWeight;
96 });
97 });
98 // Update human moves history
99 this.humanHistory.push(coup_adversaire);
100 this.humanHistory.shift();
101 },
102 },
103 };