versionpostpull
@@ -28,3 +28,4 @@ application = ProtocolTypeRouter({
|
||||
)
|
||||
),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,106 +1,418 @@
|
||||
# /pong/game/consumers.py
|
||||
|
||||
import json
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.contrib.auth.models import User
|
||||
from channels.db import database_sync_to_async
|
||||
from .matchmaking import match_maker
|
||||
from .tournament import tournament_match_maker
|
||||
from .models import Player
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GameConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
await self.accept()
|
||||
self.game = None
|
||||
print("User connected")
|
||||
async def connect(self):
|
||||
await self.accept()
|
||||
self.game = None
|
||||
print("User connected")
|
||||
|
||||
async def receive(self, text_data):
|
||||
data = json.loads(text_data)
|
||||
if data['type'] == 'authenticate':
|
||||
await self.authenticate(data['token'])
|
||||
elif data['type'] == 'authenticate2':
|
||||
await self.authenticate2(data['token_1'], data['token_2'])
|
||||
elif data['type'] == 'authenticate3':
|
||||
await self.authenticate3(data['token'])
|
||||
elif data['type'] == 'key_press':
|
||||
if self.game:
|
||||
await self.game.handle_key_press(self, data['key'])
|
||||
elif data['type'] == 'start_tournament':
|
||||
print(f"Start TOURNAMENT received by {self.user}")
|
||||
# Run the tournament in the background
|
||||
asyncio.create_task(tournament_match_maker.start_tournament())
|
||||
async def receive(self, text_data):
|
||||
data = json.loads(text_data)
|
||||
if data['type'] == 'authenticate':
|
||||
await self.authenticate(data['token'])
|
||||
elif data['type'] == 'authenticate2':
|
||||
await self.authenticate2(data['token_1'], data['token_2'])
|
||||
elif data['type'] == 'authenticate3':
|
||||
await self.authenticate3(data['token'])
|
||||
elif data['type'] == 'key_press':
|
||||
if self.game:
|
||||
await self.game.handle_key_press(self, data['key'])
|
||||
elif data['type'] == 'start_tournament':
|
||||
print(f"Start TOURNAMENT received by {self.user}")
|
||||
# Run the tournament in the background
|
||||
asyncio.create_task(tournament_match_maker.start_tournament())
|
||||
|
||||
async def authenticate(self, token):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user} authenticated")
|
||||
await self.join_waiting_room()
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
async def authenticate(self, token):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user} authenticated")
|
||||
await self.join_waiting_room()
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
|
||||
@database_sync_to_async
|
||||
def get_user_from_token(self, token):
|
||||
try:
|
||||
user = User.objects.filter(auth_token=token).first()
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
@database_sync_to_async
|
||||
def get_user_from_token(self, token):
|
||||
try:
|
||||
user = User.objects.filter(auth_token=token).first()
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
async def join_waiting_room(self):
|
||||
await self.send(text_data=json.dumps({'type': 'waiting_room'}))
|
||||
await match_maker.add_player(self)
|
||||
async def join_waiting_room(self):
|
||||
await self.send(text_data=json.dumps({'type': 'waiting_room'}))
|
||||
await match_maker.add_player(self)
|
||||
|
||||
async def authenticate2(self, token, token2):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user} authenticated")
|
||||
user2 = await self.get_user_from_token2(token2)
|
||||
if user2:
|
||||
self.user2 = user2
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user2} authenticated")
|
||||
await match_maker.create_game(self, None, True)
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
async def authenticate2(self, token, token2):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user} authenticated")
|
||||
user2 = await self.get_user_from_token2(token2)
|
||||
if user2:
|
||||
self.user2 = user2
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user2} authenticated")
|
||||
await match_maker.create_game(self, None, True)
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Authentication failed")
|
||||
|
||||
@database_sync_to_async
|
||||
def get_user_from_token2(self, token):
|
||||
try:
|
||||
user2 = User.objects.filter(auth_token=token).first()
|
||||
return user2
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
@database_sync_to_async
|
||||
def get_user_from_token2(self, token):
|
||||
try:
|
||||
user2 = User.objects.filter(auth_token=token).first()
|
||||
return user2
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
async def authenticate3(self, token):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user.username} authenticated for tournament")
|
||||
await self.join_tournament_waiting_room()
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Tournament authentication failed")
|
||||
async def authenticate3(self, token):
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
await self.send(text_data=json.dumps({'type': 'authenticated'}))
|
||||
print(f"User {self.user.username} authenticated for tournament")
|
||||
await self.join_tournament_waiting_room()
|
||||
else:
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||
print("Tournament authentication failed")
|
||||
|
||||
async def join_tournament_waiting_room(self):
|
||||
await tournament_match_maker.add_player(self)
|
||||
async def join_tournament_waiting_room(self):
|
||||
await tournament_match_maker.add_player(self)
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
if self.game:
|
||||
await self.game.end_game(disconnected_player=self)
|
||||
await match_maker.remove_player(self)
|
||||
await tournament_match_maker.remove_player(self)
|
||||
print(f"User {self.user.username if hasattr(self, 'user') else 'Unknown'} disconnected")
|
||||
async def disconnect(self, close_code):
|
||||
if self.game:
|
||||
await self.game.end_game(disconnected_player=self)
|
||||
await match_maker.remove_player(self)
|
||||
await tournament_match_maker.remove_player(self)
|
||||
print(f"User {self.user.username if hasattr(self, 'user') else 'Unknown'} disconnected")
|
||||
|
||||
async def set_game(self, game):
|
||||
print(f"({self.user}) Game set to: {game}")
|
||||
self.game = game
|
||||
async def set_game(self, game):
|
||||
print(f"({self.user}) Game set to: {game}")
|
||||
self.game = game
|
||||
|
||||
###################################################################CHAT###################################################################
|
||||
class ChatConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
|
||||
try:
|
||||
# Récupérer le nom de la room à partir de l'URL
|
||||
self.room_group_name = self.scope['url_route']['kwargs']['room_name']
|
||||
|
||||
# Accepter la connexion WebSocket
|
||||
await self.accept()
|
||||
# Ajouter l'utilisateur au groupe (room)
|
||||
await self.channel_layer.group_add(
|
||||
self.room_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
self.username = self.scope['user'].username # Assurez-vous d'avoir un utilisateur lié à la connexion
|
||||
logger.info(f"Connexion de l'utilisateur {self.username} à la room {self.room_group_name}")
|
||||
# Ajouter l'utilisateur à son propre groupe personnel (pour messages directs)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la connexion WebSocket: {str(e)}")
|
||||
|
||||
async def disconnect(self, close_code):
|
||||
try:
|
||||
# Retirer l'utilisateur du groupe (room)
|
||||
await self.channel_layer.group_discard(
|
||||
self.room_group_name,
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
# Retirer l'utilisateur de son groupe personnel
|
||||
await self.channel_layer.group_discard(
|
||||
f"user_{self.username}",
|
||||
self.channel_name
|
||||
)
|
||||
|
||||
# Envoyer un message indiquant que l'utilisateur a quitté la room
|
||||
await self.chat_message(
|
||||
'chat_message',
|
||||
self.user.username if hasattr(self, "user") else "Unknown",
|
||||
f'{self.user.username if hasattr(self, "user") else "Unknown"} a quitté le chat',
|
||||
self.room_group_name
|
||||
)
|
||||
logger.info(f"{self.user.username if hasattr(self, 'user') else 'Unknown'} déconnecté de la room {self.room_group_name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la déconnexion WebSocket: {str(e)}")
|
||||
|
||||
async def receive(self, text_data):
|
||||
try:
|
||||
# Convertir les données JSON reçues en dictionnaire Python
|
||||
data = json.loads(text_data)
|
||||
message_type = data.get('type')
|
||||
username = data.get('username')
|
||||
message = data.get('message', None)
|
||||
target_user = data.get('target_user', None)
|
||||
|
||||
logger.info(f"Message reçu: {data}")
|
||||
|
||||
# Gestion des différents types de messages
|
||||
if message_type == 'authenticate':
|
||||
logger.info(f"Authentification demandée pour {username}")
|
||||
await self.authenticate(data.get('token'), username)
|
||||
return
|
||||
|
||||
elif message_type == 'chat_message':
|
||||
logger.info(f"Message de chat envoyé par {username}: {message}")
|
||||
await self.chat_message('chat_message', username, message, self.room_group_name)
|
||||
|
||||
elif message_type == 'block_user':
|
||||
logger.info(f"{username} tente de bloquer {target_user}")
|
||||
await self.handle_block_user(data)
|
||||
|
||||
elif message_type == 'invite':
|
||||
await self.handle_invite_user(data)
|
||||
|
||||
elif message_type == 'invite_response':
|
||||
await self.handle_invite_response(data)
|
||||
|
||||
else:
|
||||
logger.warning(f"Type de message non géré: {message_type}")
|
||||
await self.chat_message('error', 'server', f"Unhandled message type: {message_type}", self.room_group_name)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Erreur de décodage JSON : {str(e)} - Données reçues : {text_data}")
|
||||
await self.chat_message('error', 'server', 'Invalid JSON format', self.room_group_name)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la réception du message: {str(e)}")
|
||||
await self.chat_message('error', 'server', 'Internal server error', self.room_group_name)
|
||||
|
||||
async def chat_message(self, message_type, username, message, room):
|
||||
"""
|
||||
Fonction générale pour envoyer tout type de message via WebSocket à tous les utilisateurs dans la room.
|
||||
"""
|
||||
logger.info(f"Envoi d'un message de type {message_type} de {username} dans la room {room}")
|
||||
|
||||
# Utilisation de channel_layer pour envoyer le message à tout le groupe (room)
|
||||
await self.channel_layer.group_send(
|
||||
room,
|
||||
{
|
||||
'type': 'send_group_message', # Nom de la méthode qui va gérer ce message
|
||||
'username': username,
|
||||
'message': message,
|
||||
'room': room
|
||||
}
|
||||
)
|
||||
|
||||
async def send_group_message(self, event):
|
||||
"""
|
||||
Cette fonction est appelée par channel_layer pour envoyer des messages à tous les utilisateurs dans une room.
|
||||
"""
|
||||
message = event['message']
|
||||
username = event.get('username', 'Anonyme')
|
||||
room = event.get('room', 'unknown')
|
||||
|
||||
logger.info(f"Diffusion d'un message de {username} à la room {room}: {message}")
|
||||
|
||||
# Envoi du message à chaque utilisateur dans la room via WebSocket
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'chat_message', # Le type de message qui sera renvoyé au client
|
||||
'username': username,
|
||||
'message': message,
|
||||
'room': room
|
||||
}))
|
||||
|
||||
async def handle_block_user(self, data):
|
||||
username = data['username']
|
||||
target_user = data['target_user']
|
||||
|
||||
logger.info(f"handle_block_user appelé avec : {data}")
|
||||
|
||||
if target_user == username:
|
||||
logger.warning(f"{username} a tenté de se bloquer lui-même.")
|
||||
await self.send(text_data=json.dumps({'type': 'error', 'message': 'You cannot block yourself'}))
|
||||
return
|
||||
|
||||
logger.info(f"{username} a bloqué {target_user}")
|
||||
|
||||
# Utilisation correcte de l' f-string pour inclure la valeur de target_user
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'block_user',
|
||||
'message': f'Vous avez bloqué les messages de {target_user}'
|
||||
}))
|
||||
|
||||
async def handle_invite_user(self, data):
|
||||
# Récupération des informations de l'invitation
|
||||
inviter = data.get('username')
|
||||
target_user = data.get('target_user')
|
||||
room = data.get('room')
|
||||
|
||||
# Validation des paramètres
|
||||
if not inviter:
|
||||
logger.error("Invitant manquant dans le message d'invitation")
|
||||
await self.chat_message('error', 'server', 'Invitant manquant', self.room_group_name)
|
||||
return
|
||||
|
||||
if not target_user:
|
||||
logger.error("Utilisateur cible manquant dans le message d'invitation")
|
||||
await self.chat_message('error', 'server', 'Utilisateur cible manquant', self.room_group_name)
|
||||
return
|
||||
|
||||
if not room:
|
||||
logger.error("Room manquante dans le message d'invitation")
|
||||
await self.chat_message('error', 'server', 'Room manquante', self.room_group_name)
|
||||
return
|
||||
|
||||
logger.info(f"Invitation envoyée de {inviter} à {target_user} dans la room {room}")
|
||||
await self.chat_message('chat_message', 'server', f'{inviter} a invité {target_user} à rejoindre une partie {room}', room)
|
||||
|
||||
# Envoi de l'invitation
|
||||
await self.channel_layer.group_send(
|
||||
room,
|
||||
{
|
||||
'type': 'invite',
|
||||
'inviter': inviter,
|
||||
'target_user': target_user,
|
||||
'room': room,
|
||||
'message': f'{inviter} vous a invité à rejoindre la room {room}.'
|
||||
}
|
||||
)
|
||||
|
||||
async def handle_invite_response(self, data):
|
||||
inviter = data.get('inviter')
|
||||
username = data.get('username') # L'utilisateur invité qui répond
|
||||
response = data.get('response')
|
||||
room = data.get('room')
|
||||
|
||||
logger.info(f"{username} a répondu '{response}' à l'invitation de {inviter}")
|
||||
await self.chat_message('chat_message', 'server', f'{username} a répondu {response} à l\'invitation.', room)
|
||||
|
||||
# Si la réponse est 'yes', informer l'invitant que l'invité a accepté
|
||||
if response.lower() == 'yes':
|
||||
try:
|
||||
# Informer l'invitant que l'invitation a été acceptée
|
||||
await self.channel_layer.group_send(
|
||||
room,
|
||||
{
|
||||
'type': 'invite_response',
|
||||
'inviter': inviter,
|
||||
'username': username,
|
||||
'response': response,
|
||||
'room': room,
|
||||
'message': f'{username} a accepté l\'invitation.'
|
||||
}
|
||||
)
|
||||
# Informer à la fois l'invité et l'invitant que le jeu va commencer
|
||||
await self.channel_layer.group_send(
|
||||
room,
|
||||
{
|
||||
'type': 'start_quick_match',
|
||||
'inviter': inviter,
|
||||
'username': username,
|
||||
'message': 'La partie va démarrer pour vous deux.',
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error while sending invite response: {str(e)}")
|
||||
await self.chat_message('error', 'server', f'Internal server error: {str(e)}', room)
|
||||
|
||||
# Méthode appelée pour envoyer l'invitation à l'utilisateur invité (target_user)
|
||||
async def invite(self, event):
|
||||
inviter = event['inviter']
|
||||
message = event['message']
|
||||
room = event['room']
|
||||
target_user = event['target_user']
|
||||
logger.info(f"invite: Envoi de l'invitation à l'utilisateur via WebSocket. Inviter={inviter}, Room={room}, Message={message}")
|
||||
|
||||
# Envoyer le message d'invitation via WebSocket
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'invite',
|
||||
'inviter': inviter,
|
||||
'target_user': target_user,
|
||||
'message': message,
|
||||
'room': room
|
||||
}))
|
||||
|
||||
async def handle_invite_response(self, data):
|
||||
inviter = data.get('inviter')
|
||||
username = data.get('username') # L'utilisateur invité qui répond
|
||||
response = data.get('response')
|
||||
room = data.get('room')
|
||||
|
||||
logger.info(f"{username} a répondu '{response}' à l'invitation de {inviter}")
|
||||
await self.chat_message('chat_message', 'server', f'{username} a répondu {response} à l\'invitation.', room)
|
||||
|
||||
# Envoi de la réponse directement à l'invitant dans la room
|
||||
await self.channel_layer.group_send(
|
||||
room,
|
||||
{
|
||||
'type': 'invite_response', # Type de message 'invite_response'
|
||||
'inviter': inviter,
|
||||
'username': username,
|
||||
'room': room,
|
||||
'message': f'{username} a répondu {response} à l\'invitation.',
|
||||
'response': response # Ajout de la réponse 'yes' ou 'no'
|
||||
}
|
||||
)
|
||||
|
||||
async def invite_response(self, event):
|
||||
message = event['message']
|
||||
response = event.get('response')
|
||||
inviter = event.get('inviter') # Récupérer l'inviteur
|
||||
|
||||
logger.info(f"invite_response: Envoi de la réponse à l'invitation via WebSocket. Message={message}, Response={response}, Inviter={inviter}")
|
||||
|
||||
# Envoyer la réponse à l'invitation via WebSocket à l'invitant
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'invite_response',
|
||||
'message': message,
|
||||
'response': response,
|
||||
'inviter': inviter
|
||||
}))
|
||||
|
||||
|
||||
async def authenticate(self, token, username):
|
||||
if not token:
|
||||
logger.error("Token est manquant, l'authentification ne peut pas se poursuivre.")
|
||||
await self.chat_message('error', 'server', 'Token is missing', self.room_group_name)
|
||||
return
|
||||
|
||||
logger.info(f"Tentative d'authentification avec le token: {token} pour l'utilisateur: {username}")
|
||||
|
||||
try:
|
||||
user = await self.get_user_from_token(token)
|
||||
if user:
|
||||
self.user = user
|
||||
logger.info(f"Utilisateur {username} authentifié avec succès")
|
||||
await self.chat_message('authenticated', username, 'Authentication successful', self.room_group_name)
|
||||
|
||||
else:
|
||||
logger.warning(f"Échec de l'authentification pour le token: {token}")
|
||||
await self.chat_message('error', username, 'Authentication failed', self.room_group_name)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de l'authentification : {str(e)}")
|
||||
await self.chat_message('error', 'server', 'Internal server error', self.room_group_name)
|
||||
|
||||
@sync_to_async
|
||||
def get_user_from_token(self, token):
|
||||
try:
|
||||
user = User.objects.filter(auth_token=token).first()
|
||||
logger.debug(f"Utilisateur trouvé : {user} pour le token : {token}")
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
logger.warning(f"Utilisateur non trouvé pour le token : {token}")
|
||||
return None
|
||||
@@ -9,7 +9,6 @@ from asgiref.sync import sync_to_async
|
||||
from .models import Tournoi
|
||||
|
||||
class Game:
|
||||
|
||||
def __init__(self, game_id, player1, player2, localgame):
|
||||
self.game_id = game_id
|
||||
self.player1 = player1
|
||||
@@ -51,7 +50,6 @@ class Game:
|
||||
self.bt1 = 0
|
||||
self.bt2 = 0
|
||||
self.start_time = datetime.now()
|
||||
self.future_ball_position = {'x': 390, 'y': 190}
|
||||
|
||||
async def start_game(self):
|
||||
print(f"- Game #{self.game_id} STARTED ({self.game_state['player1_name']} vs {self.game_state['player2_name']}) --- ({self})")
|
||||
@@ -60,42 +58,41 @@ class Game:
|
||||
|
||||
async def game_loop(self):
|
||||
print(" In the game loop..")
|
||||
x = 59
|
||||
x = 0
|
||||
while not self.ended:
|
||||
if self.botgame:
|
||||
x += 1
|
||||
if x == 60:
|
||||
# Random BOT difficulty..
|
||||
steps = 60#random.randint(10, 60)
|
||||
self.future_ball_position = await self.predict_ball_trajectory(steps)
|
||||
await self.update_bot_position()
|
||||
x = 0
|
||||
if self.botgame:
|
||||
await self.update_bot_position()
|
||||
await self.handle_pad_movement()
|
||||
await self.update_game_state()
|
||||
await self.send_game_state()
|
||||
await asyncio.sleep(1/60) # Around 60 FPS
|
||||
|
||||
async def update_bot_position(self):
|
||||
#future_ball_position = self.predict_ball_trajectory()
|
||||
target_y = self.future_ball_position['y']
|
||||
future_ball_position = self.predict_ball_trajectory()
|
||||
|
||||
target_y = future_ball_position['y']
|
||||
player2_position = self.game_state['player2_position']
|
||||
|
||||
# Adjusts bot position based on expected ball position
|
||||
if player2_position < target_y < player2_position + 80:
|
||||
pass #bot already placed
|
||||
elif player2_position < target_y:
|
||||
self.p2_mov = 1
|
||||
#self.game_state['player2_position'] = min(player2_position + (50 * self.speed), 300)
|
||||
#self.p2_mov = 1
|
||||
self.game_state['player2_position'] = min(player2_position + (50 * self.speed), 300)
|
||||
elif player2_position + 80 > target_y:
|
||||
self.p2_mov = -1
|
||||
#self.game_state['player2_position'] = max(player2_position - (50 * self.speed), 0)
|
||||
#self.p2_mov = -1
|
||||
self.game_state['player2_position'] = max(player2_position - (50 * self.speed), 0)
|
||||
|
||||
async def predict_ball_trajectory(self, steps=60):
|
||||
def predict_ball_trajectory(self, steps=60):
|
||||
|
||||
future_x = self.game_state['ball_position']['x']
|
||||
future_y = self.game_state['ball_position']['y']
|
||||
velocity_x = self.game_state['ball_velocity']['x']
|
||||
velocity_y = self.game_state['ball_velocity']['y']
|
||||
|
||||
for _ in range(steps):
|
||||
future_x += velocity_x
|
||||
if future_x <= 10:
|
||||
@@ -105,9 +102,11 @@ class Game:
|
||||
future_x = 790
|
||||
else:
|
||||
future_y += velocity_y
|
||||
# Dealing with bounces off walls
|
||||
if future_y <= 10 or future_y >= 390:
|
||||
velocity_y = -velocity_y # Reverse the direction of vertical movement
|
||||
|
||||
# Dealing with bounces off walls
|
||||
if future_y <= 10 or future_y >= 390:
|
||||
velocity_y = -velocity_y # Reverse the direction of vertical movement
|
||||
|
||||
return {'x': future_x, 'y': future_y}
|
||||
|
||||
async def update_game_state(self):
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
# /pong/game/routing.py
|
||||
|
||||
from django.urls import re_path
|
||||
from . import consumers
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.debug("Configuring WebSocket routing patterns")
|
||||
|
||||
websocket_urlpatterns = [
|
||||
re_path(r'ws/game/$', consumers.GameConsumer.as_asgi()),
|
||||
re_path(r'ws/game/$', consumers.GameConsumer.as_asgi(), name='game_ws'),
|
||||
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
|
||||
]
|
||||
|
||||
logger.info("WebSocket routing patterns configured successfully")
|
||||
|
||||
@@ -10,6 +10,13 @@ from .models import Tournoi
|
||||
from .utils import create_tournament, update_tournament, getlen
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
|
||||
TOURNAMENT_NAMES = [
|
||||
"Champions Clash", "Ultimate Showdown", "Battle Royale",
|
||||
"Victory Cup", "Legends Tournament", "Elite Series", "Clash of 42",
|
||||
"Shibuya incident", "Cunning Game", "Elite of the Stars"
|
||||
]
|
||||
|
||||
TOURNAMENT_NAMES = [
|
||||
"Champion's Clash", "Ultimate Showdown", "Battle Royale",
|
||||
"Victory's Cup", "Legends Tournament", "Elite Series", "Clash of 42",
|
||||
@@ -82,13 +89,13 @@ class TournamentMatchMaker:
|
||||
|
||||
# Tournament start method
|
||||
async def start_tournament(self):
|
||||
if len(self.waiting_players) < 3:
|
||||
|
||||
if len(self.waiting_players) < 2:
|
||||
return False
|
||||
random.shuffle(self.waiting_players)
|
||||
'''if (len(self.waiting_players) % 2) != 0:
|
||||
print("Adding a BYE to the tournament..")
|
||||
await self.add_player(None)'''
|
||||
if len(self.waiting_players) % 2 == 0:
|
||||
await self.add_player(None)
|
||||
self.tournament_state = "in_progress"
|
||||
random.shuffle(self.waiting_players)
|
||||
self.current_round = 0
|
||||
len_tournament = await sync_to_async(getlen)()
|
||||
self.final_name = self.name + " #" + str(len_tournament + 1)
|
||||
@@ -97,7 +104,7 @@ class TournamentMatchMaker:
|
||||
return True
|
||||
|
||||
async def advance_tournament(self):
|
||||
players = self.waiting_players
|
||||
players = self.waiting_players
|
||||
while len(players) > 1:
|
||||
self.current_round += 1
|
||||
print(f"Starting round {self.current_round} with {len(players)} players")
|
||||
@@ -171,12 +178,16 @@ class TournamentMatchMaker:
|
||||
print(f"Starting TOURNAMENT round #{self.current_round}")
|
||||
for match in self.rounds[-1]:
|
||||
if match.player1 and match.player2:
|
||||
# Envoyer un message pour indiquer les prochains joueurs du tournoi
|
||||
message = f"Prochain match: {match.player1.user.username} contre {match.player2.user.username}"
|
||||
await self.send_to_player(match.player2, {'type': 'tournament_match', 'message': message})
|
||||
await match_maker.notify_players(match.player1, match.player2, match.game_id, False)
|
||||
asyncio.create_task(match.start_game())
|
||||
elif match.player1:
|
||||
# Handle BYE match
|
||||
# Gestion du BYE
|
||||
message = f"Prochain match: {match.player1.user.username} contre Bot"
|
||||
await self.send_to_player(match.player1, {'type': 'tournament_match', 'message': message})
|
||||
await match_maker.notify_players(match.player1, match.player2, match.game_id, False)
|
||||
#asyncio.create_task(match.start_game())
|
||||
match.game_state['player1_score'] = 3
|
||||
match.game_state['player2_score'] = 0
|
||||
await match.end_game()
|
||||
@@ -215,7 +226,6 @@ class TournamentMatchMaker:
|
||||
self.rounds = []
|
||||
self.current_round = 0
|
||||
self.games = 0
|
||||
self.tournament_state = "waiting"
|
||||
|
||||
async def handle_match_end(self, match):
|
||||
await self.update_brackets()
|
||||
|
||||
@@ -12,9 +12,7 @@ def handle_game_data(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tour
|
||||
player_1 = get_or_create_player(p1)
|
||||
player_2 = get_or_create_player(p2)
|
||||
|
||||
print("CHAKU & THEOUCHE are the BEST")
|
||||
create_match(player_1, player_2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament)
|
||||
print("and ADRIANO is the PEST")
|
||||
|
||||
update_player_statistics(p1)
|
||||
print("UPDATE PLAYER 1")
|
||||
|
||||
@@ -95,22 +95,15 @@ def player_list_json(request):
|
||||
}
|
||||
return JsonResponse(data)
|
||||
|
||||
def get_tournoi_data(tournoi):
|
||||
return {
|
||||
"id": tournoi.id,
|
||||
"name": tournoi.name,
|
||||
"nbr_player": tournoi.nbr_player,
|
||||
"date": tournoi.date,
|
||||
"winner": {
|
||||
"id": tournoi.winner.id,
|
||||
"name": tournoi.winner.name
|
||||
} if tournoi.winner else None
|
||||
}
|
||||
|
||||
def tournoi_list_json(request):
|
||||
tournois = Tournoi.objects.select_related('winner').all() # Charge les données du gagnant
|
||||
tournois_data = [get_tournoi_data(tournoi) for tournoi in tournois]
|
||||
return JsonResponse({"tournois": tournois_data})
|
||||
tournois = Tournoi.objects.all()
|
||||
|
||||
data = {
|
||||
'tournois': list(tournois.values(
|
||||
'id', 'name', 'nbr_player', 'date', 'winner'
|
||||
))
|
||||
}
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
from web3 import Web3
|
||||
|
||||
@@ -58,19 +58,19 @@ MIDDLEWARE = [
|
||||
ROOT_URLCONF = 'pong.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'pong', 'static')], # Ensure templates are found
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'pong', 'static')], # Ensure templates are found
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
ASGI_APPLICATION = 'pong.asgi.application'
|
||||
@@ -94,18 +94,18 @@ DATABASES = {
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
@@ -121,10 +121,6 @@ USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'pong/static')]
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||
@@ -136,10 +132,11 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Channels
|
||||
# Define the channel layers for WebSockets
|
||||
# Define the channel layers for WebSockets
|
||||
CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
'BACKEND': 'channels.layers.InMemoryChannelLayer',
|
||||
},
|
||||
'default': {
|
||||
'BACKEND': 'channels.layers.InMemoryChannelLayer',
|
||||
},
|
||||
}
|
||||
|
||||
LOGGING = {
|
||||
|
||||
@@ -75,18 +75,31 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
.catch(error => console.error('Error fetching match data:', error));
|
||||
}
|
||||
|
||||
function fetchPlayers(){
|
||||
function fetchPlayers() {
|
||||
console.log('Fetching players...');
|
||||
fetch('/api/player_list/')
|
||||
.then(response => response.json())
|
||||
// Retourner la promesse pour permettre l'utilisation de .then() plus tard
|
||||
return fetch('/api/player_list/')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.players) {
|
||||
displayPlayers(data.players);
|
||||
displayPlayers(data.players); // Affiche les joueurs récupérés
|
||||
return data.players; // Retourne les joueurs pour permettre de les traiter dans un .then
|
||||
} else {
|
||||
throw new Error('No players found.');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching match data:', error));
|
||||
.catch(error => {
|
||||
console.error('Error fetching match data:', error);
|
||||
return null; // En cas d'erreur, retourne null
|
||||
});
|
||||
}
|
||||
|
||||
// Expose fetchPlayers globalement
|
||||
window.fetchPlayers = fetchPlayers;
|
||||
function fetchTournois(){
|
||||
console.log('Fetching tournois...');
|
||||
fetch('/api/tournoi_list/')
|
||||
@@ -103,11 +116,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Displaying matches:');
|
||||
const matchListBody = document.querySelector('#match-list tbody');
|
||||
matchListBody.innerHTML = '';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
|
||||
if (matches.length != 0) {
|
||||
matches.forEach(match => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${match.id}</td>
|
||||
<td>${match.player1__name}</td>
|
||||
@@ -125,7 +137,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
matchListBody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td colspan="12">No matches found.</td>
|
||||
`;
|
||||
@@ -137,11 +148,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Displaying players:');
|
||||
const playersListBody = document.querySelector('#player-list tbody');
|
||||
playersListBody.innerHTML = '';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
|
||||
if (players.length != 0) {
|
||||
players.forEach(player => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${player.id}</td>
|
||||
<td>${player.name}</td>
|
||||
@@ -160,9 +170,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
playersListBody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td colspan="12">No player found.</td>
|
||||
<td colspan="12">No matches found.</td>
|
||||
`
|
||||
playersListBody.appendChild(row);
|
||||
}
|
||||
@@ -172,25 +181,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Displaying tournois:');
|
||||
const tournoisListBody = document.querySelector('#tournoi-list tbody');
|
||||
tournoisListBody.innerHTML = '';
|
||||
const row = document.createElement('tr');
|
||||
|
||||
if (tournois.length != 0) {
|
||||
tournois.forEach(tournoi => {
|
||||
console.log('Winner:', tournoi.winner); //debug !!!!!!!!!!!!!!!!!
|
||||
console.log('Winner:', tournoi.winner__name); //debug !!!!!!!!!!!!!!!!!
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${tournoi.id}</td>
|
||||
<td>${tournoi.name}</td>
|
||||
<td>${tournoi.nbr_player}</td>
|
||||
<td>${tournoi.date}</td>
|
||||
<td>${tournoi.winner ? tournoi.winner.name : 'No one yet ...'}</td>
|
||||
<td>${tournoi.winner ? tournoi.winner.name : 'None'}</td>
|
||||
`;
|
||||
tournoisListBody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td colspan="12">No tournoi found.</td>
|
||||
<td colspan="12">No matches found.</td>
|
||||
`
|
||||
tournoisListBody.appendChild(row);
|
||||
}
|
||||
|
||||
0
pong/static/flags/de.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
0
pong/static/flags/es.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 415 KiB |
0
pong/static/flags/fr.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 946 B After Width: | Height: | Size: 946 B |
0
pong/static/flags/it.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 959 B After Width: | Height: | Size: 959 B |
0
pong/static/flags/us.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
1154
pong/static/game.js
@@ -200,9 +200,125 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.0/dist/chartjs-adapter-moment.min.js"></script>
|
||||
|
||||
<script src="{% static 'game.js' %}"></script>
|
||||
|
||||
<script src="{% static 'burger.js' %}"></script>
|
||||
<script src="{% static 'game.js' %}"></script>
|
||||
<script src="{% static 'language.js' %}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div id="player-stats" class="player-stats-popup" style="display: none;">
|
||||
<h3>Player Statistics</h3>
|
||||
<p id="stats-username"></p>
|
||||
<p id="stats-total-matches"></p>
|
||||
<p id="stats-total-wins"></p>
|
||||
<p id="stats-win-percentage"></p>
|
||||
<p id="stats-best-score"></p>
|
||||
</div>
|
||||
|
||||
<div id="chat-controls">
|
||||
<button id="show-chat">Afficher le Chat</button>
|
||||
<button id="hide-chat">Masquer le Chat</button>
|
||||
</div>
|
||||
|
||||
<div id="chat-container">
|
||||
<!-- Conteneur des journaux de chat -->
|
||||
<div id="chat-log-container">
|
||||
<div id="chat-log-main_room" class="chat-log" style="display:block;"></div>
|
||||
<div id="chat-log-tournament" class="chat-log" style="display:none;"></div>
|
||||
<div id="chat-log-quick_match" class="chat-log" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Entrées de texte et boutons d'envoi pour chaque room -->
|
||||
<div id="chat-input-container">
|
||||
<div id="chat-input-main_room" class="chat-input active">
|
||||
<input type="text" placeholder="Tapez votre message...">
|
||||
<button id="chat-button-main_room" class="chat-button">➤</button>
|
||||
</div>
|
||||
<div id="chat-input-tournament" class="chat-input">
|
||||
<input type="text" placeholder="Tapez votre message...">
|
||||
<button id="chat-button-tournament" class="chat-button">➤</button>
|
||||
</div>
|
||||
<div id="chat-input-quick_match" class="chat-input">
|
||||
<input type="text" placeholder="Tapez votre message...">
|
||||
<button id="chat-button-quick_match" class="chat-button">➤</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conteneur des onglets de room -->
|
||||
<div id="room-tabs-container">
|
||||
<div class="room-tab active" data-room="main_room">Main Room</div>
|
||||
<div class="room-tab" data-room="tournament">Tournament</div>
|
||||
<div class="room-tab" data-room="quick_match">Quick Match</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const showChatButton = document.getElementById('show-chat');
|
||||
const hideChatButton = document.getElementById('hide-chat');
|
||||
|
||||
showChatButton.addEventListener('click', function() {
|
||||
chatContainer.style.display = 'flex'; // Afficher le conteneur de chat
|
||||
});
|
||||
|
||||
hideChatButton.addEventListener('click', function() {
|
||||
chatContainer.style.display = 'none'; // Masquer le conteneur de chat
|
||||
});
|
||||
|
||||
const tabs = document.querySelectorAll('.room-tab');
|
||||
const chatLogs = document.querySelectorAll('.chat-log');
|
||||
const chatInputs = document.querySelectorAll('.chat-input');
|
||||
const chatButtons = document.querySelectorAll('.chat-button');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const roomId = this.dataset.room;
|
||||
|
||||
console.log(`Switching to room: ${roomId}`);
|
||||
|
||||
// Afficher le journal de chat correspondant
|
||||
chatLogs.forEach(log => {
|
||||
if (log.id === `chat-log-${roomId}`) {
|
||||
log.style.display = 'block';
|
||||
console.log(`Displaying chat log for: ${roomId}`);
|
||||
} else {
|
||||
log.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher l'entrée de texte correspondante
|
||||
chatInputs.forEach(input => {
|
||||
if (input.id === `chat-input-${roomId}`) {
|
||||
input.classList.add('active');
|
||||
input.style.display = 'flex';
|
||||
console.log(`Displaying chat input for: ${roomId}`);
|
||||
} else {
|
||||
input.classList.remove('active');
|
||||
input.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher le bouton correspondant
|
||||
chatButtons.forEach(button => {
|
||||
if (button.id === `chat-button-${roomId}`) {
|
||||
button.style.display = 'flex';
|
||||
console.log(`Displaying chat button for: ${roomId}`);
|
||||
} else {
|
||||
button.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Mettre à jour l'onglet actif
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
console.log(`Tab for ${roomId} is now active`);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -469,4 +469,236 @@ canvas {
|
||||
background-color: #000033;
|
||||
color: #00ffff;
|
||||
box-shadow: 0 0 20px #00ffff;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************CHAT*************************************/
|
||||
@keyframes borderGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 30px #00ffff, inset 0 0 20px #00ffff;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 60px #0099ff, inset 0 0 40px #0099ff;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 30px #00ffff, inset 0 0 20px #00ffff;
|
||||
}
|
||||
}
|
||||
|
||||
#chat-container {
|
||||
display: none; /* Masquer par défaut */
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
width: 300px;
|
||||
background-color: rgba(0, 0, 0, 0.7); /* Couleur de fond semi-transparente par défaut */
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 0 30px #00ffff, inset 0 0 20px #00ffff;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
animation: borderGlow 3s infinite; /* Ajouter l'animation */
|
||||
transition: background-color 0.3s, transform 0.3s; /* Ajouter des transitions */
|
||||
}
|
||||
|
||||
#chat-container:hover {
|
||||
background-color: rgba(0, 0, 0, 1); /* Couleur de fond opaque au survol */
|
||||
transform: scale(1.05); /* Agrandir légèrement au survol */
|
||||
}
|
||||
|
||||
.chat-log {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
background-color: transparent;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 5px;
|
||||
position: relative; /* Assurez-vous que le conteneur parent est positionné */
|
||||
}
|
||||
|
||||
.chat-input.active {
|
||||
display: flex; /* Afficher uniquement l'élément actif */
|
||||
}
|
||||
|
||||
.chat-input input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
height: 28px;
|
||||
width: 250px; /* Réduire la largeur */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-button {
|
||||
height: 50px;
|
||||
width: 50px; /* Réduire la taille du bouton */
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(45deg, #00ffff, #0099ff);
|
||||
color: #000033;
|
||||
cursor: pointer;
|
||||
position: absolute; /* Positionner le bouton de manière absolue */
|
||||
right: 10px; /* Ajuster la position à droite */
|
||||
top: 10%; /* Centrer verticalement */
|
||||
transform: translateY(-50%); /* Centrer verticalement */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
padding: 0;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
transition: background 0.3s, transform 0.3s;
|
||||
animation: buttonGlow 3s infinite; /* Ajouter l'animation */
|
||||
}
|
||||
|
||||
.chat-button:hover {
|
||||
background: linear-gradient(45deg, #0099ff, #00ffff);
|
||||
transform: translateY(-50%) scale(1.1); /* Agrandir légèrement au survol */
|
||||
}
|
||||
|
||||
.chat-button:active {
|
||||
transform: translateY(-50%) scale(0.95); /* Réduire légèrement à l'activation */
|
||||
}
|
||||
|
||||
@keyframes buttonGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px #0099ff, inset 0 0 10px #0099ff;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
}
|
||||
|
||||
#room-tabs-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.room-tab {
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #00ffff;
|
||||
background-color: #000033;
|
||||
color: #00ffff;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
opacity: 0.1;
|
||||
transition: background 0.3s, transform 0.3s; /* Ajouter des transitions */
|
||||
animation: tabGlow 3s infinite; /* Ajouter l'animation */
|
||||
}
|
||||
|
||||
.room-tab:hover {
|
||||
background-color: #00ffff;
|
||||
color: #000033;
|
||||
transform: scale(1.1); /* Agrandir légèrement au survol */
|
||||
}
|
||||
|
||||
.room-tab:active {
|
||||
transform: scale(0.95); /* Réduire légèrement à l'activation */
|
||||
}
|
||||
|
||||
.room-tab.active {
|
||||
background-color: #00ffff;
|
||||
color: #000033;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes tabGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px #0099ff, inset 0 0 10px #0099ff;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
}
|
||||
|
||||
#chat-controls {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 320px; /* Correspondre à la position à côté du conteneur de chat */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px; /* Espacement entre les boutons */
|
||||
}
|
||||
|
||||
#chat-controls button {
|
||||
width: 100px; /* Correspondre à la taille des boutons */
|
||||
height: 30px; /* Correspondre à la taille des boutons */
|
||||
font-size: 12px; /* Correspondre à la taille de la police */
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background-color: #00ffff;
|
||||
color: #000033;
|
||||
border: none;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
animation: buttonGlow 3s infinite; /* Ajouter l'animation */
|
||||
}
|
||||
|
||||
#chat-controls button:hover {
|
||||
background-color: #0099ff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#chat-controls button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
@keyframes buttonGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px #0099ff, inset 0 0 10px #0099ff;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 10px #00ffff, inset 0 0 5px #00ffff;
|
||||
}
|
||||
}
|
||||
|
||||
.player-stats-popup {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 320px; /* Adjust to appear above the chat */
|
||||
width: 300px;
|
||||
background-color: rgba(0, 0, 40, 0.9);
|
||||
color: #00ffff;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 0 20px #00ffff, inset 0 0 10px #00ffff;
|
||||
z-index: 2000;
|
||||
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
.player-stats-popup.show {
|
||||
display: block;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.player-stats-popup.hide {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||