| 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> </td> |
| 117 | <td> </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 | }); |