944a51b2a548b796465e981ffbc168f96a4863b9
[westcastle.git] / js / index.js
1 new Vue({
2 el: "#mahjong",
3 data: {
4 players: [], //array of objects, filled later
5 display: "players",
6 },
7 components: {
8 'my-players': {
9 props: ['players'],
10 template: `
11 <div id="players">
12 <div id="active">
13 <p>Présents</p>
14 <table class="list">
15 <tr v-for="p in sortedPlayers" v-if="p.available" @click="toggleAvailability(p.index)">
16 <td>{{ p.prenom }}</td>
17 <td>{{ p.nom }}</td>
18 </tr>
19 </table>
20 </div>
21 <div id="inactive">
22 <p>Absents</p>
23 <table class="list">
24 <tr v-for="p in sortedPlayers" v-if="!p.available" @click="toggleAvailability(p.index)">
25 <td>{{ p.prenom }}</td>
26 <td>{{ p.nom }}</td>
27 </tr>
28 </table>
29 </div>
30 </div>
31 `,
32 computed: {
33 sortedPlayers: function() {
34 return this.players
35 .map( (p,i) => { return Object.assign({}, p, {index: i}); })
36 .sort( (a,b) => {
37 return a.nom.localeCompare(b.nom);
38 });
39 },
40 },
41 methods: {
42 toggleAvailability: function(i) {
43 this.players[i].available = 1 - this.players[i].available;
44 this.$forceUpdate(); //TODO (Vue.set... ?!)
45 },
46 },
47 },
48 'my-ranking': {
49 props: ['players'],
50 data: function() {
51 return {
52 sortMethod: "score",
53 };
54 },
55 template: `
56 <div id="ranking">
57 <table class="ranking">
58 <tr class="title">
59 <th>Rang</th>
60 <th>Joueur</th>
61 <th @click="sortMethod='score'" class="scoring" :class="{active: sortMethod=='score'}">Score</th>
62 <th @click="sortMethod='pdt'" class="scoring" :class="{active: sortMethod=='pdt'}">PdT</th>
63 </tr>
64 <tr v-for="(p,i) in sortedPlayers">
65 <td>{{ i+1 }}</td>
66 <td>{{ p.prenom }} {{ p.nom }}</td>
67 <td>{{ p.score }}</td>
68 <td>{{ p.pdt }}</td>
69 </tr>
70 </table>
71 </div>
72 `,
73 computed: {
74 sortedPlayers: function() {
75 let sortFunc = this.sortMethod == "score"
76 ? this.sortByScore
77 : this.sortByPdt;
78 return this.players
79 .map( p => { return p; }) //to not alter original array
80 .sort(sortFunc);
81 },
82 },
83 methods: {
84 sortByScore: function(a,b) {
85 return b.score - a.score;
86 },
87 sortByPdt: function(a,b) {
88 return b.pdt - a.pdt;
89 },
90 },
91 },
92 'my-pairings': {
93 props: ['players'],
94 data: function() {
95 return {
96 unpaired: [],
97 tables: [], //array of arrays of players indices
98 scores: [], //scores for each table (3 or 4 players)
99 pdts: [], //"points de table" for each table (3 or 4 players)
100 currentIndex: -1, //table index for scoring
101 };
102 },
103 template: `
104 <div id="pairings">
105 <div v-show="currentIndex < 0">
106 <button class="block btn" @click="shuffle()">Appariement</button>
107 <div class="pairing" v-for="(table,index) in tables" :class="{scored: scores[index].length > 0}"
108 @click="showScoreForm(table,index)">
109 <p>Table {{ index+1 }}</p>
110 <table>
111 <tr v-for="(i,j) in table">
112 <td>{{ players[i].prenom }} {{ players[i].nom }}</td>
113 <td class="score"><span v-show="pdts[index].length > 0">{{ pdts[index][j] }}</span></td>
114 </tr>
115 <tr v-if="table.length < 4">
116 <td>&nbsp;</td>
117 <td>&nbsp;</td>
118 </tr>
119 </table>
120 </div>
121 <div v-if="unpaired.length>0" class="pairing unpaired">
122 <p>Exempts</p>
123 <div v-for="i in unpaired">
124 {{ players[i].prenom }} {{ players[i].nom }}
125 </div>
126 </div>
127 </div>
128 <div id="scoreInput" v-if="currentIndex >= 0">
129 <table>
130 <tr v-for="(index,i) in tables[currentIndex]">
131 <td>{{ players[tables[currentIndex][i]].prenom }} {{ players[tables[currentIndex][i]].nom }}</td>
132 <td><input type="text" v-model="pdts[currentIndex][i]" value="0"/></td>
133 </tr>
134 </table>
135 <div class="button-container">
136 <button class="btn" @click="setScore()">Enregistrer</button>
137 <button class="btn cancel" @click="resetScore()">Annuler</button>
138 </div>
139 </div>
140 </div>
141 `,
142 methods: {
143 doPairings: function() {
144 // Simple case first: 4 by 4
145 let tables = [];
146 let currentTable = [];
147 let ordering = _.shuffle(_.range(this.players.length)); //TODO: take scores into account?
148 for (let i=0; i<ordering.length; i++)
149 {
150 if ( ! this.players[ordering[i]].available )
151 continue;
152 if (currentTable.length >= 4)
153 {
154 tables.push(currentTable);
155 currentTable = [];
156 }
157 currentTable.push(ordering[i]);
158 }
159 // Analyse remainder
160 this.unpaired = [];
161 if (currentTable.length != 0)
162 {
163 if (currentTable.length < 3)
164 {
165 let missingPlayers = 3 - currentTable.length;
166 // Pick players from 'missingPlayers' random different tables, if possible
167 if (tables.length >= missingPlayers)
168 {
169 let tblNums = _.sample(_.range(tables.length), missingPlayers);
170 tblNums.forEach( num => {
171 currentTable.push(tables[num].pop());
172 });
173 }
174 }
175 if (currentTable.length >= 3)
176 tables.push(currentTable);
177 else
178 this.unpaired = currentTable;
179 }
180 this.tables = tables;
181 this.scores = tables.map( t => { return []; }); //empty scores
182 this.pdts = tables.map( t => { return []; }); //empty pdts
183 },
184 shuffle: function() {
185 this.doPairings();
186 },
187 showScoreForm: function(table,index) {
188 if (this.scores[index].length > 0)
189 return; //already scored
190 this.scores[index] = _.times(table.length, _.constant(0));
191 this.pdts[index] = _.times(table.length, _.constant(0));
192 this.currentIndex = index;
193 },
194 setScore: function() {
195 let sortedPdts = this.pdts[this.currentIndex]
196 .map( (s,i) => { return {value:s, index:i}; })
197 .sort( (a,b) => { return parseInt(b.value) - parseInt(a.value); });
198 let scores = [4, 2, 1, 0]; //TODO: biased for 3-players tables. TODO: ex-aequos ?!
199 for (let i=0; i<this.tables[this.currentIndex].length; i++)
200 {
201 this.players[this.tables[this.currentIndex][sortedPdts[i].index]].score += scores[i];
202 this.players[this.tables[this.currentIndex][i]].pdt += parseInt(this.pdts[this.currentIndex][i]);
203 }
204 this.currentIndex = -1;
205 this.writeScoreToDb();
206 },
207 resetScore: function() {
208 this.scores[this.currentIndex] = [];
209 this.pdts[this.currentIndex] = [];
210 this.currentIndex = -1;
211 },
212 writeScoreToDb: function()
213 {
214 let xhr = new XMLHttpRequest();
215 xhr.open("POST", "scripts/rw_players.php");
216 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
217 let orderedPlayers = this.players
218 .map( p => { return Object.assign({}, p); }) //deep (enough) copy
219 .sort( (a,b) => { return b.score - a.score; });
220 xhr.send("players="+encodeURIComponent(JSON.stringify(orderedPlayers)));
221 },
222 },
223 },
224 },
225 created: function() {
226 let xhr = new XMLHttpRequest();
227 let self = this;
228 xhr.onreadystatechange = function() {
229 if (this.readyState == 4 && this.status == 200)
230 {
231 let players = JSON.parse(xhr.responseText);
232 players.forEach( p => {
233 p.score = !!p.score ? parseInt(p.score) : 0;
234 p.pdt = !!p.pdt ? parseInt(p.pdt) : 0;
235 p.available = !!p.available ? p.available : 1; //use integer for fputcsv PHP func
236 });
237 self.players = players;
238 }
239 };
240 xhr.open("GET", "scripts/rw_players.php", true);
241 xhr.send(null);
242 },
243 });