Better hints on mousehover
[rpsls-bot.git] / js / rpsls.js
CommitLineData
d666e771 1const nChoice = 5; //fixed for this game
977c11c8 2
d666e771
BA
3const symbols = [ "Rock", "Lizard", "Spock", "Scissors", "Paper" ];
4
5// Same order as in symbols
6const messages = [
d90f571b
BA
7 [ "can't win vs.", "crushes", "is vaporized by", "cruches", "is covered by" ],
8 [ "is crushed by", "can't win vs.", "poisons", "is decapitated by", "eats" ],
9 [ "vaporizes", "is poisoned by", "can't win vs.", "smashes", "is disproved by" ],
10 [ "is crushed by", "decapitates", "is smashed by", "can't win vs.", "cuts" ],
11 [ "covers", "is eaten by", "disproves", "is cut by", "can't win vs." ],
d666e771
BA
12];
13
14// Rewards matrix, order as in symbols
977c11c8
BA
15const rewards = Array.from(Array(nChoice)).map( (e,i) => { //lines
16 return Array.from(Array(nChoice)).map( (f,j) => { //columns
17 // i against j: gain from i viewpoint
d666e771 18 if (j == (i+1) % nChoice || j == (i+3) % nChoice)
977c11c8 19 return 1; //I win :)
932d367f 20 else if (i != j)
977c11c8 21 return -1; //I lose :(
d666e771 22 return 0; //i==j
977c11c8
BA
23 });
24});
25
977c11c8
BA
26new Vue({
27 el: "#rpsls",
28 data: {
977c11c8
BA
29 nInput: 5, //default
30 humanHistory: [ ], //your nInput last moves, stored (oldest first)
d666e771
BA
31 humanMove: -1,
32 gameState: 0, //total points of the human
33 compMove: -1, //last move played by computer
977c11c8
BA
34 drawIsLost: false, //normal (or true: draw is considered loss)
35 rewards: [ ], //initialized at first human move with new nInput
36 weights: [ ], //same as above
d666e771
BA
37 symbols: symbols,
38 messages: messages,
977c11c8
BA
39 },
40 created: function() {
41 this.reinitialize();
42 },
43 methods: {
44 // Called on nInput change
45 reinitialize: function() {
46 // weights[i][j][k]: output i -- input j/choice k
47 this.weights = Array.from(Array(nChoice)).map( i => {
48 return Array.from(Array(this.nInput)).map( j => {
49 return Array.from(Array(nChoice)).map( k => {
50 return 0;
51 });
52 })
53 });
54 if (this.humanHistory.length > this.nInput)
55 this.humanHistory.splice(this.nInput);
56 },
d666e771
BA
57 // Play a move given current data (*after* human move)
58 play: function(humanMove) {
59 this.humanMove = humanMove;
977c11c8
BA
60 let candidates = [ ];
61 Array.from(Array(nChoice)).forEach( (e,i) => {
62 // Sum all weights from an activated input to this output
63 let sumWeights = this.weights[i].reduce( (acc,input,j) => {
64 if (this.humanHistory.length <= j)
65 return 0;
66 return input[ this.humanHistory[j] ];
67 }, 0 );
d666e771 68 let move = {
977c11c8 69 val: sumWeights,
d666e771 70 index: i,
977c11c8
BA
71 };
72 if (candidates.length == 0 || sumWeights > candidates[0].val)
d666e771 73 candidates = [move];
977c11c8 74 else if (sumWeights == candidates[0].val)
d666e771 75 candidates.push(move);
977c11c8
BA
76 });
77 // Pick a choice at random in maxValue (total random for first move)
d666e771
BA
78 let randIdx = Math.floor(Math.random() * candidates.length);
79 this.compMove = candidates[randIdx].index;
80 // Update game state
81 let reward = rewards[this.compMove][humanMove]; //viewpoint of computer
82 this.gameState -= reward;
83 this.updateWeights(reward);
84 // Update human moves history
85 this.humanHistory.push(humanMove);
86 if (this.humanHistory.length > this.nInput)
87 this.humanHistory.shift();
977c11c8 88 },
d666e771 89 updateWeights: function(reward) {
977c11c8
BA
90 let delta = Math.sign(reward);
91 if (this.drawIsLost && reward == 0)
92 delta = -1;
d666e771 93 this.weights[this.compMove].forEach( (input,i) => {
977c11c8
BA
94 if (i < this.humanHistory.length)
95 input[ this.humanHistory[i] ] += delta;
96 });
977c11c8
BA
97 // Re-center the weights
98 let sumAllWeights = this.weights.reduce( (a,output) => {
99 return a + output.reduce( (b,input) => {
100 return b + input.reduce( (c,choiceWeight) => {
101 return c + choiceWeight;
102 }, 0);
103 }, 0);
104 }, 0);
105 let meanWeight = sumAllWeights / (this.nInput * nChoice * nChoice);
106 this.weights.forEach( output => {
107 output.forEach( input => {
108 for (let i=0; i<input.length; i++)
109 input[i] -= meanWeight;
110 });
111 });
d666e771
BA
112 },
113 imgSource: function(symbol) {
114 return "img/" + symbol + ".png";
977c11c8 115 },
63a2a72c 116 },
977c11c8 117});