X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=js%2Findex.js;h=226aede5b7ae7535b2741d4973535ea46c4afe70;hb=HEAD;hp=1dce8b6790be62f102ed8d1bb94556b312ef8e99;hpb=48bee368a286e11f1be6a80779413a91feece55c;p=westcastle.git diff --git a/js/index.js b/js/index.js index 1dce8b6..226aede 100644 --- a/js/index.js +++ b/js/index.js @@ -76,10 +76,10 @@ new Vue({
- -
@@ -110,10 +110,10 @@ new Vue({
- - @@ -122,52 +122,142 @@ new Vue({
`, methods: { + // TODO: télécharger la ronde courante (faudrait aussi mémoriser les points...) + // --> je devrais séparer les components en plusieurs fichiers maintenant + cancelRound: function() { + this.scored.forEach( (s,i) => { + if (s) + { + // Cancel this table + this.currentIndex = i; //TODO: clumsy. functions should take "index" as argument + this.cancelScore(); + } + }); + this.currentIndex = -1; + this.doPairings(); + }, doPairings: function() { - // Simple case first: 4 by 4 + let rounds = JSON.parse(localStorage.getItem("rounds")) || []; + if (this.scored.some( s => { return s; })) + { + this.commitScores(); //TODO: temporary: shouldn't be here... (incremental commit) + rounds.push(this.tables); + localStorage.setItem("rounds", JSON.stringify(rounds)); + } + this.currentIndex = -1; //required if reset while scoring let tables = []; - let currentTable = []; - let ordering = _.shuffle(_.range(this.players.length)); - for (let i=0; i { return Object.assign({}, p, {index:i}); }) + .filter( p => { return p.available; }); + let repartition = _.times(Math.floor(activePlayers.length/4), _.constant(4)); + let remainder = activePlayers.length % 4; + if (remainder > 0) + repartition.push(remainder); + switch (remainder) { - if ( ! this.players[ordering[i]].available ) - continue; - if (currentTable.length >= 4) - { - tables.push(currentTable); - currentTable = []; - } - currentTable.push(ordering[i]); + case 1: + // Need 2 more + if (repartition.length-1 >= 2) + { + repartition[repartition.length-3] -- ; + repartition[repartition.length-2] -- ; + repartition[repartition.length-1] += 2; + } + break; + case 2: + // Need 1 more + if (repartition.length-1 >= 1) + { + repartition[repartition.length-2] -- ; + repartition[repartition.length-1] ++ ; + } + break; } - // Analyse remainder - this.unpaired = []; - if (currentTable.length != 0) + // 2) Shortcut for round 1: just spread at random + if (rounds.length == 0) { - if (currentTable.length < 3) - { - let missingPlayers = 3 - currentTable.length; - // Pick players from 'missingPlayers' random different tables, if possible - if (tables.length >= missingPlayers) + let currentTable = []; + let ordering = _.shuffle(_.range(activePlayers.length)); + let tableIndex = 0; + ordering.forEach( i => { + currentTable.push(activePlayers[i].index); + if (currentTable.length == repartition[tableIndex]) { - let tblNums = _.sample(_.range(tables.length), missingPlayers); - tblNums.forEach( num => { - currentTable.push(tables[num].pop()); + if (currentTable.length == 3) + currentTable.push(0); //add Toto + // flush + tables.push(currentTable); + currentTable = []; + tableIndex++; + } + }); + } + else + { + // General case after round 1: + // NOTE: alternative method, deterministic: player 1 never move, player 2 moves by 1, ...and so on + // --> but this leads to inferior pairings (e.g. 2 tables 8 players) + // ----- + // 2bis) Compute the "meeting" matrix: who played who and how many times + let meetMat = _.range(this.players.length).map( i => { + return _.times(this.players.length, _.constant(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=i+1; j<4; j++) + meetMat[t[i]][t[j]]++; + } + }); + }); + // 3) Fill tables by minimizing row sums of meetMat + const playersCount = activePlayers.length; + repartition.forEach( r => { + // Pick first player at random among active players, unless there is one unpaired guy + let firstPlayer = this.unpaired[0]; //can be undefined + if (!firstPlayer || activePlayers.length < playersCount) + { + let randIndex = _.sample( _.range(activePlayers.length) ); + firstPlayer = activePlayers[randIndex].index; + activePlayers.splice(randIndex, 1); + } + else + activePlayers.splice( activePlayers.findIndex( item => { return item.index == firstPlayer; }), 1 ); + let table = [ firstPlayer ]; + for (let i=1; i { + let count = 0; + let candidate = u.index; + table.forEach( p => { + count += meetMat[p][candidate]; + count += meetMat[candidate][p]; + }); + counts.push( {index:u.index, count:count } ); }); + counts.sort( (a,b) => { return a.count - b.count; }); + table.push(counts[0].index); + activePlayers.splice( activePlayers.findIndex( item => { return item.index == counts[0].index; }), 1 ); } - } - if (currentTable.length >= 3) - tables.push(currentTable); - else - this.unpaired = currentTable; + if (table.length == 3) + table.push(0); //add Todo + tables.push(table); + }); } - // Ensure that all tables have 4 players - tables.forEach( t => { - if (t.length < 4) - t.push(0); //index of "Toto", ghost player - }); + if (tables.length >= 1 && tables[tables.length-1].length < 3) + this.unpaired = tables.pop(); + else + this.unpaired = []; this.tables = tables; - this.sessions = tables.map( t => { return []; }); //empty sessions - this.scored = tables.map( t => { return false; }); //nothing scored yet - this.currentIndex = -1; //required if reset while scoring + this.resetScores(); + }, + resetScores: function() { + this.sessions = this.tables.map( t => { return []; }); //empty sessions + this.scored = this.tables.map( t => { return false; }); //nothing scored yet }, showScoreForm: function(table,index) { if (this.sessions[index].length == 0) @@ -217,7 +307,7 @@ new Vue({ Vue.set(this.scored, this.currentIndex, true); this.currentIndex = -1; }, - resetScore: function() { + cancelScore: function() { let pdts = this.getPdts(); for (let i=0; i<4; i++) { @@ -233,13 +323,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: `
-
+
{{ formattedTime }}
+
`, @@ -257,6 +351,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 +368,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 +379,8 @@ new Vue({ this.running = false; return; } + if (this.time == this.initialTime * 60) + new Audio("sounds/gong.mp3").play(); //gong at the beginning setTimeout(() => { if (this.running) this.time--; @@ -288,8 +389,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,10 +528,15 @@ 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; + this.commitScores(); //save players in memory }, }, });