You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
15 KiB
585 lines
15 KiB
const path = require("path");
|
|
const express = require("express");
|
|
const app = express();
|
|
const jwt = require('jsonwebtoken');
|
|
const cookieParser = require('cookie-parser');
|
|
const bodyParser = require("body-parser");
|
|
const bcrypt = require('bcrypt');
|
|
const http = require("http").Server(app);
|
|
const io = require("socket.io")(http);
|
|
|
|
const db = require(path.normalize(`${__dirname}/database.js`))
|
|
const { Room } = require(path.normalize(`${__dirname}/models/Room.js`));
|
|
const { Player } = require(path.normalize(`${__dirname}/models/Player.js`));
|
|
const secretKey = process.env.COOKIE_SECRET_KEY;
|
|
|
|
const port = 3000;
|
|
app.use(express.static("public"))
|
|
app.use(bodyParser.json());
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use(cookieParser());
|
|
|
|
// #region routing and cookies
|
|
|
|
app.get('/', (req, res) => {
|
|
return res.sendFile(path.normalize(path.join(__dirname, '/public/pages/connectionView.html')))
|
|
})
|
|
|
|
app.get('/scoreboard', (req, res) => {
|
|
return res.sendFile(path.normalize(path.join(__dirname, '/public/pages/scoreboardView.html')))
|
|
})
|
|
|
|
app.get('/register', (req, res) => {
|
|
return res.sendFile(path.normalize(path.join(__dirname, '/public/pages/signupView.html')))
|
|
})
|
|
|
|
app.get('/game', (req, res) => {
|
|
return res.sendFile(path.normalize(path.join(__dirname, '/public/pages/gameView.html')))
|
|
})
|
|
|
|
app.get('/error', (req, res) => {
|
|
res.status(200)
|
|
return res.sendFile(path.normalize(path.join(__dirname, '/public/pages/errorView.html')))
|
|
})
|
|
|
|
app.post('/logIn', (req, res) => {
|
|
const { pseudo, password } = req.body;
|
|
|
|
if (!pseudo || !password) {
|
|
return res.status(400).send({message:'pseudo and password are required.'});
|
|
}
|
|
|
|
const query = 'SELECT hashed_password FROM users WHERE pseudo = ?';
|
|
db.query(query, [pseudo], async (err, results) => {
|
|
if (err) {
|
|
console.error('Error selecting user into the database:', err);
|
|
return res.status(500).send({message: 'Internal server error.'});
|
|
}
|
|
if (results.length === 1) {
|
|
const user = results[0]
|
|
if (await bcrypt.compare(password, user.hashed_password)) {
|
|
const token = jwt.sign({ pseudo }, secretKey, { expiresIn: '1h' });
|
|
res.cookie('authToken', token, { httpOnly: true, secure: false });
|
|
|
|
return res.status(201).send({message: 'User logged in successfully.', redirectUrl: '/game' });
|
|
} else {
|
|
return res.status(401).send({message: "Password is incorrect"})
|
|
}
|
|
} else {
|
|
return res.status(401).send({message: "Username is incorrect"})
|
|
}
|
|
})
|
|
})
|
|
|
|
app.post('/register', async (req, res) => {
|
|
const { pseudo, password } = req.body;
|
|
|
|
if (!pseudo || !password) {
|
|
return res.status(400).send({message:'Pseudo and password are required.'});
|
|
}
|
|
|
|
if (pseudo.length > 255 || password.length > 255) {
|
|
return res.status(403).send({message:'Pseudo and password must be 255 caracters max'});
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
const getPseudoQuery = 'SELECT pseudo FROM users WHERE pseudo = ?'
|
|
db.query(getPseudoQuery, [pseudo], (err, results) => {
|
|
if (err) {
|
|
console.error('Error retrieving user info from the database:', err);
|
|
return res.status(500).send('Internal server error.');
|
|
}
|
|
if (results.length !== 0) {
|
|
return res.status(403).send({message:'User already exist. Try another username'});
|
|
} else {
|
|
var query = 'INSERT INTO users (pseudo, hashed_password) VALUES (?, ?)';
|
|
db.query(query, [pseudo, hashedPassword], (err, results) => {
|
|
if (err) {
|
|
console.error('Error inserting user into the database:', err);
|
|
return res.status(500).send('Internal server error.');
|
|
}
|
|
|
|
query = 'SELECT id FROM users WHERE pseudo = ?'
|
|
db.query(query, [pseudo], (err, results) => {
|
|
if (err) {
|
|
console.error('Error inserting user into the database:', err);
|
|
return res.status(500).send('Internal server error.');
|
|
}
|
|
if (results.length === 1) {
|
|
query = 'INSERT INTO score (playerId) VALUES (?)';
|
|
db.query(query, [results[0].id], (err, results) => {
|
|
if (err) {
|
|
console.error('Error inserting user into the database:', err);
|
|
return res.status(500).send('Internal server error.');
|
|
}
|
|
})
|
|
}
|
|
});
|
|
|
|
const token = jwt.sign({ pseudo }, secretKey, { expiresIn: '1h' });
|
|
res.cookie('authToken', token, { httpOnly: true, secure: false });
|
|
|
|
res.status(201).send({message: 'User registered successfully.', redirectUrl: '/game' });
|
|
})
|
|
}
|
|
});
|
|
});
|
|
|
|
app.get('/user-info', (req, res) => {
|
|
const token = req.cookies.authToken;
|
|
|
|
if (!token) {
|
|
return res.status(401).send('Access denied. No token provided.');
|
|
}
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, secretKey);
|
|
const query = 'SELECT pseudo FROM users WHERE pseudo = ?';
|
|
db.query(query, [decoded.pseudo], (err, results) => {
|
|
if (err) {
|
|
console.error('Error retrieving user info from the database:', err);
|
|
return res.status(500).send('Internal server error.');
|
|
}
|
|
if (results.length === 0) {
|
|
return res.status(401).send('User not found.');
|
|
}
|
|
return res.json(results[0]);
|
|
});
|
|
} catch (ex) {
|
|
console.log(ex)
|
|
return res.status(400).send('Invalid token.');
|
|
}
|
|
});
|
|
|
|
// #endregion routing and cookies
|
|
|
|
|
|
// #region socket and game
|
|
|
|
let matchmaking_stack = [];
|
|
let rooms = [];
|
|
let players = [];
|
|
|
|
io.on("connection", (socket) => {
|
|
console.log("New connected : ", socket.id);
|
|
|
|
socket.on("disconnect", () => {
|
|
const index = players.findIndex((p) => p.id === socket.id)
|
|
const roomIndex = rooms.findIndex(room =>
|
|
room.players.some((player) => player.id === socket.id)
|
|
);
|
|
|
|
if (roomIndex !== -1) {
|
|
const room = rooms[roomIndex];
|
|
const opponent = room.players.find(player => player.id !== socket.id);
|
|
const inRoomIndex = room.players.findIndex(player => player.id === socket.id);
|
|
|
|
if (opponent) {
|
|
io.to(opponent.id).emit('opponent left');
|
|
}
|
|
|
|
room.players.splice(inRoomIndex, 1)
|
|
|
|
if (room.players.length === 0) {
|
|
rooms.splice(roomIndex, 1);
|
|
}
|
|
}
|
|
|
|
if (index !== -1) {
|
|
players.splice(index, 1)
|
|
}
|
|
|
|
console.log(`Player disconnected: ${socket.id}`);
|
|
})
|
|
|
|
socket.on("handle error", (id, roomId) => {
|
|
const room = rooms.find((r) => r.id === roomId)
|
|
|
|
if (room != null) {
|
|
const playerIndex = room.players.findIndex((p) => p.id === id)
|
|
room.players.splice(playerIndex, 1)
|
|
|
|
if (room.players.length > 0) {
|
|
io.to(room.players[0].id).emit("opponent left")
|
|
} else {
|
|
const roomIndex = rooms.findIndex((r) => r.id === roomId)
|
|
rooms.splice(roomIndex, 1)
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on("first connection", (socketId) => {
|
|
const cookies = socket.request.headers.cookie;
|
|
if (!cookies) return
|
|
|
|
const authToken = cookies.split('; ').find(cookie => cookie.startsWith('authToken=')).split('=')[1];
|
|
if (authToken) {
|
|
try {
|
|
const decoded = jwt.verify(authToken, secretKey);
|
|
const username = decoded.pseudo;
|
|
let player = new Player(socketId, username);
|
|
|
|
const query = 'SELECT id FROM users WHERE pseudo = ?'
|
|
db.query(query, [player.username], (err, results) => {
|
|
if (err) {
|
|
// TODO
|
|
}
|
|
if (results.length === 1) {
|
|
player.dbId = results[0].id
|
|
}
|
|
});
|
|
|
|
players.push(player);
|
|
} catch (ex) {
|
|
console.error('Invalid token:', ex);
|
|
}
|
|
} else {
|
|
console.log('No auth token found in cookies.');
|
|
}
|
|
});
|
|
|
|
socket.on("Hello", (callback) => {
|
|
callback({
|
|
Hello: "World",
|
|
});
|
|
});
|
|
|
|
socket.on("room creation", (id, callback) => {
|
|
let room = new Room();
|
|
room.addPlayer(players.find((p) => p.id === id));
|
|
rooms.push(room);
|
|
|
|
callback({
|
|
roomId: room.id,
|
|
});
|
|
});
|
|
|
|
socket.on("ask for room", (roomId, id, callback) => {
|
|
let room = rooms.find((r) => r.id === roomId);
|
|
|
|
try {
|
|
if (room == null) {
|
|
callback({
|
|
status: false,
|
|
message: "No room for this code"
|
|
})
|
|
return
|
|
}
|
|
|
|
if (room.players.length >= 2) {
|
|
callback({
|
|
status: false,
|
|
message: "Room is full"
|
|
})
|
|
return
|
|
}
|
|
|
|
callback({
|
|
status: true
|
|
})
|
|
|
|
const player1 = players.find((p) => p.id === id);
|
|
const player2 = room.players.find((p) => p.id !== player1.id)
|
|
|
|
room.addPlayer(player1);
|
|
room.validBoards();
|
|
|
|
const ids = [player1.dbId, player2.dbId].sort((a, b) => a - b)
|
|
|
|
var query = 'SELECT * FROM scoreboards WHERE player1 = ? AND player2 = ?';
|
|
db.query(query, [ids[0], ids[1]], async (err, results) => {
|
|
if (err) {
|
|
// TODO
|
|
}
|
|
if (results.length === 0) {
|
|
query = 'INSERT INTO scoreboards (player1, player2) VALUES (?, ?)';
|
|
db.query(query, [ids[0], ids[1]], async (err, results) => {
|
|
if (err) {
|
|
console.log(err)
|
|
}
|
|
if (results.length === 1) {
|
|
console.log("Scoreboard create in base")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
for (let i = 0; i < room.players.length; i++) {
|
|
io.to(room.players[i].id).emit("start game")
|
|
}
|
|
|
|
askToPlay(room.start());
|
|
} catch (e){
|
|
console.log(e)
|
|
callback({
|
|
status: false,
|
|
reason: "exception"
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on("join matchmaking", (id, callback) => {
|
|
try {
|
|
let player = players.find((p) => p.id === id)
|
|
|
|
if (matchmaking_stack.length > 0) {
|
|
let room = matchmaking_stack.pop();
|
|
room.addPlayer(player);
|
|
|
|
callback({
|
|
status: true,
|
|
roomId: room.id,
|
|
})
|
|
|
|
rooms.push(room)
|
|
room.validBoards();
|
|
for (let i = 0; i < room.players.length; i++) {
|
|
io.to(room.players[i].id).emit("start game")
|
|
}
|
|
askToPlay(room.start());
|
|
} else {
|
|
let room = new Room();
|
|
room.addPlayer(player);
|
|
matchmaking_stack.push(room);
|
|
callback({
|
|
status: true,
|
|
roomId: room.id,
|
|
})
|
|
}
|
|
} catch (e) {
|
|
console.log(e);
|
|
callback({
|
|
status: false,
|
|
reason: "exception",
|
|
})
|
|
}
|
|
})
|
|
|
|
socket.on("play", (roomId, id, move, callback) => {
|
|
let room = rooms.find((r) => r.id === roomId);
|
|
|
|
try {
|
|
const results = room.move(move)
|
|
|
|
if (results.isWin) {
|
|
const winner = results.players.find((p) => p.id === results.player)
|
|
const loser = results.players.find((p) => p.id !== winner.id)
|
|
const alteredRow = "player" + (winner.dbId > loser.dbId ? 2 : 1) + "Win"
|
|
const ids = [winner.dbId, loser.dbId].sort((a, b) => a - b)
|
|
|
|
var query = 'UPDATE score SET wins = wins + 1 WHERE playerId = ?'
|
|
db.query(query, [winner.dbId], (err, results) => {
|
|
if (err) {
|
|
console.log(err)
|
|
}
|
|
})
|
|
|
|
query = 'UPDATE score SET loses = loses + 1 WHERE playerId = ?'
|
|
db.query(query, [loser.dbId], (err, results) => {
|
|
if (err) {
|
|
console.log(err)
|
|
}
|
|
})
|
|
|
|
query = `UPDATE scoreboards SET ${alteredRow} = ${alteredRow} + 1 WHERE player1 = ? AND player2 = ?`
|
|
db.query(query, [ids[0], ids[1]], (err, results) => {
|
|
if (err) {
|
|
console.log(err)
|
|
}
|
|
});
|
|
}
|
|
|
|
sendMoveToPlayers(results);
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on("get player", (roomId, id, callback) => {
|
|
let out = ""
|
|
const room = rooms.find((r) => r.id === roomId)
|
|
|
|
if (room == null) {
|
|
out = players.find((p) => p.id === id);
|
|
} else {
|
|
out = room.players.find((p) => p.id === id)
|
|
}
|
|
|
|
callback({
|
|
player: out,
|
|
});
|
|
});
|
|
|
|
socket.on("get ennemy", (roomId, id, callback) => {
|
|
const room = rooms.find((r) => r.id === roomId);
|
|
|
|
try {
|
|
const out = room.players.find((p) => p.id !== id);
|
|
|
|
callback({
|
|
status: true,
|
|
player: out,
|
|
});
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on("update grid", (id, grid, callback) => {
|
|
const player = players.find((p) => p.id === id);
|
|
|
|
try {
|
|
player.grid = grid;
|
|
callback({
|
|
status: true,
|
|
});
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on("game ended", (roomId, callback) => {
|
|
const roomIndex = rooms.findIndex((r) => r.id === roomId)
|
|
|
|
try {
|
|
rooms[roomIndex].players.forEach(player => {
|
|
player.resetGrid()
|
|
io.to(player.id).emit("go to menu")
|
|
});
|
|
|
|
rooms.splice(roomIndex, 1)
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
})
|
|
socket.on("reset grid", (roomId, callback) => {
|
|
const player = rooms.find((r) => r.id === roomId).players[0]
|
|
try {
|
|
player.resetGrid();
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
})
|
|
|
|
socket.on("update piece", (playerId, piece, callback) => {
|
|
const player = players.find((p) => p.id === playerId);
|
|
|
|
try {
|
|
const index = player.pieces.findIndex((p) => p.id === piece.id);
|
|
|
|
player.pieces[index] = piece;
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
});
|
|
|
|
socket.on("change selection status", (playerId, pieceId, status, callback) => {
|
|
try {
|
|
players
|
|
.find((p) => p.id === playerId)
|
|
.pieces.find((piece) => piece.id === pieceId).isSelected = status;
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
});
|
|
|
|
// #region rematch hanlding
|
|
|
|
socket.on("ask for rematch", (roomId, playerId, callback) => {
|
|
const room = rooms.find((r) => r.id === roomId)
|
|
|
|
try {
|
|
const opponent = room.players.find((p) => p.id !== playerId)
|
|
|
|
io.to(opponent.id).emit("ask for rematch");
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
statu: false
|
|
})
|
|
}
|
|
})
|
|
|
|
socket.on("rematch grid", (roomId, callback) => {
|
|
const room = rooms.find((r) => r.id === roomId)
|
|
|
|
try {
|
|
room.players.forEach(p => {
|
|
p.resetGrid()
|
|
io.to(p.id).emit("rematch grid")
|
|
})
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
})
|
|
|
|
socket.on("valid grid", (roomId, callback) => {
|
|
const room = rooms.find((r) => r.id === roomId)
|
|
|
|
try {
|
|
room.wantRematch = room.wantRematch + 1
|
|
|
|
if (room.wantRematch === 2) {
|
|
room.validBoards();
|
|
room.wantRematch = 0
|
|
room.players.forEach(p => {
|
|
io.to(p.id).emit("start game")
|
|
})
|
|
askToPlay(room.start());
|
|
}
|
|
} catch (e) {
|
|
console.log(e)
|
|
callback({
|
|
status: false
|
|
})
|
|
}
|
|
})
|
|
|
|
// #endregion rematch hanlding
|
|
});
|
|
|
|
const askToPlay = (player) => {
|
|
io.to(player).emit("play")
|
|
}
|
|
|
|
const sendMoveToPlayers = (moveData) => {
|
|
if (moveData.isMove === true) {
|
|
for (let i = 0; i <= 1; i++) {
|
|
io.to(moveData.players[i].id).emit("played move", moveData.isHit, moveData.isWin);
|
|
}
|
|
}
|
|
askToPlay(moveData.player)
|
|
};
|
|
|
|
// #endregion socket and game
|
|
|
|
|
|
http.listen(port, () => {
|
|
//console.log(`Listening on http://localhost:${port}`);
|
|
});
|