From: Benjamin Auder Date: Thu, 28 Dec 2017 13:55:11 +0000 (+0100) Subject: last requirements implemented; still a 'restore' bug to fix X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/css/static/%3C?a=commitdiff_plain;h=1369a09d4b3a4d3c3d19b68197a311fbb7ec97f4;p=westcastle.git last requirements implemented; still a 'restore' bug to fix --- diff --git a/README.md b/README.md index fe69cc4..b9df387 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ ## Prérequis -php (assez récent) +php >= 5.4 ## Ajustement du fichier de données 1. Renommer joueurs.csv.dist en joueurs.csv - 2. éditer joueurs.csv (ajout de joueurs, édition, suppression...). Format en lignes : prénom,nom[,score,pdt,présent] + 2. éditer joueurs.csv (ajout de joueurs, édition, suppression...). Format en lignes : prénom,nom[,pdt,session,présent] -pdt = "points de table". score,pdt,présent : optionnels (défaut 0, 0, 1) +pdt, session, présent : optionnels (par défaut resp. 0, 0, 1). + +pdt = "points de table" ; session = "mini-points" ## Lancement de l'aplication @@ -18,6 +20,8 @@ pdt = "points de table". score,pdt,présent : optionnels (défaut 0, 1. Cliquer sur les joueurs absents dans l'onglet "joueurs" 2. Aller dans la section "appariements" et cliquer sur le bouton en haut - 3. À la fin d'une ronde, cliquer sur chaque table pour indiquer les points. Pour lancer la ronde suivante, revenir en 1) + 3. À la fin d'une ronde, cliquer sur chaque table pour indiquer les (mini-)points. Pour lancer la ronde suivante, revenir en 1. + +Le classement est mis à jour dans la rubrique correspondante et dans joueurs.csv. Il peut être réinitialisé (bouton en haut). -Le classement est mis à jour dans la rubrique correspondante et dans joueurs.csv +Après chaque opération sur les points, en cas d'erreur le bouton "Restaurer" en haut à droite annule la dernière opération. diff --git a/css/index.css b/css/index.css index d92ca1c..9604004 100644 --- a/css/index.css +++ b/css/index.css @@ -65,6 +65,43 @@ table th { font-weight: bold; } +.left, .right { + float: left; + width: 50%; +} + +.button-container-horizontal, .button-container-vertical { + margin-top: 30px; + text-align: center; +} +.button-container-vertical { + float: left; +} +/* All children except first: margin: 30px */ +.button-container-horizontal .btn:not(:first-child) { + margin-right: 30px; +} +.button-container-vertical .btn { + display: block; + margin-left: 30px; +} +.button-container-vertical .btn:not(:first-child) { + margin-top: 15px; +} + +button.validate { + background-image: linear-gradient(to bottom, #6fa162, #40992e); +} +button.validate:hover { + background-image: linear-gradient(to bottom, #37cc4e, #40c24f); +} +button.cancel { + background-image: linear-gradient(to bottom, #d93470, #b82b47); +} +button.cancel:hover { + background-image: linear-gradient(to bottom, #fc433c, #d93434); +} + /* players div */ #players { @@ -77,11 +114,6 @@ table th { text-align: center; } -#active, #inactive { - float: left; - width: 50%; -} - #inactive table { opacity: 0.6; } @@ -119,6 +151,7 @@ table.list tr:not(.title):hover, table.ranking tr:not(.title):nth-child(even):ho #ranking { margin-bottom: 15px; + overflow: auto; } table.ranking { @@ -126,6 +159,8 @@ table.ranking { width: 500px; margin: 0 auto; font-size: 1.1rem; + display: block; + float: left; } table.ranking td @@ -150,22 +185,19 @@ table.ranking tr:not(.title):hover, table.ranking tr:not(.title):nth-child(even) /* pairings div */ -button.block { - display: block; - margin: 30px auto; +.warning { + color: red; + margin-top: 25px; } -.button-container { - margin-top: 30px; - text-align: center; +span.link { + color: darkblue; + cursor: pointer; } -button.cancel { - margin-left: 30px; - background-image: linear-gradient(to bottom, #d93470, #b82b47); -} -button.cancel:hover { - background-image: linear-gradient(to bottom, #fc433c, #d93434); +button.block { + display: block; + margin: 25px auto; } .toto { @@ -194,9 +226,6 @@ td.score { .unpaired { cursor: default; } -.scored { - cursor: default; -} .pairing > table { font-size: 1.1rem; diff --git a/index.html b/index.html index 1360c13..cfbee36 100644 --- a/index.html +++ b/index.html @@ -19,8 +19,8 @@
- - + +
diff --git a/js/index.js b/js/index.js index 649e44c..a4a0dc7 100644 --- a/js/index.js +++ b/js/index.js @@ -9,7 +9,7 @@ new Vue({ props: ['players'], template: `
-
+

Présents

@@ -18,7 +18,7 @@ new Vue({
-
+

Absents

@@ -46,7 +46,7 @@ new Vue({ }, }, 'my-ranking': { - props: ['players','sortByScore','rankPeople'], + props: ['players','sortByScore','writeScoreToDb'], template: `
@@ -63,6 +63,10 @@ new Vue({
{{ p.session }}
+
+ + +
`, computed: { @@ -80,23 +84,65 @@ new Vue({ return res; }, }, + methods: { + rankPeople: function() { + return this.players + .slice(1) //discard Toto + .map( p => { return Object.assign({}, p); }) //to not alter original array + .sort(this.sortByScore); + }, + resetPlayers: function() { + this.players + .slice(1) //discard Toto + .forEach( p => { + p.pdt = 0; + p.session = 0; + p.available = 1; + }); + this.writeScoreToDb(); + document.getElementById("runPairing").click(); //TODO: hack... + }, + restoreLast: function() { + let xhr = new XMLHttpRequest(); + let self = this; + xhr.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) + { + let players = JSON.parse(xhr.responseText); + if (players.length > 0) + { + players.unshift({ //add ghost 4th player for 3-players tables + prenom: "Toto", + nom: "", + pdt: 0, + session: 0, + available: 0, + }); + self.players = players; + } + } + }; + xhr.open("GET", "scripts/rw_players.php?restore=1", true); + xhr.send(null); + }, + }, }, 'my-pairings': { - props: ['players','sortByScore'], + props: ['players','writeScoreToDb'], data: function() { return { unpaired: [], tables: [], //array of arrays of players indices - pdts: [], //"points de table" for each table sessions: [], //"mini-points" for each table currentIndex: -1, //table index for scoring + scored: [], //boolean for each table index }; }, template: `
- -
Nouvelle ronde +

Table {{ index+1 }}

@@ -122,9 +168,15 @@ new Vue({
-
- - +
+ + +
+
+ Attention: un score a déjà été enregistré. + Les points indiqués ici s'ajouteront : il faut d'abord + restaurer l'état précédent. + Si c'est déjà fait, ignorer ce message :)
@@ -173,48 +225,43 @@ new Vue({ t.push(0); //index of "Toto", ghost player }); this.tables = tables; - this.pdts = tables.map( t => { return []; }); //empty pdts this.sessions = tables.map( t => { return []; }); //empty sessions - }, - shuffle: function() { - this.doPairings(); + this.scored = tables.map( t => { return false; }); //nothing scored yet + this.currentIndex = -1; //required if reset while scoring }, showScoreForm: function(table,index) { - if (this.pdts[index].length > 0) - return; //already scored - this.pdts[index] = _.times(table.length, _.constant(0)); - this.sessions[index] = _.times(table.length, _.constant(0)); + if (this.sessions[index].length == 0) + this.sessions[index] = _.times(table.length, _.constant(0)); this.currentIndex = index; }, setScore: function() { let sortedSessions = this.sessions[this.currentIndex] .map( (s,i) => { return {value:s, index:i}; }) .sort( (a,b) => { return parseInt(b.value) - parseInt(a.value); }); - let pdts = [4, 2, 1, 0]; //TODO: ex-aequos ?! - for (let i=0; i sortedSessions[i+1].value) + { + let pdt = curSum / curCount; + for (let j=start; j<=i; j++) + this.players[this.tables[this.currentIndex][sortedSessions[j].index]].pdt += pdt; + curSum = 0; + curCount = 0; + start = i+1; + } + // Update sessions: this.players[this.tables[this.currentIndex][i]].session += parseInt(this.sessions[this.currentIndex][i]); } + this.scored[this.currentIndex] = true; this.currentIndex = -1; this.writeScoreToDb(); }, - resetScore: function() { - this.pdts[this.currentIndex] = []; - this.sessions[this.currentIndex] = []; - this.currentIndex = -1; - }, - writeScoreToDb: function() - { - let xhr = new XMLHttpRequest(); - xhr.open("POST", "scripts/rw_players.php"); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - let orderedPlayers = this.players - .slice(1) //discard Toto - .map( p => { return Object.assign({}, p); }) //deep (enough) copy - .sort(this.sortByScore); - xhr.send("players="+encodeURIComponent(JSON.stringify(orderedPlayers))); - }, }, }, }, @@ -226,7 +273,7 @@ new Vue({ { let players = JSON.parse(xhr.responseText); players.forEach( p => { - p.pdt = !!p.pdt ? parseInt(p.pdt) : 0; + p.pdt = !!p.pdt ? parseFloat(p.pdt) : 0; p.session = !!p.session ? parseInt(p.session) : 0; p.available = !!p.available ? p.available : 1; //use integer for fputcsv PHP func }); @@ -244,14 +291,19 @@ new Vue({ xhr.send(null); }, methods: { - rankPeople: function() { - return this.players - .slice(1) //discard Toto - .map( p => { return Object.assign({}, p); }) //to not alter original array - .sort(this.sortByScore); - }, + // Used both in ranking and pairings: sortByScore: function(a,b) { return b.pdt - a.pdt + (Math.atan(b.session - a.session) / (Math.PI/2)) / 2; }, + writeScoreToDb: function() { + let xhr = new XMLHttpRequest(); + xhr.open("POST", "scripts/rw_players.php"); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + let orderedPlayers = this.players + .slice(1) //discard Toto + .map( p => { return Object.assign({}, p); }) //deep (enough) copy + .sort(this.sortByScore); + xhr.send("players="+encodeURIComponent(JSON.stringify(orderedPlayers))); + }, }, }); diff --git a/scripts/rw_players.php b/scripts/rw_players.php index b5bdabc..de63ad6 100644 --- a/scripts/rw_players.php +++ b/scripts/rw_players.php @@ -2,6 +2,12 @@ if (!isset($_POST["players"])) { + if (isset($_GET["restore"]) && $_GET["restore"]) + { + // Restore backup + if (!rename("../joueurs.csv.bak", "../joueurs.csv")) + exit("[]"); + } // Retrieve all players $handle = fopen("../joueurs.csv", "r"); $players = []; @@ -23,6 +29,7 @@ if (!isset($_POST["players"])) } else { + copy("../joueurs.csv", "../joueurs.csv.bak"); //backup current checkpoint // Write header + all players $handle = fopen("../joueurs.csv", "w"); fputcsv($handle, ["prenom","nom","pdt","session","present"]);