diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/court.mjs | 53 | ||||
| -rw-r--r-- | src/game.mjs | 186 | ||||
| -rw-r--r-- | src/guest.mjs | 69 | ||||
| -rw-r--r-- | src/main.js | 20 | ||||
| -rw-r--r-- | src/player.mjs | 150 | ||||
| -rw-r--r-- | src/style.css | 12 | ||||
| -rw-r--r-- | src/timer.js | 88 | ||||
| -rw-r--r-- | src/utils.mjs | 51 |
8 files changed, 629 insertions, 0 deletions
diff --git a/src/court.mjs b/src/court.mjs new file mode 100644 index 0000000..713c488 --- /dev/null +++ b/src/court.mjs @@ -0,0 +1,53 @@ +import { Manager } from "./utils.mjs"; + +class Court { + constructor(id, status) { + this.id = id; + this.status = typeof status !== "undefined" ? status : true; + this.name = "Court " + (id + 1); + } + toggle() { + this.status = !this.status; + this.print(); + } + print() { + let status = this.status ? "active" : "inactive"; + console.log(this.name + " is currently " + status); + } + render() { + let color = this.status ? "is-success" : "is-danger"; + let type = `class="button is-medium ${color}"`; + return `<button id=${"court_" + this.id} ${type}>${this.name}</button>`; + } +} + +export class CourtManager extends Manager { + maxCourtCount = 12; + + constructor() { + super("court", []); + console.log("Loading court data."); + let data = JSON.parse(localStorage.getItem(this.description)); + if (data === null || data.length != this.maxCourtCount) { + console.log("Court data unavailable: " + data); + this.generate(); + return; + } + for (let i = 0; i < this.maxCourtCount; i++) { + this.listData.push(new Court(data[i].id, data[i].status)); + } + console.log("Loaded " + this.listData.length + " courts."); + this.render(); + this.addBehaviour(); + } + generate() { + console.log("Generating court data."); + for (let i = 0; i < this.maxCourtCount; i++) { + this.listData.push(new Court(i)); + } + console.log("Generated data for " + this.listData.length + " courts"); + this.store(); + this.render(); + this.addBehaviour(); + } +} diff --git a/src/game.mjs b/src/game.mjs new file mode 100644 index 0000000..c8931ac --- /dev/null +++ b/src/game.mjs @@ -0,0 +1,186 @@ +import { Notyf } from 'notyf'; +import { Manager } from "./utils.mjs"; +import { CourtManager } from "./court.mjs"; +import { PlayerManager } from "./player.mjs"; + +class Team { + constructor(playerOne, playerTwo) { + this.playerOne = playerOne; + this.playerTwo = playerTwo; + } + print() { + console.log(this.playerOne + " and " + this.playerTwo); + } + render() { + let p1 = `<td style="width: 23%; text-align: right;">${this.playerOne}</td>`; + let p2 = `<td style="width: 23%; text-align: right;">${this.playerTwo}</td>`; + return p1 + p2; + } +} + +class Game { + constructor(id, teamOne, teamTwo, status) { + this.id = id; + this.teamOne = teamOne; + this.teamTwo = teamTwo; + this.status = typeof status !== "undefined" ? status : false; + } + print() { + let teamOne = this.teamOne.playerOne + " and " + this.teamOne.playerTwo; + let teamTwo = this.teamTwo.playerOne + " and " + this.teamTwo.playerTwo; + console.log(teamOne + " is playing against " + teamTwo); + } + render() { + let blockedState = this.teamOne.playerOne === "RESERVED"; + let type = blockedState ? `class="is-danger is-dark"` : ``; + return `<tr ${type}><td style="width: 8%; text-align: center;">${ + this.id + 1 + }</td>${this.teamOne.render()}${this.teamTwo.render()}</tr>`; + } +} + +function generateDummyGames(courts, description) { + let team = new Team(description, description); + let games = []; + for (const court of courts) { + let game = new Game(court.id, team, team); + games.push(game); + } + return games; +} + +export class GameManager extends Manager { + courts = new CourtManager(); + players = new PlayerManager(); + + constructor() { + super("game", []); + this.round = Number(localStorage.getItem("round")) || 0; + console.log("Loading game data."); + let data = JSON.parse(localStorage.getItem(this.description)); + if (data === null || data.length === 0) { + console.log("Game data unavailable: " + data); + this.generate(); + return; + } + for (let i = 0; i < data.length; i++) { + let id = Number(data[i].id); + let teamOne = new Team( + data[i].teamOne.playerOne, + data[i].teamOne.playerTwo + ); + let teamTwo = new Team( + data[i].teamTwo.playerOne, + data[i].teamTwo.playerTwo + ); + let status = Boolean(data[i].status); + this.listData.push(new Game(id, teamOne, teamTwo, status)); + } + console.log("Loaded " + this.listData.length + " games."); + this.render(); + } + isConfirmed() { + return this.listData[0].status; + } + generate() { + console.log("Generating the dummy round."); + this.listData = generateDummyGames(this.courts.listData, "---"); + console.log("Loaded " + this.listData.length + " dummy round."); + for (let i = 0; i < this.listData.length; i++) { + this.listData[i].status = true; + } + console.log(this.listData); + this.store(); + this.render(); + } + addBehaviour() { + let description = this.isConfirmed() ? "Round " + this.round : "Proposal"; + document.getElementById("game_description").innerHTML = description; + } + getBlockedGames() { + const inactiveCourts = this.courts.inactive(); + console.log("Total reserved courts are " + inactiveCourts.length + "."); + return generateDummyGames(inactiveCourts, "RESERVED"); + } + inactive(courts) { + return generateDummyGames(courts, "---"); + } + active(courts, players) { + let games = []; + for (let index = 0; index < courts.length; index++) { + const startIndex = 4 * index; + const endIndex = startIndex + 4; + const courtPlayers = players.slice(startIndex, endIndex); + const team_1 = new Team(courtPlayers[0].name, courtPlayers[3].name); + const team_2 = new Team(courtPlayers[1].name, courtPlayers[2].name); + const game = new Game(courts[index].id, team_1, team_2); + game.print(); + games.push(game); + } + return games; + } + propose() { + console.log("Proposing new games!"); + let possibleMatches = getMatchCount( + this.players.listData, + this.courts.listData + ); + console.log("Total possible matches are " + possibleMatches + "."); + this.listData = this.getBlockedGames(); + const activeCourts = this.courts.active(); + const unusedCourts = activeCourts.slice(possibleMatches + 1); + this.listData = this.listData.concat(this.inactive(unusedCourts)); + const usedCourts = activeCourts.slice(0, possibleMatches); + let requiredPlayerCount = possibleMatches * 4; + const selectedPlayers = this.players.propose(requiredPlayerCount); + console.log("The matches are:"); + this.listData = this.listData.concat( + this.active(usedCourts, selectedPlayers) + ); + this.listData.sort((a, b) => a.id - b.id); + this.render(); + } + confirm() { + if (this.isConfirmed()) { + const notification = new Notyf(); + notification.error("Create a proposal first!"); + return; + } + console.log("Confirm the proposal for round " + this.round + "."); + this.round += 1; + localStorage.setItem("round", this.round); + for (let i = 0; i < this.listData.length; i++) { + this.listData[i].status = true; + } + // Need to update the number of games played here + this.store(); + this.render(); + document.getElementById("timer-start").click(); + const notification = new Notyf(); + notification.success("Round confirmed!"); + } + reset() { + localStorage.removeItem("round"); + localStorage.removeItem(this.description); + localStorage.removeItem(this.courts.description); + document.getElementById("timer-stop").click(); + window.location.reload(); + const notification = new Notyf(); + notification.error("Session has been reset!"); + } +} + +function getMatchCount(players, courts) { + courts = courts.filter(function (c) { + return c.status; + }); + console.log("There are a total of " + courts.length + " courts available."); + console.log(players); + players = players.filter(function (p) { + return p.status; + }); + console.log("There are a total of " + players.length + " players available."); + var possibleMatches = Math.floor(players.length / 4); + possibleMatches = Math.min(possibleMatches, courts.length); + return possibleMatches; +} diff --git a/src/guest.mjs b/src/guest.mjs new file mode 100644 index 0000000..ba9e8cc --- /dev/null +++ b/src/guest.mjs @@ -0,0 +1,69 @@ +import { Manager } from "./utils.mjs"; + +export class Guest { + constructor(id, name, level, status, totalPlayed) { + this.id = id; + this.status = typeof status !== "undefined" ? status : false; + this.name = name; + this.level = typeof level !== "undefined" ? level : 1; + this.totalPlayed = typeof totalPlayed !== "undefined" ? status : 0; + } + toggle() { + this.status = !this.status; + this.print(); + } + print() { + let status = this.status ? "active" : "inactive"; + console.log(this.name + " is currently " + status); + } + render() { + let color = this.status ? "is-success" : "is-danger"; + let type = `class="button is-medium ${color}"`; + return `<button id=${"regular_" + this.id} ${type}>${this.name}</button>`; + } +} + +export class GuestManager extends Manager { + constructor() { + super("guest", []); + console.log("Loading guest data."); + let data = JSON.parse(localStorage.getItem(this.description)); + if (data === null || data.length === 0) { + console.log("Guest data unavailable: " + data); + this.generate(); + return; + } + for (let i = 0; i < data.length; i++) { + let id = Number(data[i].id); + let name = data[i].name; + let level = data[i].level; + let status = data[i].status; + let totalPlayed = data[i].totalPlayed; + this.listData.push(new Player(id, name, level, status, totalPlayed)); + } + console.log("Loaded " + this.listData.length + " players."); + this.render(); + } + generate() { + console.log("Generating guest data."); + let maxPlayerCount = 12; + for (let i = 0; i < maxPlayerCount; i++) { + let level = i < 3 ? 1 : ( i < 6 ? 3 : 7); + let p = new Player(i, "Player " + i, level, false, 0); + this.listData.push(p); + } + console.log("Generated data for " + this.listData.length + " guest"); + this.store(); + this.render(); + } + active(factor) { + factor = typeof factor !== "undefined" ? factor : 1; + let data = super.active(); + console.log("Normalize skill by a factor of " + factor + "."); + let players = data.map(function (p) { + p.skill = Math.ceil(p.skill / factor); + return p; + }); + return players; + } +}
\ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..91ac386 --- /dev/null +++ b/src/main.js @@ -0,0 +1,20 @@ +import 'notyf/notyf.min.css' +import 'bulma/css/bulma.css' +import '@fortawesome/fontawesome-free/css/all.css' +import './style.css' + +import { GameManager } from "./game.mjs"; + +// Court and player handling +let g = new GameManager(); + +// Game handling +document.getElementById("propose").addEventListener("click", () => { + g.propose(); +}); +document.getElementById("confirm").addEventListener("click", () => { + g.confirm(); +}); +document.getElementById("reset").addEventListener("click", () => { + g.reset(); +}); diff --git a/src/player.mjs b/src/player.mjs new file mode 100644 index 0000000..adf874d --- /dev/null +++ b/src/player.mjs @@ -0,0 +1,150 @@ +import { GuestManager } from "./guest.mjs"; +import { shuffle, Manager } from "./utils.mjs"; + +class Player { + constructor(id, name, level, status, totalPlayed) { + this.id = id; + this.status = typeof status !== "undefined" ? status : false; + this.name = name; + this.level = typeof level !== "undefined" ? level : 1; + this.totalPlayed = typeof totalPlayed !== "undefined" ? status : 0; + } + toggle() { + this.status = !this.status; + this.print(); + } + print() { + let status = this.status ? "active" : "inactive"; + console.log(this.name + " is currently " + status); + } + render() { + let color = this.status ? "is-success" : "is-danger"; + let type = `class="button is-medium ${color}"`; + return `<button id=${"regular_" + this.id} ${type}>${this.name}</button>`; + } +} + +function randomLevel() { + const max_level = 10; + return Math.floor(Math.random() * max_level + 1); +} + +function randomStatus() { + return Math.random() < 0.5; +} + +function parseLineToPlayer(data) { + var info = data.split(","); + var id = Number(info[0].trim()); + var name = info[1].trim() + " " + info[2].trim(); + var level = Number(info[3].trim()); + return new Player(id, name, level, true, 0); +} + +export class PlayerManager extends Manager { + // g = new GuestManager(); + + constructor() { + super("regular", []); + console.log("Loading player data."); + let data = JSON.parse(localStorage.getItem(this.description)); + if (data === null || data.length === 0) { + console.log("Player data unavailable: " + data); + this.generate(); + return; + } + for (let i = 0; i < data.length; i++) { + let id = Number(data[i].id); + let name = data[i].name; + let level = data[i].level; + let status = data[i].status; + let totalPlayed = data[i].totalPlayed; + this.listData.push(new Player(id, name, level, status, totalPlayed)); + } + console.log("Loaded " + this.listData.length + " players."); + this.render(); + this.addBehaviour(); + addLoadBehaviour(this); + } + generate() { + console.log("Generating player data."); + let maxPlayerCount = 71; + for (let i = 0; i < maxPlayerCount; i++) { + let p = new Player(i, "Player " + i, randomLevel(), randomStatus(), 0); + this.listData.push(p); + } + console.log("Generated data for " + this.listData.length + " players"); + this.store(); + this.render(); + } + import(data) { + var lines = data.split("\n"); + var players = []; + for (var i = 1; i < lines.length; i++) { + if (lines[i] == undefined || lines[i].trim() == "") { + continue; + } + players.push(parseLineToPlayer(lines[i])); + } + console.log("Loaded " + players.length + " Players"); + for (var i = 0; i < players.length; i++) { + players[i].print(); + } + this.listData = players; + this.store(); + this.render(); + } + active(factor) { + factor = typeof factor !== "undefined" ? factor : 1; + let data = super.active(); + console.log("Normalize skill by a factor of " + factor + "."); + let players = data.map(function (p) { + p.skill = Math.ceil(p.skill / factor); + return p; + }); + return players; + } + propose(count) { + console.log("Required players are " + count); + let factor = Number(document.getElementById("skill").value); + console.log("Skill normalized by a factor of " + factor + "."); + const activePlayers = this.active(factor); + console.log("Active players are:"); + console.log( + activePlayers + .map((i) => { + return i.name; + }) + .join(",") + ); + const shuffledPlayers = shuffle(activePlayers); + shuffledPlayers.sort((a, b) => a.totalPlayed - b.totalPlayed); + const selectedPlayers = shuffledPlayers.slice(0, count); + selectedPlayers.sort((a, b) => a.level - b.level); + console.log("Selected players are:"); + console.log( + selectedPlayers + .map((i) => { + return i.name; + }) + .join(",") + ); + return selectedPlayers; + } +} + +// TODO Add load behaviour to constructor +export function addLoadBehaviour(p) { + const f = document.querySelector("#player-load input[type=file]"); + f.addEventListener("change", () => { + const file = f.files[0]; + let importData; + console.log("Loading data from " + file.name); + const reader = new FileReader(); + reader.onload = function (e) { + importData = reader.result; + p.import(importData); + }; + reader.readAsText(file); + }); +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..8e3b713 --- /dev/null +++ b/src/style.css @@ -0,0 +1,12 @@ +.button-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 15px; +} +#timer-display { + text-align: center; + width: fit-content; +} +table { + font-size: 1.5em; +}
\ No newline at end of file diff --git a/src/timer.js b/src/timer.js new file mode 100644 index 0000000..09189c0 --- /dev/null +++ b/src/timer.js @@ -0,0 +1,88 @@ +let minsPerRound = Number(sessionStorage.getItem("minsPerRound")) || 12; +let timerRunningFlag = Number(sessionStorage.getItem("timerRunningFlag")) || 0; +let deadline = + Number(sessionStorage.getItem("deadline")) || new Date().getTime(); +let clock = 0; + +function setTimer(m, s) { + let timer = document.getElementById("timer-display"); + m = m < 10 ? "0" + m : m; + s = s < 10 ? "0" + s : s; + timer.value = `${m}:${s}`; +} + +function setSessionData() { + sessionStorage.setItem("deadline", deadline); + sessionStorage.setItem("minsPerRound", minsPerRound); + sessionStorage.setItem("timerRunningFlag", timerRunningFlag); +} + +function resetTimer() { + clearInterval(clock); + let mins = Math.floor(minsPerRound); + let secs = (minsPerRound - mins) * 60; + setTimer(mins, secs); + timerRunningFlag = 0; + setSessionData(); +} + +function updateTimer(milliseconds) { + const m = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + const s = Math.floor((milliseconds % (1000 * 60)) / 1000); + setTimer(m, s); +} + +function refreshTimer() { + const now = new Date().getTime(); + const millisecondsToDeadline = deadline - now; + if (millisecondsToDeadline > 0) { + updateTimer(millisecondsToDeadline); + return; + } + resetTimer(); + const notification = new Notyf({ + duration: 0, + position: { x: "center", y: "top" }, + dismissible: true, + }); + notification.error("Round completed"); +} + +function startTimer(fresh) { + if (fresh) { + let now = new Date().getTime(); + const millisecondsToDeadline = minsPerRound * 60 * 1000; + deadline = now + millisecondsToDeadline; + } + timerRunningFlag = 1; + setSessionData(); + clock = setInterval(refreshTimer, 1000); +} + +window.onload = function () { + if (timerRunningFlag) startTimer(0); + else resetTimer(); + + document.getElementById("timer-start").addEventListener("click", () => { + if (timerRunningFlag != 0) return; + startTimer(1); + }); + + document.getElementById("timer-stop").addEventListener("click", () => { + resetTimer(); + }); + + document.getElementById("timer-inc").addEventListener("click", () => { + if (timerRunningFlag == 0) { + minsPerRound += 0.5; + resetTimer(); + } + }); + + document.getElementById("timer-dec").addEventListener("click", () => { + if (timerRunningFlag == 0 && minsPerRound != 0) { + minsPerRound -= 0.5; + resetTimer(); + } + }); +}; diff --git a/src/utils.mjs b/src/utils.mjs new file mode 100644 index 0000000..06a2519 --- /dev/null +++ b/src/utils.mjs @@ -0,0 +1,51 @@ +export class Manager { + constructor(description, listData) { + this.description = description; + this.listData = listData; + this.count = listData.length; + } + store() { + let stringData = JSON.stringify(this.listData); + localStorage.setItem(this.description, stringData); + } + render() { + let renderedHTML = this.listData + .map((i) => { + return i.render(); + }) + .join(""); + let elementId = this.description + "_list"; + document.getElementById(elementId).innerHTML = renderedHTML; + this.addBehaviour(); + } + addBehaviour() { + for (let idx = 0; idx < this.listData.length; ++idx) { + const elementId = this.description + "_" + this.listData[idx].id; + const p = document.getElementById(elementId); + p.addEventListener("click", () => { + this.listData[idx].toggle(); + this.store(); + p.classList.toggle("is-success"); + p.classList.toggle("is-danger"); + }); + } + } + active() { + return this.listData.filter(function (c) { + return c.status; + }); + } + inactive() { + return this.listData.filter(function (c) { + return !c.status; + }); + } +} + +export function shuffle(array) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; +} |
