save state
[westcastle.git] / js / index.js
index 4bffa63..b51d590 100644 (file)
@@ -76,10 +76,10 @@ new Vue({
                                <div id="pairings">
                                        <div v-show="currentIndex < 0">
                                                <div class="button-container-horizontal">
-                                                       <button class="btn validate" :class="{hide: tables.length==0}" @click="commitScores()" title="Valide l'état actuel des scores (cf. rubrique Classement) en mémoire. Cette action est nécessaire au moins une fois en fin de tournoi après toutes les parties, et est recommandée après chaque ronde">
+                                                       <button class="btn cancel" :class="{hide: tables.length==0}" @click="cancelRound()" title="Annule la ronde courante : tous les scores en cours seront perdus, et un nouveau tirage effectué. ATTENTION : action irréversible">
                                                                Valider
                                                        </button>
-                                                       <button id="doPairings" class="btn" :class="{cancel: tables.length>0}" @click="doPairings()" title="Répartit les joueurs actifs aléatoirement sur les tables">
+                                                       <button id="doPairings" class="btn" :class="{cancel: tables.length>0}" :disabled="scored.some( s => { return !s; })" @click="doPairings()" title="Répartit les joueurs actifs aléatoirement sur les tables">
                                                                Nouvelle ronde
                                                        </button>
                                                </div>
@@ -110,10 +110,10 @@ new Vue({
                                                        </tr>
                                                </table>
                                                <div class="button-container-horizontal">
-                                                       <button :class="{hide:scored[currentIndex]}" class="btn validate" @click="setScore()" title="Enregistre le score dans la base (la rubrique Classement est mise à jour)">
+                                                       <button :class="{hide:scored[currentIndex]}" class="btn validate" @click="setScore()" title="Enregistre le score dans la base">
                                                                Enregistrer
                                                        </button>
-                                                       <button :class="{hide:!scored[currentIndex]}" class="btn cancel" @click="resetScore()" title="Annule le score précédemment enregistré : l'action est visible dans la rubrique Classement">
+                                                       <button :class="{hide:!scored[currentIndex]}" class="btn cancel" @click="resetScore()" title="Annule le score précédemment enregistré">
                                                                Annuler
                                                        </button>
                                                        <button class="btn" @click="closeScoreForm()">Fermer</button>
@@ -122,7 +122,95 @@ new Vue({
                                </div>
                        `,
                        methods: {
+                               // TODO: télécharger la ronde courante
+                               // TODO: mémoriser les appariements passés pour éviter que les mêmes joueurs se rencontrent plusieurs fois
+                               // --> dans la base: tableau rounds, rounds[0] : {tables[0,1,...], chacune contenant 4 indices de joueurs; + sessions[0,1,...]}
+                               // --> devrait séparer les components en plusieurs fichiers...
+                               // cas à 5 joueurs : le joueur exempt doit tourner (c'est fait automatiquement en fait)
+                               cancelRound: function() {
+                                       this.scored.forEach( (s,i) => {
+                                               if (s)
+                                               {
+                                                       // Cancel this table
+                                                       this.currentIndex = i; //TODO: clumsy. funcions should take "index" as argument
+                                                       this.resetScore();
+                                               }
+                                       });
+                                       this.currentIndex = -1;
+                                       this.doPairings();
+                               },
                                doPairings: function() {
+                                       let rounds = JSON.parse(localStorage.getItem("rounds"));
+
+                                       if (this.scored.some( s => { return s; }))
+                                       {
+                                               this.commitScores(); //TODO: temporary: shouldn't be here... (incremental commit)
+                                               if (rounds === null)
+                                                       rounds = [];
+                                               rounds.push(this.tables);
+                                       }
+
+                                       // 1) Compute the "meeting" matrix: who played who and how many times
+                                       let meetMat = _.range(this.players.length).map( i => {
+                                               _.range(this.players.length).map( j => {
+                                                       return 0;
+                                               });
+                                       });
+                                       rounds.forEach( r => { //for each round
+                                               r.forEach( t => { //for each table within round
+                                                       for (let i=0; i<4; i++) //TODO: these loops are ugly
+                                                       {
+                                                               for (let j=0; j<4; j++)
+                                                               {
+                                                                       if (j!=i)
+                                                                               meetMat[i][j]++;
+                                                               }
+                                                       }
+                                               });
+                                       });
+
+                                       // 2) Pre-compute tables repartition (in numbers): depends on active players count % 4
+                                       let activePlayers = this.players
+                                               .map( (p,i) => { return Object.Assign({}, p, {index:i}); })
+                                               .filter( p => { return p.available; });
+                                       let repartition = _.times(Math.floor(activePlayers.length/4), _.constant(4));
+                                       switch (activePlayers.length % 4)
+                                       {
+                                               case 1:
+                                                       // Need 2 more
+                                                       if (repartition.length-1 >= 2)
+                                                       {
+                                                               repartition[0]--;
+                                                               repartition[1]--;
+                                                               repartition[repartition.length-1] += 2;
+                                                       }
+                                                       break;
+                                               case 2:
+                                                       // Need 1 more
+                                                       if (repartition.length-1 >= 1)
+                                                       {
+                                                               repartition[0]--;
+                                                               repartition[repartition.length-1]++;
+                                                       }
+                                                       break;
+                                       }
+
+                                       // 3) Sort people by total games played (increasing) - naturally solve the potential unpaired case
+                                       let totalGames = _.range(this.players.length).map( i => { return 0; });
+                                       rounds.forEach( r => {
+                                               r.forEach(t => {
+                                                       t.forEach( p => {
+                                                               totalGames[p]++;
+                                                       })
+                                               })
+                                       });
+                                       let sortedPlayers = activePlayers
+                                               .map( (p,i) => { return Object.Assign({}, p, {games:totalGames[p.index]}); })
+                                               .sort( (a,b) => { return a.games - b.games; });
+
+                                       // 4) Affect people on tables, following total games sorted order (with random sampling on ex-aequos)
+                                       // --> et surtout en minimisant la somme des rencontres précédentes (ci-dessus : cas particulier rare à peu de joueurs)
+//TODO
                                        // Simple case first: 4 by 4
                                        let tables = [];
                                        let currentTable = [];
@@ -233,13 +321,17 @@ new Vue({
                                return {
                                        time: 0, //remaining time, in seconds
                                        running: false,
+                                       initialTime: 90, //1h30, in minutes
+                                       setter: false,
+                                       setterTime: 0, //to input new initial time
                                };
                        },
                        template: `
                                <div id="timer" :style="{lineHeight: divHeight + 'px', fontSize: 0.66*divHeight + 'px', width: divWidth + 'px', height: divHeight + 'px'}">
-                                       <div @click.left="pauseResume()" @click.right.prevent="reset()" :class="{timeout:time==0}">
+                                       <div v-show="!setter" @click.left="pauseResume()" @click.right.prevent="reset()" :class="{timeout:time==0}">
                                                {{ formattedTime }}
                                        </div>
+                                       <input type="text" autofocus id="setter" @keyup.enter="setTime()" @keyup.esc="setter=false" v-show="setter" v-model="setterTime"></input>
                                        <img class="close-cross" src="img/cross.svg" @click="$emit('clockover')"/>
                                </div>
                        `,
@@ -257,6 +349,11 @@ new Vue({
                                },
                        },
                        methods: {
+                               setTime: function() {
+                                       this.initialTime = this.setterTime;
+                                       this.setter = false;
+                                       this.reset();
+                               },
                                padToZero: function(a) {
                                        if (a < 10)
                                                return "0" + a;
@@ -269,7 +366,7 @@ new Vue({
                                },
                                reset: function(e) {
                                        this.running = false;
-                                       this.time = 5400; //1:30
+                                       this.time = this.initialTime * 60;
                                },
                                start: function() {
                                        if (!this.running)
@@ -280,6 +377,8 @@ new Vue({
                                                this.running = false;
                                                return;
                                        }
+                                       if (this.time == this.initialTime)
+                                               new Audio("sounds/gong.mp3").play(); //gong at the beginning
                                        setTimeout(() => {
                                                if (this.running)
                                                        this.time--;
@@ -288,8 +387,27 @@ new Vue({
                                },
                        },
                        created: function() {
+                               this.setterTime = this.initialTime;
                                this.reset();
                        },
+                       mounted: function() {
+                               let timer = document.getElementById("timer");
+                               let keyDict = {
+                                       32: () => { this.setter = true; }, //Space
+                                       27: () => { this.setter = false; }, //Esc
+                               };
+                               document.addEventListener("keyup", e => {
+                                       if (timer.style.display !== "none")
+                                       {
+                                               let func = keyDict[e.keyCode];
+                                               if (!!func)
+                                               {
+                                                       e.preventDefault();
+                                                       func();
+                                               }
+                                       }
+                               });
+                       },
                },
                'my-ranking': {
                        props: ['players','sortByScore','commitScores'],
@@ -408,7 +526,11 @@ new Vue({
                                .filter( line => { return line.length > 0; }) //remove empty lines
                                .map( line => {
                                        let parts = line.split(",");
-                                       return {prenom: parts[0], nom: parts[1], pdt: 0, session:0, available: 1};
+                                       let p = { prenom: parts[0], nom: parts[1] };
+                                       p.pdt = parts.length > 2 ? parseFloat(parts[2]) : 0;
+                                       p.session = parts.length > 3 ? parseInt(parts[3]) : 0;
+                                       p.available = parts.length > 4 ? parts[4] : 1;
+                                       return p;
                                });
                        this.addToto(players);
                        this.players = players;