Select Git revision
tournament.js
-
Tobias Ørstad authoredTobias Ørstad authored
tournament.js 20.41 KiB
const express = require("express");
const router = express.Router();
const mongo = require("mongodb");
const MongoClient = mongo.MongoClient;
const functions = require("./functions");
const connectionUrl = process.env.MONGO_CONNECTION_STRING;
router.post("/leave", (req, res) => {
// Removes the player from the tournament
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
let tournamentid, playerid;
try {
tournamentid = mongo.ObjectID(req.body.tournamentid);
playerid = mongo.ObjectID(req.body.playerid);
} catch (err) {
res.status(400).send("Invalid IDs");
return;
}
if (tournamentid == null || playerid == null) {
res.status(400).send("Missing fields");
return;
}
const db = client.db("gameWare");
const collection = "tournaments";
db.collection(collection)
.findOneAndUpdate(
{ _id: tournamentid, players: playerid },
// removes the player from the current players, decrements amount of players
{
$pull: { players: playerid },
$inc: { currentPlayers: -1 },
},
{
returnOriginal: false,
}
)
.then((updatedDocument) => {
if (updatedDocument.value.active === true) {
// If the tournament is active, we want all of the players rounds to be removed
// Finally we check to see if the tournament is left with zero players, which will delete it
db.collection("rounds")
.deleteMany({
tournamentId: tournamentid,
playerId: playerid,
})
.then((result) => {
if (parseInt(updatedDocument.value.currentPlayers) === 0) {
db.collection("tournaments")
.deleteOne({
_id: tournamentid,
})
.then(() => {
res.json({ deletedRounds: result.result.n });
client.close();
return;
});
} else {
res.json({ deletedRounds: result.result.n });
client.close();
return;
}
})
.catch((err) => {
console.log(err);
res.sendStatus(500);
});
} else {
res.json({ deletedRounds: 0 });
client.close();
return;
}
});
}
);
});
router.post("/new", (req, res) => {
// Creates a new tournament
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
if (
!req.body.games ||
!req.body.startDelay ||
!req.body.playerId ||
!req.body.name ||
!req.body.timePerRound ||
!req.body.maxPlayers ||
!req.body.roundsPerGame
) {
res.status(400).send("Missing fields");
return;
}
const db = client.db("gameWare");
const collection = "tournaments";
const tournamentdate = new Date();
const values = {};
try {
// As the games field is a string, we split it up to get an array of IDs
const games = req.body.games
.substring(1, req.body.games.length - 1)
.replace(/\s/g, "")
.split(",");
for (let i = 0; i < games.length; i++) {
games[i] = mongo.ObjectID(games[i]);
}
// Startime is the time for when the tournament proceeds with players less than max, but over two
let startTime = new Date(
tournamentdate.getTime() +
parseInt(req.body.startDelay) * 24 * 60 * 60 * 1000
);
values.players = [mongo.ObjectID(req.body.playerId)];
values.games = games;
(values.name = req.body.name),
(values.timePerRound = parseInt(req.body.timePerRound)),
(values.maxPlayers = parseInt(req.body.maxPlayers)),
(values.roundsPerGame = parseInt(req.body.roundsPerGame)),
(values.currentRound = 1),
(values.dateCreated = tournamentdate),
(values.active = true),
(values.totalGames = games.length * parseInt(req.body.roundsPerGame)),
(values.currentPlayers = 1),
(values.startTime = startTime);
} catch (err) {
res.status(400).send("Invalid fields!");
return;
}
db.collection(collection).insertOne(values, (err, result) => {
if (err) {
res.sendStatus(500); // Internal server error
return;
}
tournamentdate.setTime(
tournamentdate.getTime() + req.body.timePerRound * 24 * 60 * 60 * 1000
);
// Creates the first round
db.collection("rounds").insertOne(
{
tournamentId: mongo.ObjectID(result.insertedId),
playerId: mongo.ObjectID(req.body.playerId),
gameId: values.games[0],
scoreValue: 0,
roundNr: 1,
hasPlayed: false,
deadlineDate: tournamentdate,
tournamentPoints: 0,
},
(err) => {
if (err) {
res.sendStatus(500); // Internal server error
return;
}
// returns the tournament
res.json(result.ops[0]);
client.close();
}
);
});
}
);
});
router.post("/join", (req, res) => {
// Joins the oldest tournament on round 1 for a player
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
if (!req.body.playerId) {
res.sendStatus(400);
return;
}
let playerId;
try {
playerId = mongo.ObjectID(req.body.playerId);
} catch (err) {
res.sendStatus(400);
return;
}
const db = client.db("gameWare");
const collection = "tournaments";
db.collection(collection)
.findOneAndUpdate(
{
// the length of the array of players must be less than the maximum amount
$expr: { $gt: ["$maxPlayers", "$currentPlayers"] },
currentRound: 1,
active: true,
// the array of players cannot already contain the player
players: { $nin: [playerId] },
// we dont want tournaments that have gone past their starttime
startTime: { $gte: new Date() },
},
// Add the player to the array
{
$push: { players: playerId },
$inc: { currentPlayers: 1 },
},
{
// make sure we join the oldest tournament that matches our query.
sort: { dateCreated: 1 },
// the returned document should be the modified version
returnOriginal: false,
}
)
.then((updatedDocument) => {
if (updatedDocument.value) {
try {
tournamentdate = updatedDocument.value.dateCreated;
tournamentdate.setTime(
tournamentdate.getTime() +
updatedDocument.value.timePerRound * 24 * 60 * 60 * 1000
);
let tournyid = updatedDocument.value._id;
// create the first round for the newly joined player
db.collection("rounds").insertOne(
{
tournamentId: mongo.ObjectID(tournyid),
playerId: playerId,
gameId: mongo.ObjectID(updatedDocument.value.games[0]),
scoreValue: 0,
roundNr: 1,
hasPlayed: false,
deadlineDate: tournamentdate,
tournamentPoints: 0,
},
(err, result) => {
if (err) {
res.sendStatus(500); // Internal server error
return;
}
res.json(updatedDocument.value);
}
);
} catch (err) {
res.sendStatus(500); // Server error
}
} else {
res.status(404).send("No available tournaments");
}
});
}
);
});
router.get("/playeractive/:userid", (req, res) => {
//remove method
// Connect to database
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
// Using the database gameWare and collection tournaments
const db = client.db("gameWare");
const collection = "tournaments";
const id = mongo.ObjectId(req.params.userid);
db.collection(collection)
.find({
_id: id,
active: true,
})
.toArray((err, result) => {
if (err) {
res.sendStatus(500);
return;
}
res.json(result);
client.close();
});
}
);
});
router.get("/specific/:id", (req, res) => {
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
let id;
if (!req.params.id) {
res.sendStatus(400);
}
try {
id = mongo.ObjectID(req.params.id);
} catch (err) {
res.sendStatus(400);
}
// Using the database gameWare and collection tournaments
const db = client.db("gameWare");
const collection = "tournaments";
db.collection(collection)
.find({
_id: id,
})
.toArray((err, result) => {
if (err) {
res.sendStatus(500);
client.close();
return;
}
res.json(result[0]);
client.close();
});
}
);
});
router.get("/player/:userid/:active", (req, res) => {
// Returns the tournaments of player with active field matching parameter
// Also checks the state of all tournaments
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
// Using the database gameWare and collection tournaments
const db = client.db("gameWare");
const collection = "tournaments";
let id;
try {
id = mongo.ObjectId(req.params.userid);
} catch {
res.status(500).send("Invalid ID");
}
if (!id) {
res.status(500).send("Missing ID");
}
db.collection(collection)
.find({
players: id,
active: req.params.active === "true",
})
.toArray((err, result) => {
if (err) {
res.sendStatus(500);
return;
}
if (req.params.active === "true") {
// Checks the tournament states if want the active tournaments
let IDs = result.map((tourny) => tourny._id); // We need the IDs
doChecks(client, IDs).then(() => {
db.collection(collection)
.find({
// Now we fetch the tournaments again, as the state might have updated in the doChecks part
players: id,
active: req.params.active === "true",
})
.toArray((err, result) => {
if (err) {
res.sendStatus(500);
//console.log(err);
return;
}
res.json(result);
client.close();
return;
});
});
} else {
res.json(result);
client.close();
}
});
}
);
});
async function doChecks(client, ids) {
// Calls the roundcheck method on all tournaments
for (const tourny of ids) {
let id = mongo.ObjectID(tourny);
const check = await roundcheck(client, id);
}
return;
}
router.put("/roundcheck/:tournamentId", (req, res) => {
// Checks the state of the tournament, involves checking if players have been timed out,
// if tournament can move on, and if the tournament is over.
let tournament = null;
try {
tournament = mongo.ObjectID(req.params.tournamentId);
} catch (error) {
res.status(400).send("No document with specified id was found");
return;
}
if (!tournament) {
res.status(400).send("Request body not valid");
return;
}
MongoClient.connect(
connectionUrl,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err, client) => {
// Unable to connect to database
if (err) {
res.sendStatus(500); // Internal server error
return;
}
roundcheck(client, tournament).then((result) => {
if (result == 500) {
res.sendStatus(500);
return;
} else if (result == 400) {
res.sendStatus(400);
return;
}
res.json(result);
client.close();
return;
});
}
);
});
function roundcheck(client, tournament) {
return new Promise((resolve, reject) => {
functions.getCurrentRound(client, tournament).then((roundResult) => {
// gets the current round of the tournament
if (roundResult == 500) {
resolve(500);
} else if (roundResult.active === false) {
resolve(400);
}
// saves information we want
const active = roundResult.currentRound;
const start = roundResult.startTime;
const maxPlayers = roundResult.maxPlayers;
const currentPlayers = roundResult.currentPlayers;
const name = roundResult.name;
functions.fetchRounds(client, tournament, active).then((rounds) => {
// fetched the round objects of the active round
functions
.checkRounds(rounds, start, maxPlayers, active, currentPlayers)
.then((resultarray) => {
// check to find out if [0] tournament will move on, [1] which players are timed out
// [2] which players are still in the tournament, also fetched the rounds of the timed out players [3]
let result = resultarray[0];
let timedOut = resultarray[1];
let left = resultarray[2];
let timedOutRounds = resultarray[3];
let end = false;
if ((left.length < 2 && active != 1) || left.length == 0) {
end = true;
//if there are less then two people left in the first round, or zero people left in the first round
//the tournament ends
}
if (rounds.length > 0) {
// If there is only one player in the tournaments first round and the starttime
// has expired, we end the tournament
if (
left.length < 2 &&
active == 1 &&
new Date(start) < new Date()
) {
end = true;
}
}
functions
.timeOut(client, tournament, timedOut, timedOutRounds)
.then(
//updates the state of the tournament and ends it if necessary
() => {
functions
.notifyPlayers(client, timedOut, true, tournament, name)
.then(() => {
if (end) {
functions
.notifyPlayers(client, left, false, tournament, name)
.then(() => {
functions
.endTournament(client, tournament)
.then(() => {
resolve({
active: false,
nextRound: false,
});
});
});
} else if (result) {
//Tournament round is complete
let eligibleRounds = [];
for (let i = 0; i < rounds.length; i++) {
let round = rounds[i];
if (round.hasPlayed === true) {
eligibleRounds.push(round);
}
} //Here we find the rounds that were actually completed
for (let i = 0; i < eligibleRounds.length; i++) {
let round = eligibleRounds[i];
functions.updateScore(
client,
round._id,
i + 1,
eligibleRounds.length
);
//And here we update the tournament score for those rounds
}
functions
.updateTournamentRound(client, tournament)
.then((result) => {
//updates the tournaments round if there are more rounds left
if (result.value) {
// If the tournament has more rounds left
tourny = result.value;
const game =
tourny.games[
(tourny.currentRound - 1) %
tourny.games.length
];
const date = new Date();
date.setTime(
date.getTime() +
tourny.timePerRound * 24 * 60 * 60 * 1000
);
functions
.newRound(
client,
tournament,
tourny.players,
game,
tourny.currentRound,
date
)
.then(() => {
resolve({
active: true,
nextRound: true,
});
});
// created new rounds for all the players left
} else {
// if the tournament now is done we simply end it
functions
.notifyPlayers(
// alerts the players that the tournament is done
client,
left,
false,
tournament,
name
)
.then(() => {
functions
.endTournament(client, tournament)
.then(() => {
resolve({
active: false,
nextRound: false,
});
});
});
}
});
} else {
resolve({
active: true,
nextRound: false,
}); // Tournament still waiting for other players
}
});
}
);
});
});
});
});
}
module.exports = router;