diff --git a/.gitignore b/.gitignore index 6f747cc..4fdfabf 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Other +.vscode/ diff --git a/traque-back/admin_socket.js b/traque-back/admin_socket.js index 70a4d25..595ee89 100644 --- a/traque-back/admin_socket.js +++ b/traque-back/admin_socket.js @@ -8,11 +8,10 @@ import game from "./game.js" import zone from "./zone_manager.js" import penaltyController from "./penalty_controller.js"; import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"; -import { sha256 } from "./util.js"; - +import { createHash } from "crypto"; import { config } from "dotenv"; -config() +config(); const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH; /** @@ -26,18 +25,18 @@ export function secureAdminBroadcast(event, data) { }); } -//Array of logged in sockets +// Array of logged in sockets let loggedInSockets = []; export function initAdminSocketHandler() { - //Admin namespace + // Admin namespace io.of("admin").on("connection", (socket) => { - //Flag to check if the user is logged in, defined for each socket + // Flag to check if the user is logged in, defined for each socket console.log("Connection of an admin"); let loggedIn = false; socket.on("disconnect", () => { console.log("Disconnection of an admin"); - //Remove the socket from the logged in sockets array + // Remove the socket from the logged in sockets array loggedInSockets = loggedInSockets.filter(s => s !== socket.id); }); @@ -45,17 +44,17 @@ export function initAdminSocketHandler() { loggedInSockets = loggedInSockets.filter(s => s !== socket.id); }) - //User is attempting to log in + // User is attempting to log in socket.on("login", (password) => { - const hash = sha256(password); + const hash = createHash('sha256').update(password).digest('hex'); if (hash === ADMIN_PASSWORD_HASH && !loggedIn) { - //Attempt successful + // Attempt successful socket.emit("login_response", true); loggedInSockets.push(socket.id); loggedIn = true; - //Send the current state + // Send the current state socket.emit("game_state", game.state) - //Other settings that need initialization + // Other settings that need initialization socket.emit("penalty_settings", penaltyController.settings) socket.emit("game_settings", game.settings) socket.emit("zone_settings", zone.zoneSettings) @@ -64,9 +63,8 @@ export function initAdminSocketHandler() { begin: zone.currentStartZone, end: zone.nextZone }) - } else { - //Attempt unsuccessful + // Attempt unsuccessful socket.emit("login_response", false); } }); @@ -90,7 +88,7 @@ export function initAdminSocketHandler() { } if (!game.setZoneSettings(settings)) { socket.emit("error", "Error changing zone"); - socket.emit("zone_settings", zone.zoneSettings) //Still broadcast the old config to the client who submited an incorrect config to keep the client up to date + socket.emit("zone_settings", zone.zoneSettings) // Still broadcast the old config to the client who submited an incorrect config to keep the client up to date } else { secureAdminBroadcast("zone_settings", zone.zoneSettings) } @@ -111,7 +109,7 @@ export function initAdminSocketHandler() { }) - //User is attempting to add a new team + // User is attempting to add a new team socket.on("add_team", (teamName) => { if (!loggedIn) { socket.emit("error", "Not logged in"); @@ -124,7 +122,7 @@ export function initAdminSocketHandler() { } }); - //User is attempting to remove a team + // User is attempting to remove a team socket.on("remove_team", (teamId) => { if (!loggedIn) { socket.emit("error", "Not logged in"); @@ -137,7 +135,7 @@ export function initAdminSocketHandler() { } }); - //User is attempting to change the game state + // User is attempting to change the game state socket.on("change_state", (state) => { if (!loggedIn) { socket.emit("error", "Not logged in"); @@ -151,9 +149,9 @@ export function initAdminSocketHandler() { } }); - //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 + // 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) => { if (!loggedIn) { socket.emit("error", "Not logged in"); @@ -179,8 +177,8 @@ export function initAdminSocketHandler() { } }) - //Request an update of the team list - //We only reply to the sender to prevent spam + // Request an update of the team list + // We only reply to the sender to prevent spam socket.on("get_teams", () => { if (!loggedIn) { socket.emit("error", "Not logged in"); @@ -188,8 +186,5 @@ export function initAdminSocketHandler() { } socket.emit("teams", game.teams); }); - }); - - -} \ No newline at end of file +} diff --git a/traque-back/game.js b/traque-back/game.js index 2568526..4dafe42 100644 --- a/traque-back/game.js +++ b/traque-back/game.js @@ -2,14 +2,11 @@ This module manages the main game state, the teams, the settings and the game logic */ import { secureAdminBroadcast } from "./admin_socket.js"; -import { isInCircle } from "./map_utils.js"; import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"; - +import { isInCircle } from "./map_utils.js"; import timeoutHandler from "./timeoutHandler.js"; import penaltyController from "./penalty_controller.js"; import zoneManager from "./zone_manager.js"; - -import { getDistanceFromLatLon } from "./map_utils.js"; import { writePosition, writeCapture, writeSeePosition } from "./trajectory.js"; /** @@ -32,7 +29,7 @@ export default { loserEndGameMessage: "", winnerEndGameMessage: "", capturedMessage: "", - waitingMessage: "Jeu en préparation, veuillez patienter." + waitingMessage: "" }, /** @@ -106,7 +103,7 @@ export default { * @returns a random 4 digit number */ createCaptureCode() { - return Math.floor(Math.random() * 10000) + return Math.floor(Math.random() * 10000); }, /** @@ -115,9 +112,8 @@ export default { * @returns true if the team has been added */ addTeam(teamName) { - let id = this.getNewTeamId(); this.teams.push({ - id: id, + id: this.getNewTeamId(), name: teamName, chasing: null, chased: null, @@ -160,7 +156,7 @@ export default { updateTeamChasing() { if (this.playingTeamCount() <= 2) { if (this.state == GameState.PLAYING) { - this.finishGame() + this.finishGame(); } return false; } @@ -175,13 +171,13 @@ export default { } else { firstTeam = this.teams[i].id; } - previousTeam = this.teams[i].id + previousTeam = this.teams[i].id; } } this.getTeam(firstTeam).chased = previousTeam; this.getTeam(previousTeam).chasing = firstTeam; this.getTeam(previousTeam).enemyName = this.getTeam(firstTeam).name; - secureAdminBroadcast("teams", this.teams) + secureAdminBroadcast("teams", this.teams); return true; }, @@ -213,7 +209,7 @@ export default { updateTeam(teamId, newTeam) { this.teams = this.teams.map((t) => { if (t.id == teamId) { - return { ...t, ...newTeam } + return { ...t, ...newTeam }; } else { return t; } @@ -230,22 +226,20 @@ export default { * @returns true if the location has been updated */ updateLocation(teamId, location) { - let team = this.getTeam(teamId); - //The ID does not match any team - if (team == undefined) { - return false; - } - //The location sent by the team will be null if the browser call API dooes not succeed - //See issue #19 - if (location == null) { + const team = this.getTeam(teamId); + if (!team || !location) { return false; } + // Update of events of the game writePosition(Date.now(), teamId, location[0], location[1]); + // Update of currentLocation team.currentLocation = location; - //Update the team ready status if they are in their starting area + // Update of ready (true if the team is in the starting area) if (this.state == GameState.PLACEMENT && team.startingArea && team.startingArea && location) { - team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius) + team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius); } + // Sending new infos to the team + sendUpdatedTeamInformations(team.id); return true; }, @@ -253,14 +247,14 @@ export default { * Initialize the last sent location of the teams to their starting location */ initLastSentLocations() { - for (let team of this.teams) { + for (const team of this.teams) { team.lastSentLocation = team.currentLocation; team.locationSendDeadline = Date.now() + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000; timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline); this.getTeam(team.chasing).enemyLocation = team.lastSentLocation; sendUpdatedTeamInformations(team.id); } - for (let team of this.teams) { + for (const team of this.teams) { team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; sendUpdatedTeamInformations(team.id); } @@ -272,22 +266,24 @@ export default { * @returns true if the location has been sent */ sendLocation(teamId) { - let team = this.getTeam(teamId); - if (team == undefined) { + const team = this.getTeam(teamId); + if (!team || !team.currentLocation) { return false; } - if (team.currentLocation == null) { - return false; - } - writeSeePosition(Date.now(), teamId, team.chasing); - team.locationSendDeadline = Date.now() + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000; + const dateNow = Date.now(); + // Update of events of the game + writeSeePosition(dateNow, teamId, team.chasing); + // Update of locationSendDeadline + team.locationSendDeadline = dateNow + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000; timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline); + // Update of lastSentLocation team.lastSentLocation = team.currentLocation; - if (this.getTeam(team.chasing) != null) { - team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; - } + // Update of enemyLocation + const teamChasing = this.getTeam(team.chasing); + if (teamChasing) team.enemyLocation = teamChasing.lastSentLocation; + // Sending new infos to the team sendUpdatedTeamInformations(team.id); - return team; + return true; }, /** @@ -296,10 +292,9 @@ export default { * @returns true if the team has been deleted */ removeTeam(teamId) { - if (this.getTeam(teamId) == undefined) { + if (!this.getTeam(teamId)) { return false; } - //remove the team from the list this.teams = this.teams.filter(t => t.id !== teamId); this.updateTeamChasing(); timeoutHandler.endSendPositionTimeout(team.id); @@ -315,14 +310,19 @@ export default { * @returns {Boolean} if the capture has been successfull or not */ requestCapture(teamId, captureCode) { - let enemyTeam = this.getTeam(this.getTeam(teamId).chasing) - if (enemyTeam && enemyTeam.captureCode == captureCode) { - writeCapture(Date.now(), teamId, enemyTeam.id); - this.capture(enemyTeam.id); - this.updateTeamChasing(); - return true; + const team = this.getTeam(teamId); + const enemyTeam = this.getTeam(team.chasing); + if (!enemyTeam || enemyTeam.captureCode != captureCode) { + return false; } - return false; + // Update of events of the game + writeCapture(Date.now(), teamId, enemyTeam.id); + // Update of capture and chasing cycle + this.capture(enemyTeam.id); + // Sending new infos to the teams + sendUpdatedTeamInformations(team.id); + sendUpdatedTeamInformations(enemyTeam.id); + return true; }, /** @@ -330,7 +330,7 @@ export default { * @param {Number} teamId the Id of the captured team */ capture(teamId) { - this.getTeam(teamId).captured = true + this.getTeam(teamId).captured = true; timeoutHandler.endSendPositionTimeout(teamId); this.updateTeamChasing(); }, @@ -349,11 +349,10 @@ export default { var min = newSettings.min; var max = newSettings.max; // The end zone must be included in the start zone - var dist = getDistanceFromLatLon(min.center, max.center); - if (min.radius + dist >= max.radius) { + if (!isInCircle(min.center, max.center, max.radius-min.radius)) { return false; } - return zoneManager.udpateSettings(newSettings) + return zoneManager.udpateSettings(newSettings); }, /** @@ -362,7 +361,7 @@ export default { finishGame() { this.setState(GameState.FINISHED); zoneManager.reset(); - timeoutHandler.endAllSendPositionTimeout() + timeoutHandler.endAllSendPositionTimeout(); playersBroadcast("game_state", this.state); }, } \ No newline at end of file diff --git a/traque-back/index.js b/traque-back/index.js index 189e09c..5f126e0 100644 --- a/traque-back/index.js +++ b/traque-back/index.js @@ -1,19 +1,17 @@ import { createServer } from "http"; import express from "express"; - import { Server } from "socket.io"; import { config } from "dotenv"; -import { readFileSync } from "fs"; import { initAdminSocketHandler } from "./admin_socket.js"; import { initTeamSocket } from "./team_socket.js"; import { initPhotoUpload } from "./photo.js"; import { initTrajectories } from "./trajectory.js"; -//extract admin password from .env file + config(); const HOST = process.env.HOST; const PORT = process.env.PORT; -export const app = express() +export const app = express(); const httpServer = createServer({}, app); @@ -21,9 +19,6 @@ httpServer.listen(PORT, HOST, () => { console.log("Server running on http://" + HOST + ":" + PORT); }); - - -//set cors to allow all origins export const io = new Server(httpServer, { cors: { origin: "*", diff --git a/traque-back/map_utils.js b/traque-back/map_utils.js index 1af35a1..330c83d 100644 --- a/traque-back/map_utils.js +++ b/traque-back/map_utils.js @@ -1,3 +1,12 @@ +/** + * Convert a angle from degree to radian + * @param {Number} deg angle in degree + * @returns angle in radian + */ +function degToRad(deg) { + return deg * (Math.PI / 180); +} + /** * Compute the distance between two points givent their longitude and latitude * @param {Object} pos1 The first position @@ -5,13 +14,13 @@ * @returns the distance between the two positions in meters * @see https://gist.github.com/miguelmota/10076960 */ -export function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) { +function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) { var R = 6371; // Radius of the earth in km - var dLat = deg2rad(lat2 - lat1); // deg2rad below - var dLon = deg2rad(lon2 - lon1); + var dLat = degToRad(lat2 - lat1); + var dLon = degToRad(lon2 - lon1); var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * + Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2) ; var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); @@ -19,15 +28,6 @@ export function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng return d * 1000; } -/** - * Convert a angle from degree to radian - * @param {Number} deg angle in degree - * @returns angle in radian - */ -function deg2rad(deg) { - return deg * (Math.PI / 180) -} - /** * Check if a GPS point is in a circle * @param {Object} position The position to check, an object with lat and lng fields @@ -37,4 +37,4 @@ function deg2rad(deg) { */ export function isInCircle(position, center, radius) { return getDistanceFromLatLon(position, center) < radius; -} \ No newline at end of file +} diff --git a/traque-back/photo.js b/traque-back/photo.js index 4a1ad59..c02df53 100644 --- a/traque-back/photo.js +++ b/traque-back/photo.js @@ -13,7 +13,7 @@ const ALLOWED_MIME = [ "image/gif" ] -//Setup multer (the file upload middleware) +// Setup multer (the file upload middleware) const storage = multer.diskStorage({ // Save the file in the uploads directory destination: function (req, file, callback) { @@ -27,7 +27,7 @@ const storage = multer.diskStorage({ const upload = multer({ storage, - //Only upload the file if it is a valid mime type and the team POST parameter is a valid team + // Only upload the file if it is a valid mime type and the team POST parameter is a valid team fileFilter: function (req, file, callback) { if (ALLOWED_MIME.indexOf(file.mimetype) == -1) { callback(null, false); @@ -39,8 +39,7 @@ const upload = multer({ } }) - -//Clean the uploads directory +// Clean the uploads directory function clean() { const files = fs.readdirSync(UPLOAD_DIR); for (const file of files) { @@ -49,7 +48,6 @@ function clean() { } } - export function initPhotoUpload() { clean(); //App handler for uploading a photo and saving it to a file diff --git a/traque-back/team_socket.js b/traque-back/team_socket.js index bf37120..967969c 100644 --- a/traque-back/team_socket.js +++ b/traque-back/team_socket.js @@ -15,8 +15,8 @@ import zone from "./zone_manager.js"; * @param {*} data The payload */ export function teamBroadcast(teamId, event, data) { - for (let socketId of game.getTeam(teamId).sockets) { - io.of("player").to(socketId).emit(event, data) + for (const socketId of game.getTeam(teamId).sockets) { + io.of("player").to(socketId).emit(event, data); } } @@ -26,25 +26,19 @@ export function teamBroadcast(teamId, event, data) { * @param {String} data payload */ export function playersBroadcast(event, data) { - for (let team of game.teams) { + for (const team of game.teams) { teamBroadcast(team.id, event, data); } } /** - * Remove a player from the list of logged in players - * @param {Number} id The id of the player to log out + * Send a socket message to all the players of a team + * @param {String} teamId The team that will receive the message */ -function logoutPlayer(id) { - for (let team of game.teams) { - team.sockets = team.sockets.filter((sid) => sid != id); - } -} - export function sendUpdatedTeamInformations(teamId) { - let team = game.getTeam(teamId) + const team = game.getTeam(teamId); if (!team) { - return false; + return; } team.sockets.forEach(socketId => { io.of("player").to(socketId).emit("update_team", { @@ -61,6 +55,17 @@ export function sendUpdatedTeamInformations(teamId) { penalties: team.penalties, }) }) + secureAdminBroadcast("teams", game.teams); +} + +/** + * Remove a player from the list of logged in players + * @param {Number} id The id of the player to log out + */ +function logoutPlayer(id) { + for (const team of game.teams) { + team.sockets = team.sockets.filter((sid) => sid != id); + } } export function initTeamSocket() { @@ -70,37 +75,33 @@ export function initTeamSocket() { socket.on("disconnect", () => { console.log("Disconnection of a player"); - logoutPlayer(socket.id) + logoutPlayer(socket.id); }); socket.on("login", (loginTeamId, callback) => { - let team = game.getTeam(loginTeamId); - if (team === undefined) { - socket.emit("login_response", false); - if (typeof callback === "function") { - callback({ isLoggedIn: false, message: "Login denied" }); - } - } else { - logoutPlayer(socket.id) - team.sockets.push(socket.id); - teamId = loginTeamId; - sendUpdatedTeamInformations(loginTeamId); - socket.emit("login_response", true); - socket.emit("game_state", game.state) - socket.emit("game_settings", game.settings) - socket.emit("zone", zone.currentZone) - socket.emit("new_zone", { - begin: zone.currentStartZone, - end: zone.nextZone - }) - if (typeof callback === "function") { - callback({ isLoggedIn : true, message: "Logged in"}); - } + const team = game.getTeam(loginTeamId); + if (!team) { + callback({ isLoggedIn: false, message: "Login denied" }); + return; } + logoutPlayer(socket.id); + team.sockets.push(socket.id); + teamId = loginTeamId; + sendUpdatedTeamInformations(loginTeamId); + socket.emit("login_response", true); + socket.emit("game_state", game.state); + socket.emit("game_settings", game.settings); + socket.emit("zone", zone.currentZone); + socket.emit("new_zone", { + begin: zone.currentStartZone, + end: zone.nextZone + }) + callback({ isLoggedIn : true, message: "Logged in"}); }); socket.on("logout", () => { logoutPlayer(socket.id); + teamId = null; }) socket.on("update_position", (position) => { @@ -108,48 +109,30 @@ export function initTeamSocket() { // This is done to prevent multiple clients from sending slightly different prosition back and forth // Making the point jitter on the map if (!teamId) { - socket.emit("error", "not logged in yet"); - return; - } - let team = game.getTeam(teamId) - if (team == undefined) { - logoutPlayer(socket.id); return; } + const team = game.getTeam(teamId); if (team.sockets.indexOf(socket.id) == 0) { game.updateLocation(teamId, position); - teamBroadcast(teamId, "update_team", { currentLocation: team.currentLocation, ready: team.ready }); - secureAdminBroadcast("teams", game.teams); } }); socket.on("send_position", () => { - game.sendLocation(teamId); - let team = game.getTeam(teamId); - if (team === undefined) { - socket.emit("error", "Team not found"); + if (!teamId) { return; } - game.updateTeamChasing(); - teamBroadcast(teamId, "update_team", { enemyLocation: team.enemyLocation, locationSendDeadline: team.locationSendDeadline, lastSentLocation: team.lastSentLocation }); - secureAdminBroadcast("teams", game.teams) + game.sendLocation(teamId); }); socket.on("capture", (captureCode, callback) => { - let capturedTeam = game.getTeam(teamId)?.chasing; - if (capturedTeam !== undefined && game.requestCapture(teamId, captureCode)) { - sendUpdatedTeamInformations(teamId); - sendUpdatedTeamInformations(capturedTeam); - secureAdminBroadcast("teams", game.teams); - if (typeof callback === "function") { - callback({ hasCaptured : true, message: "Capture successful" }); - } - } else { - socket.emit("error", "Incorrect code"); - if (typeof callback === "function") { - callback({ hasCaptured : false, message: "Capture failed" }); - } + if (!teamId) { + return; } + if (!game.requestCapture(teamId, captureCode)) { + callback({ hasCaptured : false, message: "Capture failed" }); + return; + } + callback({ hasCaptured : true, message: "Capture successful" }); }) }); } \ No newline at end of file diff --git a/traque-back/util.js b/traque-back/util.js deleted file mode 100644 index 497fbf0..0000000 --- a/traque-back/util.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createHash } from "crypto"; - -/** - * Scale a value that is known to be in a range to a new range - * for instance map(50,0,100,1000,2000) will return 1500 as 50 is halfway between 0 and 100 and 1500 is halfway through 1000 and 2000 - * @param {Number} value value to map - * @param {Number} oldMin minimum value of the number - * @param {Number} oldMax maximum value of the number - * @param {Number} newMin minimum value of the output - * @param {Number} newMax maximum value of the output - * @returns - */ -export function map(value, oldMin, oldMax, newMin, newMax) { - return ((value - oldMin) / (oldMax - oldMin)) * (newMax - newMin) + newMin; -} - -export function sha256(password) { - return createHash('sha256').update(password).digest('hex');; -} diff --git a/traque-back/zone_manager.js b/traque-back/zone_manager.js index 2cd6598..73123fe 100644 --- a/traque-back/zone_manager.js +++ b/traque-back/zone_manager.js @@ -1,13 +1,25 @@ /* This module manages the play area during the game, shrinking it over time based of some settings. */ - import { randomCirclePoint } from 'random-location' import { isInCircle } from './map_utils.js'; -import { map } from './util.js'; import { playersBroadcast } from './team_socket.js'; import { secureAdminBroadcast } from './admin_socket.js'; +/** + * Scale a value that is known to be in a range to a new range + * for instance map(50,0,100,1000,2000) will return 1500 as 50 is halfway between 0 and 100 and 1500 is halfway through 1000 and 2000 + * @param {Number} value value to map + * @param {Number} oldMin minimum value of the number + * @param {Number} oldMax maximum value of the number + * @param {Number} newMin minimum value of the output + * @param {Number} newMax maximum value of the output + * @returns + */ +function map(value, oldMin, oldMax, newMin, newMax) { + return ((value - oldMin) / (oldMax - oldMin)) * (newMax - newMin) + newMin; +} + export default { //Setings storing where the zone will start, end and how it should evolve //The zone will start by staying at its max value for reductionInterval minutes