diff --git a/doc/TODO.md b/doc/TODO.md index 2d547ce..de55d1b 100644 --- a/doc/TODO.md +++ b/doc/TODO.md @@ -41,12 +41,12 @@ - [x] Refaire les flèches de chasse sur la map - [ ] Mettre en évidence le menu paramètre (configuration) - [ ] Afficher un feedback quand un paramètre est sauvegardé -- [ ] Pouvoir définir la zone de départ de chaque équipe -- [ ] Nommer les polygons par des lettres de l'alphabet +- [ ] (IMPORTANT) Pouvoir définir la zone de départ de chaque équipe +- [ ] (IMPORTANT) Nommer les polygons par des lettres de l'alphabet - [ ] Faire un menu quand on arrive sur la traque - [ ] Pouvoir load des paramètres enregistrés -- [ ] Améliorer le système de création zone (cercle et polygone) -- [ ] Améliorer la sélection du système de zone +- [ ] (IMPORTANT) Améliorer le système de création zone (cercle et polygone) +- [ ] (IMPORTANT) Améliorer la sélection du système de zone - [ ] Penser l'affichage en fin de traque ### Améliorations du jeu de la traque diff --git a/traque-back/admin_socket.js b/traque-back/admin_socket.js index 11b5991..b6b0167 100644 --- a/traque-back/admin_socket.js +++ b/traque-back/admin_socket.js @@ -1,31 +1,19 @@ -/* -This module manages the admin access to the server via websocket. -It receives messages, checks permissions, manages authentication and performs actions by calling functions from other modules. -This module also exposes functions to send messages via socket to all admins -*/ import { io } from "./index.js"; -import game from "./game.js" -import zoneManager from "./zone_manager.js" -import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"; import { createHash } from "crypto"; import { config } from "dotenv"; +import game from "./game.js" +import zoneManager from "./zone_manager.js" config(); const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH; -/** - * Send a message to all logged in admin sockets - * @param {String} event The event name - * @param {String} data The data to send - */ export function secureAdminBroadcast(event, data) { loggedInSockets.forEach(s => { io.of("admin").to(s).emit(event, data); }); } -// Array of logged in sockets let loggedInSockets = []; export function initAdminSocketHandler() { @@ -33,52 +21,63 @@ export function initAdminSocketHandler() { console.log("Connection of an admin"); let loggedIn = false; - socket.on("disconnect", () => { - console.log("Disconnection of an admin"); + const login = (password) => { + if (loggedIn) return false; + if (createHash('sha256').update(password).digest('hex') !== ADMIN_PASSWORD_HASH) return false; + loggedInSockets.push(socket.id); + loggedIn = true; + return true; + } + + const logout = () => { + if (!loggedIn) return false; loggedInSockets = loggedInSockets.filter(s => s !== socket.id); loggedIn = false; + return true; + } + + socket.on("disconnect", () => { + console.log("Disconnection of an admin"); + logout(); }); socket.on("logout", () => { - loggedInSockets = loggedInSockets.filter(s => s !== socket.id); - loggedIn = false; - }) - - socket.on("login", (password) => { - const hash = createHash('sha256').update(password).digest('hex'); - if (hash === ADMIN_PASSWORD_HASH && !loggedIn) { - loggedInSockets.push(socket.id); - loggedIn = true; - socket.emit("teams", game.teams); - socket.emit("game_state", { - state: game.state, - date: game.stateDate - }); - socket.emit("current_zone", { - begin: zoneManager.getCurrentZone(), - end: zoneManager.getNextZone(), - endDate: zoneManager.currentZoneEndDate, - }); - socket.emit("settings", game.getSettings()); - } + logout(); }); - socket.on("update_settings", (settings) => { - if (!loggedIn) return; - game.changeSettings(settings); - secureAdminBroadcast("settings", game.getSettings()); - }) + socket.on("login", (password) => { + if (!login(password)) return; + socket.emit("teams", game.teams); + socket.emit("game_state", { + state: game.state, + date: game.stateDate + }); + socket.emit("current_zone", { + begin: zoneManager.getCurrentZone(), + end: zoneManager.getNextZone(), + endDate: zoneManager.currentZoneEndDate, + }); + socket.emit("settings", game.getSettings()); + }); socket.on("add_team", (teamName) => { if (!loggedIn) return; game.addTeam(teamName); - secureAdminBroadcast("teams", game.teams); }); socket.on("remove_team", (teamId) => { if (!loggedIn) return; game.removeTeam(teamId); - secureAdminBroadcast("teams", game.teams); + }); + + socket.on("reorder_teams", (newOrder) => { + if (!loggedIn) return; + game.reorderTeams(newOrder); + }); + + socket.on("capture_team", (teamId, newTeam) => { + if (!loggedIn) return; + game.captureTeam(teamId, newTeam); }); socket.on("change_state", (state) => { @@ -86,22 +85,9 @@ export function initAdminSocketHandler() { game.setState(state); }); - // Use is sending a new list containing the new order of the teams - // Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function - // But the frontend should always send the same teams in a different order - socket.on("reorder_teams", (newOrder) => { + socket.on("update_settings", (settings) => { if (!loggedIn) return; - game.reorderTeams(newOrder); - secureAdminBroadcast("teams", game.teams); - game.teams.forEach(t => sendUpdatedTeamInformations(t.id)); + game.changeSettings(settings); }); - - socket.on("update_team", (teamId, newTeam) => { - if (!loggedIn) return; - game.updateTeam(teamId, newTeam); - secureAdminBroadcast("teams", game.teams); - sendUpdatedTeamInformations(teamId); - sendUpdatedTeamInformations(game.getTeam(teamId).chased); - }) }); } diff --git a/traque-back/game.js b/traque-back/game.js index fe6aeb5..9890491 100644 --- a/traque-back/game.js +++ b/traque-back/game.js @@ -1,19 +1,13 @@ -/* -This module manages the main game state, the teams, the settings and the game logic -*/ import { secureAdminBroadcast } from "./admin_socket.js"; -import { teamBroadcast, playersBroadcast, sendUpdatedTeamInformations, } from "./team_socket.js"; +import { teamBroadcast, playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"; import { sendPositionTimeouts, outOfZoneTimeouts } from "./timeout_handler.js"; import zoneManager from "./zone_manager.js"; import trajectory from "./trajectory.js"; -/** - * Compute the distance between two points givent their longitude and latitude - * @param {Object} pos1 The first position - * @param {Object} pos2 The second position - * @returns the distance between the two positions in meters - * @see https://gist.github.com/miguelmota/10076960 - */ +function randint(max) { + return Math.floor(Math.random() * max); +} + function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) { const degToRad = (deg) => deg * (Math.PI / 180); var R = 6371; // Radius of the earth in km @@ -29,20 +23,10 @@ function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 return d * 1000; } -/** - * Check if a GPS point is in a circle - * @param {Object} position The position to check, an object with lat and lng fields - * @param {Object} center The center of the circle, an object with lat and lng fields - * @param {Number} radius The radius of the circle in meters - * @returns - */ function isInCircle(position, center, radius) { return getDistanceFromLatLon(position, center) < radius; } -/** - * The possible states of the game - */ export const GameState = { SETUP: "setup", PLACEMENT: "placement", @@ -51,7 +35,7 @@ export const GameState = { } export default { - // List of teams, as objects. To see the fields see the addTeam methods + // List of teams, as objects. To see the fields see the addTeam method teams: [], // Current state of the game state: GameState.SETUP, @@ -65,6 +49,78 @@ export default { loser: "", }, + + + /* ------------------------------- USEFUL FUNCTIONS ------------------------------- */ + + getNewTeamId() { + let id = randint(1_000_000); + while (this.teams.find(t => t.id === id)) id = randint(1_000_000); + return id; + }, + + checkEndGame() { + if (this.teams.filter(team => !team.captured) <= 2) this.setState(GameState.FINISHED); + }, + + updateChasingChain() { + const playingTeams = this.teams.filter(team => !team.captured); + + for (let i = 0; i < playingTeams.length; i++) { + playingTeams[i].chasing = playingTeams[(i+1) % playingTeams.length].id; + playingTeams[i].chased = playingTeams[(playingTeams.length + i-1) % playingTeams.length].id; + sendUpdatedTeamInformations(playingTeams[i].id); + } + }, + + initLastSentLocations() { + const dateNow = Date.now(); + // Update of lastSentLocation + for (const team of this.teams) { + team.lastSentLocation = team.currentLocation; + team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000; + sendPositionTimeouts.set(team.id); + sendUpdatedTeamInformations(team.id); + } + // Update of enemyLocation now we have the lastSentLocation of the enemy + for (const team of this.teams) { + team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; + sendUpdatedTeamInformations(team.id); + } + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + }, + + resetTeamsInfos() { + for (const team of this.teams) { + // Chasing + team.captured = false; + team.chasing = null; + team.chased = null; + // Locations + team.lastSentLocation = null; + team.locationSendDeadline = null; + team.enemyLocation = null; + // Placement + team.ready = false; + // Zone + team.outOfZone = false; + team.outOfZoneDeadline = null; + // Stats + team.distance = 0; + team.nCaptures = 0; + team.nSentLocation = 0; + team.nObserved = 0; + team.finishDate = null; + sendUpdatedTeamInformations(team.id); + } + secureAdminBroadcast("teams", this.teams); + }, + + + + /* ------------------------------- STATE AND SETTINGS FUNCTIONS ------------------------------- */ + getSettings() { return { messages: this.messages, @@ -74,54 +130,25 @@ export default { }; }, - /** - * Update the game settings - * @param {Object} newSettings settings to be updated, can be partial - */ changeSettings(newSettings) { if ("messages" in newSettings) this.messages = {...this.messages, ...newSettings.messages}; if ("zone" in newSettings) zoneManager.changeSettings(newSettings.zone); if ("sendPositionDelay" in newSettings) sendPositionTimeouts.setDelay(newSettings.sendPositionDelay); if ("outOfZoneDelay" in newSettings) outOfZoneTimeouts.setDelay(newSettings.outOfZoneDelay); + // Broadcast new infos + secureAdminBroadcast("settings", this.getSettings()); + playersBroadcast("game_settings", this.messages); }, - /** - * Change the state of the game to newState and start the necessary processes - * @param {String} newState - */ setState(newState) { - // Checks is the newState is a Gamestate - if (Object.values(GameState).indexOf(newState) == -1) return false; - // Match case + const dateNow = Date.now(); switch (newState) { case GameState.SETUP: trajectory.stop(); zoneManager.stop(); sendPositionTimeouts.clearAll(); outOfZoneTimeouts.clearAll(); - for (let team of this.teams) { - // Chasing - team.captured = false; - team.chasing = null; - team.chased = null; - // Locations - team.lastSentLocation = null; - team.locationSendDeadline = null; - team.enemyLocation = null; - // Placement - team.ready = false; - // Zone - team.outOfZone = false; - team.outOfZoneDeadline = null; - // Stats - team.distance = 0; - team.nCaptures = 0; - team.nSentLocation = 0; - team.nObserved = 0; - team.finishDate = null; - } - this.stateDate = Date.now(); - this.updateTeamChasing(); + this.resetTeamsInfos(); break; case GameState.PLACEMENT: if (this.teams.length < 3) { @@ -132,7 +159,6 @@ export default { zoneManager.stop(); sendPositionTimeouts.clearAll(); outOfZoneTimeouts.clearAll(); - this.stateDate = Date.now(); break; case GameState.PLAYING: if (this.teams.length < 3) { @@ -142,60 +168,86 @@ export default { trajectory.start(); zoneManager.start(); this.initLastSentLocations(); - this.stateDate = Date.now(); break; case GameState.FINISHED: if (this.state != GameState.PLAYING) { secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate}); return false; } - for (const team of this.teams) { - if (!team.finishDate) team.finishDate = Date.now(); - } trajectory.stop(); zoneManager.stop(); sendPositionTimeouts.clearAll(); outOfZoneTimeouts.clearAll(); + this.teams.forEach(team => {if (!team.finishDate) team.finishDate = dateNow}); + secureAdminBroadcast("teams", this.teams); break; } // Update the state this.state = newState; + this.stateDate = dateNow; + // Broadcast new infos secureAdminBroadcast("game_state", {state: newState, stateDate: this.stateDate}); playersBroadcast("game_state", newState); + return true; + }, + + + + /* ------------------------------- MANAGE PLAYERS FUNCTIONS ------------------------------- */ + + addPlayer(teamId, socketId) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Variables + const team = this.getTeam(teamId); + // Add the player + team.sockets.push(socketId); + // Broadcast new infos secureAdminBroadcast("teams", this.teams); + return true; }, - /** - * Get a new unused team id - * @returns a new unique team id - */ - getNewTeamId() { - let id = null; - while (id === null || this.teams.find(t => t.id === id)) { - id = Math.floor(Math.random() * 1_000_000); + removePlayer(teamId, socketId) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Variables + const team = this.getTeam(teamId); + // Remove the player and its data + if (this.isCapitain(teamId, socketId)) { + team.battery = null; + team.phoneModel = null; + team.phoneName = null; } - return id; + team.sockets = team.sockets.filter((sid) => sid != socketId); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + sendUpdatedTeamInformations(team.id); + return true; }, - /** - * Return a random capture code - * @returns a random 4 digit number - */ - createCaptureCode() { - return Math.floor(Math.random() * 10000); + isPlayerCapitain(teamId, socketId) { + return this.getTeam(teamId).sockets.indexOf(socketId) == 0; + }, + + + + /* ------------------------------- MANAGE TEAMS FUNCTIONS ------------------------------- */ + + getTeam(teamId) { + return this.teams.find(t => t.id === teamId); + }, + + hasTeam(teamId) { + return this.teams.some(t => t.id === teamId); }, - /** - * Add a new team to the game - * @param {String} teamName the name of the team - */ addTeam(teamName) { this.teams.push({ // Identification sockets: [], name: teamName, - id: this.getNewTeamId(), - captureCode: this.createCaptureCode(), + id: this.getNewTeamId(this.teams), + captureCode: randint(10_000), // Chasing captured: false, chasing: null, @@ -223,204 +275,150 @@ export default { phoneName: null, battery: null, }); - this.updateTeamChasing(); - }, - - /** - * Update the chasing chain of the teams based of the ordre of the teams array - * If there are only 2 teams left, the game will end - * This function will update the chasing and chased values of each teams - */ - updateTeamChasing() { - const playingTeams = this.teams.reduce((count, team) => count + (!team.captured ? 1 : 0), 0); - if (playingTeams <= 2) { - if (this.state == GameState.PLAYING) this.setState(GameState.FINISHED); - return; - } - let firstTeam = null; - let previousTeam = null - for (let i = 0; i < this.teams.length; i++) { - if (!this.teams[i].captured) { - if (previousTeam != null) { - this.teams[i].chased = previousTeam; - this.getTeam(previousTeam).chasing = this.teams[i].id; - } else { - firstTeam = this.teams[i].id; - } - previousTeam = this.teams[i].id; - } - } - this.getTeam(firstTeam).chased = previousTeam; - this.getTeam(previousTeam).chasing = firstTeam; + this.updateChasingChain(); + // Broadcast new infos secureAdminBroadcast("teams", this.teams); + return true; }, - /** - * Rearrange the order of the teams and update the chasing chain - * @param {Array} newOrder An array of teams in the new order - */ - reorderTeams(newOrder) { - this.teams = newOrder; - this.updateTeamChasing(); + removeTeam(teamId) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Logout the team + teamBroadcast(teamId, "logout"); + this.teams = this.teams.filter(t => t.id !== teamId); + sendPositionTimeouts.clear(teamId); + outOfZoneTimeouts.clear(teamId); + this.updateChasingChain(); + this.checkEndGame(); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + return true; }, - /** - * Get a team by its ID - * @param {Number} teamId The id of the team - * @returns the team object or undefined if not found - */ - getTeam(teamId) { - return this.teams.find(t => t.id === teamId); - }, - - /** - * Update a team's values - * @param {Number} teamId The id of the team to update - * @param {Object} newTeam An object containing the new values of the team, can be partial - */ - updateTeam(teamId, newTeam) { - this.teams = this.teams.map((t) => { - if (t.id == teamId) { - return { ...t, ...newTeam }; - } else { - return t; - } - }) - this.updateTeamChasing(); - }, - - /** - * - * @param {Number} teamId The ID of the team which location will be updated - * @param {Array} location An array containing in order the latitude and longitude of the new location - */ - updateLocation(teamId, location) { + captureTeam(teamId) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Variables const team = this.getTeam(teamId); - if (!team || !location) { - return; - } + const dateNow = Date.now(); + // Make the capture + team.captured = true; + team.finishDate = dateNow; + team.chasing = null; + team.chased = null; + sendPositionTimeouts.clear(team.id); + outOfZoneTimeouts.clear(team.id); + this.updateChasingChain(); + this.checkEndGame(); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + sendUpdatedTeamInformations(team.id); + return true; + }, + + reorderTeams(newOrder) { + // Update teams + const teamMap = new Map(this.teams.map(team => [team.id, team])); + this.teams = newOrder.map(id => teamMap.get(id)); + this.updateChasingChain(); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + return true; + }, + + handicapTeam(teamId) { + // TODO + }, + + + + /* ------------------------------- PLAYERS ACTIONS FUNCTIONS ------------------------------- */ + + updateLocation(teamId, location) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + if (!location) return false; + // Variables + const team = this.getTeam(teamId); + const dateNow = Date.now(); + // Update distance if (team.currentLocation) team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]})); - // Update of events of the game - trajectory.writePosition(Date.now(), teamId, location[0], location[1]); // Update of currentLocation team.currentLocation = location; - // Update of ready (true if the team is in the starting area) - if (this.state == GameState.PLACEMENT && team.startingArea && team.startingArea && location) { + team.lastCurrentLocationDate = dateNow; + // Update of ready + if (this.state == GameState.PLACEMENT && team.startingArea) { team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius); } - // Verify zone + // Update out of zone const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] }) if (teamCurrentlyOutOfZone && !team.outOfZone) { team.outOfZone = true; - team.outOfZoneDeadline = Date.now() + outOfZoneTimeouts.duration * 60 * 1000; + team.outOfZoneDeadline = dateNow + outOfZoneTimeouts.duration * 60 * 1000; outOfZoneTimeouts.set(teamId); } else if (!teamCurrentlyOutOfZone && team.outOfZone) { team.outOfZone = false; team.outOfZoneDeadline = null; outOfZoneTimeouts.clear(teamId); } - // Sending new infos to the team + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); sendUpdatedTeamInformations(team.id); - }, - - /** - * Initialize the last sent location of the teams to their starting location - */ - initLastSentLocations() { - // Update of lastSentLocation - for (const team of this.teams) { - team.lastSentLocation = team.currentLocation; - team.locationSendDeadline = Date.now() + sendPositionTimeouts.duration * 60 * 1000; - sendPositionTimeouts.set(team.id); - sendUpdatedTeamInformations(team.id); - } - // Update of enemyLocation now we have the lastSentLocation of the enemy - for (const team of this.teams) { - team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; - sendUpdatedTeamInformations(team.id); - } - }, - - /** - * Get the most recent enemy team's location as well as setting the latest accessible location to the current one - * @param {Number} teamId The ID of the team that will send its location - */ - sendLocation(teamId) { - const team = this.getTeam(teamId); - if (!team || !team.currentLocation) { - return; - } - const enemyTeam = this.getTeam(team.chasing); - team.nSentLocation++; - enemyTeam.nObserved++; - const dateNow = Date.now(); // Update of events of the game - trajectory.writeSeePosition(dateNow, teamId, team.chasing); - // Update of locationSendDeadline + trajectory.writePosition(dateNow, team.id, location[0], location[1]); + return true; + }, + + sendLocation(teamId) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Variables + const team = this.getTeam(teamId); + const enemyTeam = this.getTeam(team.chasing); + const dateNow = Date.now(); + // Update team + team.nSentLocation++; + team.lastSentLocation = team.currentLocation; + team.enemyLocation = enemyTeam.lastSentLocation; team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000; sendPositionTimeouts.set(team.id); - // Update of lastSentLocation - team.lastSentLocation = team.currentLocation; - // Update of enemyLocation - const teamChasing = this.getTeam(team.chasing); - if (teamChasing) team.enemyLocation = teamChasing.lastSentLocation; - // Sending new infos to the team + // Update enemy + enemyTeam.nObserved++; + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); sendUpdatedTeamInformations(team.id); sendUpdatedTeamInformations(enemyTeam.id); + // Update of events of the game + trajectory.writeSeePosition(dateNow, team.id, enemyTeam.id); + return true; }, - /** - * Remove a team by its ID - * @param {Number} teamId The id of the team to remove - */ - removeTeam(teamId) { - if (!this.getTeam(teamId)) { - return; - } - teamBroadcast("logout"); - this.teams = this.teams.filter(t => t.id !== teamId); - this.updateTeamChasing(); - sendPositionTimeouts.clear(teamId); - outOfZoneTimeouts.clear(teamId); - }, - - /** - * Request a capture initiated by the team with id teamId (the one trying to capture) - * If the captureCode match, the team chased by teamId will be set to captured - * And the chase chain will be updated - * @param {Number} teamId The id of the capturing team - * @param {Number} captureCode The code sent by the capturing that only the captured team know, used to verify the authenticity of the capture - */ - requestCapture(teamId, captureCode) { + tryCapture(teamId, captureCode) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Variables const team = this.getTeam(teamId); const enemyTeam = this.getTeam(team.chasing); - if (!enemyTeam || enemyTeam.captureCode != captureCode) { - return; - } + const dateNow = Date.now(); + // Verify the capture + if (enemyTeam.captureCode != captureCode) return false; + // Make the capture team.nCaptures++; - // Update of events of the game - trajectory.writeCapture(Date.now(), teamId, enemyTeam.id); - // Update of capture and chasing cycle - this.capture(enemyTeam.id); - // Sending new infos to the teams + enemyTeam.captured = true; + enemyTeam.finishDate = dateNow; + enemyTeam.chasing = null; + enemyTeam.chased = null; + sendPositionTimeouts.clear(enemyTeam.id); + outOfZoneTimeouts.clear(enemyTeam.id); + this.updateChasingChain(); + this.checkEndGame(); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); sendUpdatedTeamInformations(team.id); sendUpdatedTeamInformations(enemyTeam.id); + // Update of events of the game + trajectory.writeCapture(dateNow, team.id, enemyTeam.id); + return true; }, - - /** - * Set a team to captured and update the chase chain - * @param {Number} teamId the Id of the captured team - */ - capture(teamId) { - const team = this.getTeam(teamId); - team.captured = true; - team.finishDate = Date.now(); - sendPositionTimeouts.clear(teamId); - outOfZoneTimeouts.clear(teamId); - this.updateTeamChasing(); - }, - - handicapTeam(teamId) { - // TODO - } } diff --git a/traque-back/team_socket.js b/traque-back/team_socket.js index 9f29ca0..f9c50a1 100644 --- a/traque-back/team_socket.js +++ b/traque-back/team_socket.js @@ -3,7 +3,6 @@ This file manages team access to the server via websocket. It receives messages, checks permissions, manages authentication and performs actions by calling functions from other modules. This module also exposes functions to send messages via socket to all teams */ -import { secureAdminBroadcast } from "./admin_socket.js"; import { io } from "./index.js"; import game from "./game.js"; import zoneManager from "./zone_manager.js"; @@ -24,9 +23,7 @@ export function teamBroadcast(teamId, event, data) { * @param {String} data payload */ export function playersBroadcast(event, data) { - for (const team of game.teams) { - teamBroadcast(team.id, event, data); - } + game.teams.forEach(team => teamBroadcast(team.id, event, data)); } /** @@ -42,7 +39,7 @@ export function sendUpdatedTeamInformations(teamId) { captureCode: team.captureCode, // Chasing captured: team.captured, - enemyName: game.getTeam(team.chasing)? game.getTeam(team.chasing).name : null, + enemyName: game.getTeam(team.chasing)?.name ?? null, // Locations lastSentLocation: team.lastSentLocation, enemyLocation: team.enemyLocation, @@ -57,10 +54,9 @@ export function sendUpdatedTeamInformations(teamId) { distance: team.distance, nCaptures: team.nCaptures, nSentLocation: team.nSentLocation, - startDate: game.startDate, + stateDate: game.stateDate, finishDate: team.finishDate, - }) - secureAdminBroadcast("teams", game.teams); + }); } export function initTeamSocket() { @@ -68,42 +64,36 @@ export function initTeamSocket() { console.log("Connection of a player"); let teamId = null; - const logoutPlayer = () => { + const login = (loginTeamId) => { + logout(); + if (!game.addPlayer(loginTeamId, socket.id)) return false; + teamId = loginTeamId; + return true; + } + + const logout = () => { if (!teamId) return; - const team = game.getTeam(teamId); - if (team.sockets.indexOf(socket.id) == 0) { - team.battery = null; - team.phoneModel = null; - team.phoneName = null; - } - // Delete the player from the team - team.sockets = team.sockets.filter((sid) => sid != socket.id); - secureAdminBroadcast("teams", game.teams); - socket.emit("logout"); + game.removePlayer(teamId, socket.id); teamId = null; } socket.on("disconnect", () => { console.log("Disconnection of a player"); - logoutPlayer(); + logout(); }); socket.on("logout", () => { - logoutPlayer(); + logout(); }); socket.on("login", (loginTeamId, callback) => { - logoutPlayer(); - const team = game.getTeam(loginTeamId); - if (!team) { + if (!login(loginTeamId)) { callback({ isLoggedIn: false, message: "Login denied" }); return; } - teamId = loginTeamId; - team.sockets.push(socket.id); sendUpdatedTeamInformations(loginTeamId); socket.emit("game_state", game.state); - socket.emit("game_settings", game.settings); + socket.emit("game_settings", game.messages); socket.emit("zone", { type: zoneManager.settings.type, begin: zoneManager.getCurrentZone(), @@ -115,13 +105,9 @@ export function initTeamSocket() { socket.on("update_position", (position) => { if (!teamId) return; - const team = game.getTeam(teamId); - // Only the first socket can update the current position since he is the one whose location is tracked - if (team.sockets.indexOf(socket.id) == 0) { + if (game.isPlayerCapitain(teamId, socket.id)) { game.updateLocation(teamId, position); - team.lastCurrentLocationDate = Date.now(); } - secureAdminBroadcast("teams", game.teams); }); socket.on("send_position", () => { @@ -131,27 +117,25 @@ export function initTeamSocket() { socket.on("capture", (captureCode, callback) => { if (!teamId) return; - game.requestCapture(teamId, captureCode); - callback({ hasCaptured : true, message: "Capture successful" }); + if (game.tryCapture(teamId, captureCode)) { + callback({ hasCaptured : true, message: "Capture successful" }); + } else { + callback({ hasCaptured : false, message: "Capture denied" }); + } }); socket.on("device_info", (infos) => { if (!teamId) return; - const team = game.getTeam(teamId); - // Only the first socket shares its infos since he is the one whose location is tracked - if (team.sockets.indexOf(socket.id) == 0) { - team.phoneModel = infos.model; - team.phoneName = infos.name; + if (game.isPlayerCapitain(teamId, socket.id)) { + game.updateTeam(teamId, {phoneModel: infos.model, phoneName: infos.name}); } }); socket.on("battery_update", (batteryLevel) => { if (!teamId) return; - const team = game.getTeam(teamId); - // Only the first socket shares its infos since he is the one whose location is tracked - if (team.sockets.indexOf(socket.id) == 0) { - team.battery = batteryLevel; + if (game.isPlayerCapitain(teamId, socket.id)) { + game.updateTeam(teamId, {battery: batteryLevel}); } }); }); -} \ No newline at end of file +} diff --git a/traque-front/app/admin/components/teamSidePanel.jsx b/traque-front/app/admin/components/teamSidePanel.jsx index a099c4d..cd71ef6 100644 --- a/traque-front/app/admin/components/teamSidePanel.jsx +++ b/traque-front/app/admin/components/teamSidePanel.jsx @@ -89,7 +89,7 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) { />
- +
diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js index 7393e67..8bc5c67 100644 --- a/traque-front/app/admin/page.js +++ b/traque-front/app/admin/page.js @@ -16,8 +16,8 @@ const LiveMap = dynamic(() => import('./components/liveMap'), { ssr: false }); export default function AdminPage() { const { useProtect } = useAdminConnexion(); - const [selectedTeamId, setSelectedTeamId] = useState(null); const { changeState, getTeam } = useAdmin(); + const [selectedTeamId, setSelectedTeamId] = useState(null); const [mapStyle, setMapStyle] = useState(mapStyles.default); const [showZones, setShowZones] = useState(true); const [showNames, setShowNames] = useState(true); diff --git a/traque-front/app/admin/parameters/components/circleZoneSelector.jsx b/traque-front/app/admin/parameters/components/circleZoneSelector.jsx index 7dc272d..76cb337 100644 --- a/traque-front/app/admin/parameters/components/circleZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/circleZoneSelector.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Circle } from "react-leaflet"; import "leaflet/dist/leaflet.css"; -import { BlueButton, GreenButton, RedButton } from "@/components/button"; +import { CustomButton } from "@/components/button"; import { CustomMapContainer, MapEventListener } from "@/components/map"; import { TextInput } from "@/components/input"; import useAdmin from "@/hook/useAdmin"; @@ -43,8 +43,7 @@ export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, ap } function customStringToInt(e) { - const res = parseInt(e, 10); - return isNaN(res) ? null : res; + return parseInt(e, 10) || null; } return ( @@ -56,8 +55,8 @@ export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, ap
- {editMode == EditMode.MIN && setEditMode(EditMode.MAX)}>Click to edit first zone} - {editMode == EditMode.MAX && setEditMode(EditMode.MIN)}>Click to edit last zone} + {editMode == EditMode.MIN && setEditMode(EditMode.MAX)}>Click to edit first zone} + {editMode == EditMode.MAX && setEditMode(EditMode.MIN)}>Click to edit last zone}

