mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 02:10:18 +01:00
improved documentation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user