diff --git a/Transcendence/Transcendance-Test.zip b/Transcendence/Transcendance-Test.zip
deleted file mode 100644
index 766bda1..0000000
Binary files a/Transcendence/Transcendance-Test.zip and /dev/null differ
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/db.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/db.js
deleted file mode 100755
index afc4f32..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/db.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import 'dotenv/config';
-import { Pool } from 'pg';
-
-const pool = new Pool
-({
- user: process.env.POSTGRES_USER,
- host: process.env.POSTGRES_HOST,
- database: process.env.POSTGRES_DB,
- password: process.env.POSTGRES_PASSWORD,
- port: 5432,
-});
-
-async function waitForDb(retries = 10, delay = 2000)
-{
- for (let i = 0; i < retries; i++)
- {
- try
- {
- await pool.query('SELECT 1');
- console.log('Database is ready!');
- return ;
- }
- catch (err)
- {
- await new Promise(r => setTimeout(r, delay));
- }
- }
- throw new Error('Could not connect to database after multiple attempts');
-}
-
-async function runMigrations()
-{
- try
- {
- // Add total_points column if it doesn't exist
- await pool.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='total_points') THEN
- ALTER TABLE users ADD COLUMN total_points INT DEFAULT 0;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='games_played') THEN
- ALTER TABLE users ADD COLUMN games_played INT DEFAULT 0;
- END IF;
- IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='users' AND column_name='games_won') THEN
- ALTER TABLE users ADD COLUMN games_won INT DEFAULT 0;
- END IF;
- END $$;
- `);
- console.log('Migrations completed!');
- }
- catch (err)
- {
- console.error('Error running migrations:', err);
- }
-}
-
-async function createTables()
-{
- try
- {
- await pool.query(`
- CREATE TABLE IF NOT EXISTS users (
- id SERIAL PRIMARY KEY,
- username VARCHAR(50) UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- email VARCHAR(100),
- avatar_url TEXT DEFAULT '/avatar/default.png',
- total_points INT DEFAULT 0,
- games_played INT DEFAULT 0,
- games_won INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT NOW()
- );
-
- CREATE TABLE IF NOT EXISTS messages(
- id SERIAL PRIMARY KEY,
- sender_id INT REFERENCES users(id),
- received_id INT REFERENCES users(id),
- content TEXT,
- created_at TIMESTAMP DEFAULT NOW()
- );
-
- CREATE TABLE IF NOT EXISTS friendship (
- id_user1 INT NOT NULL,
- id_user2 INT NOT NULL,
- status VARCHAR(20) NOT NULL,
- created_at TIMESTAMP DEFAULT NOW(),
- CHECK (id_user1 < id_user2),
- PRIMARY KEY (id_user1, id_user2),
- FOREIGN KEY (id_user1) REFERENCES users(id) ON DELETE CASCADE,
- FOREIGN KEY (id_user2) REFERENCES users(id) ON DELETE CASCADE
- );
-
- CREATE TABLE IF NOT EXISTS oauth_clients (
- id SERIAL PRIMARY KEY,
- provider VARCHAR(50) NOT NULL,
- client_id VARCHAR(200) NOT NULL,
- client_secret TEXT,
- redirect_uri VARCHAR(255),
- created_at TIMESTAMP DEFAULT NOW(),
- 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!');
- }
- catch (err)
- {
- console.error('Error creating tables:', err);
- }
-}
-
-async function query(text, params)
-{
- return (pool.query(text, params));
-}
-
-async function ensureOauthClient(provider, client_id, client_secret, redirect_uri)
-{
- try
- {
- const res = await pool.query(
- `SELECT id FROM oauth_clients WHERE provider = $1 AND client_id = $2`, [provider, client_id]
- );
- if (res.rows.length > 0)
- return res.rows[0];
- const insert = await pool.query(
- `INSERT INTO oauth_clients (provider, client_id, client_secret, redirect_uri) VALUES ($1, $2, $3, $4) RETURNING id`,
- [provider, client_id, client_secret, redirect_uri]
- );
- return insert.rows[0];
- }
- catch (err)
- {
- console.error('Error ensuring oauth client:', err);
- throw err;
- }
-}
-
-export
-{
- waitForDb,
- createTables,
- runMigrations,
- query,
- ensureOauthClient
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/dockerfile b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/dockerfile
deleted file mode 100755
index b660389..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/dockerfile
+++ /dev/null
@@ -1,15 +0,0 @@
-FROM node:20-alpine
-
-WORKDIR /app
-
-COPY package*.json ./
-
-RUN npm install
-
-COPY . .
-
-EXPOSE 3001
-
-ENV NODE_ENV=development
-
-CMD ["node", "index.js"]
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/index.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/index.js
deleted file mode 100755
index c09c61b..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/index.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import express from 'express';
-import http from 'http';
-import cors from 'cors';
-import {Server} from 'socket.io';
-import authRouter from './routes/auth.js';
-import chatRouter from './routes/global_chat.js';
-import gameRoomRouter from './routes/game_room.js';
-import avatarRouter from './routes/avatar.js';
-import friendsRouter from './routes/friends.js';
-import playerStatsRouter from './routes/player_stats.js';
-import {waitForDb, createTables, runMigrations, ensureOauthClient} from './db.js';
-import setupSocketIO from './services/socket.js';
-import avatarService from './services/avatar.js';
-
-const app = express();
-const server = http.createServer(app);
-const io = new Server(server,
-{
- cors:
- {
- origin: "*",
- methods: ["GET", "POST"]
- }
-});
-
-app.use(cors());
-app.use(express.json());
-
-setupSocketIO(io);
-
-async function startServer()
-{
- await waitForDb();
- await createTables();
- await runMigrations();
-
- // Ensure GitHub OAuth client is registered in DB
- try {
- await ensureOauthClient('github', process.env.GITHUB_CLIENT_ID, process.env.GITHUB_CLIENT_SECRET, process.env.GITHUB_CALLBACK_URL || process.env.GITHUB_REDIRECT_URI);
- } catch (e) {
- console.warn('OAuth client might already exist or failed to register:', e.message);
- }
-
- app.use('/avatar', express.static(avatarService.AVATAR_DIR));
- app.use('/api/auth', authRouter);
- app.use('/api/global_chat', chatRouter);
- app.use('/api/rooms', gameRoomRouter);
- app.use('/api/avatar', avatarRouter);
- app.use('/api/friends', friendsRouter);
- app.use('/api/stats', playerStatsRouter);
- app.get('/api', (req, res) => res.send('Backend running'));
-
- server.listen(3001, () =>
- {
- console.log('Server ready and listening');
- });
-}
-
-startServer();
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/package.json b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/package.json
deleted file mode 100755
index a83699d..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "module",
- "dependencies":
- {
- "express": "^4.18.2",
- "pg": "^8.11.3",
- "bcrypt": "^5.1.0",
- "jsonwebtoken": "^9.0.2",
- "dotenv": "^17.2.3",
- "socket.io": "^4.6.1",
- "cors": "^2.8.5",
- "passport": "0.7.0",
- "passport-github2": "0.1.12",
- "express-session": "1.18.0",
- "multer": "^1.4.5-lts.1",
- "file-type": "^19.0.0"
- }
-}
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/auth.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/auth.js
deleted file mode 100755
index f80ff21..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/auth.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import express from 'express';
-import authService from '../services/auth.js';
-import fetch from 'node-fetch';
-import bcrypt from 'bcrypt';
-import jwt from 'jsonwebtoken';
-import {query} from '../db.js';
-import crypto from 'crypto';
-
-const router = express.Router();
-
-router.post('/register', async(req, res) =>
-{
- const {username, password} = req.body;
- if (!username || !password)
- return (res.status(400).json({error: 'Missing fields'}));
-
- const result = await authService.register(username, password);
- res.status(result.status).json(result.data);
-});
-
-router.post('/login', async(req, res) =>
-{
- console.log("received login!");
- const {username, password} = req.body;
- const result = await authService.login(username, password);
- res.status(result.status).json(result.data);
-});
-
-router.get('/github', (req, res) => {
- const githubAuthUrl = `https://github.com/login/oauth/authorize?` +
- `client_id=${process.env.GITHUB_CLIENT_ID}&` +
- `redirect_uri=${encodeURIComponent(process.env.GITHUB_CALLBACK_URL || process.env.GITHUB_REDIRECT_URI)}&` +
- `scope=user:email`;
-
- res.redirect(githubAuthUrl);
-});
-
-router.get('/github/callback', async (req, res) => {
- const code = req.query.code;
- if (!code) {
- return res.status(400).send('Missing code');
- }
- try {
- const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
- method: 'POST',
- headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
- body: JSON.stringify({
- client_id: process.env.GITHUB_CLIENT_ID,
- client_secret: process.env.GITHUB_CLIENT_SECRET,
- code: code
- })
- });
- const tokenData = await tokenResponse.json();
- const accessToken = tokenData.access_token;
- if (!accessToken) throw new Error('No access token');
-
-
- const userResponse = await fetch('https://api.github.com/user', {
- headers: { 'Authorization': `Bearer ${accessToken}`, 'User-Agent': 'Transcendence' }
- });
- const ghUser = await userResponse.json();
- const ghUsername = ghUser.login || `github_${ghUser.id}`;
-
-
- let result = await query(`SELECT id FROM users WHERE username = $1`, [ghUsername]);
- let userId;
- if (result.rows.length > 0) {
- userId = result.rows[0].id;
- } else {
- const randomPwd = crypto.randomBytes(16).toString('hex');
- const passwordHash = await bcrypt.hash(randomPwd, 10);
- await query(`INSERT INTO users (username, password_hash) VALUES ($1, $2)`, [ghUsername, passwordHash]);
- const inserted = await query(`SELECT id FROM users WHERE username = $1`, [ghUsername]);
- userId = inserted.rows[0].id;
- }
-
- // Issue JWT
- const token = jwt.sign({ userId: userId, username: ghUsername }, process.env.JWT_SECRET, { expiresIn: '1h' });
-
- // Send token to opener window and close popup
- res.send(`
`);
- } catch (err) {
- console.error(err);
- res.status(500).send('GitHub OAuth error');
- }
-});
-
-export default router;
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/avatar.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/avatar.js
deleted file mode 100755
index 117f5e1..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/avatar.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import express from 'express';
-import multer from 'multer';
-import avatarService from '../services/avatar.js';
-import authenticateToken from '../middleware/auth.js';
-
-const router = express.Router();
-
-// Configue multer to use RAM
-const storage = multer.memoryStorage();
-const upload = multer
-({
- storage: storage,
- limits:
- {
- fileSize: 5 * 1024 * 1024 // 5mb
- }
-});
-
-router.post('/upload', authenticateToken, upload.single('avatar'), async(req, res) =>
-{
- if (!req.file)
- return res.status(400).json({ error: 'No file uploaded' });
-
- const result = await avatarService.uploadAvatar(req.user.userId, req.file);
- res.status(result.status).json(result.data);
-});
-
-router.delete('/', authenticateToken, async(req, res) =>
-{
- const result = await avatarService.deleteAvatar(req.user.userId);
- res.status(result.status).json(result.data);
-});
-
-router.get('/me', authenticateToken, async(req, res) =>
-{
- console.log('GET /me hit, user:', req.user);
- const result = await avatarService.getAvatarUrl(req.user.userId);
- res.status(result.status).json(result.data);
-});
-
-router.get('/user/:userId', async(req, res) =>
-{
- const userId = parseInt(req.params.userId);
- if (isNaN(userId))
- return res.status(400).json({ error: 'Invalid user ID' });
-
- const result = await avatarService.getAvatarUrl(userId);
- res.status(result.status).json(result.data);
-});
-
-export default router;
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/friends.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/friends.js
deleted file mode 100755
index 0722388..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/friends.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import express from 'express';
-import friendsService from '../services/friends.js';
-import authenticateToken from '../middleware/auth.js';
-
-const router = express.Router();
-
-// Get friends list
-router.get('/', authenticateToken, async (req, res) => {
- const result = await friendsService.getFriends(req.user.userId);
- res.status(result.status).json(result.data);
-});
-
-// Get pending friend requests
-router.get('/requests', authenticateToken, async (req, res) => {
- const result = await friendsService.getPendingRequests(req.user.userId);
- res.status(result.status).json(result.data);
-});
-
-// Search users
-router.get('/search', authenticateToken, async (req, res) => {
- const { q } = req.query;
- if (!q || q.trim().length === 0) {
- return res.status(400).json({ error: 'Search query required' });
- }
- const result = await friendsService.searchUsers(req.user.userId, q.trim());
- res.status(result.status).json(result.data);
-});
-
-// Send friend request
-router.post('/request/:userId', authenticateToken, async (req, res) => {
- const toUserId = parseInt(req.params.userId);
- if (isNaN(toUserId)) {
- return res.status(400).json({ error: 'Invalid user ID' });
- }
- const result = await friendsService.sendFriendRequest(req.user.userId, toUserId);
- res.status(result.status).json(result.data);
-});
-
-// Accept friend request
-router.post('/accept/:userId', authenticateToken, async (req, res) => {
- const fromUserId = parseInt(req.params.userId);
- if (isNaN(fromUserId)) {
- return res.status(400).json({ error: 'Invalid user ID' });
- }
- const result = await friendsService.acceptFriendRequest(req.user.userId, fromUserId);
- res.status(result.status).json(result.data);
-});
-
-// Decline friend request
-router.post('/decline/:userId', authenticateToken, async (req, res) => {
- const fromUserId = parseInt(req.params.userId);
- if (isNaN(fromUserId)) {
- return res.status(400).json({ error: 'Invalid user ID' });
- }
- const result = await friendsService.declineFriendRequest(req.user.userId, fromUserId);
- res.status(result.status).json(result.data);
-});
-
-// Remove friend
-router.delete('/:userId', authenticateToken, async (req, res) => {
- const friendId = parseInt(req.params.userId);
- if (isNaN(friendId)) {
- return res.status(400).json({ error: 'Invalid user ID' });
- }
- const result = await friendsService.removeFriend(req.user.userId, friendId);
- res.status(result.status).json(result.data);
-});
-
-export default router;
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/game_room.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/game_room.js
deleted file mode 100755
index b144b71..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/game_room.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import express from 'express';
-import gameRoomService from '../services/game_room.js';
-import authenticateToken from '../middleware/auth.js';
-import { getIO, broadcastRoomsList } from '../services/socket.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'});
- }
-});
-
-// Get list of rooms currently being played (for spectators)
-router.get('/playing', authenticateToken, async(req, res) =>
-{
- try
- {
- const rooms = await gameRoomService.listPlayingRooms();
- res.json(rooms);
- }
- catch (err)
- {
- console.error(err);
- res.status(500).json({error: 'Server error'});
- }
-});
-
-// IMPORTANT: This route must be before /:roomId to avoid "current" being interpreted as a roomId
-router.get('/current', authenticateToken, async(req, res) =>
-{
- try
- {
- const room = await gameRoomService.getCurrentRoom(req.user.userId);
- if (!room)
- return res.status(204).send(); // No content - user is not in any room
- res.json(room);
- }
- 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, req.user.userId);
-
- // Broadcast updated rooms list to all clients
- const io = getIO();
- if (io) {
- broadcastRoomsList(io);
- }
-
- 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);
-
- // Broadcast updated rooms list to all clients
- const io = getIO();
- if (io) {
- broadcastRoomsList(io);
- }
-
- 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);
-
- // Broadcast updated rooms list to all clients
- const io = getIO();
- if (io) {
- broadcastRoomsList(io);
- }
-
- res.json({message: 'Left room successfully'});
- }
- catch(err)
- {
- console.error(err);
- res.status(500).json({error: 'Server error'});
- }
-});
-
-// Join a room as spectator
-router.post('/:roomId/spectate', authenticateToken, async(req, res) =>
-{
- try
- {
- const room = await gameRoomService.spectateRoom(req.params.roomId, req.user.userId);
- res.json(room);
- }
- catch(err)
- {
- console.error(err);
- if (err.message.includes('not found') || err.message.includes('not in playing') || err.message.includes('already in'))
- res.status(400).json({error: err.message});
- else
- res.status(500).json({error: err.message});
- }
-});
-
-// Leave spectator mode
-router.post('/:roomId/leave-spectate', authenticateToken, async(req, res) =>
-{
- try
- {
- await gameRoomService.leaveSpectateRoom(req.params.roomId, req.user.userId);
- res.json({message: 'Left spectator mode successfully'});
- }
- catch(err)
- {
- console.error(err);
- res.status(500).json({error: 'Server error'});
- }
-});
-
-export default router;
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/global_chat.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/global_chat.js
deleted file mode 100755
index 41279c6..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/global_chat.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import express from 'express';
-import chatService from '../services/global_chat.js';
-import authenticateToken from '../middleware/auth.js';
-const router = express.Router();
-
-router.get('/messages', authenticateToken, async(req, res) =>
-{
- try
- {
- const messages = await chatService.getRecentMessages(50);
- res.json(messages);
- }
- catch(err)
- {
- console.error(err);
- res.status(500).json({error: 'Server error'});
- }
-});
-
-export default router;
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/player_stats.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/player_stats.js
deleted file mode 100755
index 661d715..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/routes/player_stats.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import express from 'express';
-import playerStatsService from '../services/player_stats.js';
-import authenticateToken from '../middleware/auth.js';
-const router = express.Router();
-
-// Get current user's stats
-router.get('/me', authenticateToken, async (req, res) => {
- try {
- const stats = await playerStatsService.getStatsByUserId(req.user.userId);
- if (!stats) {
- return res.status(404).json({ error: 'User not found' });
- }
- res.json(stats);
- } catch (err) {
- console.error('Error getting user stats:', err);
- res.status(500).json({ error: 'Server error' });
- }
-});
-
-// Get stats by username
-router.get('/user/:username', authenticateToken, async (req, res) => {
- try {
- const stats = await playerStatsService.getStatsByUsername(req.params.username);
- if (!stats) {
- return res.status(404).json({ error: 'User not found' });
- }
- res.json(stats);
- } catch (err) {
- console.error('Error getting user stats:', err);
- res.status(500).json({ error: 'Server error' });
- }
-});
-
-// Get leaderboard
-router.get('/leaderboard', authenticateToken, async (req, res) => {
- try {
- const limit = Math.min(parseInt(req.query.limit) || 10, 50);
- const leaderboard = await playerStatsService.getLeaderboard(limit);
- res.json(leaderboard);
- } catch (err) {
- console.error('Error getting leaderboard:', err);
- res.status(500).json({ error: 'Server error' });
- }
-});
-
-export default router;
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/auth.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/auth.js
deleted file mode 100755
index ca36c25..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/auth.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import bcrypt from 'bcrypt';
-import jwt from 'jsonwebtoken';
-import {query} from '../db.js';
-
-async function login(username, password)
-{
- try
- {
- const result = await query
- (
- `SELECT id, password_hash FROM users WHERE username = $1`,
- [username]
- );
- if (result.rows.length === 0)
- return ({status: 401, data: {error: 'Invalid credentials'}});
-
- const user = result.rows[0];
- const match = await bcrypt.compare(password, user.password_hash);
- if (!match)
- return ({status: 401, data: {error: 'Invalid credentials'}});
-
- const token = jwt.sign
- (
- {
- userId: user.id,
- username: username
- },
- process.env.JWT_SECRET,
- {expiresIn: '1h'}
- );
-
- return ({status: 200, data: {token}});
- }
- catch (err)
- {
- console.error(err);
- return ({status: 500, data: {error: 'Server error'}});
- }
-};
-
-async function register(username, password)
-{
- try
- {
- const password_hash = await bcrypt.hash(password, 10);
- await query
- (
- `INSERT INTO users (username, password_hash) VALUES ($1, $2)`,
- [username, password_hash]
- );
- return ({status: 201, data: {message: 'User created'}});
- }
- catch (err)
- {
- if (err.code === '23505')
- return ({status: 409, data: {error: 'Username already exists'}});
-
- console.error(err);
- return ({status: 500, data: {error: 'Server error'}});
- }
-};
-
-export default {register, login};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/avatar.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/avatar.js
deleted file mode 100755
index 05d3b66..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/avatar.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import {query} from '../db.js';
-import path from 'path';
-import fs from 'fs';
-import {fileURLToPath} from 'url';
-import {fileTypeFromBuffer} from 'file-type';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const AVATAR_DIR = path.join(__dirname, '../avatar');
-const DEFAULT_AVATAR = '/avatar/default.png';
-const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5mb
-const ALLOWED_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
-
-// Verify that the avatar folder already exists
-if (!fs.existsSync(AVATAR_DIR))
-{
- fs.mkdirSync(AVATAR_DIR, { recursive: true });
-}
-
-async function uploadAvatar(userId, file)
-{
- try
- {
- // File check (type and size)
- if (!file)
- return ({status: 400, data: {error: 'No file provided'}});
-
- if (!ALLOWED_TYPES.includes(file.mimetype))
- return ({status: 400, data: { error: 'Invalid file type. Only JPEG, PNG, GIF, and WebP allowed'}});
-
- const fileType = await fileTypeFromBuffer(file.buffer);
- if (!fileType || !['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(fileType.mime))
- return ({status: 400, data: {error: 'Invalid file content. File does not match allowed image types'}});
-
- if (file.size > MAX_FILE_SIZE)
- return ({status: 400, data: {error: 'File too large. Maximum size is 5MB'}});
-
-
- const currentAvatar = await getCurrentAvatar(userId);
- if (currentAvatar === null)
- return ({status: 404, data: {error: 'User not found'}});
-
- // Create a unique name for the new avatar to avoid duplicates
- const fileExt = path.extname(file.originalname);
- const fileName = `user_${userId}_${Date.now()}${fileExt}`;
- const avatarPath = `/avatar/${fileName}`;
-
- // Save the new avatar in the folder
- const filePath = path.join(AVATAR_DIR, fileName);
- fs.writeFileSync(filePath, file.buffer);
-
- await setAvatar(avatarPath, userId);
-
- deleteNonDefault(currentAvatar);
- return ({status: 200, data: {avatar_url: avatarPath}});
- }
- catch (err)
- {
- console.error('Avatar upload error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-async function deleteAvatar(userId) {
- try
- {
- const currentAvatar = await getCurrentAvatar(userId);
- if (currentAvatar === null)
- return ({status: 404, data: {error: 'User not found'}});
-
- // Reset the avatar to the default one
- await setAvatar(DEFAULT_AVATAR, userId);
-
- deleteNonDefault(currentAvatar);
- return ({status: 200, data: {avatar_url: DEFAULT_AVATAR}});
- }
- catch (err)
- {
- console.error('Avatar delete error:', err);
- return ({status: 500, data: {error: 'Server error'}});
- }
-}
-
-async function setAvatar(newAvatar, userId)
-{
- await query
- (
- 'UPDATE users SET avatar_url = $1 WHERE id = $2',
- [newAvatar, userId]
- );
-}
-
-async function getCurrentAvatar(userId)
-{
- const res = await query
- (
- 'SELECT avatar_url FROM users WHERE id = $1',
- [userId]
- );
- if (res.rows.length === 0)
- return (null);
- return (res.rows[0].avatar_url);
-}
-
-function deleteNonDefault(curAvatar)
-{
- if (curAvatar && curAvatar !== DEFAULT_AVATAR)
- {
- const fileName = path.basename(curAvatar);
- const filePath = path.join(AVATAR_DIR, fileName);
-
- if (fs.existsSync(filePath))
- fs.unlinkSync(filePath);
- }
-}
-
-async function getAvatarUrl(userId)
-{
- try
- {
- const result = await query
- (
- 'SELECT avatar_url FROM users WHERE id = $1',
- [userId]
- );
-
- if (result.rows.length === 0)
- return ({status: 404, data: {error: 'User not found'}});
-
- const avatarUrl = result.rows[0].avatar_url || DEFAULT_AVATAR;
- return ({status: 200, data: {avatar_url: avatarUrl}});
- }
- catch (err)
- {
- console.error('Get avatar error:', err);
- return ({status: 500, data: {error: 'Server error'}});
- }
-}
-
-export default
-{
- uploadAvatar,
- deleteAvatar,
- getAvatarUrl,
- AVATAR_DIR,
- DEFAULT_AVATAR
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/friends.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/friends.js
deleted file mode 100755
index f912188..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/friends.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import { query } from '../db.js';
-
-/**
- * Get list of friends for a user
- */
-async function getFriends(userId) {
- try {
- const result = await query(
- `SELECT u.id, u.username, u.avatar_url, u.total_points, u.games_played, u.games_won
- FROM friendship f
- JOIN users u ON (
- CASE
- WHEN f.id_user1 = $1 THEN f.id_user2 = u.id
- ELSE f.id_user1 = u.id
- END
- )
- WHERE (f.id_user1 = $1 OR f.id_user2 = $1)
- AND f.status = 'accepted'`,
- [userId]
- );
- return { status: 200, data: { friends: result.rows } };
- } catch (err) {
- console.error('Get friends error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Get pending friend requests received by user
- */
-async function getPendingRequests(userId) {
- try {
- const result = await query(
- `SELECT u.id, u.username, u.avatar_url, f.created_at
- FROM friendship f
- JOIN users u ON (
- CASE
- WHEN f.id_user1 = $1 THEN f.id_user2 = u.id
- ELSE f.id_user1 = u.id
- END
- )
- WHERE (f.id_user1 = $1 OR f.id_user2 = $1)
- AND f.status = 'pending_' || $1::text`,
- [userId]
- );
- return { status: 200, data: { requests: result.rows } };
- } catch (err) {
- console.error('Get pending requests error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Search users by username
- */
-async function searchUsers(userId, searchTerm) {
- try {
- const result = await query(
- `SELECT id, username, avatar_url
- FROM users
- WHERE username ILIKE $1
- AND id != $2
- LIMIT 20`,
- [`%${searchTerm}%`, userId]
- );
- return { status: 200, data: { users: result.rows } };
- } catch (err) {
- console.error('Search users error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Send a friend request
- */
-async function sendFriendRequest(fromUserId, toUserId) {
- try {
- if (fromUserId === toUserId) {
- return { status: 400, data: { error: 'Cannot add yourself as friend' } };
- }
-
- // Check if user exists
- const userCheck = await query('SELECT id FROM users WHERE id = $1', [toUserId]);
- if (userCheck.rows.length === 0) {
- return { status: 404, data: { error: 'User not found' } };
- }
-
- // Ensure id_user1 < id_user2 for the constraint
- const id1 = Math.min(fromUserId, toUserId);
- const id2 = Math.max(fromUserId, toUserId);
-
- // Check existing friendship
- const existing = await query(
- 'SELECT status FROM friendship WHERE id_user1 = $1 AND id_user2 = $2',
- [id1, id2]
- );
-
- if (existing.rows.length > 0) {
- const status = existing.rows[0].status;
- if (status === 'accepted') {
- return { status: 400, data: { error: 'Already friends' } };
- }
- if (status.startsWith('pending_')) {
- return { status: 400, data: { error: 'Friend request already exists' } };
- }
- }
-
- // Status indicates who needs to accept: pending_
- const status = `pending_${toUserId}`;
-
- await query(
- `INSERT INTO friendship (id_user1, id_user2, status)
- VALUES ($1, $2, $3)
- ON CONFLICT (id_user1, id_user2)
- DO UPDATE SET status = $3`,
- [id1, id2, status]
- );
-
- return { status: 200, data: { message: 'Friend request sent' } };
- } catch (err) {
- console.error('Send friend request error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Accept a friend request
- */
-async function acceptFriendRequest(userId, fromUserId) {
- try {
- const id1 = Math.min(userId, fromUserId);
- const id2 = Math.max(userId, fromUserId);
-
- const result = await query(
- `UPDATE friendship
- SET status = 'accepted'
- WHERE id_user1 = $1 AND id_user2 = $2
- AND status = $3
- RETURNING *`,
- [id1, id2, `pending_${userId}`]
- );
-
- if (result.rows.length === 0) {
- return { status: 404, data: { error: 'Friend request not found' } };
- }
-
- return { status: 200, data: { message: 'Friend request accepted' } };
- } catch (err) {
- console.error('Accept friend request error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Decline a friend request
- */
-async function declineFriendRequest(userId, fromUserId) {
- try {
- const id1 = Math.min(userId, fromUserId);
- const id2 = Math.max(userId, fromUserId);
-
- const result = await query(
- `DELETE FROM friendship
- WHERE id_user1 = $1 AND id_user2 = $2
- AND status = $3
- RETURNING *`,
- [id1, id2, `pending_${userId}`]
- );
-
- if (result.rows.length === 0) {
- return { status: 404, data: { error: 'Friend request not found' } };
- }
-
- return { status: 200, data: { message: 'Friend request declined' } };
- } catch (err) {
- console.error('Decline friend request error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-/**
- * Get list of friend IDs for a user (for quick lookup)
- */
-async function getFriendIds(userId) {
- try {
- const result = await query(
- `SELECT
- CASE
- WHEN f.id_user1 = $1 THEN f.id_user2
- ELSE f.id_user1
- END as friend_id
- FROM friendship f
- WHERE (f.id_user1 = $1 OR f.id_user2 = $1)
- AND f.status = 'accepted'`,
- [userId]
- );
- return result.rows.map(row => row.friend_id);
- } catch (err) {
- console.error('Get friend IDs error:', err);
- return [];
- }
-}
-
-/**
- * Remove a friend
- */
-async function removeFriend(userId, friendId) {
- try {
- const id1 = Math.min(userId, friendId);
- const id2 = Math.max(userId, friendId);
-
- const result = await query(
- `DELETE FROM friendship
- WHERE id_user1 = $1 AND id_user2 = $2
- AND status = 'accepted'
- RETURNING *`,
- [id1, id2]
- );
-
- if (result.rows.length === 0) {
- return { status: 404, data: { error: 'Friendship not found' } };
- }
-
- return { status: 200, data: { message: 'Friend removed' } };
- } catch (err) {
- console.error('Remove friend error:', err);
- return { status: 500, data: { error: 'Server error' } };
- }
-}
-
-export default {
- getFriends,
- getFriendIds,
- getPendingRequests,
- searchUsers,
- sendFriendRequest,
- acceptFriendRequest,
- declineFriendRequest,
- removeFriend
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/game_room.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/game_room.js
deleted file mode 100755
index e4afaa3..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/game_room.js
+++ /dev/null
@@ -1,254 +0,0 @@
-import {query} from '../db.js';
-
-// Create the room with name as the only parameter
-// max_players, status and the other variables have their default values defined in db.js
-async function createRoom(name, userId)
-{
- const result = await query
- (
- `INSERT INTO game_rooms (name) VALUES ($1) RETURNING *`,
- [name]
- );
- const room = result.rows[0];
-
- await query
- (
- 'INSERT INTO game_players (room_id, user_id) VALUES ($1, $2)',
- [room.id, userId]
- );
- return (room);
-}
-
-async function getRoomById(roomId)
-{
- const result = await query
- (
- 'SELECT * FROM game_rooms WHERE id = $1',
- [roomId]
- );
- return (result.rows[0]);
-}
-
-// List all the waiting rooms and the player amount in each of them
-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 player_count DESC, r.created_at DESC`
- );
- return (result.rows);
-}
-
-async function listPlayingRooms()
-{
- 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 = 'playing'
- GROUP BY r.id
- ORDER BY player_count DESC, r.created_at DESC`
- );
- return (result.rows);
-}
-
-async function spectateRoom(roomId, userId)
-{
- const room = await getRoomById(roomId);
- if (!room)
- throw new Error('Room not found');
-
- if (room.status !== 'playing')
- throw new Error('Room is not in playing status');
-
- // Check if user is already a player in any active game
- const playerInGame = await query
- (
- `SELECT r.id, r.name, r.status
- FROM game_rooms r
- JOIN game_players gp ON r.id = gp.room_id
- WHERE gp.user_id = $1 AND r.status IN ('waiting', 'playing')
- LIMIT 1`,
- [userId]
- );
-
- if (playerInGame.rows.length > 0)
- {
- const gameRoom = playerInGame.rows[0];
- if (gameRoom.id === parseInt(roomId))
- throw new Error('You cannot spectate a game you are playing in');
- else
- throw new Error('You are already in an active game');
- }
-
- return (room);
-}
-
-async function leaveSpectateRoom(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]
- );
- }
-}
-
-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]
- );
- }
-}
-
-// List the players in the room and their scores
-// Useful for the scoreboard and also tell which player is currently drawing
-async function getRoomPlayers(roomId)
-{
- const result = await query
- (
- `SELECT gp.*, u.username, u.avatar_url, u.total_points, u.games_played, u.games_won
- 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);
-}
-
-// Get the current room of a user (if any)
-async function getCurrentRoom(userId)
-{
- const result = await query
- (
- `SELECT r.*
- FROM game_rooms r
- JOIN game_players gp ON r.id = gp.room_id
- WHERE gp.user_id = $1 AND r.status IN ('waiting', 'playing')
- LIMIT 1`,
- [userId]
- );
- return (result.rows[0] || null);
-}
-
-// Update room status (waiting, playing, ended)
-async function updateRoomStatus(roomId, status)
-{
- const validStatuses = ['waiting', 'playing', 'ended'];
- if (!validStatuses.includes(status))
- throw new Error('Invalid status');
-
- let updateQuery = 'UPDATE game_rooms SET status = $1';
- const params = [status, roomId];
-
- if (status === 'playing')
- {
- updateQuery += ', started_at = NOW()';
- }
- else if (status === 'ended')
- {
- updateQuery += ', ended_at = NOW()';
- }
-
- updateQuery += ' WHERE id = $2 RETURNING *';
-
- const result = await query(updateQuery, params);
- return (result.rows[0]);
-}
-
-async function resetRoomScores(roomId)
-{
- await query
- (
- 'UPDATE game_players SET score = 0 WHERE room_id = $1',
- [roomId]
- );
-}
-
-async function cleanupEndedRooms()
-{
- await query
- (
- 'DELETE FROM game_players WHERE room_id IN (SELECT id FROM game_rooms WHERE status = $1)',
- ['ended']
- );
-
- await query
- (
- 'DELETE FROM game_rooms WHERE status = $1',
- ['ended']
- );
-}
-
-export default
-{
- createRoom,
- getRoomById,
- listActiveRooms,
- listPlayingRooms,
- spectateRoom,
- leaveSpectateRoom,
- joinRoom,
- leaveRoom,
- getRoomPlayers,
- getCurrentRoom,
- updateRoomStatus,
- resetRoomScores,
- cleanupEndedRooms
-};
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/github.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/github.js
deleted file mode 100755
index e69de29..0000000
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/global_chat.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/global_chat.js
deleted file mode 100755
index 16e8065..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/global_chat.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import {query} from '../db.js';
-
-async function saveMessage(userId, content)
-{
- const result = await query
- (
- 'INSERT INTO messages (sender_id, content) VALUES ($1 ,$2) RETURNING *',
- [userId, content]
- );
- return (result.rows[0]);
-}
-
-async function getRecentMessages(limit = 50)
-{
- const result = await query
- (
- `SELECT m.sender_id, m.content, m.created_at, u.username
- FROM messages m
- JOIN users u ON m.sender_id = u.id
- ORDER BY m.created_at DESC
- LIMIT $1`,
- [limit]
- );
- return (result.rows.reverse());
-}
-
-export default {saveMessage, getRecentMessages};
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/player_stats.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/player_stats.js
deleted file mode 100755
index 41d820a..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/player_stats.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { query } from '../db.js';
-
-// Get player stats by user ID
-async function getStatsByUserId(userId) {
- const result = await query(
- `SELECT id, username, avatar_url, total_points, games_played, games_won, created_at
- FROM users WHERE id = $1`,
- [userId]
- );
- return result.rows[0] || null;
-}
-
-// Get player stats by username
-async function getStatsByUsername(username) {
- const result = await query(
- `SELECT id, username, avatar_url, total_points, games_played, games_won, created_at
- FROM users WHERE username = $1`,
- [username]
- );
- return result.rows[0] || null;
-}
-
-// Update player points (add points to total)
-async function addPoints(userId, points) {
- const result = await query(
- `UPDATE users SET total_points = COALESCE(total_points, 0) + $1 WHERE id = $2 RETURNING total_points`,
- [points, userId]
- );
- return result.rows[0]?.total_points || 0;
-}
-
-// Update player points by username
-async function addPointsByUsername(username, points) {
- const result = await query(
- `UPDATE users SET total_points = COALESCE(total_points, 0) + $1 WHERE username = $2 RETURNING total_points`,
- [points, username]
- );
- return result.rows[0]?.total_points || 0;
-}
-
-// Increment games played
-async function incrementGamesPlayed(userId) {
- await query(
- `UPDATE users SET games_played = COALESCE(games_played, 0) + 1 WHERE id = $1`,
- [userId]
- );
-}
-
-// Increment games won
-async function incrementGamesWon(userId) {
- await query(
- `UPDATE users SET games_won = COALESCE(games_won, 0) + 1 WHERE id = $1`,
- [userId]
- );
-}
-
-// Get leaderboard (top players by points)
-async function getLeaderboard(limit = 10) {
- const result = await query(
- `SELECT id, username, avatar_url, total_points, games_played, games_won
- FROM users
- WHERE total_points > 0
- ORDER BY total_points DESC
- LIMIT $1`,
- [limit]
- );
- return result.rows;
-}
-
-// Get user ID by username
-async function getUserIdByUsername(username) {
- const result = await query(
- `SELECT id FROM users WHERE username = $1`,
- [username]
- );
- return result.rows[0]?.id || null;
-}
-
-export default {
- getStatsByUserId,
- getStatsByUsername,
- addPoints,
- addPointsByUsername,
- incrementGamesPlayed,
- incrementGamesWon,
- getLeaderboard,
- getUserIdByUsername
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/socket.js b/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/socket.js
deleted file mode 100755
index 5888950..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/backend/services/socket.js
+++ /dev/null
@@ -1,795 +0,0 @@
-import jwt from 'jsonwebtoken';
-import chatService from './global_chat.js';
-import friendsService from './friends.js';
-import gameRoomService from './game_room.js';
-import playerStatsService from './player_stats.js';
-
-// Store game state per room
-const gameRooms = new Map();
-
-// Store tetris duel rooms { roomCode → Map }
-const tetrisRooms = new Map();
-
-// Store io instance globally for use in routes
-let ioInstance = null;
-
-export function getIO() {
- return ioInstance;
-}
-
-// Broadcast rooms list to all connected clients
-async function broadcastRoomsList(io) {
- try {
- const rooms = await gameRoomService.listActiveRooms();
- io.emit('game-rooms-updated', { rooms });
- } catch (err) {
- console.error('Error broadcasting rooms list:', err);
- }
-}
-
-// Check if a playing game has only 1 player left and auto-stop it
-async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
- if (!dbRoomId) return;
-
- try {
- // Check if room is in 'playing' status
- const room = await gameRoomService.getRoomById(dbRoomId);
- if (!room || room.status !== 'playing') return;
-
- // Count remaining players
- const players = await gameRoomService.getRoomPlayers(dbRoomId);
- if (players.length <= 1) {
- console.log(`Room ${dbRoomId} has only ${players.length} player(s) left, ending game`);
-
- // Update room status to 'ended'
- await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
- await gameRoomService.resetRoomScores(dbRoomId);
-
- // Remove from game state
- gameRooms.delete(roomId);
-
- // Notify remaining player(s)
- io.to(roomId).emit('game-ended');
- io.to(roomId).emit('game-message', {
- message: 'La partie s\'est terminée car il ne reste qu\'un seul joueur',
- type: 'info'
- });
-
- // Broadcast updated rooms list
- broadcastRoomsList(io);
- }
- } catch (err) {
- console.error('Error checking single player game:', err);
- }
-}
-
-// Save round points to database (only the difference from round start)
-async function saveRoundPoints(currentScores, roundStartScores) {
- for (const [username, currentPoints] of Object.entries(currentScores)) {
- const startPoints = roundStartScores[username] || 0;
- const pointsEarned = currentPoints - startPoints;
- if (pointsEarned !== 0) {
- try {
- await playerStatsService.addPointsByUsername(username, pointsEarned);
- console.log(`Saved ${pointsEarned} points for ${username}`);
- } catch (err) {
- console.error(`Error saving points for ${username}:`, err);
- }
- }
- }
-}
-
-function setupSocketIO(io)
-{
- ioInstance = io;
- io.use((socket, next) =>
- {
- const token = socket.handshake.auth.token;
- if (!token)
- return (next(new Error('Authentication error')));
-
- try
- {
- const decoded = jwt.verify(token, process.env.JWT_SECRET);
- socket.user = decoded;
- next();
- }
- catch(err)
- {
- next(new Error('Authentication error'));
- }
- });
-
- io.on('connection', async (socket) =>
- {
- console.log(`User connected: ${socket.user.username}`);
-
- socket.join('general-chat');
-
- // Send recent messages and friend IDs on connection
- try {
- const [recentMessages, friendIds] = await Promise.all([
- chatService.getRecentMessages(50),
- friendsService.getFriendIds(socket.user.userId)
- ]);
-
- socket.emit('chat-init', {
- messages: recentMessages,
- friendIds: friendIds
- });
- } catch (err) {
- console.error('Error fetching initial data:', err);
- }
-
- socket.on('chat-message', async(data) =>
- {
- try
- {
- const message = await chatService.saveMessage(socket.user.userId, data.content);
- socket.broadcast.to('general-chat').emit('chat-message',
- {
- id: message.id,
- sender_id: socket.user.userId,
- username: socket.user.username,
- content: message.content,
- created_at: message.created_at
- });
- }
- catch (err)
- {
- console.error('Error saving message:', err);
- socket.emit('error', {message: 'Failed to send message'});
- }
- });
-
- // ============================================
- // GAME ROOM EVENTS
- // ============================================
-
- // Join a game room
- socket.on('game-join-room', async (data) => {
- console.log('Received game-join-room from', socket.user.username, 'data:', data);
- const roomId = `game-room-${data.roomId}`;
- socket.join(roomId);
- socket.gameRoomId = roomId;
- socket.gameRoomDbId = data.roomId;
- console.log(`${socket.user.username} joined ${roomId}, socket.gameRoomId set to:`, socket.gameRoomId);
-
- // Send confirmation to the socket that joined
- socket.emit('game-room-joined', {
- roomId: data.roomId,
- success: true
- });
-
- // Get updated player list from DB
- try {
- const players = await gameRoomService.getRoomPlayers(data.roomId);
- // Notify ALL players in the room (including the one who joined) with updated player list
- io.to(roomId).emit('game-players-updated', { players });
- } catch (err) {
- console.error('Error getting room players:', err);
- }
-
- // Notify others in the room that someone joined
- socket.to(roomId).emit('game-player-joined', {
- username: socket.user.username,
- userId: socket.user.userId
- });
-
- // Broadcast rooms list update to everyone
- broadcastRoomsList(io);
-
- // Send current game state if game is in progress
- const gameState = gameRooms.get(roomId);
- if (gameState && gameState.isPlaying) {
- socket.emit('game-state-sync', {
- isPlaying: gameState.isPlaying,
- drawer: gameState.drawer,
- wordLength: gameState.currentWord ? gameState.currentWord.length : 0,
- revealedLetters: gameState.revealedLetters,
- revealedWord: gameState.revealedWord || [],
- guessedLetters: gameState.guessedLetters,
- players: gameState.players
- });
- }
- });
-
- // Leave a game room
- socket.on('game-leave-room', async () => {
- if (socket.gameRoomId) {
- const roomId = socket.gameRoomId;
- const dbRoomId = socket.gameRoomDbId;
-
- socket.to(roomId).emit('game-player-left', {
- username: socket.user.username,
- userId: socket.user.userId
- });
- socket.leave(roomId);
- console.log(`${socket.user.username} left ${roomId}`);
-
- // Get updated player list and broadcast to remaining players
- if (dbRoomId) {
- try {
- const players = await gameRoomService.getRoomPlayers(dbRoomId);
- io.to(roomId).emit('game-players-updated', { players });
- } catch (err) {
- // Room may have been deleted
- console.log('Room may have been deleted:', err.message);
- }
- }
-
- socket.gameRoomId = null;
- socket.gameRoomDbId = null;
-
- // Check if game should auto-stop due to single player
- await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
- // Broadcast updated rooms list
- broadcastRoomsList(io);
- }
- });
-
- // Join a game room as spectator
- socket.on('game-spectate-room', async (data) => {
- console.log('Received game-spectate-room from', socket.user.username, 'data:', data);
- const roomId = `game-room-${data.roomId}`;
-
- // Verify room exists and is in playing status, and user is not already in a game
- try {
- const room = await gameRoomService.spectateRoom(data.roomId, socket.user.userId);
-
- socket.join(roomId);
- socket.gameRoomId = roomId;
- socket.gameRoomDbId = data.roomId;
- socket.isSpectator = true;
- console.log(`${socket.user.username} joined ${roomId} as spectator`);
-
- // Send confirmation
- socket.emit('game-spectate-joined', {
- roomId: data.roomId,
- success: true
- });
-
- // Notify others that a spectator joined
- socket.to(roomId).emit('game-spectator-joined', {
- username: socket.user.username
- });
-
- // Send current game state
- const gameState = gameRooms.get(roomId);
- if (gameState && gameState.isPlaying) {
- socket.emit('game-state-sync', {
- isPlaying: gameState.isPlaying,
- drawer: gameState.drawer,
- wordLength: gameState.currentWord ? gameState.currentWord.length : 0,
- revealedLetters: gameState.revealedLetters,
- revealedWord: gameState.revealedWord || [],
- guessedLetters: gameState.guessedLetters,
- players: gameState.players,
- scores: gameState.scores || {}
- });
- }
- } catch (err) {
- console.error('Error joining as spectator:', err);
- socket.emit('game-spectate-error', {
- error: err.message || 'Cannot spectate this room'
- });
- }
- });
-
- // Leave spectator mode
- socket.on('game-leave-spectate', () => {
- if (socket.gameRoomId && socket.isSpectator) {
- const roomId = socket.gameRoomId;
-
- socket.to(roomId).emit('game-spectator-left', {
- username: socket.user.username
- });
-
- socket.leave(roomId);
- console.log(`${socket.user.username} left spectator mode in ${roomId}`);
-
- socket.gameRoomId = null;
- socket.gameRoomDbId = null;
- socket.isSpectator = false;
- }
- });
-
-
- // Start the game
- socket.on('game-start', async (data) => {
- console.log('Received game-start event from', socket.user.username);
- console.log('socket.gameRoomId:', socket.gameRoomId);
-
- // Security check: need at least 2 players
- if (!data.players || data.players.length < 2) {
- console.log('Game start rejected: not enough players');
- socket.emit('game-start-error', {
- error: 'Il faut au moins 2 joueurs pour commencer'
- });
- return;
- }
-
- const gameStartedData = {
- drawer: data.drawer,
- players: data.players
- };
-
- const roomId = socket.gameRoomId;
-
- // If no roomId, still start the game for this socket only
- if (!roomId) {
- console.log('WARNING: No roomId for socket, starting game for this socket only');
- socket.emit('game-started', gameStartedData);
- return;
- }
-
- // Verify player count from database
- const dbRoomId = socket.gameRoomDbId;
- if (dbRoomId) {
- try {
- const players = await gameRoomService.getRoomPlayers(dbRoomId);
- if (players.length < 2) {
- console.log(`Game start rejected: only ${players.length} player(s) in room`);
- socket.emit('game-start-error', {
- error: 'Il faut au moins 2 joueurs pour commencer'
- });
- return;
- }
- } catch (err) {
- console.error('Error checking player count:', err);
- }
- }
-
- // Update room status to 'playing' in database
- if (dbRoomId) {
- try {
- await gameRoomService.updateRoomStatus(dbRoomId, 'playing');
- console.log(`Room ${dbRoomId} status updated to 'playing'`);
- } catch (err) {
- console.error('Error updating room status to playing:', err);
- }
- }
-
- // Initialize scores for all players
- const scores = {};
- data.players.forEach(p => scores[p] = 0);
-
- const gameState = {
- isPlaying: true,
- currentWord: '',
- revealedLetters: [],
- drawer: data.drawer,
- players: data.players,
- currentPlayerIndex: 0,
- guessedLetters: [],
- scores: scores,
- roundStartScores: { ...scores }
- };
- gameRooms.set(roomId, gameState);
-
- // Emit to OTHER players in the room
- socket.to(roomId).emit('game-started', gameStartedData);
-
- // Emit directly to this socket (the one who started the game)
- socket.emit('game-started', gameStartedData);
-
- console.log(`Game started in ${roomId} by ${socket.user.username}`);
-
- // Broadcast updated rooms list (this room should no longer appear)
- broadcastRoomsList(io);
- });
-
- // Drawer sets the word
- socket.on('game-set-word', (data) => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- const gameState = gameRooms.get(roomId);
- if (!gameState) return;
-
- gameState.currentWord = data.word.toLowerCase();
- gameState.revealedLetters = new Array(data.word.length).fill(false);
- gameState.revealedWord = new Array(data.word.length).fill('_');
- gameState.guessedLetters = [];
- gameState.wrongGuesses = 0;
-
- // Initialize scores if not already done
- if (!gameState.scores) {
- gameState.scores = {};
- gameState.players.forEach(p => gameState.scores[p] = 0);
- }
-
- // Notify all players (without revealing the word)
- io.to(roomId).emit('game-word-set', {
- wordLength: data.word.length,
- drawer: socket.user.username,
- revealedWord: gameState.revealedWord,
- scores: gameState.scores
- });
- });
-
- // Drawing data (real-time)
- socket.on('game-draw', (data) => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- // Spectators cannot draw
- if (socket.isSpectator) {
- console.log(`Spectator ${socket.user.username} tried to draw - blocked`);
- return;
- }
-
- // Broadcast drawing to all other players in the room
- socket.to(roomId).emit('game-draw', {
- x1: data.x1,
- y1: data.y1,
- x2: data.x2,
- y2: data.y2,
- color: data.color,
- lineWidth: data.lineWidth
- });
- });
-
- // Clear canvas
- socket.on('game-clear-canvas', () => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- // Spectators cannot clear canvas
- if (socket.isSpectator) return;
-
- socket.to(roomId).emit('game-clear-canvas');
- });
-
- // Player makes a guess
- socket.on('game-guess', (data) => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- // Spectators cannot make guesses
- if (socket.isSpectator) {
- console.log(`Spectator ${socket.user.username} tried to guess - blocked`);
- return;
- }
-
- const gameState = gameRooms.get(roomId);
- if (!gameState || !gameState.currentWord) return;
-
- const guess = data.guess.toLowerCase();
- const isLetter = guess.length === 1;
- let success = false;
- let points = 0;
- const username = socket.user.username;
-
- // Initialize scores if needed
- if (!gameState.scores) {
- gameState.scores = {};
- gameState.players.forEach(p => gameState.scores[p] = 0);
- }
- if (!gameState.scores[username]) {
- gameState.scores[username] = 0;
- }
-
- if (isLetter) {
- // Check if letter was already guessed
- if (gameState.guessedLetters.includes(guess)) {
- socket.emit('game-guess-result', {
- guess,
- success: false,
- type: 'letter',
- message: 'Lettre deja proposee',
- username: username,
- scores: gameState.scores
- });
- return;
- }
-
- gameState.guessedLetters.push(guess);
-
- // Check each position and reveal the actual letter
- let lettersFound = 0;
- for (let i = 0; i < gameState.currentWord.length; i++) {
- if (gameState.currentWord[i] === guess) {
- gameState.revealedLetters[i] = true;
- gameState.revealedWord[i] = guess;
- success = true;
- lettersFound++;
- }
- }
-
- // Points: 10 per letter found, -5 for wrong guess
- if (success) {
- points = lettersFound * 10;
- gameState.scores[username] += points;
- } else {
- points = -5;
- gameState.scores[username] += points;
- gameState.wrongGuesses++;
- }
- } else {
- // Full word guess
- success = guess === gameState.currentWord;
- if (success) {
- gameState.revealedLetters = gameState.revealedLetters.map(() => true);
- gameState.revealedWord = gameState.currentWord.split('');
- // Bonus points for guessing the whole word
- const remainingLetters = gameState.revealedLetters.filter(r => !r).length;
- points = 50 + (remainingLetters * 5);
- gameState.scores[username] += points;
- } else {
- points = -10;
- gameState.scores[username] += points;
- gameState.wrongGuesses++;
- }
- }
-
- // Broadcast result to all players with the revealed word (actual letters)
- io.to(roomId).emit('game-guess-result', {
- guess,
- success,
- type: isLetter ? 'letter' : 'word',
- username: username,
- revealedLetters: gameState.revealedLetters,
- revealedWord: gameState.revealedWord,
- points: points,
- scores: gameState.scores
- });
-
- // Check if word is complete
- if (gameState.revealedLetters.every(r => r)) {
- // Bonus points for the drawer
- const drawerBonus = Math.max(0, 30 - (gameState.wrongGuesses * 5));
- if (gameState.scores[gameState.drawer]) {
- gameState.scores[gameState.drawer] += drawerBonus;
- }
-
- // Save points to database for all players
- saveRoundPoints(gameState.scores, gameState.roundStartScores || {});
- // Update round start scores for next round
- gameState.roundStartScores = { ...gameState.scores };
-
- io.to(roomId).emit('game-word-found', {
- word: gameState.currentWord,
- winner: username,
- scores: gameState.scores,
- drawerBonus: drawerBonus
- });
- }
- });
-
- // Next round
- socket.on('game-next-round', (data) => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- const gameState = gameRooms.get(roomId);
- if (!gameState) return;
-
- gameState.currentWord = '';
- gameState.revealedLetters = [];
- gameState.guessedLetters = [];
- gameState.drawer = data.drawer;
-
- io.to(roomId).emit('game-new-round', {
- drawer: data.drawer
- });
- });
-
- socket.on('leave-room-during-game', async () => {
- const roomId = socket.gameRoomId;
- const dbRoomId = socket.gameRoomDbId;
- const userId = socket.user.userId;
- const username = socket.user.username;
-
- if (!roomId || !dbRoomId || !userId) return;
-
- console.log(`Player ${username} leaving room ${roomId} during game`);
-
- try
- {
- socket.leave(roomId);
-
- await gameRoomService.leaveRoom(dbRoomId, userId);
-
- io.to(roomId).emit('game-player-left', {
- username: username,
- message: `${username} a quitté la partie`
- });
-
- const gameState = gameRooms.get(roomId);
- if (gameState)
- {
- gameState.players = gameState.players.filter(p => p !== username);
- delete gameState.scores[username];
-
- io.to(roomId).emit('scores-updated', gameState.scores);
- }
-
- await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
-
- socket.gameRoomId = null;
- socket.gameRoomDbId = null;
-
- broadcastRoomsList(io);
- }
- catch (err)
- {
- console.error('Error leaving room during game:', err);
- }
- });
-
- // End game
- socket.on('game-end', async () => {
- const roomId = socket.gameRoomId;
- if (!roomId) return;
-
- // Update room status to 'waiting' in database
- const dbRoomId = socket.gameRoomDbId;
- if (dbRoomId) {
- try {
- await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
- await gameRoomService.resetRoomScores(dbRoomId);
- console.log(`Room ${dbRoomId} status updated to 'waiting'`);
- } catch (err) {
- console.error('Error updating room status to waiting:', err);
- }
- }
-
- gameRooms.delete(roomId);
- io.to(roomId).emit('game-ended');
-
- // Broadcast updated rooms list
- broadcastRoomsList(io);
- });
-
- // ============================================
- // TETRIS DUEL EVENTS
- // ============================================
-
- socket.on('tetris:join', ({ roomCode }) => {
- const code = String(roomCode).toUpperCase().slice(0, 8);
-
- // Quitter l'ancienne room tetris si besoin
- if (socket.tetrisRoomCode) {
- _tetrisLeave(socket);
- }
-
- if (!tetrisRooms.has(code)) {
- tetrisRooms.set(code, new Map());
- }
- const room = tetrisRooms.get(code);
-
- if (room.size >= 2) {
- socket.emit('tetris:room-status', { status: 'full', players: [] });
- return;
- }
-
- room.set(socket.id, socket);
- socket.tetrisRoomCode = code;
-
- const players = [...room.values()].map(s => s.user.username);
-
- if (room.size === 1) {
- socket.emit('tetris:room-status', { status: 'waiting', players });
- } else {
- // Notifier les deux joueurs
- for (const s of room.values()) {
- s.emit('tetris:room-status', { status: 'ready', players });
- }
- // Notifier l'adversaire qu'un nouveau joueur a rejoint
- for (const [id, s] of room) {
- if (id !== socket.id) {
- s.emit('tetris:opponent-joined', { username: socket.user.username });
- }
- }
- }
- });
-
- socket.on('tetris:leave', () => {
- _tetrisLeave(socket);
- });
-
- // Relay pur : grid-update → adversaire uniquement
- socket.on('tetris:grid-update', (data) => {
- _tetrisRelayToOpponent(socket, 'tetris:grid-update', data);
- });
-
- // Relay pur : lines-cleared → adversaire uniquement
- socket.on('tetris:lines-cleared', (data) => {
- _tetrisRelayToOpponent(socket, 'tetris:lines-cleared', data);
- });
-
- // start-duel → relayé aux DEUX joueurs de la room (inclut l'émetteur)
- socket.on('tetris:start-duel', () => {
- const code = socket.tetrisRoomCode;
- if (!code) return;
- const room = tetrisRooms.get(code);
- if (!room || room.size < 2) return;
- for (const s of room.values()) {
- s.emit('tetris:start-duel');
- }
- });
-
- // game-over → relayé en opponent-game-over chez l'adversaire
- socket.on('tetris:game-over', (data) => {
- _tetrisRelayToOpponent(socket, 'tetris:opponent-game-over', data);
- });
-
- socket.on('disconnect', async () =>
- {
- // Nettoyage room tetris
- if (socket.tetrisRoomCode) {
- _tetrisLeave(socket);
- }
-
- console.log(`User disconnected: ${socket.user.username}`);
-
- // Notify game room if player/spectator was in one
- if (socket.gameRoomId) {
- const roomId = socket.gameRoomId;
- const dbRoomId = socket.gameRoomDbId;
-
- // If spectator, just notify and leave
- if (socket.isSpectator) {
- socket.to(roomId).emit('game-spectator-left', {
- username: socket.user.username
- });
- console.log(`Spectator ${socket.user.username} disconnected from ${roomId}`);
- } else {
- // Regular player disconnect
- socket.to(roomId).emit('game-player-left', {
- username: socket.user.username,
- userId: socket.user.userId
- });
-
- // Get updated player list and broadcast
- if (dbRoomId) {
- try {
- const players = await gameRoomService.getRoomPlayers(dbRoomId);
- io.to(roomId).emit('game-players-updated', { players });
- } catch (err) {
- console.log('Room may have been deleted on disconnect:', err.message);
- }
- }
-
- // Check if game should auto-stop due to single player
- await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
-
- // Broadcast updated rooms list
- broadcastRoomsList(io);
- }
- }
- });
- });
-}
-
-// ── Helpers tetris duel ──────────────────────────────────────────────────
-
-function _tetrisLeave(socket) {
- const code = socket.tetrisRoomCode;
- if (!code) return;
- const room = tetrisRooms.get(code);
- if (room) {
- room.delete(socket.id);
- // Notifier l'adversaire restant
- for (const s of room.values()) {
- s.emit('tetris:opponent-left');
- s.emit('tetris:room-status', { status: 'waiting', players: [s.user.username] });
- }
- if (room.size === 0) tetrisRooms.delete(code);
- }
- socket.tetrisRoomCode = null;
-}
-
-function _tetrisRelayToOpponent(socket, event, data) {
- const code = socket.tetrisRoomCode;
- if (!code) return;
- const room = tetrisRooms.get(code);
- if (!room) return;
- for (const [id, s] of room) {
- if (id !== socket.id) s.emit(event, data);
- }
-}
-
-export { broadcastRoomsList };
-export default setupSocketIO;
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/dockerfile b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/dockerfile
deleted file mode 100755
index 310e2ed..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM nginx:alpine
-COPY src /usr/share/nginx/html
-COPY nginx.conf /etc/nginx/conf.d/default.conf
-EXPOSE 80
-CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/nginx.conf b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/nginx.conf
deleted file mode 100755
index 273e6d8..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/nginx.conf
+++ /dev/null
@@ -1,38 +0,0 @@
-server {
- listen 80;
-
- root /usr/share/nginx/html;
- index index.html;
-
- # Frontend
- location / {
- try_files $uri /index.html;
- }
-
- # Backend API
- location /api/ {
- proxy_pass http://backend:3001;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- }
-
- # Socket.IO WebSocket proxying
- location /socket.io/ {
- proxy_pass http://backend:3001;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
-
- location /avatar/ {
- proxy_pass http://backend:3001/avatar/;
- proxy_http_version 1.1;
- proxy_set_header Host $host;
- proxy_hide_header Content-Type;
- add_header Cache-Control "public, max-age=3600";
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ajout du multiplayer b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ajout du multiplayer
deleted file mode 100755
index a02e738..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ajout du multiplayer
+++ /dev/null
@@ -1,45 +0,0 @@
-Je veux faire un mode duel du tetris,
-il est fonctionnel est solo.
-
-Pour commencer, je vais devoir
-creer une div qui sera le rendu
-du joueur second joueur. il sera a droite de la
-div principale qui lui meme sera legerement decale vers la gauche,
-cette div sera grossierement identitique en style qui la div principale
-
-je ne vais pas voir en temps reelle
-les pieces du joueur qui tombe.
-A la place quand le joueur 2 a mis une piece, il envoie un signal,
-le joueur 1 envoie un signal egalement quand la piece tombe.
-
-en mode duel, quand une ligne est clear, il envoie la ligne
-moins la cellule qui a provoquer le clear (donc avec un trou pile
-la ou la derniere piece est arrive) au joueur opposant.
-
-Ce qui a pour effet de decaler toute les lignes vers le haut
-a l'opposant pour recevoir la ligne recu avec le trou.
-
-Pour se faire je vais devoir connecter les deux joueurs.
-
-Il me faudra :
-
-une class Duel il aura pour methode et membre:
-
-action_queue: c'est un tableau qui repertorie tous les
-signaux a traiter, c'est un tableau qui sera partager
-entre le joueur1 et le joueur2
-
-
-Syncronise_game: fonction qui traite les
-actions de action_queue et qui verifie l'integrite du duel,
-il va par exemple regarder l'etat du jeu de chaque joueur
-pour voir s'il correspond bien a ce qui est attendu
-
-il y aura different type de signaux.
-Le signal bloc pose avec le type de bloc,
-sa rotation et as position
-
-le signal line cleared, avec le nombre de ligne
-cleared et on ajoute le trou aussi
-
-Aucune idee de si je dois utiliser web socket ou autre
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/app.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/app.js
deleted file mode 100755
index 02998e6..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/app.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * Application entry point
- * Initializes windows and handles menu interactions
- */
-import { windowRegistry } from './windows.js';
-import { LoginWindow } from './login.js';
-import { GlobalChat } from './global_chat.js';
-import { AvatarWindow } from './avatar.js';
-import { FriendsWindow } from './friends.js';
-import { GameRoomWindow } from './game_room.js';
-
-/**
- * Main application class
- * Handles initialization and menu interactions
- */
-class App {
- constructor() {
- this.initWindows();
- this.initMenu();
- this.initPage();
- this.initEasterEgg();
- }
-
- /**
- * Initializes all windows
- */
- initWindows() {
- new LoginWindow();
- new GlobalChat();
- new AvatarWindow();
- new FriendsWindow();
- new GameRoomWindow();
- }
-
- /**
- * Initializes the main menu
- * Uses event delegation instead of IDs
- */
- initMenu() {
- const menu = document.querySelector('.menu');
- if (!menu) {
- console.warn('Menu not found');
- return;
- }
-
- const actionMap = {
- 'login': 'login',
- 'chat': 'chat',
- 'avatar': 'avatar',
- 'friends': 'friends'
- };
-
- // Event delegation on the menu
- menu.addEventListener('click', (e) => {
- const button = e.target.closest('.menu__item');
- if (!button) return;
-
- const action = button.dataset.action;
-
- // Actions with associated windows
- if (actionMap[action]) {
- windowRegistry.toggle(actionMap[action]);
- return;
- }
-
- });
- }
-
- initPage() {
- const page = document.querySelector('.page');
- if (!page) {
- return;
- }
-
- const actionMap = {
- 'gameroom': 'gameroom'
- };
-
- // Event delegation on the menu
- page.addEventListener('click', (e) => {
- const button = e.target.closest('.page__item');
- if (!button) return;
-
- const action = button.dataset.action;
-
- // Actions with associated windows
- if (actionMap[action]) {
- windowRegistry.toggle(actionMap[action]);
- return;
- }
-
- });
- }
-
- /**
- * Initializes the easter egg button
- */
- initEasterEgg() {
- const easterEgg = document.querySelector('.easter-egg');
- if (easterEgg) {
- easterEgg.addEventListener('click', () => {
- alert('DONT CLICK!');
- });
- }
- }
-}
-
-// Start the application when DOM is ready
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', () => new App());
-} else {
- new App();
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/background.png b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/background.png
deleted file mode 100755
index 8afd0a3..0000000
Binary files a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/background.png and /dev/null differ
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/grwweg.png b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/grwweg.png
deleted file mode 100755
index 716affb..0000000
Binary files a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/assets/grwweg.png and /dev/null differ
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/avatar.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/avatar.js
deleted file mode 100755
index 77dccf3..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/avatar.js
+++ /dev/null
@@ -1,271 +0,0 @@
-import { Window } from './windows.js';
-import { API, STORAGE_KEYS, CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-/**
- * Avatar management window
- * Allows viewing and modifying the user's avatar
- */
-export class AvatarWindow extends Window {
- constructor() {
- super({
- name: 'avatar',
- title: 'Avatar',
- cssClasses: ['avatar-window']
- });
-
- this.buildUI();
- this.bindEvents();
- if (localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN)) {
- this.loadAvatar();
- }
-
- // Listen for login events
- eventBus.on(Events.USER_LOGGED_IN, () => this.loadAvatar());
- }
-
- /**
- * Builds the user interface
- */
- buildUI() {
- // Avatar preview
- this.preview = this.createElement('img', CSS.AVATAR_PREVIEW, {
- alt: 'Avatar'
- });
-
- // Username display
- this.username = this.createElement('div', CSS.AVATAR_USERNAME);
-
- // Stats display
- this.statsContainer = this.createElement('div', 'avatar__stats');
- this.pointsDisplay = this.createElement('div', 'avatar__stat');
- this.gamesPlayedDisplay = this.createElement('div', 'avatar__stat');
- this.gamesWonDisplay = this.createElement('div', 'avatar__stat');
- this.statsContainer.append(this.pointsDisplay, this.gamesPlayedDisplay, this.gamesWonDisplay);
-
- // Hidden file input
- this.fileInput = this.createElement('input', 'avatar__file-input', {
- type: 'file',
- accept: 'image/*'
- });
-
- // Controls
- this.controls = this.createElement('div', CSS.AVATAR_CONTROLS);
-
- this.chooseBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], {
- text: 'Choose image'
- });
-
- this.saveBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Save avatar'
- });
-
- this.refreshBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], {
- text: 'Refresh'
- });
-
- this.controls.append(this.chooseBtn, this.saveBtn, this.refreshBtn);
-
- // Feedback message
- this.message = this.createElement('div', CSS.MESSAGE);
-
- // Assembly
- this.body.append(
- this.preview,
- this.username,
- this.statsContainer,
- this.fileInput,
- this.controls,
- this.message
- );
- }
-
- /**
- * Attaches event handlers
- */
- bindEvents() {
- this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));
- this.chooseBtn.addEventListener('click', () => this.fileInput.click());
- this.saveBtn.addEventListener('click', () => this.uploadAvatar());
- this.refreshBtn.addEventListener('click', () => this.loadAvatar());
- }
-
- /**
- * Handles file selection
- * @param {Event} e
- */
- handleFileSelect(e) {
- const file = e.target.files?.[0];
- if (!file) return;
-
- const reader = new FileReader();
- reader.onload = (ev) => {
- this.preview.src = ev.target.result;
- };
- reader.readAsDataURL(file);
- }
-
- /**
- * Decodes a JWT token and returns the payload
- * @param {string} token
- * @returns {object|null}
- */
- decodeToken(token) {
- try {
- const payload = token.split('.')[1];
- return JSON.parse(atob(payload));
- } catch {
- return null;
- }
- }
-
- /**
- * Loads avatar from the server
- */
- async loadAvatar() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- console.log('No token, skipping avatar load');
- return;
- }
-
- // Extract username from JWT token
- const tokenData = this.decodeToken(token);
- if (tokenData?.username) {
- this.username.textContent = tokenData.username;
- }
-
- try {
- const response = await fetch(API.AVATAR.GET, {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!response.ok) {
- console.warn('Failed to load avatar, status:', response.status);
- return;
- }
-
- const data = await response.json();
-
- if (data?.avatar_url) {
- this.preview.src = data.avatar_url;
- } else {
- console.warn('Avatar URL not found in response');
- }
- } catch (error) {
- console.error('Error loading avatar:', error);
- }
-
- // Load stats
- await this.loadStats();
- }
-
- /**
- * Loads player stats from the server
- */
- async loadStats() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) return;
-
- try {
- const response = await fetch(API.STATS.ME, {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!response.ok) {
- console.warn('Failed to load stats, status:', response.status);
- return;
- }
-
- const data = await response.json();
- this.updateStatsDisplay(data);
- } catch (error) {
- console.error('Error loading stats:', error);
- }
- }
-
- /**
- * Updates the stats display
- * @param {object} stats
- */
- updateStatsDisplay(stats) {
- this.pointsDisplay.innerHTML = `Points: ${stats.total_points || 0}`;
- this.gamesPlayedDisplay.innerHTML = `Parties: ${stats.games_played || 0}`;
- this.gamesWonDisplay.innerHTML = `Victoires: ${stats.games_won || 0}`;
- }
-
- /**
- * Uploads avatar to the server
- */
- async uploadAvatar() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('You must be logged in', 'error');
- return;
- }
-
- const file = this.fileInput.files?.[0];
- if (!file) {
- this.showMessage('Select an image first', 'error');
- return;
- }
-
- const formData = new FormData();
- formData.append('avatar', file);
-
- try {
- this.showMessage('Uploading...', 'info');
-
- const response = await fetch(API.AVATAR.UPLOAD, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`
- },
- body: formData
- });
-
- const data = await response.json();
-
- if (!response.ok) {
- const errorMsg = data?.error || data?.message || 'Upload failed';
- this.showMessage(errorMsg, 'error');
- return;
- }
-
- if (data?.avatar_url) {
- this.preview.src = data.avatar_url;
- }
-
- this.showMessage('Avatar saved!', 'success');
- eventBus.emit(Events.AVATAR_UPDATED, { url: data?.avatar_url });
-
- } catch (error) {
- console.error('Avatar upload error:', error);
- this.showMessage('Upload error', 'error');
- }
- }
-
- /**
- * Displays a feedback message
- * @param {string} text - Message text
- * @param {'success'|'error'|'info'} type - Message type
- */
- showMessage(text, type = 'info') {
- this.message.textContent = text;
- this.message.className = CSS.MESSAGE;
-
- if (type === 'success') {
- this.message.classList.add(CSS.MESSAGE_SUCCESS);
- } else if (type === 'error') {
- this.message.classList.add(CSS.MESSAGE_ERROR);
- } else {
- this.message.classList.add(CSS.MESSAGE_INFO);
- }
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/config.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/config.js
deleted file mode 100755
index aa39164..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/config.js
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * Centralized application configuration
- */
-
-// API Endpoints
-export const API = {
- AUTH: {
- LOGIN: '/api/auth/login',
- REGISTER: '/api/auth/register',
- GITHUB: '/api/auth/github'
- },
- AVATAR: {
- GET: '/api/avatar/me',
- UPLOAD: '/api/avatar/upload'
- },
- FRIENDS: {
- LIST: '/api/friends',
- REQUESTS: '/api/friends/requests',
- SEARCH: '/api/friends/search',
- REQUEST: '/api/friends/request',
- ACCEPT: '/api/friends/accept',
- DECLINE: '/api/friends/decline'
- },
- ROOMS: {
- LIST: '/api/rooms',
- PLAYING: '/api/rooms/playing',
- CREATE: '/api/rooms',
- GET: (id) => `/api/rooms/${id}`,
- PLAYERS: (id) => `/api/rooms/${id}/players`,
- JOIN: (id) => `/api/rooms/${id}/join`,
- LEAVE: (id) => `/api/rooms/${id}/leave`,
- SPECTATE: (id) => `/api/rooms/${id}/spectate`,
- LEAVE_SPECTATE: (id) => `/api/rooms/${id}/leave-spectate`,
- CURRENT: '/api/rooms/current'
- },
- STATS: {
- ME: '/api/stats/me',
- USER: (username) => `/api/stats/user/${username}`,
- LEADERBOARD: '/api/stats/leaderboard'
- }
-};
-
-// localStorage keys
-export const STORAGE_KEYS = {
- AUTH_TOKEN: 'auth_token'
-};
-
-// Window configuration
-export const WINDOW_CONFIG = {
- DEFAULT_WIDTH: 320,
- DEFAULT_HEIGHT: 220,
- Z_INDEX_BASE: 100
-};
-
-// CSS classes (BEM convention)
-export const CSS = {
- // Menu
- MENU: 'menu',
- MENU_ITEM: 'menu__item',
- MENU_ITEM_ACTIVE: 'menu__item--active',
-
- // Windows
- WINDOW: 'window',
- WINDOW_VISIBLE: 'window--visible',
- WINDOW_HEADER: 'window__header',
- WINDOW_TITLE: 'window__title',
- WINDOW_CLOSE: 'window__close',
- WINDOW_BODY: 'window__body',
-
- // Buttons
- BTN: 'btn',
- BTN_PRIMARY: 'btn--primary',
- BTN_SECONDARY: 'btn--secondary',
- BTN_SUCCESS: 'btn--success',
- BTN_DANGER: 'btn--danger',
- BTN_GITHUB: 'btn--github',
-
- // Forms
- INPUT: 'input',
- INPUT_GROUP: 'input-group',
-
- // Messages
- MESSAGE: 'message',
- MESSAGE_SUCCESS: 'message--success',
- MESSAGE_ERROR: 'message--error',
- MESSAGE_INFO: 'message--info',
-
- // Chat
- CHAT: 'chat',
- CHAT_OUTPUT: 'chat__output',
- CHAT_INPUT: 'chat__input',
- CHAT_CONTROLS: 'chat__controls',
- CHAT_MESSAGE: 'chat__message',
- CHAT_SYSTEM: 'chat__system',
-
- // Avatar
- AVATAR: 'avatar',
- AVATAR_PREVIEW: 'avatar__preview',
- AVATAR_CONTROLS: 'avatar__controls',
- AVATAR_USERNAME: 'avatar__username',
-
- // Friends
- FRIENDS: 'friends',
- FRIENDS_TABS: 'friends__tabs',
- FRIENDS_TAB: 'friends__tab',
- FRIENDS_TAB_ACTIVE: 'friends__tab--active',
- FRIENDS_CONTENT: 'friends__content',
- FRIENDS_LIST: 'friends__list',
- FRIENDS_ITEM: 'friends__item',
- FRIENDS_AVATAR: 'friends__avatar',
- FRIENDS_NAME: 'friends__name',
- FRIENDS_ACTIONS: 'friends__actions',
- FRIENDS_SEARCH: 'friends__search',
- FRIENDS_EMPTY: 'friends__empty',
-
- // Game Rooms
- GAMEROOM: 'gameroom',
- GAMEROOM_TABS: 'gameroom__tabs',
- GAMEROOM_TAB: 'gameroom__tab',
- GAMEROOM_TAB_ACTIVE: 'gameroom__tab--active',
- GAMEROOM_CONTENT: 'gameroom__content',
- GAMEROOM_LIST: 'gameroom__list',
- GAMEROOM_ITEM: 'gameroom__item',
- GAMEROOM_NAME: 'gameroom__name',
- GAMEROOM_PLAYERS: 'gameroom__players',
- GAMEROOM_ACTIONS: 'gameroom__actions',
- GAMEROOM_CREATE: 'gameroom__create',
- GAMEROOM_LOBBY: 'gameroom__lobby',
- GAMEROOM_PLAYER_LIST: 'gameroom__player-list',
- GAMEROOM_PLAYER: 'gameroom__player',
- GAMEROOM_PLAYER_AVATAR: 'gameroom__player-avatar',
- GAMEROOM_PLAYER_NAME: 'gameroom__player-name',
- GAMEROOM_PLAYER_SCORE: 'gameroom__player-score'
-};
-
-// Colors (for reference, mainly used in CSS)
-export const COLORS = {
- PRIMARY: '#0066cc',
- SUCCESS: '#3cff01',
- ERROR: '#ff4d4d',
- GITHUB: '#24292e',
- BACKGROUND: '#000',
- SURFACE: '#222',
- TEXT: '#fff'
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/duel.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/duel.js
deleted file mode 100755
index 1df4822..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/duel.js
+++ /dev/null
@@ -1,148 +0,0 @@
-// ─────────────────────────────────────────────
-// DUEL
-// ─────────────────────────────────────────────
-
-class Duel {
- constructor(socket, tetrisGame, onStatusChange, onStart) {
- this.socket = socket;
- this.tetrisGame = tetrisGame;
- this.onStatusChange = onStatusChange; // (status, opponentName) => void
- this.onStart = onStart; // () => void — déclenche le début du jeu local
-
- this.action_queue = [];
- this.opponentGrid = this._emptyGrid();
- this.opponentScore = 0;
- this.roomCode = null;
- this.isReady = false;
-
- this._bindSocketEvents();
- }
-
- // ─── Connexion ────────────────────────────
-
- join(roomCode) {
- this.roomCode = roomCode;
- this.socket.emit('tetris:join', { roomCode });
- }
-
- startDuel() {
- if (!this.isReady) return;
- this.socket.emit('tetris:start-duel');
- }
-
- leave() {
- if (!this.roomCode) return;
- this.socket.emit('tetris:leave');
- this.roomCode = null;
- this.isReady = false;
- this.opponentGrid = this._emptyGrid();
- this.opponentScore = 0;
- }
-
- // ─── Hooks appelés par tetris.js ──────────
-
- onLocalBlockPlaced(grid, score) {
- if (!this.isReady) return;
- this.socket.emit('tetris:grid-update', { grid, score });
- }
-
- onLocalLinesCleared(count, holeCol) {
- if (!this.isReady) return;
- const garbageLines = [];
- for (let i = 0; i < count; i++)
- garbageLines.push(this._buildGarbageLine(holeCol));
- this.socket.emit('tetris:lines-cleared', { count, holeCol, garbageLines });
- }
-
- onLocalGameOver(score) {
- if (!this.isReady) return;
- this.socket.emit('tetris:game-over', { score });
- }
-
- // ─── Traitement de la queue ───────────────
-
- synchronize_game() {
- while (this.action_queue.length > 0) {
- const action = this.action_queue.shift();
- this._processAction(action);
- }
- }
-
- _processAction(action) {
- switch (action.type) {
- case 'GRID_UPDATE':
- this.opponentGrid = action.grid;
- this.opponentScore = action.score;
- document.getElementById('opponent-score').textContent = action.score;
- renderOpponent(this.opponentGrid);
- break;
-
- case 'LINES_CLEARED':
- this.tetrisGame.addGarbageLines(action.garbageLines);
- break;
-
- case 'OPPONENT_GAME_OVER':
- this._showOpponentOverlay('YOU WIN', action.score);
- break;
- }
- }
-
- // ─── Liaison socket ───────────────────────
-
- _bindSocketEvents() {
- this.socket.on('tetris:room-status', (data) => {
- this.isReady = data.status === 'ready';
- const opponentName = data.players.find(p => p !== this.socket.username) || 'Adversaire';
- document.getElementById('opponent-name').textContent = opponentName;
- this.onStatusChange(data.status, opponentName);
- });
-
- this.socket.on('tetris:opponent-joined', (data) => {
- document.getElementById('opponent-name').textContent = data.username;
- });
-
- this.socket.on('tetris:opponent-left', () => {
- this.isReady = false;
- this.onStatusChange('waiting', null);
- this._showOpponentOverlay('DÉCONNECTÉ');
- });
-
- this.socket.on('tetris:grid-update', (data) => {
- this.action_queue.push({ type: 'GRID_UPDATE', grid: data.grid, score: data.score });
- });
-
- this.socket.on('tetris:lines-cleared', (data) => {
- this.action_queue.push({ type: 'LINES_CLEARED', garbageLines: data.garbageLines });
- });
-
- this.socket.on('tetris:opponent-game-over', (data) => {
- this.action_queue.push({ type: 'OPPONENT_GAME_OVER', score: data.score });
- });
-
- this.socket.on('tetris:start-duel', () => {
- if (this.onStart) this.onStart();
- });
- }
-
- // ─── Utilitaires ─────────────────────────
-
- _buildGarbageLine(holeCol) {
- return Array.from({ length: 10 }, (_, i) => i === holeCol ? 0 : 8);
- }
-
- _emptyGrid() {
- return Array.from({ length: 20 }, () => Array(10).fill(0));
- }
-
- _showOpponentOverlay(title, score) {
- const overlayEl = document.getElementById('overlay-opponent');
- document.getElementById('overlay-opponent-title').textContent = title;
- const scoreEl = document.getElementById('overlay-opponent-score');
- if (scoreEl) scoreEl.textContent = score !== undefined ? `Score : ${score}` : '';
- overlayEl.classList.add('visible');
- }
-
- hideOpponentOverlay() {
- document.getElementById('overlay-opponent').classList.remove('visible');
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/element.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/element.js
deleted file mode 100755
index e46e637..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/element.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * DOM element utilities
- * This module provides helper functions for creating elements
- * without depending on specific HTML IDs
- */
-
-/**
- * Creates a DOM element with options
- * @param {string} tag - HTML tag
- * @param {Object} options - Configuration options
- * @param {string|string[]} [options.classes] - CSS classes
- * @param {string} [options.text] - Element text
- * @param {string} [options.html] - Inner HTML
- * @param {Object} [options.attrs] - Additional attributes
- * @param {Object} [options.style] - Inline styles (avoid using)
- * @param {Object} [options.events] - Events to attach
- * @param {HTMLElement[]} [options.children] - Child elements
- * @returns {HTMLElement}
- */
-export function createElement(tag, options = {}) {
- const element = document.createElement(tag);
-
- // Classes
- if (options.classes) {
- const classes = Array.isArray(options.classes) ? options.classes : [options.classes];
- element.className = classes.filter(Boolean).join(' ');
- }
-
- // Text
- if (options.text) {
- element.textContent = options.text;
- }
-
- // HTML
- if (options.html) {
- element.innerHTML = options.html;
- }
-
- // Attributes
- if (options.attrs) {
- Object.entries(options.attrs).forEach(([key, value]) => {
- element.setAttribute(key, value);
- });
- }
-
- // Styles (use sparingly)
- if (options.style) {
- Object.assign(element.style, options.style);
- }
-
- // Events
- if (options.events) {
- Object.entries(options.events).forEach(([event, handler]) => {
- element.addEventListener(event, handler);
- });
- }
-
- // Children
- if (options.children) {
- options.children.forEach(child => {
- if (child) element.appendChild(child);
- });
- }
-
- return element;
-}
-
-/**
- * Selects an element by its data-attribute
- * @param {string} attr - Attribute name (without 'data-')
- * @param {string} value - Value to search for
- * @param {HTMLElement} [parent=document] - Parent element
- * @returns {HTMLElement|null}
- */
-export function findByData(attr, value, parent = document) {
- return parent.querySelector(`[data-${attr}="${value}"]`);
-}
-
-/**
- * Selects all elements by their data-attribute
- * @param {string} attr - Attribute name (without 'data-')
- * @param {string} [value] - Value to search for (optional)
- * @param {HTMLElement} [parent=document] - Parent element
- * @returns {HTMLElement[]}
- */
-export function findAllByData(attr, value, parent = document) {
- const selector = value ? `[data-${attr}="${value}"]` : `[data-${attr}]`;
- return Array.from(parent.querySelectorAll(selector));
-}
-
-/**
- * Adds or removes a class based on a condition
- * @param {HTMLElement} element
- * @param {string} className
- * @param {boolean} condition
- */
-export function toggleClass(element, className, condition) {
- if (condition) {
- element.classList.add(className);
- } else {
- element.classList.remove(className);
- }
-}
-
-/**
- * Escapes HTML to prevent XSS
- * @param {string} text
- * @returns {string}
- */
-export function escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/events.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/events.js
deleted file mode 100755
index e82be3c..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/events.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * EventBus - Centralized event system
- * Enables communication between modules without circular dependencies
- */
-class EventBus {
- constructor() {
- this.listeners = new Map();
- }
-
- /**
- * Subscribe to an event
- * @param {string} event - Event name
- * @param {Function} callback - Function to call
- * @returns {Function} Unsubscribe function
- */
- on(event, callback) {
- if (!this.listeners.has(event)) {
- this.listeners.set(event, new Set());
- }
- this.listeners.get(event).add(callback);
-
- return () => this.off(event, callback);
- }
-
- /**
- * Subscribe to an event once
- * @param {string} event - Event name
- * @param {Function} callback - Function to call
- */
- once(event, callback) {
- const wrapper = (data) => {
- this.off(event, wrapper);
- callback(data);
- };
- this.on(event, wrapper);
- }
-
- /**
- * Unsubscribe from an event
- * @param {string} event - Event name
- * @param {Function} callback - Function to remove
- */
- off(event, callback) {
- if (this.listeners.has(event)) {
- this.listeners.get(event).delete(callback);
- }
- }
-
- /**
- * Emit an event
- * @param {string} event - Event name
- * @param {*} data - Data to transmit
- */
- emit(event, data) {
- console.log(`EventBus: Emitting event "${event}"`, data);
- if (this.listeners.has(event)) {
- const listeners = this.listeners.get(event);
- console.log(`EventBus: "${event}" has ${listeners.size} listener(s)`);
- this.listeners.get(event).forEach(callback => {
- try {
- callback(data);
- } catch (error) {
- console.error(`Error in listener for "${event}":`, error);
- }
- });
- } else {
- console.warn(`EventBus: No listeners for event "${event}"`);
- }
- }
-}
-
-// Exported singleton instance
-export const eventBus = new EventBus();
-
-// Available events (for documentation and autocompletion)
-export const Events = {
- // Authentication
- USER_LOGGED_IN: 'user:logged-in',
- USER_LOGGED_OUT: 'user:logged-out',
- USER_REGISTERED: 'user:registered',
-
- // Windows
- WINDOW_OPENED: 'window:opened',
- WINDOW_CLOSED: 'window:closed',
-
- // Avatar
- AVATAR_UPDATED: 'avatar:updated',
-
- // Chat
- CHAT_CONNECTED: 'chat:connected',
- CHAT_DISCONNECTED: 'chat:disconnected',
- CHAT_MESSAGE_RECEIVED: 'chat:message-received',
-
- // Game Rooms
- ROOM_JOINED: 'room:joined',
- ROOM_LEFT: 'room:left',
- ROOM_CREATED: 'room:created'
-};
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/friends.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/friends.js
deleted file mode 100755
index 19d042e..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/friends.js
+++ /dev/null
@@ -1,453 +0,0 @@
-import { Window } from './windows.js';
-import { API, STORAGE_KEYS, CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-/**
- * Friends management window
- * Allows viewing friends, requests, and searching users
- */
-export class FriendsWindow extends Window {
- constructor() {
- super({
- name: 'friends',
- title: 'Amis',
- cssClasses: ['friends-window']
- });
-
- this.currentTab = 'friends';
- this.buildUI();
- this.bindEvents();
-
- eventBus.on(Events.USER_LOGGED_IN, () => this.loadCurrentTab());
- }
-
- /**
- * Builds the user interface
- */
- buildUI() {
- // Tabs
- this.tabs = this.createElement('div', CSS.FRIENDS_TABS);
-
- this.friendsTab = this.createElement('button', [CSS.FRIENDS_TAB, CSS.FRIENDS_TAB_ACTIVE], {
- text: 'Amis'
- });
- this.friendsTab.dataset.tab = 'friends';
-
- this.requestsTab = this.createElement('button', CSS.FRIENDS_TAB, {
- text: 'Demandes'
- });
- this.requestsTab.dataset.tab = 'requests';
-
- this.searchTab = this.createElement('button', CSS.FRIENDS_TAB, {
- text: 'Rechercher'
- });
- this.searchTab.dataset.tab = 'search';
-
- this.tabs.append(this.friendsTab, this.requestsTab, this.searchTab);
-
- // Content area
- this.content = this.createElement('div', CSS.FRIENDS_CONTENT);
-
- // Search input (hidden by default)
- this.searchContainer = this.createElement('div', CSS.FRIENDS_SEARCH);
- this.searchInput = this.createElement('input', CSS.INPUT, {
- type: 'text',
- placeholder: 'Rechercher un utilisateur...'
- });
- this.searchBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Chercher'
- });
- this.searchContainer.append(this.searchInput, this.searchBtn);
- this.searchContainer.style.display = 'none';
-
- // List container
- this.list = this.createElement('div', CSS.FRIENDS_LIST);
-
- // Message
- this.message = this.createElement('div', CSS.MESSAGE);
-
- this.content.append(this.searchContainer, this.list, this.message);
-
- // Assembly
- this.body.append(this.tabs, this.content);
- }
-
- /**
- * Attaches event handlers
- */
- bindEvents() {
- this.tabs.addEventListener('click', (e) => {
- const tab = e.target.closest(`.${CSS.FRIENDS_TAB}`);
- if (tab) {
- this.switchTab(tab.dataset.tab);
- }
- });
-
- this.searchBtn.addEventListener('click', () => this.searchUsers());
- this.searchInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') this.searchUsers();
- });
- }
-
- /**
- * Switches between tabs
- */
- switchTab(tabName) {
- this.currentTab = tabName;
-
- // Update tab styles
- [this.friendsTab, this.requestsTab, this.searchTab].forEach(tab => {
- tab.classList.toggle(CSS.FRIENDS_TAB_ACTIVE, tab.dataset.tab === tabName);
- });
-
- // Show/hide search
- this.searchContainer.style.display = tabName === 'search' ? 'flex' : 'none';
-
- this.loadCurrentTab();
- }
-
- /**
- * Loads data for current tab
- */
- loadCurrentTab() {
- switch (this.currentTab) {
- case 'friends':
- this.loadFriends();
- break;
- case 'requests':
- this.loadRequests();
- break;
- case 'search':
- this.list.innerHTML = '';
- this.showMessage('Entrez un nom pour rechercher', 'info');
- break;
- }
- }
-
- /**
- * Gets auth headers
- */
- getHeaders() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- return {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- };
- }
-
- /**
- * Loads friends list
- */
- async loadFriends() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour voir vos amis', 'info');
- return;
- }
-
- try {
- const response = await fetch(API.FRIENDS.LIST, {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.renderFriendsList(data.friends || []);
- } catch (error) {
- console.error('Load friends error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- /**
- * Loads pending requests
- */
- async loadRequests() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour voir les demandes', 'info');
- return;
- }
-
- try {
- const response = await fetch(API.FRIENDS.REQUESTS, {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.renderRequestsList(data.requests || []);
- } catch (error) {
- console.error('Load requests error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- /**
- * Searches users
- */
- async searchUsers() {
- const query = this.searchInput.value.trim();
- if (!query) {
- this.showMessage('Entrez un nom pour rechercher', 'info');
- return;
- }
-
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour rechercher', 'info');
- return;
- }
-
- try {
- const response = await fetch(`${API.FRIENDS.SEARCH}?q=${encodeURIComponent(query)}`, {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.renderSearchResults(data.users || []);
- } catch (error) {
- console.error('Search error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- /**
- * Renders friends list
- */
- renderFriendsList(friends) {
- this.list.innerHTML = '';
- this.message.textContent = '';
-
- if (friends.length === 0) {
- this.showMessage('Aucun ami pour le moment', 'info');
- return;
- }
-
- friends.forEach(friend => {
- const item = this.createFriendItem(friend, 'friend');
- this.list.appendChild(item);
- });
- }
-
- /**
- * Renders requests list
- */
- renderRequestsList(requests) {
- this.list.innerHTML = '';
- this.message.textContent = '';
-
- if (requests.length === 0) {
- this.showMessage('Aucune demande en attente', 'info');
- return;
- }
-
- requests.forEach(request => {
- const item = this.createFriendItem(request, 'request');
- this.list.appendChild(item);
- });
- }
-
- /**
- * Renders search results
- */
- renderSearchResults(users) {
- this.list.innerHTML = '';
- this.message.textContent = '';
-
- if (users.length === 0) {
- this.showMessage('Aucun utilisateur trouve', 'info');
- return;
- }
-
- users.forEach(user => {
- const item = this.createFriendItem(user, 'search');
- this.list.appendChild(item);
- });
- }
-
- /**
- * Creates a friend/user item
- */
- createFriendItem(user, type) {
- const item = this.createElement('div', CSS.FRIENDS_ITEM);
-
- const avatar = this.createElement('img', CSS.FRIENDS_AVATAR, {
- alt: user.username
- });
- avatar.src = user.avatar_url || '/avatar/default.png';
-
- const infoContainer = this.createElement('div', 'friends__info');
-
- const name = this.createElement('span', CSS.FRIENDS_NAME, {
- text: user.username
- });
-
- infoContainer.appendChild(name);
-
- // Show stats for friends
- if (type === 'friend' && user.total_points !== undefined) {
- const stats = this.createElement('span', 'friends__stats', {
- text: `${user.total_points || 0} pts`
- });
- infoContainer.appendChild(stats);
- }
-
- const actions = this.createElement('div', CSS.FRIENDS_ACTIONS);
-
- if (type === 'friend') {
- const removeBtn = this.createElement('button', [CSS.BTN, CSS.BTN_DANGER], {
- text: 'Retirer'
- });
- removeBtn.addEventListener('click', () => this.removeFriend(user.id));
- actions.appendChild(removeBtn);
- } else if (type === 'request') {
- const acceptBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SUCCESS], {
- text: 'Accepter'
- });
- acceptBtn.addEventListener('click', () => this.acceptRequest(user.id));
-
- const declineBtn = this.createElement('button', [CSS.BTN, CSS.BTN_DANGER], {
- text: 'Refuser'
- });
- declineBtn.addEventListener('click', () => this.declineRequest(user.id));
-
- actions.append(acceptBtn, declineBtn);
- } else if (type === 'search') {
- const addBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Ajouter'
- });
- addBtn.addEventListener('click', () => this.sendRequest(user.id, addBtn));
- actions.appendChild(addBtn);
- }
-
- item.append(avatar, infoContainer, actions);
- return item;
- }
-
- /**
- * Sends a friend request
- */
- async sendRequest(userId, button) {
- try {
- const response = await fetch(`${API.FRIENDS.REQUEST}/${userId}`, {
- method: 'POST',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- button.textContent = 'Envoye';
- button.disabled = true;
- this.showMessage('Demande envoyee', 'success');
- } catch (error) {
- console.error('Send request error:', error);
- this.showMessage('Erreur', 'error');
- }
- }
-
- /**
- * Accepts a friend request
- */
- async acceptRequest(userId) {
- try {
- const response = await fetch(`${API.FRIENDS.ACCEPT}/${userId}`, {
- method: 'POST',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.showMessage('Ami ajoute', 'success');
- this.loadRequests();
- } catch (error) {
- console.error('Accept request error:', error);
- this.showMessage('Erreur', 'error');
- }
- }
-
- /**
- * Declines a friend request
- */
- async declineRequest(userId) {
- try {
- const response = await fetch(`${API.FRIENDS.DECLINE}/${userId}`, {
- method: 'POST',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.showMessage('Demande refusee', 'success');
- this.loadRequests();
- } catch (error) {
- console.error('Decline request error:', error);
- this.showMessage('Erreur', 'error');
- }
- }
-
- /**
- * Removes a friend
- */
- async removeFriend(userId) {
- try {
- const response = await fetch(`${API.FRIENDS.LIST}/${userId}`, {
- method: 'DELETE',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.showMessage('Ami retire', 'success');
- this.loadFriends();
- } catch (error) {
- console.error('Remove friend error:', error);
- this.showMessage('Erreur', 'error');
- }
- }
-
- /**
- * Shows a message
- */
- showMessage(text, type = 'info') {
- this.message.textContent = text;
- this.message.className = CSS.MESSAGE;
-
- if (type === 'success') {
- this.message.classList.add(CSS.MESSAGE_SUCCESS);
- } else if (type === 'error') {
- this.message.classList.add(CSS.MESSAGE_ERROR);
- } else {
- this.message.classList.add(CSS.MESSAGE_INFO);
- }
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.css b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.css
deleted file mode 100755
index b40f7ff..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.css
+++ /dev/null
@@ -1,1021 +0,0 @@
-:root {
- --color-primary: #0066cc;
- --color-primary-hover: #0052a3;
- --color-success: #3cff01;
- --color-success-dark: #28a745;
- --color-error: #ff4d4d;
- --color-warning: #ffc107;
- --color-github: #24292e;
-
- --color-bg: #000;
-
- --app-background-base: radial-gradient(
- circle at top,
- #1b2735,
- #090a0f
- );
-
- /* --app-background-image: url("./assets/background.png"); */
-
- --color-surface: #222;
- --color-surface-light: #333;
- --color-text: #fff;
- --color-text-muted: #aaa;
-
- --font-size-base: 10px;
- --font-size-sm: 1.2rem;
- --font-size-md: 1.4rem;
- --font-size-lg: 1.6rem;
- --font-size-xl: 3rem;
-
- --spacing-xs: 4px;
- --spacing-sm: 8px;
- --spacing-md: 12px;
- --spacing-lg: 16px;
- --spacing-xl: 24px;
-
- --radius-sm: 4px;
- --radius-md: 6px;
- --radius-lg: 12px;
- --radius-full: 50%;
-
- --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
- --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
- --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
-
- --transition-fast: 150ms ease;
- --transition-normal: 250ms ease;
-
- --z-menu: 2;
- --z-window: 100;
- --z-modal: 200;
-}
-
-/* ============================================
- RESET & BASE
- ============================================ */
-*,
-*::before,
-*::after {
- box-sizing: border-box;
-}
-
-html {
- height: 100%;
- background-image:
- var(--app-background-base);
-
- background-size:
- contain,
- cover;
-
- background-position:
- center,
- center;
-
- background-repeat:
- no-repeat,
- no-repeat;
-}
-
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- color: var(--color-text);
- line-height: 1.5;
-}
-
-.app {
- position: relative;
- width: 70%;
- min-width: 800px;
- margin: 0 auto;
-}
-
-
-/* ============================================
- TYPOGRAPHY
- ============================================ */
-.title {
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- text-transform: uppercase;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 20px;
- font-size: var(--font-size-xl);
- text-align: center;
- text-shadow: 2px 2px 10px black;
- z-index: 1;
- font-family: "Cinzel Decorative", cursive;
- color: var(--color-success);
- margin: 0;
- padding: var(--spacing-md);
-}
-
-/* ============================================
- MENU
- ============================================ */
-
-.menu {
- position: fixed;
- top: 0;
- left: 50px;
- padding: 0;
- margin: 0;
- z-index: var(--z-menu);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.menu__item {
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-align: left;
-}
-
-.menu__item:hover {
- background: var(--color-surface-light);
- font-size: var(--font-size-lg);
-}
-
-.menu__item--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-/* ============================================
- GAME
- ============================================ */
-
-.game {
- position: fixed;
- top: 0;
- right: 50px;
- padding: 0;
- margin: 0;
- z-index: var(--z-menu);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.game__item {
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-align: right;
-}
-
-.game__item:hover {
- background: var(--color-surface-light);
- font-size: var(--font-size-lg);
-}
-
-.game__item--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-/* ============================================
- PAGES
- ============================================ */
-
-.page {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
-
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-
- z-index: var(--z-menu);
-}
-
-.page__item {
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-align: right;
-}
-
-.page__item:hover {
- background: var(--color-surface-light);
- font-size: var(--font-size-lg);
-}
-
-.page__item--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-/* ============================================
- BUTTONS
- ============================================ */
-
-.btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- font-weight: 500;
- border: none;
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-decoration: none;
-}
-
-.btn:hover {
- opacity: 0.9;
- transform: translateY(-1px);
-}
-
-.btn:active {
- transform: translateY(0);
-}
-
-.btn--primary {
- background: var(--color-primary);
- color: var(--color-text);
-}
-
-.btn--primary:hover {
- background: var(--color-primary-hover);
-}
-
-.btn--secondary {
- background: var(--color-surface-light);
- color: var(--color-text);
-}
-
-.btn--success {
- background: var(--color-success-dark);
- color: var(--color-text);
-}
-
-.btn--danger {
- background: var(--color-error);
- color: var(--color-text);
-}
-
-.btn--github {
- background: var(--color-github);
- color: var(--color-text);
-}
-
-.btn--ghost {
- background: transparent;
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
-}
-
-/* ============================================
- INPUTS
- ============================================ */
-.input {
- width: 100%;
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- border-radius: var(--radius-md);
- transition: border-color var(--transition-fast);
-}
-
-.input:focus {
- outline: none;
- border-color: var(--color-primary);
-}
-
-.input::placeholder {
- color: var(--color-text-muted);
-}
-
-.input-group {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-/* ============================================
- WINDOWS
- ============================================ */
-.window {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--color-bg);
- border: 2px ridge var(--color-text);
- color: var(--color-text);
- z-index: var(--z-window);
- display: none;
- flex-direction: column;
- min-width: 280px;
- box-shadow: var(--shadow-lg);
-}
-
-.window--visible {
- display: flex;
-}
-
-.window--left {
- left: 25%;
-}
-
-.window--right {
- left: 75%;
-}
-
-.window__header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--color-surface);
- cursor: move;
- user-select: none;
-}
-
-.window__title {
- font-weight: 500;
- font-size: var(--font-size-md);
-}
-
-.window__close {
- cursor: pointer;
- font-size: var(--font-size-lg);
- opacity: 0.8;
- transition: opacity var(--transition-fast);
- background: none;
- border: none;
- color: var(--color-text);
- padding: 0;
- line-height: 1;
-}
-
-.window__close:hover {
- opacity: 1;
-}
-
-.window__body {
- padding: var(--spacing-md);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- flex: 1;
- overflow: auto;
-}
-
-/* ============================================
- MESSAGES
- ============================================ */
-.message {
- font-size: var(--font-size-sm);
- padding: var(--spacing-xs);
- border-radius: var(--radius-sm);
-}
-
-.message--success {
- color: var(--color-success);
-}
-
-.message--error {
- color: var(--color-error);
-}
-
-.message--info {
- color: var(--color-text-muted);
-}
-
-/* ============================================
- LOGIN WINDOW
- ============================================ */
-.login {
- width: 320px;
-}
-
-.login__form {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-.login__actions {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-xs);
-}
-
-.login__divider {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- color: var(--color-text-muted);
- font-size: var(--font-size-sm);
- margin: var(--spacing-sm) 0;
-}
-
-.login__divider::before,
-.login__divider::after {
- content: '';
- flex: 1;
- height: 1px;
- background: var(--color-surface-light);
-}
-
-/* ============================================
- CHAT WINDOW
- ============================================ */
-.chat {
- width: 380px;
- height: 400px;
-}
-
-.chat__output {
- flex: 1;
- overflow-y: auto;
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- min-height: 150px;
-}
-
-.chat__message {
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--color-surface-light);
- border-radius: var(--radius-sm);
- font-size: var(--font-size-sm);
-}
-
-.chat__message--own {
- background: var(--color-primary);
- align-self: flex-end;
-}
-
-.chat__friend-indicator {
- display: inline-block;
- width: 8px;
- height: 8px;
- background-color: var(--color-success);
- border-radius: 50%;
- margin-right: var(--spacing-xs);
- vertical-align: middle;
-}
-
-.chat__system {
- color: var(--color-text-muted);
- font-size: var(--font-size-sm);
- font-style: italic;
- text-align: center;
-}
-
-.chat__system--error {
- color: var(--color-error);
-}
-
-.chat__system--success {
- color: var(--color-success);
-}
-
-.chat__input-container {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-sm);
-}
-
-.chat__input {
- flex: 1;
-}
-
-.chat__controls {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-sm);
-}
-
-/* ============================================
- AVATAR WINDOW
- ============================================ */
-.avatar-window {
- width: 360px;
-}
-
-.avatar__preview {
- width: 120px;
- height: 120px;
- object-fit: cover;
- border-radius: var(--radius-full);
- border: 3px solid var(--color-text);
- box-shadow: var(--shadow-md);
- background: var(--color-surface);
- align-self: center;
-}
-
-.avatar__username {
- font-size: var(--font-size-lg);
- font-weight: 600;
- text-align: center;
- color: var(--color-text);
- margin-top: var(--spacing-sm);
-}
-
-.avatar__controls {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- align-items: center;
-}
-
-.avatar__file-input {
- display: none;
-}
-
-/* ============================================
- EASTER EGG BUTTON
- ============================================ */
-/* .easter-egg {
- position: absolute;
- top: 20%;
- left: 50%;
- transform: translateX(-50%);
- z-index: 1;
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- cursor: pointer;
- font-size: var(--font-size-md);
- border-radius: var(--radius-md);
- transition: all var(--transition-fast);
-}
-
-.easter-egg:hover {
- background: var(--color-error);
- border-color: var(--color-error);
-} */
-
-/* ============================================
- UTILITIES
- ============================================ */
-.hidden {
- display: none !important;
-}
-
-.visually-hidden {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-
-.text-center {
- text-align: center;
-}
-
-.flex-center {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-/* ============================================
- FRIENDS WINDOW
- ============================================ */
-.friends-window {
- width: 400px;
- height: 450px;
-}
-
-.friends__tabs {
- display: flex;
- gap: var(--spacing-xs);
- margin-bottom: var(--spacing-sm);
-}
-
-.friends__tab {
- flex: 1;
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border: 1px solid var(--color-surface-light);
- color: var(--color-text);
- cursor: pointer;
- font-size: var(--font-size-sm);
- transition: all var(--transition-fast);
-}
-
-.friends__tab:hover {
- background: var(--color-surface-light);
-}
-
-.friends__tab--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-.friends__content {
- display: flex;
- flex-direction: column;
- flex: 1;
- overflow: hidden;
-}
-
-.friends__search {
- display: flex;
- gap: var(--spacing-sm);
- margin-bottom: var(--spacing-sm);
-}
-
-.friends__search .input {
- flex: 1;
-}
-
-.friends__list {
- flex: 1;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-.friends__item {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
-}
-
-.friends__avatar {
- width: 40px;
- height: 40px;
- border-radius: var(--radius-full);
- object-fit: cover;
- border: 2px solid var(--color-surface-light);
-}
-
-.friends__name {
- flex: 1;
- font-size: var(--font-size-md);
- font-weight: 500;
-}
-
-.friends__actions {
- display: flex;
- gap: var(--spacing-xs);
-}
-
-.friends__actions .btn {
- padding: var(--spacing-xs) var(--spacing-sm);
- font-size: var(--font-size-sm);
-}
-
-.friends__empty {
- text-align: center;
- color: var(--color-text-muted);
- padding: var(--spacing-lg);
-}
-
-/* ============================================
- GAME ROOM WINDOW
- ============================================ */
-.gameroom-window {
- width: 600px;
- height: 800px;
-}
-
-.gameroom__tabs {
- display: flex;
- gap: var(--spacing-xs);
- margin-bottom: var(--spacing-sm);
-}
-
-.gameroom__tab {
- flex: 1;
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border: 1px solid var(--color-surface-light);
- color: var(--color-text);
- cursor: pointer;
- font-size: var(--font-size-sm);
- transition: all var(--transition-fast);
-}
-
-.gameroom__tab:hover {
- background: var(--color-surface-light);
-}
-
-.gameroom__tab--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-.gameroom__content {
- display: flex;
- flex-direction: column;
- flex: 1;
- overflow: hidden;
-}
-
-.gameroom__create {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- margin-bottom: var(--spacing-sm);
-}
-
-.gameroom__list {
- flex: 1;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-.gameroom__item {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
-}
-
-.gameroom__name {
- flex: 1;
- font-size: var(--font-size-md);
- font-weight: 500;
-}
-
-.gameroom__players {
- font-size: var(--font-size-sm);
- color: var(--color-text-muted);
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--color-surface-light);
- border-radius: var(--radius-sm);
-}
-
-.gameroom__actions {
- display: flex;
- gap: var(--spacing-xs);
-}
-
-.gameroom__actions .btn {
- padding: var(--spacing-xs) var(--spacing-sm);
- font-size: var(--font-size-sm);
-}
-
-.gameroom__lobby {
- display: flex;
- flex-direction: column;
- flex: 1;
- gap: var(--spacing-sm);
-}
-
-.gameroom__lobby-title {
- margin: 0;
- font-size: var(--font-size-lg);
- text-align: center;
- color: var(--color-success);
-}
-
-.gameroom__player-list {
- flex: 1;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
- padding: var(--spacing-sm);
-}
-
-.gameroom__player {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--color-surface-light);
- border-radius: var(--radius-sm);
-}
-
-.gameroom__player-avatar {
- width: 32px;
- height: 32px;
- border-radius: var(--radius-full);
- object-fit: cover;
- border: 2px solid var(--color-surface-light);
-}
-
-.gameroom__player-name {
- flex: 1;
- font-size: var(--font-size-md);
-}
-
-.gameroom__player-stats {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- gap: 2px;
-}
-
-.gameroom__player-score {
- font-size: var(--font-size-sm);
- color: var(--color-success);
- font-weight: 500;
-}
-
-.gameroom__player-total {
- font-size: var(--font-size-sm);
- color: var(--color-text-muted);
-}
-
-.gameroom__empty {
- text-align: center;
- color: var(--color-text-muted);
- padding: var(--spacing-lg);
-}
-
-/* ============================================
- GAME - JEU DU PENDU/DESSIN
- ============================================ */
-
-.gameroom__lobby-buttons {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: auto;
-}
-
-.gameroom__lobby-buttons .btn {
- flex: 1;
-}
-
-.gameroom__game {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- flex: 1;
-}
-
-.gameroom__game-info {
- text-align: center;
-}
-
-.gameroom__drawer-info {
- font-size: var(--font-size-md);
- color: var(--color-text-muted);
- padding: var(--spacing-xs);
-}
-
-.gameroom__scores-display {
- font-size: var(--font-size-sm);
- color: var(--color-success);
- padding: var(--spacing-xs);
- background: var(--color-surface);
- border-radius: var(--radius-sm);
- margin-top: var(--spacing-xs);
-}
-
-.gameroom__drawer-info--winner {
- color: var(--color-success);
- font-weight: bold;
- animation: pulse 0.5s ease-in-out 3;
-}
-
-@keyframes pulse {
- 0%, 100% { transform: scale(1); }
- 50% { transform: scale(1.05); }
-}
-
-.gameroom__word-display {
- font-size: var(--font-size-xl);
- font-family: monospace;
- text-align: center;
- letter-spacing: 8px;
- padding: var(--spacing-md);
- background: var(--color-surface);
- border-radius: var(--radius-md);
- min-height: 60px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--color-success);
-}
-
-.gameroom__canvas-container {
- display: flex;
- justify-content: center;
-}
-
-.gameroom__canvas {
- background: var(--color-surface-light);
- border-radius: var(--radius-md);
- cursor: crosshair;
- border: 2px solid var(--color-surface-light);
-}
-
-.gameroom__draw-tools {
- display: flex;
- gap: var(--spacing-sm);
- justify-content: center;
- align-items: center;
-}
-
-.gameroom__color-picker {
- width: 40px;
- height: 32px;
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- background: transparent;
-}
-
-.gameroom__word-input-container,
-.gameroom__guess-container {
- display: flex;
- gap: var(--spacing-sm);
-}
-
-.gameroom__word-input-container .input,
-.gameroom__guess-container .input {
- flex: 1;
-}
-
-.gameroom__guess-container .input:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.gameroom__guess-container .btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.gameroom__guess-history {
- flex: 1;
- min-height: 60px;
- max-height: 100px;
- overflow-y: auto;
- background: var(--color-surface);
- border-radius: var(--radius-md);
- padding: var(--spacing-sm);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.gameroom__guess-item {
- font-size: var(--font-size-sm);
- padding: var(--spacing-xs) var(--spacing-sm);
- border-radius: var(--radius-sm);
-}
-
-.gameroom__guess-item--success {
- background: rgba(60, 255, 1, 0.2);
- color: var(--color-success);
-}
-
-.gameroom__guess-item--fail {
- background: rgba(255, 77, 77, 0.2);
- color: var(--color-error);
-}
-
-.gameroom__game-buttons {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-sm);
-}
-
-.gameroom__game-buttons .btn {
- flex: 1;
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.html b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.html
deleted file mode 100755
index 51aef37..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
- Lobby
-
-
-
-
-
-
- Lobby
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game_room.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game_room.js
deleted file mode 100755
index d5881e9..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/game_room.js
+++ /dev/null
@@ -1,1580 +0,0 @@
-import { Window } from './windows.js';
-import { API, STORAGE_KEYS, CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-export class GameRoomWindow extends Window {
- constructor() {
- super({
- name: 'gameroom',
- title: 'Game Rooms',
- cssClasses: ['gameroom-window']
- });
-
- this.currentTab = 'browse';
- this.currentRoom = null;
- this.roomsList = [];
- this.socket = null;
- this.isSpectating = false;
- this.messageTimeout = null;
- this.buildUI();
- this.bindEvents();
-
- // Handle page close/refresh to disconnect socket
- window.addEventListener('beforeunload', () => {
- if (this.socket?.connected) {
- this.socket.disconnect();
- }
- });
-
- eventBus.on(Events.USER_LOGGED_IN, () => {
- this.updateTabsAccess();
- this.checkCurrentRoom();
- });
- eventBus.on(Events.USER_LOGGED_OUT, () => {
- this.handleLogout();
- });
-
- this.updateTabsAccess();
-
- // Verifier si l'utilisateur est deja dans un salon au chargement
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (token)
- this.checkCurrentRoom();
- }
-
- buildUI() {
- this.tabs = this.createElement('div', CSS.GAMEROOM_TABS);
-
- this.browseTab = this.createElement('button', [CSS.GAMEROOM_TAB, CSS.GAMEROOM_TAB_ACTIVE], {
- text: 'Salons'
- });
- this.browseTab.dataset.tab = 'browse';
-
- this.spectatorTab = this.createElement('button', CSS.GAMEROOM_TAB, {
- text: 'Spectateur'
- });
- this.spectatorTab.dataset.tab = 'spectator';
-
- this.createTab = this.createElement('button', CSS.GAMEROOM_TAB, {
- text: 'Creer'
- });
- this.createTab.dataset.tab = 'create';
-
- this.lobbyTab = this.createElement('button', CSS.GAMEROOM_TAB, {
- text: 'Lobby'
- });
- this.lobbyTab.dataset.tab = 'lobby';
- this.lobbyTab.style.display = 'none';
-
- this.tabs.append(this.browseTab, this.spectatorTab, this.createTab, this.lobbyTab);
-
- this.content = this.createElement('div', CSS.GAMEROOM_CONTENT);
-
- this.createContainer = this.createElement('div', CSS.GAMEROOM_CREATE);
- this.roomNameInput = this.createElement('input', CSS.INPUT, {
- type: 'text',
- placeholder: 'Nom du salon...'
- });
- this.createBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Creer le salon'
- });
- this.createContainer.append(this.roomNameInput, this.createBtn);
- this.createContainer.style.display = 'none';
-
- this.lobbyContainer = this.createElement('div', CSS.GAMEROOM_LOBBY);
- this.lobbyTitle = this.createElement('h3', 'gameroom__lobby-title', { text: '' });
- this.playerList = this.createElement('div', CSS.GAMEROOM_PLAYER_LIST);
-
- // Boutons du lobby
- this.lobbyButtons = this.createElement('div', 'gameroom__lobby-buttons');
- this.startGameBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SUCCESS], {
- text: 'Lancer le jeu'
- });
- this.leaveBtn = this.createElement('button', [CSS.BTN, CSS.BTN_DANGER], {
- text: 'Quitter'
- });
- this.lobbyButtons.append(this.startGameBtn, this.leaveBtn);
-
- // Container du jeu (caché par défaut)
- this.gameContainer = this.createElement('div', 'gameroom__game');
- this.gameContainer.style.display = 'none';
- this.buildGameUI();
-
- this.lobbyContainer.append(this.lobbyTitle, this.playerList, this.lobbyButtons, this.gameContainer);
- this.lobbyContainer.style.display = 'none';
-
- this.list = this.createElement('div', CSS.GAMEROOM_LIST);
-
- this.spectatorList = this.createElement('div', CSS.GAMEROOM_LIST);
- this.spectatorList.style.display = 'none';
-
- this.message = this.createElement('div', CSS.MESSAGE);
-
- this.content.append(this.createContainer, this.lobbyContainer, this.list, this.spectatorList, this.message);
-
- this.body.append(this.tabs, this.content);
- }
-
- buildGameUI() {
- // Zone d'info du jeu
- this.gameInfo = this.createElement('div', 'gameroom__game-info');
- this.currentDrawerInfo = this.createElement('div', 'gameroom__drawer-info', { text: '' });
- this.scoresDisplay = this.createElement('div', 'gameroom__scores-display');
- this.gameInfo.append(this.currentDrawerInfo, this.scoresDisplay);
-
- // Affichage du mot caché
- this.wordDisplay = this.createElement('div', 'gameroom__word-display');
-
- // Canvas de dessin
- this.canvasContainer = this.createElement('div', 'gameroom__canvas-container');
- this.canvas = document.createElement('canvas');
- this.canvas.className = 'gameroom__canvas';
- this.canvas.width = 380;
- this.canvas.height = 200;
- this.ctx = this.canvas.getContext('2d');
- this.canvasContainer.appendChild(this.canvas);
-
- // Outils de dessin
- this.drawTools = this.createElement('div', 'gameroom__draw-tools');
- this.colorPicker = this.createElement('input', 'gameroom__color-picker');
- this.colorPicker.type = 'color';
- this.colorPicker.value = '#ffffff';
- this.clearCanvasBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], { text: 'Effacer' });
- this.drawTools.append(this.colorPicker, this.clearCanvasBtn);
- this.drawTools.style.display = 'none';
-
- // Zone pour choisir le mot (pour le dessinateur)
- this.wordInputContainer = this.createElement('div', 'gameroom__word-input-container');
- this.wordInput = this.createElement('input', CSS.INPUT, {
- type: 'text',
- placeholder: 'Entrez le mot a faire deviner...'
- });
- this.confirmWordBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], { text: 'OK' });
- this.wordInputContainer.append(this.wordInput, this.confirmWordBtn);
- this.wordInputContainer.style.display = 'none';
-
- // Zone pour deviner (pour les autres joueurs)
- this.guessContainer = this.createElement('div', 'gameroom__guess-container');
- this.letterInput = this.createElement('input', CSS.INPUT, {
- type: 'text',
- placeholder: 'Proposez une lettre ou le mot...',
- maxLength: '50'
- });
- this.guessBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], { text: 'Deviner' });
- this.guessContainer.append(this.letterInput, this.guessBtn);
- this.guessContainer.style.display = 'none';
-
- // Historique des tentatives
- this.guessHistory = this.createElement('div', 'gameroom__guess-history');
-
- // Boutons du jeu
- this.gameButtons = this.createElement('div', 'gameroom__game-buttons');
- this.backToLobbyBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], { text: 'Quitter la partie' });
- this.endRoundBtn = this.createElement('button', [CSS.BTN, CSS.BTN_DANGER], { text: 'Terminer le jeu' });
- this.gameButtons.append(this.backToLobbyBtn, this.endRoundBtn);
-
- this.gameContainer.append(
- this.gameInfo,
- this.wordDisplay,
- this.canvasContainer,
- this.drawTools,
- this.wordInputContainer,
- this.guessContainer,
- this.guessHistory,
- this.gameButtons
- );
-
- // Initialiser les variables du jeu
- this.gameState = {
- isPlaying: false,
- currentWord: '',
- wordLength: 0,
- revealedLetters: [],
- revealedWord: [],
- drawer: null,
- players: [],
- currentPlayerIndex: 0,
- guessedLetters: [],
- scores: {}
- };
-
- this.initDrawing();
- }
-
- initDrawing() {
- this.isDrawing = false;
- this.lastX = 0;
- this.lastY = 0;
-
- this.canvas.addEventListener('mousedown', (e) => {
- if (!this.gameState.isPlaying || !this.isCurrentUserDrawer() || this.isSpectating) return;
- this.isDrawing = true;
- [this.lastX, this.lastY] = [e.offsetX, e.offsetY];
- });
-
- this.canvas.addEventListener('mousemove', (e) => {
- if (!this.isDrawing) return;
-
- const x1 = this.lastX;
- const y1 = this.lastY;
- const x2 = e.offsetX;
- const y2 = e.offsetY;
- const color = this.colorPicker.value;
- const lineWidth = 3;
-
- // Dessiner localement
- this.drawLine(x1, y1, x2, y2, color, lineWidth);
-
- // Envoyer aux autres joueurs via WebSocket
- if (this.socket?.connected) {
- this.socket.emit('game-draw', { x1, y1, x2, y2, color, lineWidth });
- }
-
- [this.lastX, this.lastY] = [x2, y2];
- });
-
- this.canvas.addEventListener('mouseup', () => this.isDrawing = false);
- this.canvas.addEventListener('mouseout', () => this.isDrawing = false);
-
- this.clearCanvasBtn.addEventListener('click', () => {
- this.clearCanvas();
- // Notifier les autres
- if (this.socket?.connected) {
- this.socket.emit('game-clear-canvas');
- }
- });
- }
-
- drawLine(x1, y1, x2, y2, color, lineWidth) {
- this.ctx.beginPath();
- this.ctx.strokeStyle = color;
- this.ctx.lineWidth = lineWidth;
- this.ctx.lineCap = 'round';
- this.ctx.moveTo(x1, y1);
- this.ctx.lineTo(x2, y2);
- this.ctx.stroke();
- }
-
- clearCanvas() {
- this.ctx.fillStyle = '#333';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- }
-
- bindEvents() {
- this.tabs.addEventListener('click', (e) => {
- const tab = e.target.closest(`.${CSS.GAMEROOM_TAB}`);
- if (tab) {
- this.switchTab(tab.dataset.tab);
- }
- });
-
- this.createBtn.addEventListener('click', () => this.createRoom());
- this.roomNameInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') this.createRoom();
- });
-
- this.leaveBtn.addEventListener('click', () => this.leaveRoom());
- this.startGameBtn.addEventListener('click', () => this.startGame());
-
- // Events du jeu
- this.confirmWordBtn.addEventListener('click', () => this.confirmWord());
- this.wordInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') this.confirmWord();
- });
-
- this.guessBtn.addEventListener('click', () => this.makeGuess());
- this.letterInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') this.makeGuess();
- });
-
- this.backToLobbyBtn.addEventListener('click', () => this.backToLobby());
- this.endRoundBtn.addEventListener('click', () => this.endGame());
- }
-
- // ============================================
- // SOCKET.IO CONNECTION
- // ============================================
-
- async connectToGameSocket() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token || !this.currentRoom) return;
-
- // Ensure socket is connected
- await this.ensureSocketConnected();
-
- // Join the room
- if (this.socket?.connected) {
- console.log('Socket connected, joining room:', this.currentRoom.id);
- this.socket.emit('game-join-room', { roomId: this.currentRoom.id });
- }
- }
-
- async loadSocketIO() {
- if (window.io) return;
-
- return new Promise((resolve, reject) => {
- const script = document.createElement('script');
- script.src = '/socket.io/socket.io.js';
-
- script.onload = () => {
- console.log('Socket.IO loaded for game');
- resolve();
- };
-
- script.onerror = () => {
- console.error('Failed to load Socket.IO');
- reject(new Error('Socket.IO load failed'));
- };
-
- document.head.appendChild(script);
- });
- }
-
- setupGameSocketListeners() {
- this.socketReady = false;
-
- this.socket.on('connect', () => {
- console.log('Game socket connected, id:', this.socket.id);
- if (this.currentRoom) {
- console.log('Joining room:', this.currentRoom.id);
- this.socket.emit('game-join-room', { roomId: this.currentRoom.id });
- }
- });
-
- this.socket.on('connect_error', (err) => {
- console.error('Game socket connection error:', err.message);
- });
-
- // Confirmation that we joined the room
- this.socket.on('game-room-joined', (data) => {
- console.log('Successfully joined game room:', data.roomId);
- this.socketReady = true;
- });
-
- // Real-time rooms list update
- this.socket.on('game-rooms-updated', (data) => {
- console.log('Rooms list updated:', data.rooms?.length, 'rooms');
- if (this.currentTab === 'browse') {
- this.roomsList = data.rooms || [];
- this.renderRoomsList(this.roomsList);
- }
- });
-
- // Real-time player list update in lobby
- this.socket.on('game-players-updated', (data) => {
- console.log('Players list updated:', data.players?.length, 'players');
- if (this.currentRoom) {
- this.renderPlayersList(data.players || []);
- }
- });
-
- // Player joined/left
- this.socket.on('game-player-joined', (data) => {
- console.log(`${data.username} joined the room`);
- });
-
- this.socket.on('game-player-left', (data) => {
- this.showMessage(`${data.username} a quitté le salon`, 'info');
- console.log(`${data.username} joined the room`);
-
- if (this.gameState.isPlaying)
- {
- if (this.gameState.players)
- this.gameState.players = this.gameState.players.filter(p => p !== data.username);
- }
-
- if (this.gameState.scores)
- {
- delete this.gameState.scores[data.username];
- this.updateScoresDisplay(this.gameState.scores);
- }
-
- if (this.gameState.drawer === data.username)
- {
- this.showMessage('Le dessinateur a quitté la partie, fin du jeu', 'info');
- }
-
- if (this.currentRoom && !this.gameState.isPlaying)
- this.loadLobby();
- });
-
- // Game started
- this.socket.on('game-started', (data) => {
- console.log('Received game-started event:', data);
- this.gameState.isPlaying = true;
- this.gameState.drawer = data.drawer;
- this.gameState.players = data.players;
- this.gameState.currentPlayerIndex = data.players.indexOf(data.drawer);
-
- // Initialize scores
- this.gameState.scores = {};
- data.players.forEach(p => this.gameState.scores[p] = 0);
-
- this.showGameUI();
- this.setupRound();
- });
-
- // Game start error
- this.socket.on('game-start-error', (data) => {
- console.error('Game start error:', data.error);
- this.showMessage(data.error || 'Impossible de démarrer la partie', 'error');
- });
-
- // Word was set by drawer
- this.socket.on('game-word-set', (data) => {
- console.log(`Word set by ${data.drawer}, length: ${data.wordLength}`);
- this.gameState.wordLength = data.wordLength;
- this.gameState.revealedLetters = new Array(data.wordLength).fill(false);
- this.gameState.revealedWord = data.revealedWord || new Array(data.wordLength).fill('_');
-
- if (data.scores) {
- this.updateScoresDisplay(data.scores);
- }
-
- this.updateWordDisplay();
-
- // Don't change UI for spectators
- if (this.isSpectating) {
- this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie';
- return;
- }
-
- this.currentDrawerInfo.textContent = `${data.drawer} dessine (${data.wordLength} lettres)`;
-
- // Enable guess input for non-drawers
- if (!this.isCurrentUserDrawer()) {
- this.guessContainer.style.display = 'flex';
- this.letterInput.disabled = false;
- this.guessBtn.disabled = false;
- this.letterInput.placeholder = 'Proposez une lettre ou le mot...';
- this.letterInput.focus();
- }
- });
-
- // Drawing received
- this.socket.on('game-draw', (data) => {
- this.drawLine(data.x1, data.y1, data.x2, data.y2, data.color, data.lineWidth);
- });
-
- // Clear canvas
- this.socket.on('game-clear-canvas', () => {
- this.clearCanvas();
- });
-
- // Guess result
- this.socket.on('game-guess-result', (data) => {
- this.addGuessToHistory(data.guess, data.success, data.type, data.username, data.points || 0);
-
- if (data.revealedLetters) {
- this.gameState.revealedLetters = data.revealedLetters;
- }
- if (data.revealedWord) {
- this.gameState.revealedWord = data.revealedWord;
- }
- if (data.scores) {
- this.updateScoresDisplay(data.scores);
- }
- this.updateWordDisplay();
- });
-
- // Word found
- this.socket.on('game-word-found', (data) => {
- if (data.scores) {
- this.updateScoresDisplay(data.scores);
- }
- this.wordFound(data.word, data.winner, data.drawerBonus || 0);
- });
-
- // New round
- this.socket.on('game-new-round', (data) => {
- this.gameState.drawer = data.drawer;
- this.gameState.currentPlayerIndex = this.gameState.players.indexOf(data.drawer);
- this.setupRound();
- });
-
- // Game ended
- this.socket.on('game-ended', () => {
- // If spectating, return to spectator list
- if (this.isSpectating) {
- this.resetGameUI();
- this.currentRoom = null;
- this.isSpectating = false;
- this.switchTab('spectator');
- this.showMessage('La partie est terminée', 'info');
- } else {
- this.resetGameUI();
- this.loadLobby();
- }
- });
-
- // Game message from server
- this.socket.on('game-message', (data) => {
- this.showMessage(data.message, data.type || 'info');
- });
-
- // Sync state for late joiners
- this.socket.on('game-state-sync', (data) => {
- if (data.isPlaying) {
- this.gameState.isPlaying = true;
- this.gameState.drawer = data.drawer;
- this.gameState.wordLength = data.wordLength;
- this.gameState.revealedLetters = data.revealedLetters || [];
- this.gameState.revealedWord = data.revealedWord || new Array(data.wordLength).fill('_');
- this.gameState.players = data.players;
- this.gameState.scores = data.scores || {};
-
- this.showGameUI();
- this.updateWordDisplay();
-
- // Update scores display
- if (data.scores) {
- this.updateScoresDisplay(data.scores);
- }
-
- this.currentDrawerInfo.textContent = `${data.drawer} dessine (${data.wordLength} lettres)`;
-
- // Don't enable input for spectators
- if (this.isSpectating) {
- this.guessContainer.style.display = 'none';
- this.wordInputContainer.style.display = 'none';
- this.drawTools.style.display = 'none';
- this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie';
- } else if (!this.isCurrentUserDrawer()) {
- this.guessContainer.style.display = 'flex';
- if (data.wordLength > 0) {
- this.letterInput.disabled = false;
- this.guessBtn.disabled = false;
- this.letterInput.placeholder = 'Proposez une lettre ou le mot...';
- } else {
- this.letterInput.disabled = true;
- this.guessBtn.disabled = true;
- this.letterInput.placeholder = 'En attente du mot...';
- }
- }
- }
- });
-
- // Spectator events
- this.socket.on('game-spectate-joined', (data) => {
- console.log('Successfully joined as spectator:', data.roomId);
- this.isSpectating = true;
-
- // Prepare UI for spectating
- this.spectatorList.style.display = 'none';
- this.list.style.display = 'none';
- this.createContainer.style.display = 'none';
- this.lobbyContainer.style.display = 'flex';
-
- // Hide lobby elements, keep game container for when state syncs
- this.playerList.style.display = 'none';
- this.lobbyButtons.style.display = 'none';
- this.lobbyTitle.textContent = 'Mode Spectateur';
-
- this.showMessage('Vous regardez la partie...', 'success');
- // The game state will be synced via game-state-sync event
- });
-
- this.socket.on('game-spectate-error', (data) => {
- console.error('Spectate error:', data.error);
- this.showMessage(data.error || 'Impossible de regarder cette partie', 'error');
- });
-
- this.socket.on('game-spectator-joined', (data) => {
- console.log(`Spectator ${data.username} joined`);
- });
-
- this.socket.on('game-spectator-left', (data) => {
- console.log(`Spectator ${data.username} left`);
- });
- }
-
- disconnectGameSocket() {
- if (this.socket) {
- if (this.isSpectating) {
- this.socket.emit('game-leave-spectate');
- } else {
- this.socket.emit('game-leave-room');
- }
- }
- }
-
- // ============================================
- // UI HELPERS
- // ============================================
-
- isLoggedIn() {
- return !!localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- }
-
- updateTabsAccess() {
- const loggedIn = this.isLoggedIn();
-
- this.createTab.disabled = !loggedIn;
- this.createTab.style.opacity = loggedIn ? '1' : '0.5';
- this.createTab.title = loggedIn ? '' : 'Connectez-vous pour creer un salon';
-
- if (!loggedIn && this.currentTab === 'create') {
- this.switchTab('browse');
- }
- }
-
- handleLogout() {
- this.disconnectGameSocket();
- if (this.currentRoom) {
- this.exitLobby();
- }
- this.updateTabsAccess();
- if (this.currentTab !== 'browse') {
- this.switchTab('browse');
- }
- }
-
- switchTab(tabName) {
- if (tabName === 'lobby' && !this.currentRoom) {
- return;
- }
-
- if (tabName === 'create' && !this.isLoggedIn()) {
- this.showMessage('Connectez-vous pour creer un salon', 'info');
- return;
- }
-
- this.currentTab = tabName;
-
- [this.browseTab, this.spectatorTab, this.createTab, this.lobbyTab].forEach(tab => {
- tab.classList.toggle(CSS.GAMEROOM_TAB_ACTIVE, tab.dataset.tab === tabName);
- });
-
- this.createContainer.style.display = tabName === 'create' ? 'flex' : 'none';
- this.lobbyContainer.style.display = tabName === 'lobby' ? 'flex' : 'none';
- this.list.style.display = tabName === 'browse' ? 'flex' : 'none';
- this.spectatorList.style.display = tabName === 'spectator' ? 'flex' : 'none';
-
- this.loadCurrentTab();
- }
-
- loadCurrentTab() {
- switch (this.currentTab) {
- case 'browse':
- this.loadRooms();
- // Connect to socket to receive real-time room updates
- this.ensureSocketConnected();
- break;
- case 'spectator':
- this.loadPlayingRooms();
- this.ensureSocketConnected();
- break;
- case 'create':
- this.message.textContent = '';
- this.ensureSocketConnected();
- break;
- case 'lobby':
- if (this.currentRoom) {
- this.loadLobby();
- }
- break;
- }
- }
-
- async ensureSocketConnected() {
- if (!this.isLoggedIn()) return;
- if (this.socket?.connected) return;
-
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) return;
-
- await this.loadSocketIO();
-
- const ioConfig = {
- auth: { token },
- reconnection: true,
- reconnectionAttempts: 5,
- reconnectionDelay: 1000,
- transports: ['websocket', 'polling']
- };
-
- const altPort = window.GLOBAL_CHAT_ALT_PORT;
- if (altPort) {
- const host = location.hostname || 'localhost';
- this.socket = io(`http://${host}:${altPort}`, ioConfig);
- } else {
- this.socket = io(ioConfig);
- }
-
- this.setupGameSocketListeners();
- }
-
- getHeaders() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- return {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- };
- }
-
- async loadRooms() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour voir les salons', 'info');
- return;
- }
-
- try {
- const response = await fetch(API.ROOMS.LIST, {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.roomsList = data || [];
- this.renderRoomsList(this.roomsList);
- } catch (error) {
- console.error('Load rooms error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- async checkCurrentRoom() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- return null;
- }
-
- try {
- const response = await fetch(API.ROOMS.CURRENT, {
- headers: this.getHeaders()
- });
-
- // 204 No Content means user is not in any room
- if (response.status === 204) {
- return null;
- }
-
- if (!response.ok) {
- return null;
- }
-
- const data = await response.json();
- if (data && data.id) {
- this.currentRoom = data;
- this.enterLobby(data);
- return data;
- }
- return null;
- } catch (error) {
- console.error('Check current room error:', error);
- return null;
- }
- }
-
- roomNameExists(name) {
- const normalizedName = name.toLowerCase().trim();
- return this.roomsList.some(room => room.name.toLowerCase().trim() === normalizedName);
- }
-
- renderRoomsList(rooms) {
- this.list.innerHTML = '';
- this.message.textContent = '';
-
- if (rooms.length === 0) {
- this.showMessage('Aucun salon disponible', 'info');
- return;
- }
-
- rooms.forEach(room => {
- const item = this.createRoomItem(room);
- this.list.appendChild(item);
- });
- }
-
- createRoomItem(room) {
- const item = this.createElement('div', CSS.GAMEROOM_ITEM);
-
- const name = this.createElement('span', CSS.GAMEROOM_NAME, {
- text: room.name
- });
-
- const players = this.createElement('span', CSS.GAMEROOM_PLAYERS, {
- text: `${room.player_count || 0}/${room.max_players || 8}`
- });
-
- const actions = this.createElement('div', CSS.GAMEROOM_ACTIONS);
-
- const joinBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SUCCESS], {
- text: 'Rejoindre'
- });
- joinBtn.addEventListener('click', () => this.joinRoom(room.id));
- actions.appendChild(joinBtn);
-
- item.append(name, players, actions);
- return item;
- }
-
- createSpectatorRoomItem(room) {
- const item = this.createElement('div', CSS.GAMEROOM_ITEM);
-
- const name = this.createElement('span', CSS.GAMEROOM_NAME, {
- text: room.name
- });
-
- const players = this.createElement('span', CSS.GAMEROOM_PLAYERS, {
- text: `${room.player_count || 0}/${room.max_players || 8}`
- });
-
- const status = this.createElement('span', 'gameroom__status', {
- text: '🎮 En cours'
- });
- status.style.color = '#4CAF50';
- status.style.fontWeight = 'bold';
-
- const actions = this.createElement('div', CSS.GAMEROOM_ACTIONS);
-
- const spectateBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Regarder'
- });
- spectateBtn.addEventListener('click', () => this.spectateRoom(room.id));
- actions.appendChild(spectateBtn);
-
- item.append(name, players, status, actions);
- return item;
- }
-
- async loadPlayingRooms() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour voir les parties en cours', 'info');
- return;
- }
-
- try {
- const response = await fetch(API.ROOMS.PLAYING, {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.renderPlayingRoomsList(data || []);
- } catch (error) {
- console.error('Load playing rooms error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- renderPlayingRoomsList(rooms) {
- this.spectatorList.innerHTML = '';
- this.message.textContent = '';
-
- if (rooms.length === 0) {
- this.showMessage('Aucune partie en cours', 'info');
- return;
- }
-
- rooms.forEach(room => {
- const item = this.createSpectatorRoomItem(room);
- this.spectatorList.appendChild(item);
- });
- }
-
- async spectateRoom(roomId) {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour regarder', 'info');
- return;
- }
-
- // Check if user is already in a room as a player
- if (this.currentRoom && !this.isSpectating) {
- this.showMessage('Vous êtes déjà dans un salon. Quittez-le d\'abord.', 'error');
- return;
- }
-
- // Check if already spectating another game
- if (this.isSpectating && this.currentRoom && this.currentRoom.id !== roomId) {
- this.showMessage('Vous regardez déjà une autre partie', 'error');
- return;
- }
-
- try {
- const response = await fetch(API.ROOMS.SPECTATE(roomId), {
- method: 'POST',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Impossible de regarder cette partie', 'error');
- return;
- }
-
- // Store room info and mark as spectating
- this.currentRoom = data;
- this.isSpectating = true;
-
- // Join as spectator via socket
- await this.ensureSocketConnected();
- if (this.socket?.connected) {
- this.socket.emit('game-spectate-room', { roomId: roomId });
- }
-
- this.showMessage('Connexion à la partie...', 'info');
- } catch (error) {
- console.error('Spectate room error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- async createRoom() {
- const name = this.roomNameInput.value.trim();
- if (!name) {
- this.showMessage('Entrez un nom pour le salon', 'error');
- return;
- }
-
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour creer un salon', 'info');
- return;
- }
-
- if (this.currentRoom) {
- this.showMessage('Vous etes deja dans un salon. Quittez-le d\'abord.', 'error');
- return;
- }
-
- try {
- const currentResponse = await fetch(API.ROOMS.CURRENT, {
- headers: this.getHeaders()
- });
- if (currentResponse.ok && currentResponse.status !== 204) {
- const currentData = await currentResponse.json();
- if (currentData && currentData.id) {
- this.currentRoom = currentData;
- this.enterLobby(currentData);
- this.showMessage('Vous etes deja dans un salon', 'error');
- return;
- }
- }
- } catch (e) {
- // Continue
- }
-
- try {
- const listResponse = await fetch(API.ROOMS.LIST, {
- headers: this.getHeaders()
- });
- if (listResponse.ok) {
- this.roomsList = await listResponse.json() || [];
- }
- } catch (e) {
- // Continue
- }
-
- if (this.roomNameExists(name)) {
- this.showMessage('Un salon avec ce nom existe deja', 'error');
- return;
- }
-
- try {
- const response = await fetch(API.ROOMS.CREATE, {
- method: 'POST',
- headers: this.getHeaders(),
- body: JSON.stringify({ name })
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.roomNameInput.value = '';
- this.currentRoom = data;
- this.showMessage('Salon cree', 'success');
- eventBus.emit(Events.ROOM_CREATED, data);
- this.enterLobby(data);
- } catch (error) {
- console.error('Create room error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- async joinRoom(roomId) {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) {
- this.showMessage('Connectez-vous pour rejoindre', 'info');
- return;
- }
-
- if (this.currentRoom) {
- this.showMessage('Vous etes deja dans un salon. Quittez-le d\'abord.', 'error');
- return;
- }
-
- try {
- const currentResponse = await fetch(API.ROOMS.CURRENT, {
- headers: this.getHeaders()
- });
- if (currentResponse.ok && currentResponse.status !== 204) {
- const currentData = await currentResponse.json();
- if (currentData && currentData.id) {
- this.currentRoom = currentData;
- this.enterLobby(currentData);
- this.showMessage('Vous etes deja dans un salon', 'error');
- return;
- }
- }
- } catch (e) {
- // Continue
- }
-
- try {
- const response = await fetch(API.ROOMS.JOIN(roomId), {
- method: 'POST',
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- const roomResponse = await fetch(API.ROOMS.GET(roomId), {
- headers: this.getHeaders()
- });
- const roomData = await roomResponse.json();
-
- this.currentRoom = roomData;
- eventBus.emit(Events.ROOM_JOINED, roomData);
- this.enterLobby(roomData);
- } catch (error) {
- console.error('Join room error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- enterLobby(room) {
- this.currentRoom = room;
- this.lobbyTab.style.display = 'block';
- this.lobbyTitle.textContent = room.name;
- this.switchTab('lobby');
-
- // Connect to WebSocket for real-time sync
- this.connectToGameSocket();
- }
-
- async loadLobby() {
- if (!this.currentRoom) return;
-
- this.gameState.scores = {};
-
- try {
- const response = await fetch(API.ROOMS.PLAYERS(this.currentRoom.id), {
- headers: this.getHeaders()
- });
- const data = await response.json();
-
- if (!response.ok) {
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- this.renderPlayersList(data || []);
- } catch (error) {
- console.error('Load lobby error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- renderPlayersList(players) {
- this.playerList.innerHTML = '';
-
- if (players.length === 0) {
- const empty = this.createElement('div', 'gameroom__empty', {
- text: 'Aucun joueur'
- });
- this.playerList.appendChild(empty);
- // Disable start button if no players
- this.startGameBtn.disabled = true;
- this.startGameBtn.style.opacity = '0.5';
- this.startGameBtn.title = 'Il faut au moins 2 joueurs';
- return;
- }
-
- players.forEach(player => {
- const item = this.createElement('div', CSS.GAMEROOM_PLAYER);
-
- const avatar = this.createElement('img', CSS.GAMEROOM_PLAYER_AVATAR, {
- alt: player.username
- });
- avatar.src = player.avatar_url || '/avatar/default.png';
-
- const name = this.createElement('span', CSS.GAMEROOM_PLAYER_NAME, {
- text: player.username
- });
-
- const statsContainer = this.createElement('div', 'gameroom__player-stats');
-
- const score = this.createElement('span', CSS.GAMEROOM_PLAYER_SCORE, {
- text: `${player.score || 0} pts`
- });
-
- const totalPoints = this.createElement('span', 'gameroom__player-total', {
- text: `Total: ${player.total_points || 0}`
- });
-
- statsContainer.append(score, totalPoints);
- item.append(avatar, name, statsContainer);
- this.playerList.appendChild(item);
- });
-
- // Enable/disable start button based on player count
- if (players.length < 2) {
- this.startGameBtn.disabled = true;
- this.startGameBtn.style.opacity = '0.5';
- this.startGameBtn.title = 'Il faut au moins 2 joueurs';
- } else {
- this.startGameBtn.disabled = false;
- this.startGameBtn.style.opacity = '1';
- this.startGameBtn.title = '';
- }
- }
-
- async leaveRoom() {
- if (!this.currentRoom) return;
-
- // End game if playing
- if (this.gameState.isPlaying) {
- this.endGame();
- }
-
- this.disconnectGameSocket();
-
- try {
- const response = await fetch(API.ROOMS.LEAVE(this.currentRoom.id), {
- method: 'POST',
- headers: this.getHeaders()
- });
-
- if (!response.ok) {
- const data = await response.json();
- this.showMessage(data.error || 'Erreur', 'error');
- return;
- }
-
- eventBus.emit(Events.ROOM_LEFT, this.currentRoom);
- this.exitLobby();
- } catch (error) {
- console.error('Leave room error:', error);
- this.showMessage('Erreur de connexion', 'error');
- }
- }
-
- exitLobby() {
- this.currentRoom = null;
- this.lobbyTab.style.display = 'none';
- this.playerList.innerHTML = '';
- this.lobbyTitle.textContent = '';
- this.resetGameUI();
- this.switchTab('browse');
- }
-
- showMessage(text, type = 'info') {
- // Clear any existing timeout
- if (this.messageTimeout) {
- clearTimeout(this.messageTimeout);
- }
-
- this.message.textContent = text;
- this.message.className = CSS.MESSAGE;
-
- if (type === 'success') {
- this.message.classList.add(CSS.MESSAGE_SUCCESS);
- } else if (type === 'error') {
- this.message.classList.add(CSS.MESSAGE_ERROR);
- } else {
- this.message.classList.add(CSS.MESSAGE_INFO);
- }
-
- // Auto-clear message after 5 seconds
- this.messageTimeout = setTimeout(() => {
- this.message.textContent = '';
- this.message.className = CSS.MESSAGE;
- }, 5000);
- }
-
- // ============================================
- // LOGIQUE DU JEU
- // ============================================
-
- getCurrentUsername() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (!token) return null;
- try {
- const payload = JSON.parse(atob(token.split('.')[1]));
- return payload.username || payload.sub || 'Joueur';
- } catch {
- return 'Joueur';
- }
- }
-
- isCurrentUserDrawer() {
- return this.gameState.drawer === this.getCurrentUsername();
- }
-
- showGameUI() {
- this.gameContainer.style.display = 'flex';
- this.playerList.style.display = 'none';
- this.lobbyButtons.style.display = 'none';
- this.clearCanvas();
- this.guessHistory.innerHTML = '';
-
- // If spectating, show indicator and disable interactions
- if (this.isSpectating) {
- this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie';
- this.currentDrawerInfo.style.backgroundColor = '#2196F3';
- this.currentDrawerInfo.style.color = 'white';
- this.currentDrawerInfo.style.padding = '8px';
- this.currentDrawerInfo.style.borderRadius = '4px';
- this.currentDrawerInfo.style.textAlign = 'center';
-
- // Change button text for spectators
- this.backToLobbyBtn.textContent = 'Arrêter de regarder';
- this.endRoundBtn.style.display = 'none'; // Hide end game button for spectators
- } else {
- this.backToLobbyBtn.textContent = 'Quitter la partie';
- this.endRoundBtn.style.display = 'inline-block';
- }
- }
-
- resetGameUI() {
- this.gameState.isPlaying = false;
- this.gameState.currentWord = '';
- this.gameState.wordLength = 0;
- this.gameState.revealedLetters = [];
- this.gameState.revealedWord = [];
- this.gameState.drawer = null;
- this.isSpectating = false;
-
- this.gameState.scores = {};
- this.gameState.players = [];
- this.gameState.currentPlayerIndex = 0;
- this.gameState.guessedLetters = [];
-
- // Clear scores display
- if (this.scoresDisplay)
- this.scoresDisplay.textContent = '';
-
- if (this.guessHistory)
- this.guessHistory.innerHTML = '';
-
- this.clearCanvas();
-
- this.gameContainer.style.display = 'none';
- this.playerList.style.display = 'flex';
- this.lobbyButtons.style.display = 'flex';
-
- this.wordInputContainer.style.display = 'none';
- this.guessContainer.style.display = 'none';
- this.drawTools.style.display = 'none';
-
- // Reset spectator styling
- this.currentDrawerInfo.style.backgroundColor = '';
- this.currentDrawerInfo.style.color = '';
- this.currentDrawerInfo.style.padding = '';
- this.currentDrawerInfo.style.borderRadius = '';
- this.currentDrawerInfo.style.textAlign = '';
- this.currentDrawerInfo.classList.remove('gameroom__drawer-info--winner');
- }
-
- async startGame() {
- console.log('startGame called');
-
- // Load player list
- await this.loadLobby();
-
- const playerElements = this.playerList.querySelectorAll('.gameroom__player-name');
- const players = Array.from(playerElements).map(el => el.textContent);
-
- console.log('Players found:', players);
-
- if (players.length < 2) {
- this.showMessage('Il faut au moins 2 joueurs pour commencer', 'error');
- return;
- }
-
- const drawer = players[0];
-
- console.log('Socket connected:', this.socket?.connected, 'Socket ready:', this.socketReady);
-
- // Send start game event via WebSocket
- if (this.socket?.connected) {
- console.log('Emitting game-start event');
- this.socket.emit('game-start', { drawer, players });
- } else {
- console.log('No socket, using local fallback');
- // Fallback local - start immediately
- this.gameState.isPlaying = true;
- this.gameState.players = players;
- this.gameState.drawer = drawer;
- this.gameState.currentPlayerIndex = 0;
- this.showGameUI();
- this.setupRound();
- }
- }
-
- setupRound() {
- this.gameState.currentWord = '';
- this.gameState.wordLength = 0;
- this.gameState.revealedLetters = [];
- this.gameState.revealedWord = [];
- this.gameState.guessedLetters = [];
-
- this.currentDrawerInfo.textContent = `C'est au tour de ${this.gameState.drawer} de dessiner`;
- this.currentDrawerInfo.classList.remove('gameroom__drawer-info--winner');
- this.wordDisplay.textContent = '';
- this.guessHistory.innerHTML = '';
- this.clearCanvas();
-
- // Spectators cannot interact
- if (this.isSpectating) {
- this.wordInputContainer.style.display = 'none';
- this.guessContainer.style.display = 'none';
- this.drawTools.style.display = 'none';
- this.currentDrawerInfo.textContent = '👁️ MODE SPECTATEUR - Vous regardez la partie';
- return;
- }
-
- if (this.isCurrentUserDrawer()) {
- // Drawer chooses a word
- this.wordInputContainer.style.display = 'flex';
- this.guessContainer.style.display = 'none';
- this.drawTools.style.display = 'none';
- this.currentDrawerInfo.textContent = 'Choisissez un mot a faire deviner';
- } else {
- // Others see the guess input (disabled while waiting for word)
- this.wordInputContainer.style.display = 'none';
- this.guessContainer.style.display = 'flex';
- this.drawTools.style.display = 'none';
- this.letterInput.disabled = true;
- this.guessBtn.disabled = true;
- this.letterInput.placeholder = 'En attente du mot...';
- this.currentDrawerInfo.textContent = `${this.gameState.drawer} choisit un mot...`;
- }
- }
-
- confirmWord() {
- const word = this.wordInput.value.trim().toLowerCase();
- if (!word || word.length < 2) {
- this.showMessage('Le mot doit faire au moins 2 lettres', 'error');
- return;
- }
-
- if (!/^[a-z]+$/.test(word)) {
- this.showMessage('Le mot ne doit contenir que des lettres', 'error');
- return;
- }
-
- this.gameState.currentWord = word;
- this.gameState.wordLength = word.length;
- this.gameState.revealedLetters = new Array(word.length).fill(false);
- this.gameState.revealedWord = new Array(word.length).fill('_');
-
- this.wordInput.value = '';
- this.wordInputContainer.style.display = 'none';
- this.drawTools.style.display = 'flex';
-
- // Send word to server via WebSocket
- if (this.socket?.connected) {
- this.socket.emit('game-set-word', { word });
- }
-
- this.updateWordDisplay();
- this.currentDrawerInfo.textContent = `Dessinez pour faire deviner le mot (${word.length} lettres)`;
- }
-
- updateWordDisplay() {
- // If drawer, show from currentWord
- if (this.isCurrentUserDrawer() && this.gameState.currentWord) {
- let display = '';
- for (let i = 0; i < this.gameState.currentWord.length; i++) {
- if (this.gameState.revealedLetters && this.gameState.revealedLetters[i]) {
- display += this.gameState.currentWord[i] + ' ';
- } else {
- display += '_ ';
- }
- }
- this.wordDisplay.textContent = display.trim();
- return;
- }
-
- // For guessers, use revealedWord from server
- if (this.gameState.revealedWord && this.gameState.revealedWord.length > 0) {
- this.wordDisplay.textContent = this.gameState.revealedWord.join(' ');
- return;
- }
-
- // Fallback: show underscores based on wordLength
- if (this.gameState.wordLength > 0) {
- this.wordDisplay.textContent = '_ '.repeat(this.gameState.wordLength).trim();
- }
- }
-
- makeGuess() {
- const guess = this.letterInput.value.trim().toLowerCase();
- if (!guess) return;
-
- this.letterInput.value = '';
-
- // Send guess via WebSocket
- if (this.socket?.connected) {
- this.socket.emit('game-guess', { guess });
- } else {
- // Fallback local (for testing)
- this.processGuessLocally(guess);
- }
- }
-
- processGuessLocally(guess) {
- const username = this.getCurrentUsername();
-
- if (guess.length > 1) {
- const success = guess === this.gameState.currentWord;
- this.addGuessToHistory(guess, success, 'word', username);
- if (success) {
- this.gameState.revealedWord = this.gameState.currentWord.split('');
- this.wordFound(this.gameState.currentWord, username);
- }
- return;
- }
-
- if (this.gameState.guessedLetters.includes(guess)) {
- this.showMessage('Lettre deja proposee', 'info');
- return;
- }
-
- this.gameState.guessedLetters.push(guess);
-
- let found = false;
- for (let i = 0; i < this.gameState.currentWord.length; i++) {
- if (this.gameState.currentWord[i] === guess) {
- this.gameState.revealedLetters[i] = true;
- this.gameState.revealedWord[i] = guess;
- found = true;
- }
- }
-
- this.addGuessToHistory(guess, found, 'letter', username);
- this.updateWordDisplay();
-
- if (this.gameState.revealedLetters.every(r => r)) {
- this.wordFound(this.gameState.currentWord, username);
- }
- }
-
- addGuessToHistory(guess, success, type, username, points = 0) {
- const item = this.createElement('div', 'gameroom__guess-item');
- item.classList.add(success ? 'gameroom__guess-item--success' : 'gameroom__guess-item--fail');
-
- const typeText = type === 'letter' ? 'lettre' : 'mot';
- const pointsText = points !== 0 ? ` (${points > 0 ? '+' : ''}${points} pts)` : '';
-
- if (success) {
- item.textContent = `${username}: "${guess}" - Bon ${typeText}!${pointsText}`;
- } else {
- item.textContent = `${username}: "${guess}" - Mauvais ${typeText}${pointsText}`;
- }
-
- this.guessHistory.appendChild(item);
- this.guessHistory.scrollTop = this.guessHistory.scrollHeight;
- }
-
- updateScoresDisplay(scores) {
- if (!scores) return;
- this.gameState.scores = scores;
-
- // Update scores display in game UI
- if (this.scoresDisplay) {
- const sortedScores = Object.entries(scores)
- .sort((a, b) => b[1] - a[1])
- .map(([name, score]) => `${name}: ${score}`)
- .join(' | ');
- this.scoresDisplay.textContent = sortedScores;
- }
-
- // Update player list with scores if visible
- const playerItems = this.playerList.querySelectorAll('.gameroom__player');
- playerItems.forEach(item => {
- const nameEl = item.querySelector('.gameroom__player-name');
- const scoreEl = item.querySelector('.gameroom__player-score');
- if (nameEl && scoreEl) {
- const playerName = nameEl.textContent;
- const score = scores[playerName] || 0;
- scoreEl.textContent = `${score} pts`;
- }
- });
- }
-
- wordFound(word, winner, drawerBonus = 0) {
- let message = `${winner} a trouve le mot: ${word}!`;
- if (drawerBonus > 0 && this.gameState.drawer) {
- message += ` (${this.gameState.drawer} +${drawerBonus} pts)`;
- }
- this.currentDrawerInfo.textContent = message;
- this.currentDrawerInfo.classList.add('gameroom__drawer-info--winner');
-
- this.guessContainer.style.display = 'none';
- this.drawTools.style.display = 'none';
-
- // Reveal full word
- this.wordDisplay.textContent = word.split('').join(' ');
-
- // Auto next round after delay
- setTimeout(() => {
- if (this.gameState.isPlaying) {
- this.nextRound();
- }
- }, 3000);
- }
-
- nextRound() {
- // Move to next player
- this.gameState.currentPlayerIndex = (this.gameState.currentPlayerIndex + 1) % this.gameState.players.length;
- const nextDrawer = this.gameState.players[this.gameState.currentPlayerIndex];
-
- if (this.socket?.connected) {
- this.socket.emit('game-next-round', { drawer: nextDrawer });
- } else {
- this.gameState.drawer = nextDrawer;
- this.setupRound();
- }
- }
-
- backToLobby() {
- if (this.socket?.connected) {
- this.socket.emit('leave-room-during-game');
- }
-
- // Return to lobby without ending game for others
- this.resetGameUI();
- this.exitLobby();
- this.showMessage('Vous avez quitté la partie', 'info');
- }
-
- endGame() {
- if (this.socket?.connected) {
- this.socket.emit('game-end');
- }
- this.resetGameUI();
- this.showMessage('Jeu termine', 'info');
-
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/global_chat.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/global_chat.js
deleted file mode 100755
index 2936321..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/global_chat.js
+++ /dev/null
@@ -1,320 +0,0 @@
-import { Window } from './windows.js';
-import { STORAGE_KEYS, CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-/**
- * Global chat window
- * Uses Socket.IO for real-time communication
- */
-export class GlobalChat extends Window {
- constructor() {
- super({
- name: 'chat',
- title: 'Global Chat',
- cssClasses: ['chat']
- });
-
- this.socket = null;
- this.connected = false;
- this.friendIds = new Set();
- this.currentUserId = null;
- this.currentUsername = null;
-
- this.buildUI();
- this.bindEvents();
- }
-
- /**
- * Builds the user interface
- */
- buildUI() {
- // Message display area
- this.output = this.createElement('div', CSS.CHAT_OUTPUT);
-
- // Input container
- this.inputContainer = this.createElement('div', 'chat__input-container');
-
- this.input = this.createElement('input', [CSS.INPUT, CSS.CHAT_INPUT], {
- type: 'text',
- placeholder: 'Type your message...'
- });
-
- this.sendBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Send'
- });
-
- this.inputContainer.append(this.input, this.sendBtn);
-
- // Connection controls
- this.controls = this.createElement('div', CSS.CHAT_CONTROLS);
-
- this.connectBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SUCCESS], {
- text: 'Connect'
- });
-
- this.reconnectBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Reconnect'
- });
-
- this.controls.append(this.connectBtn, this.reconnectBtn);
-
- // Assembly
- this.body.append(this.output, this.inputContainer, this.controls);
- }
-
- /**
- * Attaches event handlers
- */
- bindEvents() {
- this.sendBtn.addEventListener('click', () => this.sendMessage());
- this.connectBtn.addEventListener('click', () => this.connect());
- this.reconnectBtn.addEventListener('click', () => this.reconnect());
-
- // Send with Enter
- this.input.addEventListener('keypress', (e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- this.sendMessage();
- }
- });
- }
-
- /**
- * Displays a system message
- * @param {string} text - Message text
- * @param {'info'|'error'|'success'} type - Message type
- */
- addSystemMessage(text, type = 'info') {
- const msg = this.createElement('div', CSS.CHAT_SYSTEM);
-
- if (type === 'error') {
- msg.classList.add('chat__system--error');
- } else if (type === 'success') {
- msg.classList.add('chat__system--success');
- }
-
- msg.textContent = text;
- this.output.appendChild(msg);
- this.scrollToBottom();
- }
-
- /**
- * Displays a chat message
- * @param {string} username - User name
- * @param {string} content - Message content
- * @param {boolean} isOwn - Is this the current user's message
- * @param {boolean} isFriend - Is this user a friend
- */
- addChatMessage(username, content, isOwn = false, isFriend = false) {
- const msg = this.createElement('div', CSS.CHAT_MESSAGE);
-
- if (isOwn) {
- msg.classList.add('chat__message--own');
- }
-
- const friendIndicator = isFriend ? '' : '';
- msg.innerHTML = `${friendIndicator}${this.escapeHtml(username)}: ${this.escapeHtml(content)}`;
- this.output.appendChild(msg);
- this.scrollToBottom();
- }
-
- /**
- * Escapes HTML to prevent XSS
- * @param {string} text
- * @returns {string}
- */
- escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
-
- /**
- * Scrolls the message area to the bottom
- */
- scrollToBottom() {
- this.output.scrollTop = this.output.scrollHeight;
- }
-
- /**
- * Sends a message
- */
- sendMessage() {
- const content = this.input.value.trim();
- if (!content) return;
-
- if (!this.socket?.connected) {
- this.addSystemMessage('Error: you are not connected to the global chat', 'error');
- return;
- }
-
- this.socket.emit('chat-message', { content });
- this.addChatMessage('Me', content, true);
- this.input.value = '';
- }
-
- /**
- * Reconnects to the server
- */
- async reconnect() {
- if (this.socket) {
- try {
- this.socket.close();
- } catch (e) {
- // Ignore
- }
- this.socket = null;
- }
-
- this.connected = false;
- this.addSystemMessage('Reconnecting...');
- await this.connect();
- }
-
- decodeToken(token)
- {
- try
- {
- const payload = token.split('.')[1];
- return (JSON.parse(atob(payload)));
- }
- catch
- {
- return (null);
- }
- }
-
- /**
- * Connects to the Socket.IO server
- */
- async connect() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
-
- if (!token) {
- this.addSystemMessage('Error: you must be logged in to use the global chat', 'error');
- return;
- }
-
- const tokenData = this.decodeToken(token);
-
- if (tokenData) {
- this.currentUserId = tokenData.id || tokenData.userId || tokenData.user_id || tokenData.sub || null;
- this.currentUsername = tokenData.username || tokenData.name || null;
- }
-
- if (this.socket?.connected) {
- this.addSystemMessage('Already connected to global chat');
- return;
- }
-
- // Load Socket.IO if needed
- await this.loadSocketIO();
-
- const ioConfig = {
- auth: { token },
- reconnection: true,
- reconnectionAttempts: 5,
- reconnectionDelay: 1000,
- transports: ['websocket', 'polling']
- };
-
- // Optional alternative port
- const altPort = window.GLOBAL_CHAT_ALT_PORT;
- if (altPort) {
- const host = location.hostname || 'localhost';
- this.socket = io(`http://${host}:${altPort}`, ioConfig);
- } else {
- this.socket = io(ioConfig);
- }
-
- this.setupSocketListeners();
- }
-
- /**
- * Loads the Socket.IO script if needed
- */
- async loadSocketIO() {
- if (window.io) return;
-
- return new Promise((resolve, reject) => {
- const script = document.createElement('script');
- script.src = '/socket.io/socket.io.js';
-
- script.onload = () => {
- console.log('Socket.IO loaded');
- resolve();
- };
-
- script.onerror = () => {
- console.error('Failed to load Socket.IO');
- reject(new Error('Socket.IO load failed'));
- };
-
- document.head.appendChild(script);
- });
- }
-
- /**
- * Sets up Socket.IO listeners
- */
- setupSocketListeners() {
- this.socket.on('connect', () => {
- console.log('Socket connected, ID:', this.socket.id);
- this.connected = true;
- this.output.innerHTML = '';
- this.addSystemMessage('Connected to global chat', 'success');
- eventBus.emit(Events.CHAT_CONNECTED, { socketId: this.socket.id });
- });
-
- this.socket.on('connect_error', (err) => {
- console.error('Socket connection error:', err.message);
- this.addSystemMessage(`Connection error: ${err.message}`, 'error');
- });
-
- this.socket.on('disconnect', (reason) => {
- console.log('Socket disconnected:', reason);
- this.connected = false;
- this.addSystemMessage(`Disconnected (${reason})`);
- eventBus.emit(Events.CHAT_DISCONNECTED, { reason });
- });
-
- this.socket.on('chat-init', (data) => {
- this.friendIds = new Set(data.friendIds || []);
-
- // Display recent messages
- data.messages.forEach(msg => {
- const isOwn = this.isOwnMessage(msg);
- const isFriend = !isOwn && this.friendIds.has(msg.sender_id);
- const displayUsername = isOwn ? 'Me' : msg.username;
- this.addChatMessage(displayUsername, msg.content, isOwn, isFriend);
- });
- });
-
- this.socket.on('chat-message', (msg) => {
- const isOwn = this.isOwnMessage(msg);
- if (isOwn)
- return;
-
- const isFriend = this.friendIds.has(msg.sender_id);
- this.addChatMessage(msg.username, msg.content, false, isFriend);
- eventBus.emit(Events.CHAT_MESSAGE_RECEIVED, msg);
- });
- }
-
- isOwnMessage(msg)
- {
- if (this.currentUserId !== null && msg.sender_id !== undefined && msg.sender_id !== null)
- {
- if (String(this.currentUserId) === String(msg.sender_id))
- return (true);
- }
-
- if (this.currentUsername && msg.username)
- {
- if (this.currentUsername.toLowerCase() === msg.username.toLowerCase())
- return (true);
- }
-
- return (false);
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.css b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.css
deleted file mode 100755
index 3e260a9..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.css
+++ /dev/null
@@ -1,682 +0,0 @@
-/* ============================================
- TRANSCENDENCE - Main Stylesheet
- Convention: BEM (Block__Element--Modifier)
- ============================================ */
-
-/* ============================================
- CSS VARIABLES
- ============================================ */
-:root {
- --color-primary: #0066cc;
- --color-primary-hover: #0052a3;
- --color-success: #3cff01;
- --color-success-dark: #28a745;
- --color-error: #ff4d4d;
- --color-warning: #ffc107;
- --color-github: #24292e;
-
- --color-bg: #a3a3a3;
-
- --app-background-base: radial-gradient(
- circle at top,
- #000000,
- #4d4d4d
- );
-
- --app-background-image: url("./assets/background.png");
-
- --color-surface: #222;
- --color-surface-light: #333;
- --color-text: #fff;
- --color-text-muted: #aaa;
-
- --font-size-base: 10px;
- --font-size-sm: 1.2rem;
- --font-size-md: 1.4rem;
- --font-size-lg: 1.6rem;
- --font-size-xl: 3rem;
-
- --spacing-xs: 4px;
- --spacing-sm: 8px;
- --spacing-md: 12px;
- --spacing-lg: 16px;
- --spacing-xl: 24px;
-
- --radius-sm: 4px;
- --radius-md: 6px;
- --radius-lg: 12px;
- --radius-full: 50%;
-
- --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
- --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
- --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
-
- --transition-fast: 150ms ease;
- --transition-normal: 250ms ease;
-
- --z-menu: 2;
- --z-window: 100;
- --z-modal: 200;
-}
-
-/* ============================================
- RESET & BASE
- ============================================ */
-*,
-*::before,
-*::after {
- box-sizing: border-box;
-}
-
-html {
- height: 100%;
- background-image:
- var(--app-background-image),
- var(--app-background-base);
-
- background-size:
- contain,
- cover;
-
- background-position:
- center,
- center;
-
- background-repeat:
- no-repeat,
- no-repeat;
-}
-
-body {
- margin: 0;
- width: 70%;
- min-width: 800px;
- margin: 0 auto;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- color: var(--color-text);
- line-height: 1.5;
-}
-
-/* ============================================
- TYPOGRAPHY
- ============================================ */
-
-.title {
- position: absolute;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- text-transform: uppercase;
-
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 20px;
-
- font-size: var(--font-size-xl);
- text-align: center;
- text-shadow: 2px 2px 10px black;
- z-index: 1;
- font-family: "Cinzel Decorative", cursive;
- color: rgba(248, 252, 2, 0.6);
-
- margin: 0;
- padding: var(--spacing-md);
-
- /* Rectangle + rounded corners */
- background-color: rgba(247, 7, 67, 0.6);
- border: 2px solid rgba(0, 0, 0, 0.6);
- border-radius: 15px;
-}
-
-
-/* ============================================
- MENU
- ============================================ */
-
-.menu {
- position: fixed;
- top: 0;
- left: 50px;
- padding: 0;
- margin: 0;
- z-index: var(--z-menu);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.menu__item {
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-align: left;
-}
-
-.menu__item:hover {
- background: var(--color-surface-light);
- font-size: var(--font-size-lg);
-}
-
-.menu__item--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-/* ============================================
- GAME
- ============================================ */
-
-.game {
- position: fixed;
- top: 0;
- right: 50px;
- padding: 0;
- margin: 0;
- z-index: var(--z-menu);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
-}
-
-.game__item {
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-align: right;
-}
-
-.game__item:hover {
- background: var(--color-surface-light);
- font-size: var(--font-size-lg);
-}
-
-.game__item--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-/* ============================================
- BUTTONS
- ============================================ */
-.btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- font-weight: 500;
- border: none;
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--transition-fast);
- text-decoration: none;
-}
-
-.btn:hover {
- opacity: 0.9;
- transform: translateY(-1px);
-}
-
-.btn:active {
- transform: translateY(0);
-}
-
-.btn--primary {
- background: var(--color-primary);
- color: var(--color-text);
-}
-
-.btn--primary:hover {
- background: var(--color-primary-hover);
-}
-
-.btn--secondary {
- background: var(--color-surface-light);
- color: var(--color-text);
-}
-
-.btn--success {
- background: var(--color-success-dark);
- color: var(--color-text);
-}
-
-.btn--danger {
- background: var(--color-error);
- color: var(--color-text);
-}
-
-.btn--github {
- background: var(--color-github);
- color: var(--color-text);
-}
-
-.btn--ghost {
- background: transparent;
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
-}
-
-/* ============================================
- INPUTS
- ============================================ */
-.input {
- width: 100%;
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: var(--font-size-md);
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- border-radius: var(--radius-md);
- transition: border-color var(--transition-fast);
-}
-
-.input:focus {
- outline: none;
- border-color: var(--color-primary);
-}
-
-.input::placeholder {
- color: var(--color-text-muted);
-}
-
-.input-group {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-/* ============================================
- WINDOWS
- ============================================ */
-.window {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: var(--color-bg);
- border: 2px ridge var(--color-text);
- color: var(--color-text);
- z-index: var(--z-window);
- display: none;
- flex-direction: column;
- min-width: 280px;
- box-shadow: var(--shadow-lg);
-}
-
-.window--visible {
- display: flex;
-}
-
-.window--left {
- left: 25%;
-}
-
-.window--right {
- left: 75%;
-}
-
-.window__header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: var(--spacing-sm) var(--spacing-md);
- background: var(--color-surface);
- cursor: move;
- user-select: none;
-}
-
-.window__title {
- font-weight: 500;
- font-size: var(--font-size-md);
-}
-
-.window__close {
- cursor: pointer;
- font-size: var(--font-size-lg);
- opacity: 0.8;
- transition: opacity var(--transition-fast);
- background: none;
- border: none;
- color: var(--color-text);
- padding: 0;
- line-height: 1;
-}
-
-.window__close:hover {
- opacity: 1;
-}
-
-.window__body {
- padding: var(--spacing-md);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- flex: 1;
- overflow: auto;
-}
-
-/* ============================================
- MESSAGES
- ============================================ */
-.message {
- font-size: var(--font-size-sm);
- padding: var(--spacing-xs);
- border-radius: var(--radius-sm);
-}
-
-.message--success {
- color: var(--color-success);
-}
-
-.message--error {
- color: var(--color-error);
-}
-
-.message--info {
- color: var(--color-text-muted);
-}
-
-/* ============================================
- LOGIN WINDOW
- ============================================ */
-.login {
- width: 320px;
-}
-
-.login__form {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-.login__actions {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-xs);
-}
-
-.login__divider {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- color: var(--color-text-muted);
- font-size: var(--font-size-sm);
- margin: var(--spacing-sm) 0;
-}
-
-.login__divider::before,
-.login__divider::after {
- content: '';
- flex: 1;
- height: 1px;
- background: var(--color-surface-light);
-}
-
-/* ============================================
- CHAT WINDOW
- ============================================ */
-.chat {
- width: 380px;
- height: 400px;
-}
-
-.chat__output {
- flex: 1;
- overflow-y: auto;
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- min-height: 150px;
-}
-
-.chat__message {
- padding: var(--spacing-xs) var(--spacing-sm);
- background: var(--color-surface-light);
- border-radius: var(--radius-sm);
- font-size: var(--font-size-sm);
-}
-
-.chat__message--own {
- background: var(--color-primary);
- align-self: flex-end;
-}
-
-.chat__friend-indicator {
- display: inline-block;
- width: 8px;
- height: 8px;
- background-color: var(--color-success);
- border-radius: 50%;
- margin-right: var(--spacing-xs);
- vertical-align: middle;
-}
-
-.chat__system {
- color: var(--color-text-muted);
- font-size: var(--font-size-sm);
- font-style: italic;
- text-align: center;
-}
-
-.chat__system--error {
- color: var(--color-error);
-}
-
-.chat__system--success {
- color: var(--color-success);
-}
-
-.chat__input-container {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-sm);
-}
-
-.chat__input {
- flex: 1;
-}
-
-.chat__controls {
- display: flex;
- gap: var(--spacing-sm);
- margin-top: var(--spacing-sm);
-}
-
-/* ============================================
- AVATAR WINDOW
- ============================================ */
-.avatar-window {
- width: 360px;
-}
-
-.avatar__preview {
- width: 120px;
- height: 120px;
- object-fit: cover;
- border-radius: var(--radius-full);
- border: 3px solid var(--color-text);
- box-shadow: var(--shadow-md);
- background: var(--color-surface);
- align-self: center;
-}
-
-.avatar__username {
- font-size: var(--font-size-lg);
- font-weight: 600;
- text-align: center;
- color: var(--color-text);
- margin-top: var(--spacing-sm);
-}
-
-.avatar__controls {
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
- align-items: center;
-}
-
-.avatar__file-input {
- display: none;
-}
-
-/* ============================================
- EASTER EGG BUTTON
- ============================================ */
-/* .easter-egg {
- position: absolute;
- top: 20%;
- left: 50%;
- transform: translateX(-50%);
- z-index: 1;
- background: var(--color-surface);
- color: var(--color-text);
- border: 1px solid var(--color-surface-light);
- padding: var(--spacing-sm) var(--spacing-md);
- cursor: pointer;
- font-size: var(--font-size-md);
- border-radius: var(--radius-md);
- transition: all var(--transition-fast);
-}
-
-.easter-egg:hover {
- background: var(--color-error);
- border-color: var(--color-error);
-} */
-
-/* ============================================
- UTILITIES
- ============================================ */
-.hidden {
- display: none !important;
-}
-
-.visually-hidden {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-
-.text-center {
- text-align: center;
-}
-
-.flex-center {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-/* ============================================
- FRIENDS WINDOW
- ============================================ */
-.friends-window {
- width: 400px;
- height: 450px;
-}
-
-.friends__tabs {
- display: flex;
- gap: var(--spacing-xs);
- margin-bottom: var(--spacing-sm);
-}
-
-.friends__tab {
- flex: 1;
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border: 1px solid var(--color-surface-light);
- color: var(--color-text);
- cursor: pointer;
- font-size: var(--font-size-sm);
- transition: all var(--transition-fast);
-}
-
-.friends__tab:hover {
- background: var(--color-surface-light);
-}
-
-.friends__tab--active {
- background: var(--color-primary);
- border-color: var(--color-primary);
-}
-
-.friends__content {
- display: flex;
- flex-direction: column;
- flex: 1;
- overflow: hidden;
-}
-
-.friends__search {
- display: flex;
- gap: var(--spacing-sm);
- margin-bottom: var(--spacing-sm);
-}
-
-.friends__search .input {
- flex: 1;
-}
-
-.friends__list {
- flex: 1;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-sm);
-}
-
-.friends__item {
- display: flex;
- align-items: center;
- gap: var(--spacing-sm);
- padding: var(--spacing-sm);
- background: var(--color-surface);
- border-radius: var(--radius-md);
-}
-
-.friends__avatar {
- width: 40px;
- height: 40px;
- border-radius: var(--radius-full);
- object-fit: cover;
- border: 2px solid var(--color-surface-light);
-}
-
-.friends__name {
- flex: 1;
- font-size: var(--font-size-md);
- font-weight: 500;
-}
-
-.friends__actions {
- display: flex;
- gap: var(--spacing-xs);
-}
-
-.friends__actions .btn {
- padding: var(--spacing-xs) var(--spacing-sm);
- font-size: var(--font-size-sm);
-}
-
-.friends__empty {
- text-align: center;
- color: var(--color-text-muted);
- padding: var(--spacing-lg);
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.html b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.html
deleted file mode 100755
index ee45125..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/index.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
- Transcendence.io
-
-
-
-
-
-
- Transcendence.io
-
-
-
-
-
-
-
-
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/login.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/login.js
deleted file mode 100755
index f83dfec..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/login.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import { Window } from './windows.js';
-import { API, STORAGE_KEYS, CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-/**
- * Login and registration window
- * Emits events instead of directly importing other windows
- */
-export class LoginWindow extends Window {
- constructor() {
- super({
- name: 'login',
- title: 'Login',
- cssClasses: ['login']
- });
-
- this.buildUI();
- this.bindEvents();
- this.checkIfAlreadyLoggedIn();
- }
-
- /**
- * Builds the user interface
- */
- buildUI() {
- // Main form
- this.form = this.createElement('div', 'login__form');
-
- // Username field
- this.usernameInput = this.createElement('input', CSS.INPUT, {
- type: 'text',
- placeholder: 'Username'
- });
-
- // Password field
- this.passwordInput = this.createElement('input', CSS.INPUT, {
- type: 'password',
- placeholder: 'Password'
- });
-
- // Action buttons
- this.actions = this.createElement('div', 'login__actions');
-
- this.loginBtn = this.createElement('button', [CSS.BTN, CSS.BTN_PRIMARY], {
- text: 'Sign in'
- });
-
- this.registerBtn = this.createElement('button', [CSS.BTN, CSS.BTN_SECONDARY], {
- text: 'Register'
- });
-
- this.actions.append(this.loginBtn, this.registerBtn);
-
- // Feedback message
- this.message = this.createElement('div', CSS.MESSAGE);
-
- // Divider
- this.divider = this.createElement('div', 'login__divider', {
- text: 'or'
- });
-
- // GitHub button
- this.githubBtn = this.createElement('button', [CSS.BTN, CSS.BTN_GITHUB], {
- text: 'Sign in with GitHub'
- });
-
- // Assembly
- this.form.append(
- this.usernameInput,
- this.passwordInput,
- this.actions,
- this.message,
- this.divider,
- this.githubBtn
- );
-
- this.body.appendChild(this.form);
- }
-
- /**
- * Attaches event handlers
- */
- bindEvents() {
- this.loginBtn.addEventListener('click', () => this.handleLogin());
- this.registerBtn.addEventListener('click', () => this.handleRegister());
- this.githubBtn.addEventListener('click', () => this.handleGitHubLogin());
-
- // Login with Enter
- this.passwordInput.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- this.handleLogin();
- }
- });
- }
-
- /**
- * Checks if user is already logged in
- */
- checkIfAlreadyLoggedIn() {
- const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
- if (token) {
- this.showMessage('You are already logged in!', 'success');
- }
- }
-
- /**
- * Handles login
- */
- async handleLogin() {
- const username = this.usernameInput.value.trim();
- const password = this.passwordInput.value;
-
- if (!username || !password) {
- this.showMessage('Please fill in all fields', 'error');
- return;
- }
-
- this.showMessage('Signing in...', 'info');
-
- try {
- const response = await fetch(API.AUTH.LOGIN, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ username, password })
- });
-
- const data = await response.json();
-
- if (response.ok && data.token) {
- console.log('Login successful, storing token');
- localStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, data.token);
- this.showMessage('Login successful! Welcome.', 'success');
-
- console.log('Emitting USER_LOGGED_IN event');
- // Emit login event
- eventBus.emit(Events.USER_LOGGED_IN, { username, token: data.token });
-
- console.log('Token stored:', !!localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN));
-
- // Close window after delay
- setTimeout(() => this.hide(), 1500);
- } else {
- const errorMsg = data?.message || 'Login failed';
- this.showMessage(errorMsg, 'error');
- }
- } catch (error) {
- console.error('Login error:', error);
- this.showMessage('Server connection error', 'error');
- }
- }
-
- /**
- * Handles registration
- */
- async handleRegister() {
- const username = this.usernameInput.value.trim();
- const password = this.passwordInput.value;
-
- if (!username || !password) {
- this.showMessage('Please fill in all fields', 'error');
- return;
- }
-
- this.showMessage('Registering...', 'info');
-
- try {
- const response = await fetch(API.AUTH.REGISTER, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ username, password })
- });
-
- const data = await response.json();
-
- if (response.ok) {
- this.showMessage('Registration successful! You can now sign in.', 'success');
- eventBus.emit(Events.USER_REGISTERED, { username });
- } else {
- const errorMsg = data?.message || 'Registration failed';
- this.showMessage(errorMsg, 'error');
- }
- } catch (error) {
- console.error('Registration error:', error);
- this.showMessage('Server connection error', 'error');
- }
- }
-
- /**
- * Handles GitHub OAuth login
- */
- handleGitHubLogin() {
- const width = 600;
- const height = 700;
- const left = (screen.width - width) / 2;
- const top = (screen.height - height) / 2;
-
- const popup = window.open(
- API.AUTH.GITHUB,
- 'githubOAuth',
- `width=${width},height=${height},left=${left},top=${top}`
- );
-
- const handleMessage = (event) => {
- if (event.data?.token) {
- localStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, event.data.token);
- this.showMessage('GitHub login successful! Welcome.', 'success');
-
- // Emit login event
- eventBus.emit(Events.USER_LOGGED_IN, {
- provider: 'github',
- token: event.data.token
- });
-
- window.removeEventListener('message', handleMessage);
- if (popup) popup.close();
- }
- };
-
- window.addEventListener('message', handleMessage, { once: true });
- }
-
- /**
- * Displays a feedback message
- * @param {string} text - Message text
- * @param {'success'|'error'|'info'} type - Message type
- */
- showMessage(text, type = 'info') {
- this.message.textContent = text;
- this.message.className = CSS.MESSAGE;
-
- if (type === 'success') {
- this.message.classList.add(CSS.MESSAGE_SUCCESS);
- } else if (type === 'error') {
- this.message.classList.add(CSS.MESSAGE_ERROR);
- } else {
- this.message.classList.add(CSS.MESSAGE_INFO);
- }
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/pieces.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/pieces.js
deleted file mode 100755
index 9af9cad..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/pieces.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// ─────────────────────────────────────────────
-// PIÈCES
-// ─────────────────────────────────────────────
-
-class Piece {
- constructor(startX, startY) {
- this.position = { x: startX, y: startY };
- this.currentRotation = 0;
- this.rotations = this.defineRotations();
- this.shape = this.rotations[0];
- this.color = this.getColor();
- }
- defineRotations() { return [[[1]]]; }
- getColor() { return 1; }
- getPosition() { return { ...this.position }; }
- getShape() { return this.shape; }
- moveDown() { this.position.y++; }
- moveLeft() { this.position.x--; }
- moveRight() { this.position.x++; }
- rotateLeft() {
- this.currentRotation = (this.currentRotation - 1 + this.rotations.length) % this.rotations.length;
- this.shape = this.rotations[this.currentRotation];
- }
- rotateRight() {
- this.currentRotation = (this.currentRotation + 1) % this.rotations.length;
- this.shape = this.rotations[this.currentRotation];
- }
-}
-
-class PieceT extends Piece {
- defineRotations() {
- return [
- [[0,1,0],[1,1,1],[0,0,0]],
- [[0,1,0],[0,1,1],[0,1,0]],
- [[0,0,0],[1,1,1],[0,1,0]],
- [[0,1,0],[1,1,0],[0,1,0]]
- ];
- }
- getColor() { return 1; }
-}
-
-class PieceL extends Piece {
- defineRotations() {
- return [
- [[0,0,1],[1,1,1],[0,0,0]],
- [[0,1,0],[0,1,0],[0,1,1]],
- [[0,0,0],[1,1,1],[1,0,0]],
- [[1,1,0],[0,1,0],[0,1,0]]
- ];
- }
- getColor() { return 2; }
-}
-
-class PieceReverseL extends Piece {
- defineRotations() {
- return [
- [[1,0,0],[1,1,1],[0,0,0]],
- [[0,1,1],[0,1,0],[0,1,0]],
- [[0,0,0],[1,1,1],[0,0,1]],
- [[0,1,0],[0,1,0],[1,1,0]]
- ];
- }
- getColor() { return 3; }
-}
-
-class PieceI extends Piece {
- defineRotations() {
- return [
- [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
- [[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]]
- ];
- }
- getColor() { return 4; }
-}
-
-class PieceZ extends Piece {
- defineRotations() {
- return [
- [[1,1,0],[0,1,1],[0,0,0]],
- [[0,0,1],[0,1,1],[0,1,0]]
- ];
- }
- getColor() { return 5; }
-}
-
-class PieceReverseZ extends Piece {
- defineRotations() {
- return [
- [[0,1,1],[1,1,0],[0,0,0]],
- [[0,1,0],[0,1,1],[0,0,1]]
- ];
- }
- getColor() { return 6; }
-}
-
-class PieceO extends Piece {
- defineRotations() { return [[[1,1],[1,1]]]; }
- getColor() { return 7; }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/renderer.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/renderer.js
deleted file mode 100755
index f024216..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/renderer.js
+++ /dev/null
@@ -1,126 +0,0 @@
-// ─────────────────────────────────────────────
-// RENDU
-// ─────────────────────────────────────────────
-
-const CELL = 30;
-const COLORS = ['#070712','#a855f7','#f97316','#3b82f6','#06b6d4','#ef4444','#22c55e','#eab308','#555577'];
-
-const ctxMain = document.getElementById('canvas-main').getContext('2d');
-const ctxNext = document.getElementById('canvas-next').getContext('2d');
-const ctxHold = document.getElementById('canvas-hold').getContext('2d');
-const ctxOpponent = document.getElementById('canvas-opponent').getContext('2d');
-
-function drawCell(ctx, x, y, colorIndex, size) {
- const p = 1;
- ctx.fillStyle = COLORS[colorIndex];
- ctx.fillRect(x * size + p, y * size + p, size - p * 2, size - p * 2);
- // Highlight
- ctx.fillStyle = 'rgba(255,255,255,0.25)';
- ctx.fillRect(x * size + p, y * size + p, size - p * 2, 3);
- ctx.fillRect(x * size + p, y * size + p, 3, size - p * 2);
- // Ombre
- ctx.fillStyle = 'rgba(0,0,0,0.35)';
- ctx.fillRect(x * size + p, (y + 1) * size - p - 3, size - p * 2, 3);
- ctx.fillRect((x + 1) * size - p - 3, y * size + p, 3, size - p * 2);
-}
-
-function clearCanvas(ctx, w, h) {
- ctx.fillStyle = '#070712';
- ctx.fillRect(0, 0, w, h);
-}
-
-function drawGridLines(ctx, cols, rows, size) {
- ctx.strokeStyle = 'rgba(255,255,255,0.04)';
- ctx.lineWidth = 1;
- for (let x = 0; x <= cols; x++) {
- ctx.beginPath(); ctx.moveTo(x * size, 0); ctx.lineTo(x * size, rows * size); ctx.stroke();
- }
- for (let y = 0; y <= rows; y++) {
- ctx.beginPath(); ctx.moveTo(0, y * size); ctx.lineTo(cols * size, y * size); ctx.stroke();
- }
-}
-
-function drawGhost(ctx, piece, grid) {
- if (!piece) return;
- const ghost = { x: piece.getPosition().x, y: piece.getPosition().y };
- const shape = piece.getShape();
-
- while (true) {
- ghost.y++;
- let valid = true;
- for (let row = 0; row < shape.length && valid; row++)
- for (let col = 0; col < shape[row].length && valid; col++)
- if (shape[row][col] !== 0) {
- const ny = ghost.y + row;
- const nx = ghost.x + col;
- if (ny >= grid.length || grid[ny][nx] !== 0) valid = false;
- }
- if (!valid) { ghost.y--; break; }
- }
-
- if (ghost.y === piece.getPosition().y) return;
-
- ctx.strokeStyle = 'rgba(255,255,255,0.15)';
- ctx.lineWidth = 1;
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0)
- ctx.strokeRect(
- (ghost.x + col) * CELL + 2,
- (ghost.y + row) * CELL + 2,
- CELL - 4, CELL - 4
- );
-}
-
-function drawMiniPiece(ctx, piece, canvasW, canvasH) {
- clearCanvas(ctx, canvasW, canvasH);
- if (!piece) return;
- const shape = piece.getShape();
- const color = piece.getColor();
- const s = 20;
- const offsetX = Math.floor((canvasW / s - shape[0].length) / 2);
- const offsetY = Math.floor((canvasH / s - shape.length) / 2);
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0)
- drawCell(ctx, offsetX + col, offsetY + row, color, s);
-}
-
-function render() {
- // Grille principale
- clearCanvas(ctxMain, 300, 600);
- drawGridLines(ctxMain, 10, 20, CELL);
-
- for (let y = 0; y < game.grid.length; y++)
- for (let x = 0; x < game.grid[y].length; x++)
- if (game.grid[y][x] !== 0)
- drawCell(ctxMain, x, y, game.grid[y][x], CELL);
-
- // Ghost + pièce courante
- if (game.currentPiece) {
- drawGhost(ctxMain, game.currentPiece, game.grid);
- const { x, y } = game.currentPiece.getPosition();
- const shape = game.currentPiece.getShape();
- const color = game.currentPiece.getColor();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0)
- drawCell(ctxMain, x + col, y + row, color, CELL);
- }
-
- // Panneaux miniatures
- drawMiniPiece(ctxNext, game.nextPiece, 100, 80);
- drawMiniPiece(ctxHold, game.storedPiece, 100, 80);
-
- // Score
- document.getElementById('score-display').textContent = game.score;
-}
-
-function renderOpponent(opponentGrid) {
- clearCanvas(ctxOpponent, 300, 600);
- drawGridLines(ctxOpponent, 10, 20, CELL);
- for (let y = 0; y < opponentGrid.length; y++)
- for (let x = 0; x < opponentGrid[y].length; x++)
- if (opponentGrid[y][x] !== 0)
- drawCell(ctxOpponent, x, y, opponentGrid[y][x], CELL);
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.css b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.css
deleted file mode 100755
index 9d29537..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.css
+++ /dev/null
@@ -1,355 +0,0 @@
-:root {
- --bg: #070712;
- --panel: #0d0d1f;
- --border: #1a1a3e;
- --accent: #00ffe7;
- --accent2:#ff00aa;
- --dim: #3a3a6a;
- --text: #c0c0e0;
-}
-
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-body {
- background: var(--bg);
- font-family: 'Share Tech Mono', monospace;
- color: var(--text);
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- overflow: hidden;
-}
-
-body::before {
- content: '';
- position: fixed;
- inset: 0;
- background-image:
- linear-gradient(rgba(0,255,231,0.03) 1px, transparent 1px),
- linear-gradient(90deg, rgba(0,255,231,0.03) 1px, transparent 1px);
- background-size: 40px 40px;
- pointer-events: none;
- z-index: 0;
-}
-
-h1 {
- font-family: 'Orbitron', monospace;
- font-weight: 900;
- font-size: 2.2rem;
- letter-spacing: 0.4em;
- color: var(--accent);
- text-shadow: 0 0 20px var(--accent), 0 0 40px var(--accent);
- margin-bottom: 20px;
- position: relative;
- z-index: 1;
-}
-
-/* ── Zone de jeu globale ── */
-#game-area {
- display: flex;
- gap: 32px;
- align-items: flex-start;
- position: relative;
- z-index: 1;
-}
-
-/* ── Section locale (légèrement décalée à gauche par le flex naturel) ── */
-#local-section {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-#app {
- display: flex;
- gap: 16px;
- align-items: flex-start;
-}
-
-/* ── Section adversaire ── */
-#opponent-section {
- display: none; /* masqué jusqu'à connexion duel */
- gap: 16px;
- align-items: flex-start;
-}
-#opponent-section.visible {
- display: flex;
-}
-
-.opponent-info-panel {
- width: 130px;
-}
-
-/* ── Panneaux ── */
-.panel {
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 14px;
- width: 130px;
- box-shadow: 0 0 20px rgba(0,255,231,0.05);
-}
-
-.panel-title {
- font-family: 'Orbitron', monospace;
- font-size: 0.6rem;
- letter-spacing: 0.2em;
- color: var(--accent);
- text-transform: uppercase;
- margin-bottom: 10px;
- text-align: center;
-}
-
-canvas { display: block; border-radius: 4px; }
-
-#canvas-main {
- border: 1px solid var(--border);
- box-shadow: 0 0 30px rgba(0,255,231,0.08), inset 0 0 30px rgba(0,0,0,0.5);
-}
-
-#canvas-next, #canvas-hold {
- border: 1px solid var(--border);
- margin: 0 auto;
-}
-
-/* ── Canvas adversaire ── */
-#canvas-opponent {
- border: 1px solid var(--accent2);
- box-shadow: 0 0 30px rgba(255,0,170,0.08), inset 0 0 30px rgba(0,0,0,0.5);
-}
-
-/* ── Score ── */
-.score-block {
- margin-top: 14px;
- text-align: center;
-}
-
-.score-label {
- font-size: 0.55rem;
- letter-spacing: 0.2em;
- color: var(--dim);
- text-transform: uppercase;
- margin-bottom: 4px;
-}
-
-.score-value {
- font-family: 'Orbitron', monospace;
- font-size: 1.4rem;
- font-weight: 700;
- color: var(--accent);
- text-shadow: 0 0 10px var(--accent);
-}
-
-/* ── Boutons ── */
-.btn-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-top: 14px;
-}
-
-button {
- font-family: 'Orbitron', monospace;
- font-size: 0.55rem;
- letter-spacing: 0.15em;
- font-weight: 700;
- text-transform: uppercase;
- padding: 10px 8px;
- border: 1px solid;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.2s;
- background: transparent;
- width: 100%;
-}
-
-#btn-start { color: var(--accent); border-color: var(--accent); }
-#btn-start:hover:not(:disabled) { background: var(--accent); color: var(--bg); box-shadow: 0 0 15px var(--accent); }
-
-#btn-pause { color: var(--accent2); border-color: var(--accent2); }
-#btn-pause:hover:not(:disabled) { background: var(--accent2); color: var(--bg); box-shadow: 0 0 15px var(--accent2); }
-
-#btn-stop { color: #ef4444; border-color: #ef4444; }
-#btn-stop:hover:not(:disabled) { background: #ef4444; color: var(--bg); box-shadow: 0 0 15px #ef4444; }
-
-button:disabled { opacity: 0.3; cursor: not-allowed; }
-
-/* ── Contrôles ── */
-.controls-list {
- margin-top: 14px;
- font-size: 0.6rem;
- line-height: 2;
- color: var(--dim);
-}
-.controls-list span { color: var(--text); }
-
-/* ── Overlays ── */
-#main-wrapper,
-#opponent-wrapper { position: relative; }
-
-#overlay,
-#overlay-opponent {
- display: none;
- position: absolute;
- top: 0; left: 0;
- width: 300px;
- height: 600px;
- background: rgba(7,7,18,0.88);
- border-radius: 4px;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 12px;
- z-index: 10;
- pointer-events: none;
-}
-#overlay.visible,
-#overlay-opponent.visible { display: flex; }
-
-#overlay-title {
- font-family: 'Orbitron', monospace;
- font-size: 1.4rem;
- font-weight: 900;
- letter-spacing: 0.2em;
- color: var(--accent2);
- text-shadow: 0 0 20px var(--accent2);
-}
-
-#overlay-score {
- font-family: 'Orbitron', monospace;
- font-size: 0.9rem;
- color: var(--accent);
-}
-
-#overlay-opponent-title {
- font-family: 'Orbitron', monospace;
- font-size: 1.4rem;
- font-weight: 900;
- letter-spacing: 0.2em;
- color: var(--accent);
- text-shadow: 0 0 20px var(--accent);
-}
-
-#overlay-opponent-score {
- font-family: 'Orbitron', monospace;
- font-size: 0.9rem;
- color: var(--accent2);
-}
-
-/* ── Panneau duel ── */
-#duel-panel {
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 12px 20px;
- margin-bottom: 14px;
- position: relative;
- z-index: 1;
- display: flex;
- align-items: center;
- gap: 14px;
- box-shadow: 0 0 20px rgba(255,0,170,0.04);
-}
-
-.duel-row {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-
-#input-room-code {
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 4px;
- color: var(--accent2);
- font-family: 'Orbitron', monospace;
- font-size: 0.7rem;
- letter-spacing: 0.15em;
- padding: 6px 10px;
- width: 120px;
- text-transform: uppercase;
- outline: none;
- transition: border-color 0.2s;
-}
-#input-room-code:focus {
- border-color: var(--accent2);
- box-shadow: 0 0 8px rgba(255,0,170,0.2);
-}
-
-#btn-join-duel { color: var(--accent2); border-color: var(--accent2); width: auto; padding: 6px 14px; }
-#btn-join-duel:hover:not(:disabled) { background: var(--accent2); color: var(--bg); box-shadow: 0 0 12px var(--accent2); }
-
-#btn-leave-duel { color: #ef4444; border-color: #ef4444; width: auto; padding: 6px 14px; }
-#btn-leave-duel:hover:not(:disabled) { background: #ef4444; color: var(--bg); box-shadow: 0 0 12px #ef4444; }
-
-#duel-status {
- font-size: 0.6rem;
- letter-spacing: 0.1em;
- color: var(--dim);
- min-width: 120px;
-}
-#duel-status.waiting { color: #f97316; }
-#duel-status.ready { color: var(--accent); }
-
-/* ── Settings Panel ── */
-#settings-panel {
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 14px 20px;
- margin-top: -250px;
- margin-left: -600px;
- box-shadow: 0 0 20px rgba(0,255,231,0.05);
- position: relative;
- z-index: 1;
- display: flex;
- flex-direction: column;
- gap: 10px;
- width: fit-content;
-}
-
-.settings-title {
- font-family: 'Orbitron', monospace;
- font-size: 0.6rem;
- letter-spacing: 0.2em;
- color: var(--accent);
- text-transform: uppercase;
- text-align: center;
- margin-bottom: 4px;
-}
-
-.settings-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
- font-size: 0.6rem;
- color: var(--dim);
- letter-spacing: 0.05em;
-}
-
-#settings-panel input[type="number"] {
- background: var(--bg);
- border: 1px solid var(--border);
- border-radius: 4px;
- color: var(--accent);
- font-family: 'Orbitron', monospace;
- font-size: 0.65rem;
- padding: 4px 8px;
- width: 80px;
- text-align: right;
- outline: none;
- transition: border-color 0.2s;
-}
-
-#settings-panel input[type="number"]:focus {
- border-color: var(--accent);
- box-shadow: 0 0 8px rgba(0,255,231,0.2);
-}
-
-#settings-panel input[type="number"]:disabled {
- opacity: 0.3;
- cursor: not-allowed;
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.html b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.html
deleted file mode 100755
index ea0a17c..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.html
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
-
-
-
- TETRIS
-
-
-
-
-
-TETRIS
-
-
-
-
Duel
-
-
-
-
-
-
—
-
-
-
-
-
-
-
-
-
-
-
Hold
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Next
-
-
-
-
← → Déplacer
-
↓ Descendre
-
Q Rot. gauche
-
W Rot. droite
-
Espace Drop
-
C Hold
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Paramètres
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.js
deleted file mode 100755
index 6032065..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/tetris.js
+++ /dev/null
@@ -1,366 +0,0 @@
-// ─────────────────────────────────────────────
-// LOGIQUE TETRIS
-// ─────────────────────────────────────────────
-
-class Tetris {
- constructor(onRender, onGameOver, onBlockPlaced = null, onLinesCleared = null) {
- this.onRender = onRender;
- this.onGameOver = onGameOver;
- this.onBlockPlaced = onBlockPlaced;
- this.onLinesCleared = onLinesCleared;
-
- this.grid = this._createGrid(10, 20);
- this.bufferGrid = this._createGrid(10, 5);
- this.currentPiece = null;
- this.storedPiece = null;
- this.nextPiece = null;
-
- this.score = 0;
- this.initialTimeToDown = 1000;
- this.timeToDown = 1000;
- this.hardening = 1000;
- this.count = 0;
- this.decrementTTD = 100;
-
- this.lastLandingCol = 4;
-
- this.isRunning = false;
- this.isPaused = false;
- this.canStore = true;
-
- this.animationFrameId = null;
- this.lastTime = 0;
- this.accumulator = 0;
-
- this._keyHandler = this._handleKey.bind(this);
- }
-
- configure({ timeToDown, hardening, decrementTTD }) {
- if (timeToDown !== undefined) this.initialTimeToDown = this.timeToDown = timeToDown;
- if (hardening !== undefined) this.hardening = hardening;
- if (decrementTTD !== undefined) this.decrementTTD = decrementTTD;
- }
-
- _createGrid(w, h) {
- return Array.from({ length: h }, () => Array(w).fill(0));
- }
-
- start() {
- if (this.isRunning) return;
- this.isRunning = true;
- this.isPaused = false;
- this.grid = this._createGrid(10, 20);
- this.score = 0;
- this.count = 0;
- this.timeToDown = this.initialTimeToDown;
- this.storedPiece = null;
- this.canStore = true;
- this._spawnNewPiece();
- document.addEventListener('keydown', this._keyHandler);
- this._startGameLoop();
- }
-
- stop() {
- this.isRunning = false;
- this.isPaused = false;
- if (this.animationFrameId !== null) {
- cancelAnimationFrame(this.animationFrameId);
- this.animationFrameId = null;
- }
- this.accumulator = 0;
- this.lastTime = 0;
- document.removeEventListener('keydown', this._keyHandler);
- }
-
- pause() {
- if (!this.isRunning) return;
- this.isPaused = !this.isPaused;
- if (!this.isPaused) {
- this.lastTime = 0;
- this._startGameLoop();
- }
- }
-
- _startGameLoop() {
- this.lastTime = 0;
- this.accumulator = 0;
-
- const gameLoop = (currentTime) => {
- if (!this.isRunning) return;
-
- if (this.isPaused) {
- this.animationFrameId = requestAnimationFrame(gameLoop);
- return;
- }
-
- if (this.lastTime === 0) {
- this.lastTime = currentTime;
- this.animationFrameId = requestAnimationFrame(gameLoop);
- return;
- }
-
- const deltaTime = currentTime - this.lastTime;
- this.lastTime = currentTime;
- this.accumulator += deltaTime;
-
- while (this.isRunning && this.accumulator >= this.timeToDown) {
- this._tick();
- this.accumulator -= this.timeToDown;
- if (this.accumulator > this.timeToDown * 3) {
- this.accumulator = 0;
- break;
- }
- }
-
- this.onRender();
- this.animationFrameId = requestAnimationFrame(gameLoop);
- };
-
- this.animationFrameId = requestAnimationFrame(gameLoop);
- }
-
- _tick() {
- if (!this.currentPiece) return;
- if (this._canMoveDown()) {
- this.currentPiece.moveDown();
- } else {
- this._lockPiece();
- this.verifierLignes();
- this._makeHarder();
- this._spawnNewPiece();
- this.canStore = true;
- if (!this._canSpawn()) this._gameOver();
- }
- }
-
- _handleKey(e) {
- if (!this.isRunning || !this.currentPiece) return;
-
- switch (e.key) {
- case 'ArrowLeft':
- e.preventDefault();
- if (!this.isPaused && this._canMoveLeft()) this.currentPiece.moveLeft();
- break;
- case 'ArrowRight':
- e.preventDefault();
- if (!this.isPaused && this._canMoveRight()) this.currentPiece.moveRight();
- break;
- case 'ArrowDown':
- e.preventDefault();
- if (!this.isPaused && this._canMoveDown()) {
- this.currentPiece.moveDown();
- this.score += 1;
- this.accumulator = 0;
- }
- break;
- case ' ':
- e.preventDefault();
- if (!this.isPaused) this._hardDrop();
- break;
- case 'q': case 'Q':
- e.preventDefault();
- if (!this.isPaused) this._rotatePiece(-1);
- break;
- case 'w': case 'W':
- e.preventDefault();
- if (!this.isPaused) this._rotatePiece(1);
- break;
- case 'c': case 'C':
- e.preventDefault();
- if (!this.isPaused) this._storePiece();
- break;
- }
-
- this.onRender();
- }
-
- _hardDrop() {
- if (!this.currentPiece) return;
- let dist = 0;
- while (this._canMoveDown()) { this.currentPiece.moveDown(); dist++; }
- this.score += dist * 2;
- this._lockPiece();
- this.verifierLignes();
- this._makeHarder();
- this._spawnNewPiece();
- this.canStore = true;
- this.accumulator = 0;
- if (!this._canSpawn()) this._gameOver();
- }
-
- _rotatePiece(direction) {
- if (!this.currentPiece) return;
- const originalPos = { ...this.currentPiece.getPosition() };
-
- if (direction === -1) this.currentPiece.rotateLeft();
- else this.currentPiece.rotateRight();
-
- if (!this._isValidPosition()) {
- this.currentPiece.moveRight();
- if (this._isValidPosition()) return;
-
- this.currentPiece.moveLeft();
- this.currentPiece.moveLeft();
- if (this._isValidPosition()) return;
-
- this.currentPiece.moveLeft();
- if (this._isValidPosition()) return;
-
- this.currentPiece.moveRight();
- this.currentPiece.moveRight();
- this.currentPiece.position.y--;
- if (this._isValidPosition()) return;
-
- this.currentPiece.position.y = originalPos.y;
- this.currentPiece.position.x = originalPos.x;
- if (direction === -1) this.currentPiece.rotateRight();
- else this.currentPiece.rotateLeft();
- }
- }
-
- _storePiece() {
- if (!this.canStore || !this.currentPiece) return;
-
- if (this.storedPiece === null) {
- this.storedPiece = this.currentPiece;
- this._spawnNewPiece();
- } else {
- const temp = this.storedPiece;
- this.storedPiece = this.currentPiece;
- this.currentPiece = temp;
- this.currentPiece.position.x = 3;
- this.currentPiece.position.y = 0;
- }
- this.canStore = false;
- this.accumulator = 0;
- }
-
- _spawnNewPiece() {
- this.currentPiece = this.nextPiece || this._createRandomPiece();
- this.nextPiece = this._createRandomPiece();
- this._updateBufferGrid();
- }
-
- _createRandomPiece() {
- const types = [PieceT, PieceL, PieceReverseL, PieceI, PieceZ, PieceReverseZ, PieceO];
- return new types[Math.floor(Math.random() * types.length)](3, 0);
- }
-
- _updateBufferGrid() {
- this.bufferGrid = this._createGrid(10, 5);
- if (!this.nextPiece) return;
- const shape = this.nextPiece.getShape();
- const offsetX = Math.floor((10 - shape[0].length) / 2);
- for (let y = 0; y < shape.length; y++)
- for (let x = 0; x < shape[y].length; x++)
- if (shape[y][x] !== 0)
- this.bufferGrid[y + 1][x + offsetX] = this.nextPiece.getColor();
- }
-
- verifierLignes() {
- let cleared = 0;
- for (let y = this.grid.length - 1; y >= 0; y--) {
- if (this.grid[y].every(c => c !== 0)) {
- this.grid.splice(y, 1);
- this.grid.unshift(Array(10).fill(0));
- cleared++;
- y++;
- }
- }
- const points = [0, 100, 300, 500, 800];
- this.score += points[cleared];
- this.count += points[cleared];
- if (this.onLinesCleared && cleared > 0)
- this.onLinesCleared(cleared, this.lastLandingCol);
- }
-
- _makeHarder() {
- if (this.count >= this.hardening) {
- this.count = 0;
- this.timeToDown = Math.max(100, this.timeToDown - this.decrementTTD);
- }
- }
-
- _canMoveDown() {
- if (!this.currentPiece) return false;
- const { x, y } = this.currentPiece.getPosition();
- const shape = this.currentPiece.getShape();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0) {
- const ny = y + row + 1;
- const nx = x + col;
- if (ny >= this.grid.length || this.grid[ny][nx] !== 0) return false;
- }
- return true;
- }
-
- _canMoveLeft() {
- if (!this.currentPiece) return false;
- const { x, y } = this.currentPiece.getPosition();
- const shape = this.currentPiece.getShape();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0) {
- const nx = x + col - 1;
- if (nx < 0 || this.grid[y + row][nx] !== 0) return false;
- }
- return true;
- }
-
- _canMoveRight() {
- if (!this.currentPiece) return false;
- const { x, y } = this.currentPiece.getPosition();
- const shape = this.currentPiece.getShape();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0) {
- const nx = x + col + 1;
- if (nx >= this.grid[0].length || this.grid[y + row][nx] !== 0) return false;
- }
- return true;
- }
-
- _isValidPosition() {
- if (!this.currentPiece) return false;
- const { x, y } = this.currentPiece.getPosition();
- const shape = this.currentPiece.getShape();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0) {
- const gx = x + col;
- const gy = y + row;
- if (gx < 0 || gx >= this.grid[0].length ||
- gy < 0 || gy >= this.grid.length ||
- this.grid[gy][gx] !== 0) return false;
- }
- return true;
- }
-
- _canSpawn() { return this._isValidPosition(); }
-
- _lockPiece() {
- if (!this.currentPiece) return;
- const { x, y } = this.currentPiece.getPosition();
- const shape = this.currentPiece.getShape();
- const color = this.currentPiece.getColor();
- for (let row = 0; row < shape.length; row++)
- for (let col = 0; col < shape[row].length; col++)
- if (shape[row][col] !== 0)
- this.grid[y + row][x + col] = color;
- this.lastLandingCol = x + Math.floor(shape[0].length / 2);
- if (this.onBlockPlaced) this.onBlockPlaced(this.grid.map(r => [...r]));
- }
-
- addGarbageLines(lines) {
- if (!this.isRunning || !lines.length) return;
- this.grid.splice(0, lines.length);
- for (const line of lines) this.grid.push([...line]);
- if (!this._isValidPosition()) this._gameOver();
- }
-
- _gameOver() {
- this.stop();
- this.onGameOver(this.score);
- }
-}
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ui.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ui.js
deleted file mode 100755
index d738e54..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/ui.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// ─────────────────────────────────────────────
-// UI
-// ─────────────────────────────────────────────
-
-const btnStart = document.getElementById('btn-start');
-const btnPause = document.getElementById('btn-pause');
-const btnStop = document.getElementById('btn-stop');
-const overlay = document.getElementById('overlay');
-const inputTTD = document.getElementById('input-ttd');
-const inputHardening = document.getElementById('input-hardening');
-const inputDecrement = document.getElementById('input-decrement');
-
-// Duel UI
-const btnJoinDuel = document.getElementById('btn-join-duel');
-const btnLeaveDuel = document.getElementById('btn-leave-duel');
-const inputRoomCode = document.getElementById('input-room-code');
-const duelStatusEl = document.getElementById('duel-status');
-const opponentSection = document.getElementById('opponent-section');
-
-function updateButtons() {
- btnStart.disabled = game.isRunning;
- btnPause.disabled = !game.isRunning;
- btnStop.disabled = !game.isRunning;
- btnPause.textContent = game.isPaused ? 'Resume' : 'Pause';
- inputTTD.disabled = game.isRunning;
- inputHardening.disabled = game.isRunning;
- inputDecrement.disabled = game.isRunning;
-}
-
-function showOverlay(title, score) {
- document.getElementById('overlay-title').textContent = title;
- document.getElementById('overlay-score').textContent = score !== undefined ? `Score : ${score}` : '';
- overlay.classList.add('visible');
-}
-
-function hideOverlay() {
- overlay.classList.remove('visible');
-}
-
-// ─────────────────────────────────────────────
-// SOCKET + DUEL
-// ─────────────────────────────────────────────
-
-const socket = io({
- auth: { token: localStorage.getItem('auth_token') },
- reconnection: true,
- reconnectionAttempts: 5,
- reconnectionDelay: 1000,
- transports: ['websocket', 'polling']
-});
-
-let duel = null;
-
-function updateDuelStatus(status, opponentName) {
- duelStatusEl.className = '';
- if (status === 'waiting') {
- duelStatusEl.textContent = 'En attente d\'un adversaire…';
- duelStatusEl.classList.add('waiting');
- opponentSection.classList.remove('visible');
- } else if (status === 'ready') {
- duelStatusEl.textContent = `Prêt — ${opponentName}`;
- duelStatusEl.classList.add('ready');
- opponentSection.classList.add('visible');
- if (duel) duel.hideOpponentOverlay();
- renderOpponent(duel ? duel.opponentGrid : Array.from({length:20}, () => Array(10).fill(0)));
- } else {
- duelStatusEl.textContent = '—';
- opponentSection.classList.remove('visible');
- }
-}
-
-function startLocalGame() {
- hideOverlay();
- game.start();
- updateButtons();
- render();
-}
-
-btnJoinDuel.addEventListener('click', () => {
- const code = inputRoomCode.value.trim().toUpperCase();
- if (!code) return;
- if (duel) { duel.leave(); }
- duel = new Duel(socket, game, updateDuelStatus, startLocalGame);
- duel.join(code);
- btnJoinDuel.disabled = true;
- btnLeaveDuel.disabled = false;
- inputRoomCode.disabled = true;
- updateDuelStatus('waiting', null);
-});
-
-btnLeaveDuel.addEventListener('click', () => {
- if (duel) { duel.leave(); duel = null; }
- btnJoinDuel.disabled = false;
- btnLeaveDuel.disabled = true;
- inputRoomCode.disabled = false;
- updateDuelStatus(null, null);
-});
-
-// ─────────────────────────────────────────────
-// INIT
-// ─────────────────────────────────────────────
-
-const game = new Tetris(
- // onRender
- () => {
- if (duel) duel.synchronize_game();
- render();
- updateButtons();
- },
- // onGameOver
- (score) => {
- if (duel) duel.onLocalGameOver(score);
- render();
- updateButtons();
- showOverlay('GAME OVER', score);
- },
- // onBlockPlaced — relay duel
- (grid) => {
- if (duel) duel.onLocalBlockPlaced(grid, game.score);
- },
- // onLinesCleared — relay duel
- (count, holeCol) => {
- if (duel) duel.onLocalLinesCleared(count, holeCol);
- }
-);
-
-btnStart.addEventListener('click', () => {
- if (duel && duel.isReady) {
- duel.startDuel(); // déclenche les deux parties via le serveur
- } else {
- startLocalGame(); // solo
- }
-});
-
-btnPause.addEventListener('click', () => {
- game.pause();
- updateButtons();
- if (game.isPaused) showOverlay('PAUSE');
- else hideOverlay();
-});
-
-btnStop.addEventListener('click', () => {
- game.stop();
- updateButtons();
- render();
- showOverlay('STOPPED');
-});
-
-function applySettings() {
- game.configure({
- timeToDown: parseInt(inputTTD.value, 10),
- hardening: parseInt(inputHardening.value, 10),
- decrementTTD: parseInt(inputDecrement.value, 10),
- });
-}
-
-inputTTD.addEventListener('change', applySettings);
-inputHardening.addEventListener('change', applySettings);
-inputDecrement.addEventListener('change', applySettings);
-
-render();
-updateButtons();
diff --git a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/windows.js b/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/windows.js
deleted file mode 100755
index 229a229..0000000
--- a/Transcendence/Transcendance-Test/Transcendence/srcs/frontend/src/windows.js
+++ /dev/null
@@ -1,234 +0,0 @@
-import { CSS } from './config.js';
-import { eventBus, Events } from './events.js';
-
-/**
- * Centralized window registry
- * Manages window visibility and positioning
- */
-class WindowRegistry {
- constructor() {
- this.windows = new Map();
- }
-
- /**
- * Registers a window in the registry
- * @param {string} name - Unique window name
- * @param {Window} window - Window instance
- */
- register(name, window) {
- this.windows.set(name, window);
- }
-
- /**
- * Gets a window by its name
- * @param {string} name - Window name
- * @returns {Window|undefined}
- */
- get(name) {
- return this.windows.get(name);
- }
-
- /**
- * Returns all visible windows
- * @returns {Window[]}
- */
- getVisible() {
- return Array.from(this.windows.values()).filter(w => w.isVisible());
- }
-
- /**
- * Reorganizes visible windows
- */
- reorganize() {
- const visible = this.getVisible();
-
- if (visible.length === 0) return;
-
- if (visible.length === 1) {
- visible[0].setPosition('center');
- } else if (visible.length === 2) {
- visible[0].setPosition('left');
- visible[1].setPosition('right');
- } else {
- visible.forEach(w => w.setPosition('center'));
- }
- }
-
- /**
- * Shows a window and reorganizes
- * @param {string} name - Window name
- */
- show(name) {
- const window = this.get(name);
- if (window) {
- window.show();
- }
- }
-
- /**
- * Hides a window and reorganizes
- * @param {string} name - Window name
- */
- hide(name) {
- const window = this.get(name);
- if (window) {
- window.hide();
- }
- }
-
- /**
- * Toggles window visibility
- * @param {string} name - Window name
- */
- toggle(name) {
- const window = this.get(name);
- if (window) {
- window.toggle();
- }
- }
-}
-
-// Singleton registry instance
-export const windowRegistry = new WindowRegistry();
-
-/**
- * Base class for all windows
- * Uses CSS classes instead of inline styles
- */
-export class Window {
- /**
- * @param {Object} options - Configuration options
- * @param {string} options.name - Unique name for the registry
- * @param {string} options.title - Title displayed in the header
- * @param {string[]} [options.cssClasses] - Additional CSS classes
- */
- constructor({ name, title, cssClasses = [] }) {
- this.name = name;
-
- // Create main container
- this.element = document.createElement('div');
- this.element.className = [CSS.WINDOW, ...cssClasses].join(' ');
-
- // Header
- this.header = document.createElement('div');
- this.header.className = CSS.WINDOW_HEADER;
-
- this.titleElement = document.createElement('span');
- this.titleElement.className = CSS.WINDOW_TITLE;
- this.titleElement.textContent = title;
-
- this.closeBtn = document.createElement('button');
- this.closeBtn.className = CSS.WINDOW_CLOSE;
- this.closeBtn.textContent = '✖';
- this.closeBtn.setAttribute('aria-label', 'Close');
- this.closeBtn.addEventListener('click', () => this.hide());
-
- this.header.append(this.titleElement, this.closeBtn);
-
- // Body
- this.body = document.createElement('div');
- this.body.className = CSS.WINDOW_BODY;
-
- // Assembly
- this.element.append(this.header, this.body);
- document.body.appendChild(this.element);
-
- // Register in the registry
- windowRegistry.register(name, this);
- }
-
- /**
- * Checks if the window is visible
- * @returns {boolean}
- */
- isVisible() {
- return this.element.classList.contains(CSS.WINDOW_VISIBLE);
- }
-
- /**
- * Sets the window position
- * @param {'center'|'left'|'right'} position
- */
- setPosition(position) {
- this.element.classList.remove('window--left', 'window--right');
-
- if (position === 'left') {
- this.element.classList.add('window--left');
- } else if (position === 'right') {
- this.element.classList.add('window--right');
- }
- }
-
- /**
- * Shows the window
- */
- show() {
- const wasHidden = !this.isVisible();
- this.element.classList.add(CSS.WINDOW_VISIBLE);
-
- if (wasHidden) {
- windowRegistry.reorganize();
- eventBus.emit(Events.WINDOW_OPENED, { name: this.name });
- }
- }
-
- /**
- * Hides the window
- */
- hide() {
- const wasVisible = this.isVisible();
- this.element.classList.remove(CSS.WINDOW_VISIBLE);
-
- if (wasVisible) {
- windowRegistry.reorganize();
- eventBus.emit(Events.WINDOW_CLOSED, { name: this.name });
- }
- }
-
- /**
- * Toggles visibility
- */
- toggle() {
- if (this.isVisible()) {
- this.hide();
- } else {
- this.show();
- }
- }
-
- /**
- * Updates the window title
- * @param {string} title
- */
- setTitle(title) {
- this.titleElement.textContent = title;
- }
-
- /**
- * Creates an element with CSS classes
- * @param {string} tag - HTML tag
- * @param {string|string[]} classes - CSS classes
- * @param {Object} [attrs] - Additional attributes
- * @returns {HTMLElement}
- */
- createElement(tag, classes, attrs = {}) {
- const element = document.createElement(tag);
- const classList = Array.isArray(classes) ? classes : [classes];
- element.className = classList.filter(Boolean).join(' ');
-
- Object.entries(attrs).forEach(([key, value]) => {
- if (key === 'text') {
- element.textContent = value;
- } else if (key === 'html') {
- element.innerHTML = value;
- } else {
- element.setAttribute(key, value);
- }
- });
-
- return element;
- }
-}
-
-// Export old class name for compatibility (alias)
-export { Window as fenetre };