Reduction number

@@ -78,7 +77,7 @@ export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, ap
- Apply + Apply
diff --git a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx index ad58143..4a6e51b 100644 --- a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { Polyline, Polygon, Marker } from "react-leaflet"; +import { Polyline } from "react-leaflet"; import "leaflet/dist/leaflet.css"; -import { GreenButton } from "@/components/button"; +import { CustomButton } from "@/components/button"; import { ReorderList } from "@/components/list"; import { CustomMapContainer, MapEventListener } from "@/components/map"; import { TextInput } from "@/components/input"; @@ -88,7 +88,7 @@ export default function PolygonZoneSelector({zoneSettings, modifyZoneSettings, a
- Apply + Apply
diff --git a/traque-front/app/admin/parameters/components/teamManager.jsx b/traque-front/app/admin/parameters/components/teamManager.jsx index 709ee16..bddd933 100644 --- a/traque-front/app/admin/parameters/components/teamManager.jsx +++ b/traque-front/app/admin/parameters/components/teamManager.jsx @@ -2,11 +2,7 @@ import { ReorderList } from '@/components/list'; import useAdmin from '@/hook/useAdmin'; function TeamManagerItem({ team }) { - const { updateTeam, removeTeam } = useAdmin(); - - function handleRemove() { - removeTeam(team.id); - } + const { captureTeam, removeTeam } = useAdmin(); return (
@@ -14,8 +10,8 @@ function TeamManagerItem({ team }) {

{team.name}

{String(team.id).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")}

- updateTeam(team.id, { captured: !team.captured })} /> - + captureTeam(team.id)} /> + removeTeam(team.id)} />
@@ -26,7 +22,7 @@ export default function TeamManager() { const { teams, reorderTeams } = useAdmin(); return ( - + reorderTeams(teams.map(team => team.id))}> {(team) => ( )} diff --git a/traque-front/app/admin/parameters/page.js b/traque-front/app/admin/parameters/page.js index e64e5c0..b221eb7 100644 --- a/traque-front/app/admin/parameters/page.js +++ b/traque-front/app/admin/parameters/page.js @@ -4,7 +4,7 @@ import dynamic from "next/dynamic"; import Link from "next/link"; import { TextInput } from "@/components/input"; import { Section } from "@/components/section"; -import { BlueButton } from "@/components/button"; +import { CustomButton } from "@/components/button"; import { useAdminConnexion } from "@/context/adminConnexionContext"; import useAdmin from '@/hook/useAdmin'; import Messages from "./components/messages"; @@ -74,7 +74,7 @@ export default function ConfigurationPage() {
- {localZoneSettings && Change zone type} + {localZoneSettings && Change zone type}
{localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE && diff --git a/traque-front/components/button.jsx b/traque-front/components/button.jsx index bf63a6d..c96c27e 100644 --- a/traque-front/components/button.jsx +++ b/traque-front/components/button.jsx @@ -1,22 +1,15 @@ -export function BlueButton({ children, ...props }) { - return ( - - ); -} +export function CustomButton({ color, children, ...props }) { + const colorClasses = { + blue: 'bg-blue-600 hover:bg-blue-500', + red: 'bg-red-600 hover:bg-red-500', + green: 'bg-green-600 hover:bg-green-500', + yellow: 'bg-yellow-600 hover:bg-yellow-500', + purple: 'bg-purple-600 hover:bg-purple-500', + gray: 'bg-gray-600 hover:bg-gray-500', + }; -export function RedButton({ children, ...props }) { return ( - - ); -} - -export function GreenButton({ children, ...props }) { - return ( - ); diff --git a/traque-front/components/list.jsx b/traque-front/components/list.jsx index c987331..b8216cf 100644 --- a/traque-front/components/list.jsx +++ b/traque-front/components/list.jsx @@ -17,10 +17,10 @@ export function List({array, children}) { } export function ReorderList({droppableId, array, setArray, children}) { - const [arrayLocal, setArrayLocal] = useState(array); + const [localArray, setLocalArray] = useState(array); useEffect(() => { - setArrayLocal(array); + setLocalArray(array); }, [array]) function reorder(list, startIndex, endIndex) { @@ -34,7 +34,7 @@ export function ReorderList({droppableId, array, setArray, children}) { if (!result.destination) return; if (result.destination.index === result.source.index) return; const newArray = reorder(array, result.source.index, result.destination.index); - setArrayLocal(newArray); + setLocalArray(newArray); setArray(newArray); } @@ -44,7 +44,7 @@ export function ReorderList({droppableId, array, setArray, children}) { {provided => (
    - {arrayLocal.map((elem, i) => ( + {localArray.map((elem, i) => (
  • {provided => ( diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index bf267dd..0f140f2 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -23,8 +23,8 @@ export default function useAdmin() { adminSocket.emit("remove_team", teamId); } - function updateTeam(teamId, team) { - adminSocket.emit("update_team", teamId, team); + function captureTeam(teamId) { + adminSocket.emit("capture_team", teamId); } function changeState(state) { @@ -35,5 +35,5 @@ export default function useAdmin() { adminSocket.emit("update_settings", settings); } - return { ...adminContext, getTeam, reorderTeams, addTeam, removeTeam, updateTeam, changeState, updateSettings }; + return { ...adminContext, getTeam, reorderTeams, addTeam, removeTeam, captureTeam, changeState, updateSettings }; }