improved documentation

This commit is contained in:
2024-06-10 22:14:24 +00:00
parent 9e595baf4b
commit 303cc83c54
8 changed files with 172 additions and 21 deletions

View File

@@ -13,8 +13,5 @@ RUN npm install
# Copy the rest of project files into this image
COPY . .
# Expose application port
EXPOSE 3001
# Start the application
CMD npm start

View File

@@ -1,3 +1,8 @@
/*
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 zone from "./zone_manager.js"

View File

@@ -1,3 +1,6 @@
/*
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";
@@ -5,6 +8,9 @@ import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js"
import penaltyController from "./penalty_controller.js";
import zoneManager from "./zone_manager.js";
/**
* The possible states of the game
*/
export const GameState = {
SETUP: "setup",
PLACEMENT: "placement",
@@ -13,8 +19,11 @@ export const GameState = {
}
export default {
//List of teams, as objects. To see the fields see the addTeam methods
teams : [],
//Current state of the game
state : GameState.SETUP,
//Settings of the game
settings : {
loserEndGameMessage: "",
winnerEndGameMessage: "",
@@ -22,11 +31,21 @@ export default {
waitingMessage: "Jeu en préparation, veuillez patienter."
},
/**
* Update the game settings
* @param {Object} newSettings settings to be updated, can be partial
* @returns true if the settings are applied
*/
changeSettings(newSettings) {
this.settings = {...this.settings, ...newSettings};
return true;
},
/**
* Change the state of the game to newState and start the necessary processes
* @param {String} newState
* @returns true if the state has been changed
*/
setState(newState) {
if (Object.values(GameState).indexOf(newState) == -1) {
return false;
@@ -65,6 +84,10 @@ export default {
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)) {
@@ -73,10 +96,19 @@ export default {
return id;
},
/**
* Return a random capture code
* @returns a random 4 digit number
*/
createCaptureCode() {
return Math.floor(Math.random() * 10000)
},
/**
* Add a new team to the game
* @param {String} teamName the name of the team
* @returns true if the team has been added
*/
addTeam(teamName) {
let id = this.getNewTeamId();
this.teams.push({
@@ -100,6 +132,10 @@ export default {
return true;
},
/**
* Count the number of teams that are not captured
* @returns the number of teams that are not captured
*/
playingTeamCount() {
let res = 0;
this.teams.forEach((t) => {
@@ -110,6 +146,12 @@ export default {
return res;
},
/**
* 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 enemyName, chasing and chased values of each teams
* @returns true if successful
*/
updateTeamChasing() {
if (this.playingTeamCount() <= 2) {
if (this.state == GameState.PLAYING) {
@@ -138,15 +180,31 @@ export default {
return true;
},
/**
* Rearrange the order of the teams and update the chasing chain
* @param {Array} newOrder An array of teams in the new order
* @returns
*/
reorderTeams(newOrder) {
this.teams = newOrder;
return this.updateTeamChasing();
},
/**
* 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
* @returns true if the team has been updated
*/
updateTeam(teamId, newTeam) {
this.teams = this.teams.map((t) => {
if (t.id == teamId) {
@@ -160,11 +218,20 @@ export default {
return true;
},
/**
*
* @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
* @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) {
return false;
}
@@ -176,8 +243,9 @@ export default {
return true;
},
//Make it so that when a team requests the location of a team that has never sent their locaiton
//Their position at the begining of the game is sent
/**
* Initialize the last sent location of the teams to their starting location
*/
initLastSentLocations() {
for (let team of this.teams) {
team.lastSentLocation = team.currentLocation;
@@ -186,6 +254,11 @@ export default {
}
},
/**
* 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
* @returns true if the location has been sent
*/
sendLocation(teamId) {
let team = this.getTeam(teamId);
if (team == undefined) {
@@ -203,6 +276,11 @@ export default {
return team;
},
/**
* Remove a team by its ID
* @param {Number} teamId The id of the team to remove
* @returns true if the team has been deleted
*/
removeTeam(teamId) {
if (this.getTeam(teamId) == undefined) {
return false;
@@ -254,6 +332,9 @@ export default {
return zoneManager.udpateSettings(newSettings)
},
/**
* Set the game state as finished, as well as resetting the zone manager
*/
finishGame() {
this.setState(GameState.FINISHED);
zoneManager.reset();

View File

@@ -1,3 +1,10 @@
/**
* 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
*/
export 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
@@ -12,10 +19,22 @@ 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
* @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
*/
export function isInCircle(position, center, radius) {
return getDistanceFromLatLon(position, center) < radius;
}

View File

@@ -1,3 +1,6 @@
/*
This module manages the verification of the game rules and the penalties.
*/
import { isInCircle } from "./map_utils.js";
import { sendUpdatedTeamInformations, teamBroadcast } from "./team_socket.js";
import { secureAdminBroadcast } from "./admin_socket.js";
@@ -5,15 +8,22 @@ import game, {GameState} from "./game.js";
import zone from "./zone_manager.js";
export default {
// Object mapping team id to the date they left the zone as a UNIX millisecond timestamp
outOfBoundsSince: {},
// Id of the interval checking the rules
checkIntervalId: null,
settings: {
//Time in minutes before a team is penalized for not updating their position
allowedTimeOutOfZone: 10,
//Time in minutes before a team is penalized for not updating their position
allowedTimeBetweenPositionUpdate: 10,
//Number of penalties needed to be eliminated
maxPenalties: 3
},
/**
* Start the penalty controller, watch the team positions and apply penalties if necessary
*/
start() {
this.outOfBoundsSince = {};
if (this.checkIntervalId) {
@@ -28,6 +38,9 @@ export default {
}, 100);
},
/**
* Stop the penalty controller
*/
stop() {
this.outOfBoundsSince = {};
if (this.checkIntervalId) {
@@ -36,6 +49,11 @@ export default {
}
},
/**
* Update the penalty controller settings
* @param {Object} newSettings the object containing the settings to be udpated, can be partial
* @returns true if the settings were updated, false otherwise
*/
updateSettings(newSettings) {
//Sanitize input
if (newSettings.maxPenalties && (isNaN(parseInt(newSettings.maxPenalties)) || newSettings.maxPenalties < 0)) { return false }
@@ -72,6 +90,10 @@ export default {
secureAdminBroadcast("teams", game.teams)
},
/**
* Check if each team has too many penalties and eliminate them if necessary
* Also send a socket message to the team and the chased team
*/
checkPenalties() {
for (let team of game.teams) {
if (team.penalties >= this.settings.maxPenalties) {
@@ -84,6 +106,11 @@ export default {
}
},
/**
* Watch the position of each team and apply penalties if necessary
* If a team is out of the zone, a warning will be sent to them
* A warning is also sent one minute before the penalty is applied
*/
watchZone() {
game.teams.forEach((team) => {
if (team.captured) { return }
@@ -110,7 +137,10 @@ export default {
})
},
/**
* Watch the date of the last position update of each team and apply penalties if necessary
* Also send a message one minute before the penalty is applied
*/
watchPositionUpdate() {
game.teams.forEach((team) => {
//If the team has not sent their location for more than the allowed period, automatically send it and add a penalty

View File

@@ -1,3 +1,6 @@
/*
This module manages the handler for uploading photos, as well as serving the correct file on requests based on the team ID and current game state
*/
import { app } from "./index.js";
import multer from "multer";
import fs from "fs";
@@ -10,27 +13,21 @@ const ALLOWED_MIME = [
"image/gif"
]
//Setup multer (the file upload middleware)
const storage = multer.diskStorage({
// Save the file in the uploads directory
destination: function (req, file, callback) {
callback(null, UPLOAD_DIR);
},
// Save the file with the team ID as the filename
filename: function (req, file, callback) {
callback(null, req.query.team);
}
});
function clean() {
const files = fs.readdirSync(UPLOAD_DIR);
for (const file of files) {
const filePath = path.join(UPLOAD_DIR, file);
fs.unlinkSync(filePath);
}
}
const upload = multer({
storage,
//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);
@@ -43,13 +40,25 @@ const upload = multer({
})
//Clean the uploads directory
function clean() {
const files = fs.readdirSync(UPLOAD_DIR);
for (const file of files) {
const filePath = path.join(UPLOAD_DIR, file);
fs.unlinkSync(filePath);
}
}
export function initPhotoUpload() {
clean();
//App handler for uploading a photo and saving it to a file
app.post("/upload", upload.single('file'), (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
console.log("upload", req.query)
res.send("")
})
//App handler for serving the photo of a team given its secret ID
app.get("/photo/my", (req, res) => {
let team = game.getTeam(Number(req.query.team));
if (team) {
@@ -60,6 +69,7 @@ export function initPhotoUpload() {
res.send(400, "Team not found")
}
})
//App handler for serving the photo of the team chased by the team given by its secret ID
app.get("/photo/enemy", (req, res) => {
let team = game.getTeam(Number(req.query.team));
if (team) {

View File

@@ -1,3 +1,8 @@
/*
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";

View File

@@ -1,3 +1,7 @@
/*
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';
@@ -6,11 +10,9 @@ import { secureAdminBroadcast } from './admin_socket.js';
export default {
//Setings storing where the zone will start, end and how it should evolve
//The zone will start by staying at its mzx value for reductionInterval minutes
//The zone will start by staying at its max value for reductionInterval minutes
//and then reduce during reductionDuration minutes, then wait again...
//The reduction factor is such that after reductionCount the zone will be the min zone
//a call to onZoneUpdate will be made every updateIntervalSeconds when the zone is changing
//a call to onNextZoneUpdate will be made when the zone reduction ends and a new next zone is announced
zoneSettings: {
min: { center: null, radius: null },
max: { center: null, radius: null },
@@ -194,11 +196,13 @@ export default {
}, this.zoneSettings.updateIntervalSeconds * 1000);
},
//a call to onNextZoneUpdate will be made when the zone reduction ends and a new next zone is announced
onNextZoneUpdate(newZone) {
playersBroadcast("new_zone", newZone)
secureAdminBroadcast("new_zone", newZone)
},
},
//a call to onZoneUpdate will be made every updateIntervalSeconds when the zone is changing
onZoneUpdate(zone) {
playersBroadcast("zone", zone)
secureAdminBroadcast("zone", zone)