game_room system added + readme changelog
This commit is contained in:
@@ -24,4 +24,25 @@ Gestion de friendship dans POSTGRESQL:
|
|||||||
Ressource:
|
Ressource:
|
||||||
https://www.postgresql.org/docs/
|
https://www.postgresql.org/docs/
|
||||||
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
||||||
https://docs.github.com/fr/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
|
https://docs.github.com/fr/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
BACKEND
|
||||||
|
|
||||||
|
17/01 Ajout du service/route pour le systeme de game_room
|
||||||
|
permet aux joueurs de creer et rejoindre des rooms
|
||||||
|
une room vide est automatiquement detruite.
|
||||||
|
Presence d'une fonction affichant toutes les rooms joignables
|
||||||
|
ainsi qu'une autre fonction pour afficher tous les joueurs de la room avec
|
||||||
|
leur scores et leur etat actuel.
|
||||||
|
Aucun moyen de changer l'etat de la room de waiting a en cours ou finished
|
||||||
|
ca attendra le systeme du jeu
|
||||||
|
|
||||||
|
DATABASE
|
||||||
|
|
||||||
|
17/01 Ajout des tables game_rooms, game_players, game_rounds, words
|
||||||
|
- nom, status et parametres de la game
|
||||||
|
- joueurs dans la game, leur scores et leur role actuel (dessinateur, devineur)
|
||||||
|
- historique de la game, qui a dessine quoi precedemment ainsi que les timers des rounds, sera aussi utile si on veut faire les stats de compte a l'avenir.
|
||||||
|
- contient la liste des mots utilisable par les joueurs
|
||||||
@@ -69,6 +69,44 @@ async function createTables()
|
|||||||
created_at TIMESTAMP DEFAULT NOW(),
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
UNIQUE(provider, client_id)
|
UNIQUE(provider, client_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS game_rooms (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
status VARCHAR(20) DEFAULT 'waiting',
|
||||||
|
max_players INT DEFAULT 8,
|
||||||
|
current_round INT DEFAULT 0,
|
||||||
|
max_rounds INT DEFAULT 3,
|
||||||
|
round_duration INT DEFAULT 90,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
started_at TIMESTAMP,
|
||||||
|
ended_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS game_players (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
room_id INT REFERENCES game_rooms(id) ON DELETE CASCADE,
|
||||||
|
user_id INT REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
score INT DEFAULT 0,
|
||||||
|
is_drawing BOOLEAN DEFAULT FALSE,
|
||||||
|
joined_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE(room_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS words (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
word VARCHAR(50) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS game_rounds (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
room_id INT REFERENCES game_rooms(id) ON DELETE CASCADE,
|
||||||
|
round_number INT NOT NULL,
|
||||||
|
word_id INT REFERENCES words(id),
|
||||||
|
drawer_id INT REFERENCES users(id),
|
||||||
|
started_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
ended_at TIMESTAMP
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
console.log('Tables created!');
|
console.log('Tables created!');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import cors from 'cors';
|
|||||||
import {Server} from 'socket.io';
|
import {Server} from 'socket.io';
|
||||||
import authRouter from './routes/auth.js';
|
import authRouter from './routes/auth.js';
|
||||||
import chatRouter from './routes/global_chat.js';
|
import chatRouter from './routes/global_chat.js';
|
||||||
|
import gameRoomRouter from './routes/game_room.js';
|
||||||
import {waitForDb, createTables, ensureOauthClient} from './db.js';
|
import {waitForDb, createTables, ensureOauthClient} from './db.js';
|
||||||
import setupSocketIO from './services/socket.js';
|
import setupSocketIO from './services/socket.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const io = new Server(server,
|
const io = new Server(server,
|
||||||
{
|
{
|
||||||
cors:
|
cors:
|
||||||
{
|
{
|
||||||
origin: "*",
|
origin: "*",
|
||||||
@@ -37,6 +38,7 @@ async function startServer()
|
|||||||
|
|
||||||
app.use('/api/auth', authRouter);
|
app.use('/api/auth', authRouter);
|
||||||
app.use('/api/global_chat', chatRouter);
|
app.use('/api/global_chat', chatRouter);
|
||||||
|
app.use('/api/rooms', gameRoomRouter);
|
||||||
app.get('/api', (req, res) => res.send('Backend running'));
|
app.get('/api', (req, res) => res.send('Backend running'));
|
||||||
|
|
||||||
server.listen(3001, () =>
|
server.listen(3001, () =>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
|
||||||
|
//Check si le webtoken est valide
|
||||||
export default function authMiddleware(req, res, next)
|
export default function authMiddleware(req, res, next)
|
||||||
{
|
{
|
||||||
const header = req.headers.authorization;
|
const header = req.headers.authorization;
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import gameRoomService from '../services/game_room.js';
|
||||||
|
import authenticateToken from '../middleware/auth.js';
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const rooms = await gameRoomService.listActiveRooms();
|
||||||
|
res.json(rooms);
|
||||||
|
}
|
||||||
|
catch (err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({error: 'Server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:roomId', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const room = await gameRoomService.getRoomById(req.params.roomId);
|
||||||
|
if (!room)
|
||||||
|
return (res.status(404).json({error: 'Room not found'}));
|
||||||
|
res.json(room);
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({error: 'Server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:roomId/players', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const players = await gameRoomService.getRoomPlayers(req.params.roomId);
|
||||||
|
res.json(players);
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({error: 'Server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const {name} = req.body;
|
||||||
|
if (!name)
|
||||||
|
return (res.status(400).json({error: 'Room name required'}));
|
||||||
|
const room = await gameRoomService.createRoom(name);
|
||||||
|
res.status(201).json(room);
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({error: 'Server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:roomId/join', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const player = await gameRoomService.joinRoom(req.params.roomId, req.user.userId);
|
||||||
|
res.json(player);
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
if (err.message.includes('full') || err.message.includes('already'))
|
||||||
|
res.status(400).json({error: err.message});
|
||||||
|
else
|
||||||
|
res.status(500).json({error: err.message});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:roomId/leave', authenticateToken, async(req, res) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await gameRoomService.leaveRoom(req.params.roomId, req.user.userId);
|
||||||
|
res.json({message: 'Left room successfully'});
|
||||||
|
}
|
||||||
|
catch(err)
|
||||||
|
{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({error: 'Server error'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import {query} from '../db.js';
|
||||||
|
|
||||||
|
// Creer la room avec comme seul parametre le nom
|
||||||
|
// max_players, status et ses autres variables sont aux valeurs definis dans db.js
|
||||||
|
async function createRoom(name)
|
||||||
|
{
|
||||||
|
const result = await query
|
||||||
|
(
|
||||||
|
`INSERT INTO game_rooms (name) VALUES ($1) RETURNING *`,
|
||||||
|
[name]
|
||||||
|
);
|
||||||
|
return (result.rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRoomById(roomId)
|
||||||
|
{
|
||||||
|
const result = await query
|
||||||
|
(
|
||||||
|
'SELECT * FROM game_rooms WHERE id = $1',
|
||||||
|
[roomId]
|
||||||
|
);
|
||||||
|
return (result.rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Liste toutes les rooms en attente
|
||||||
|
//ainsi que le nombre de joueurs dans chaque room
|
||||||
|
//utile pour montrer toutes les rooms joignables
|
||||||
|
async function listActiveRooms()
|
||||||
|
{
|
||||||
|
const result = await query
|
||||||
|
(
|
||||||
|
`SELECT r.*, COUNT(p.id) as player_count
|
||||||
|
FROM game_rooms r
|
||||||
|
LEFT JOIN game_players p ON r.id = p.room_id
|
||||||
|
WHERE r.status = 'waiting'
|
||||||
|
GROUP BY r.id
|
||||||
|
ORDER BY r.created_at DESC`
|
||||||
|
);
|
||||||
|
return (result.rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinRoom(roomId, userId)
|
||||||
|
{
|
||||||
|
const room = await getRoomById(roomId);
|
||||||
|
if (!room)
|
||||||
|
throw new Error('Room not found');
|
||||||
|
|
||||||
|
const playerCount = await query
|
||||||
|
(
|
||||||
|
'SELECT COUNT(*) FROM game_players WHERE room_id = $1',
|
||||||
|
[roomId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parseInt(playerCount.rows[0].count) >= 8)
|
||||||
|
throw new Error('Room is full');
|
||||||
|
if (room.status !== 'waiting')
|
||||||
|
throw new Error('Game already started or ended');
|
||||||
|
const result = await query
|
||||||
|
(
|
||||||
|
'INSERT INTO game_players (room_id, user_id) VALUES ($1, $2) RETURNING *',
|
||||||
|
[roomId, userId]
|
||||||
|
);
|
||||||
|
return (result.rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function leaveRoom(roomId, userId)
|
||||||
|
{
|
||||||
|
await query
|
||||||
|
(
|
||||||
|
'DELETE FROM game_players WHERE room_id = $1 AND user_id = $2',
|
||||||
|
[roomId, userId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const playerCount = await query
|
||||||
|
(
|
||||||
|
'SELECT COUNT(*) FROM game_players WHERE room_id = $1',
|
||||||
|
[roomId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parseInt(playerCount.rows[0].count) === 0)
|
||||||
|
{
|
||||||
|
await query
|
||||||
|
(
|
||||||
|
'DELETE FROM game_rooms WHERE id = $1',
|
||||||
|
[roomId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Renvoie la liste des joueurs trie selon leur score
|
||||||
|
//Cette liste donne egalement l'info sur qui dessine actuellement
|
||||||
|
//Utile pour le jeu en lui meme et le scoreboard de la game
|
||||||
|
async function getRoomPlayers(roomId)
|
||||||
|
{
|
||||||
|
const result = await query
|
||||||
|
(
|
||||||
|
`SELECT gp.*, u.username
|
||||||
|
FROM game_players gp
|
||||||
|
JOIN users u ON gp.user_id = u.id
|
||||||
|
WHERE gp.room_id = $1
|
||||||
|
ORDER BY gp.score DESC`,
|
||||||
|
[roomId]
|
||||||
|
);
|
||||||
|
return (result.rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default
|
||||||
|
{
|
||||||
|
createRoom,
|
||||||
|
getRoomById,
|
||||||
|
listActiveRooms,
|
||||||
|
joinRoom,
|
||||||
|
leaveRoom,
|
||||||
|
getRoomPlayers
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user