diff --git a/.gitignore b/.gitignore index a8727e7..acddfd3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ __pycache__/ data/ .env makefile +docker-compose.old.yml +docker-compose.yml logs/django.log +pong/settings.py \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e4c2828..aa21870 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,65 +1,4 @@ services: - setup: - image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} - container_name: setup - user: "0" - volumes: - - certs:/usr/share/elasticsearch/config/certs - command: > - bash -c ' - if [ x${ELASTIC_PASSWORD} == x ]; then - echo "Set the ELASTIC_PASSWORD environment variable in the .env file"; - exit 1; - elif [ x${KIBANA_PASSWORD} == x ]; then - echo "Set the KIBANA_PASSWORD environment variable in the .env file"; - exit 1; - fi; - if [ ! -f config/certs/ca.zip ]; then - echo "Creating CA"; - bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip; - unzip config/certs/ca.zip -d config/certs; - fi; - if [ ! -f config/certs/certs.zip ]; then - echo "Creating certs"; - echo -ne \ - "instances:\n"\ - " - name: es01\n"\ - " dns:\n"\ - " - es01\n"\ - " - localhost\n"\ - " ip:\n"\ - " - 127.0.0.1\n"\ - " - name: kibana\n"\ - " dns:\n"\ - " - kibana\n"\ - " - localhost\n"\ - " ip:\n"\ - " - 127.0.0.1\n"\ - > config/certs/instances.yml; - - bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key; - unzip config/certs/certs.zip -d config/certs; - fi; - - echo "Setting file permissions" - chown -R root:root config/certs; - find . -type d -exec chmod 750 \{\} \;; - find . -type f -exec chmod 640 \{\} \;; - - echo "Waiting for Elasticsearch availability"; - until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done; - echo "Setting kibana_system password"; - until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; - echo "All done!"; - ' - healthcheck: - test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"] - interval: 1s - timeout: 5s - retries: 120 - - - backend: build: context: . @@ -114,104 +53,6 @@ services: timeout: 5s retries: 5 - es01: - image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} - container_name: es01 - depends_on: - setup: - condition: service_healthy - volumes: - - certs:/usr/share/elasticsearch/config/certs:ro - - pong_es_data_01:/usr/share/elasticsearch/data - labels: - co.elastic.logs/module: elasticsearch - ports: - - 9200:9200 - environment: - - node.name=es01 - - cluster.name=${CLUSTER_NAME} - - discovery.type=single-node - - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - - bootstrap.memory_lock=true - - xpack.security.enabled=true - - xpack.security.http.ssl.enabled=true - - xpack.security.http.ssl.key=certs/es01/es01.key - - xpack.security.http.ssl.certificate=certs/es01/es01.crt - - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - - xpack.security.transport.ssl.enabled=true - - xpack.security.transport.ssl.key=certs/es01/es01.key - - xpack.security.transport.ssl.certificate=certs/es01/es01.crt - - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - - xpack.security.transport.ssl.verification_mode=certificate - - xpack.license.self_generated.type=${LICENSE} - healthcheck: - test: - [ - "CMD-SHELL", - "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'", - ] - interval: 10s - timeout: 10s - retries: 120 - - kibana: - image: docker.elastic.co/kibana/kibana:${STACK_VERSION} - container_name: kibana - labels: - co.elastic.logs/module: kibana - depends_on: - es01: - condition: service_healthy - volumes: - - certs:/usr/share/kibana/config/certs:ro - - pong_kibana:/usr/share/kibana/data - ports: - - 5601:5601 - environment: - - SERVERNAME=kibana - - ELASTICSEARCH_HOSTS=https://es01:9200 - - ELASTICSEARCH_USERNAME=${KIBANA_USERNAME} - - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD} - - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt - - XPACK_SECURITY_ENCRYPTIONKEY=${ENCRYPTION_KEY} - - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${ENCRYPTION_KEY} - - XPACK_REPORTING_ENCRYPTIONKEY=${ENCRYPTION_KEY} - healthcheck: - test: - [ - "CMD-SHELL", - "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'" - ] - interval: 10s - timeout: 10s - retries: 120 - - logstash01: - image: docker.elastic.co/logstash/logstash:${STACK_VERSION} - container_name: logstash01 - labels: - co.elastic.logs/module: logstash - user: root - depends_on: - es01: - condition: service_healthy - kibana: - condition: service_healthy - volumes: - - certs:/usr/share/logstash/certs - - pong_logstash_data01:/usr/share/logstash/data - - ./config/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro - - pong_django_logs:/usr/share/logstash/logs - ports: - - "5044:5044/udp" - command: logstash -f /usr/share/logstash/pipeline/logstash.conf - environment: - - NODE_NAME="logstash" - - ELASTIC_HOSTS=https://es01:9200 - - ELASTIC_USER=${ELASTIC_USERNAME} - - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - - xpack.monitoring.enabled=false - volumes: pong: driver: local @@ -227,14 +68,6 @@ volumes: o: bind pong_pg_data: driver: local - pong_es_data_01: - driver: local - pong_kibana: - driver: local - pong_logstash_data01: - driver: local - certs: - driver: local networks: app-network: diff --git a/makefile b/makefile index 0c6d19d..45a9a55 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ CONTAINER=$(c) up: down $(COMPOSE) build - $(COMPOSE) up -d $(CONTAINER) || true + $(COMPOSE) up --remove-orphans $(CONTAINER) build: $(COMPOSE) build $(CONTAINER) @@ -32,7 +32,7 @@ ps: db-shell: $(COMPOSE) exec db psql -U 42student players_db -re: destroy down up +re: destroy up help: @echo "Usage:" diff --git a/manage.py b/manage.py index d01a08d..252b26c 100644 --- a/manage.py +++ b/manage.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" import os import sys diff --git a/pong/asgi.py b/pong/asgi.py index b47b355..3a8dc68 100644 --- a/pong/asgi.py +++ b/pong/asgi.py @@ -18,7 +18,7 @@ django.setup() from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack -import pong.game.routing # Import your routing module +import pong.game.routing application = ProtocolTypeRouter({ "http": get_asgi_application(), diff --git a/pong/game/consumers.py b/pong/game/consumers.py index e3d68a1..8412496 100644 --- a/pong/game/consumers.py +++ b/pong/game/consumers.py @@ -5,6 +5,8 @@ from channels.generic.websocket import AsyncWebsocketConsumer 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 +import asyncio class GameConsumer(AsyncWebsocketConsumer): async def connect(self): @@ -18,11 +20,15 @@ class GameConsumer(AsyncWebsocketConsumer): 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']) - else: - await match_maker.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) @@ -74,11 +80,27 @@ class GameConsumer(AsyncWebsocketConsumer): 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 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 set_game(self, game): + print(f"({self.user}) Game set to: {game}") self.game = game diff --git a/pong/game/game.py b/pong/game/game.py index fd8c25c..2235970 100644 --- a/pong/game/game.py +++ b/pong/game/game.py @@ -4,10 +4,9 @@ import json import asyncio import random from datetime import datetime -from .utils import endfortheouche +from .utils import handle_game_data from asgiref.sync import sync_to_async - class Game: def __init__(self, game_id, player1, player2, localgame): self.game_id = game_id @@ -24,7 +23,8 @@ class Game: 'ball_position': {'x': 390, 'y': 190}, 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, 'player1_score': 0, - 'player2_score': 0 + 'player2_score': 0, + 'game_text': '' } else: self.botgame = player2 is None @@ -36,7 +36,8 @@ class Game: 'ball_position': {'x': 390, 'y': 190}, 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, 'player1_score': 0, - 'player2_score': 0 + 'player2_score': 0, + 'game-text': '' } self.speed = 1 self.game_loop_task = None @@ -45,32 +46,65 @@ class Game: self.p2_mov = 0 self.bt1 = 0 self.bt2 = 0 - self.start_time = None + self.start_time = datetime.now() async def start_game(self): - print(f"- Game #{self.game_id} STARTED") + print(f"- Game #{self.game_id} STARTED ({self.game_state['player1_name']} vs {self.game_state['player2_name']}) --- ({self})") self.game_loop_task = asyncio.create_task(self.game_loop()) - self.start_time = datetime.now() print(f" Begin MATCH at: {self.start_time}") async def game_loop(self): print(" In the game loop..") + x = 0 while not self.ended: if self.botgame: - await self.update_bot_position() + x += 1 + if x == 60: + await self.update_bot_position() + x = 0 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): - target_y = self.game_state['ball_position']['y'] - if self.game_state['player2_position'] < target_y < self.game_state['player2_position'] + 80: - pass - elif self.game_state['player2_position'] < target_y: - self.game_state['player2_position'] = min(self.game_state['player2_position'] + (5 * self.speed), 300) - elif self.game_state['player2_position'] + 80 > target_y: - self.game_state['player2_position'] = max(self.game_state['player2_position'] - (5 * self.speed), 0) + 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) + elif player2_position + 80 > target_y: + #self.p2_mov = -1 + self.game_state['player2_position'] = max(player2_position - (50 * self.speed), 0) + + 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: + future_x = 10 + velocity_x = -velocity_x + elif future_x >= 790: + 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 + + return {'x': future_x, 'y': future_y} async def update_game_state(self): if self.ended: @@ -94,21 +128,18 @@ class Game: self.game_state['ball_velocity']['x'] *= -1 self.bt2 += 1 self.update_ball_velocity() - # Check for scoring - #print(f"########### score user 1 {self.game_state['player1_score']} ###########") - #print(f"§§§§§§§§§§§ score user 2 {self.game_state['player2_score']} §§§§§§§§§§§") - + # Check if some player won the game if self.game_state['ball_position']['x'] <= 10: self.game_state['player2_score'] += 1 if self.game_state['player2_score'] > 2: - print("Here !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + self.game_state['game_text'] = f"{self.game_state['player2_name']} WINS!" await self.send_game_state() await self.end_game() self.reset_ball() elif self.game_state['ball_position']['x'] >= 790: self.game_state['player1_score'] += 1 if self.game_state['player1_score'] > 2: - print("Here !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + self.game_state['game_text'] = f"{self.game_state['player1_name']} WINS!" await self.send_game_state() await self.end_game() self.reset_ball() @@ -181,16 +212,14 @@ class Game: self.game_state['player2_position'] = min(self.game_state['player2_position'] + (5 * self.speed), 300) async def end_game(self, disconnected_player=None): - print("end GAME CALLED") if not self.ended: - print("Game ended !!!!! ") self.ended = True if self.game_loop_task: self.game_loop_task.cancel() - print(f"- Game #{self.game_id} ENDED") + print(f"- Game #{self.game_id} ENDED --- ({self})") end_time = datetime.now() - duration = (end_time - self.start_time).total_seconds() / 60 + duration = (end_time - self.start_time).total_seconds() / 60 # Notify that one player left the game if disconnected_player: @@ -201,7 +230,8 @@ class Game: 'player': disconnected_name }) if not self.botgame: - await remaining_player.send(message) + if not self.localgame: + await remaining_player.send(message) # Notify both players that the game has ended end_message = json.dumps({ 'type': 'game_ended', @@ -211,7 +241,6 @@ class Game: if not self.botgame: if not self.localgame: await self.player2.send(end_message) - print("save data") - await sync_to_async(endfortheouche)(self.game_state['player1_name'], self.game_state['player2_name'], + await sync_to_async(handle_game_data)(self.game_state['player1_name'], self.game_state['player2_name'], self.game_state['player1_score'], self.game_state['player2_score'], self.bt1, self.bt2, duration, False, None) diff --git a/pong/game/matchmaking.py b/pong/game/matchmaking.py index 4939c87..6382b95 100644 --- a/pong/game/matchmaking.py +++ b/pong/game/matchmaking.py @@ -26,7 +26,8 @@ class MatchMaker: for game in self.active_games.values(): if player in [game.player1, game.player2]: await game.end_game(disconnected_player=player) - del self.active_games[game.game_id] + if game.game_id in self.active_games: + del self.active_games[game.game_id] break async def match_loop(self): @@ -102,14 +103,5 @@ class MatchMaker: 'player2': 'BOT' })) - async def handle_key_press(self, player, key): - for game in self.active_games.values(): - if player in [game.player1, game.player2]: - await game.handle_key_press(player, key) - break - # Instance of the class match_maker = MatchMaker() - -# to get what you want use get_player_p_win(player_name) !! (voir utils.py) -# diff --git a/pong/game/models.py b/pong/game/models.py index 2586446..d72178f 100644 --- a/pong/game/models.py +++ b/pong/game/models.py @@ -4,7 +4,6 @@ from django.db import models from django.contrib.auth.models import User User.add_to_class('auth_token', models.CharField(max_length=100, null=True, blank=True, unique=True)) -# Create your models here. class Player(models.Model): name = models.CharField(max_length=100) @@ -55,7 +54,7 @@ class Match(models.Model): super().clean() def save(self, *args, **kwargs): - self.clean() # Appel de la méthode clean() avant d'enregistrer + self.clean() super().save(*args, **kwargs) def __str__(self): diff --git a/pong/game/templates/pong/tournament_brackets.html b/pong/game/templates/pong/tournament_brackets.html new file mode 100644 index 0000000..f7f0999 --- /dev/null +++ b/pong/game/templates/pong/tournament_brackets.html @@ -0,0 +1,54 @@ + + + + + + Tournament Brackets + + + +
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/pong/game/templates/pong/tournament_waiting_room.html b/pong/game/templates/pong/tournament_waiting_room.html new file mode 100644 index 0000000..cb77338 --- /dev/null +++ b/pong/game/templates/pong/tournament_waiting_room.html @@ -0,0 +1,20 @@ + +
+

Tournament Waiting Room

+ +

Players currently waiting: {{ players_count }}

+

Minimum players needed to start: {{ min_players_to_start }}

+ +

Players:

+ + + {% if players_count >= min_players_to_start %} + + {% else %} +

Waiting for more players to join...

+ {% endif %} +
\ No newline at end of file diff --git a/pong/game/tournament.py b/pong/game/tournament.py new file mode 100644 index 0000000..4c45336 --- /dev/null +++ b/pong/game/tournament.py @@ -0,0 +1,194 @@ +# /pong/game/tournament.py + +import json +import asyncio +from django.template.loader import render_to_string +import random +from .matchmaking import match_maker +from .game import Game + +class TournamentMatch(Game): + def __init__(self, game_id, player1, player2, tournament): + # Initialize the parent Game class with the provided parameters + super().__init__(game_id, player1, player2, False) + # Store the current game instance in active games + match_maker.active_games[game_id] = self + # Set the game for the players + '''player1.set_game(self) + print(f"{player1.user.username} set to game #{self}") + if player2: + player2.set_game(self) + print(f"{player2.user.username} set to game #{self}")''' + # Store the tournament instance + self.tournament = tournament + + async def end_game(self, disconnected_player=None): + # Call the parent class's end_game method + await super().end_game(disconnected_player) + # Handle the end of the match in the tournament context + await self.tournament.handle_match_end(self) + del match_maker.active_games[self.game_id] + +class TournamentMatchMaker: + def __init__(self): + self.waiting_players = [] + self.matches = [] + self.rounds = [] + self.current_round = 0 + self.games = 0 + self.tournament_state = "waiting" # Can be "waiting", "in_progress", or "ended" + + async def add_player(self, player): + if self.tournament_state == "waiting" and player not in self.waiting_players: + self.waiting_players.append(player) + print(f"User {player.user.username} joins the TOURNAMENT WAITING ROOM") + await self.update_waiting_room() + + async def update_waiting_room(self): + html = self.generate_waiting_room_html() + for player in self.waiting_players: + await self.send_to_player(player, { + 'type': 'update_tournament_waiting_room', + 'html': html + }) + + def generate_waiting_room_html(self): + context = { + 'players': [player.user.username for player in self.waiting_players], + 'tournament_state': self.tournament_state, + 'players_count': len(self.waiting_players), + 'min_players_to_start': 2 # You can adjust this number as needed + } + return render_to_string('pong/tournament_waiting_room.html', context) + + async def send_to_player(self, player, data): + await player.send(json.dumps(data)) + + async def remove_player(self, player): + if player in self.waiting_players: + self.waiting_players.remove(player) + await self.update_waiting_room() + + # Tournament start method + async def start_tournament(self): + if len(self.waiting_players) < 2: + return False + self.tournament_state = "in_progress" + random.shuffle(self.waiting_players) + self.current_round = 0 + await self.advance_tournament() + return True + + async def advance_tournament(self): + players = self.waiting_players + while len(players) > 1: + self.current_round += 1 + print(f"Starting round {self.current_round} with {len(players)} players") + await self.create_matches(players) + await self.update_brackets() + await self.start_round_matches() + # Wait for all matches in the current round to finish + current_round_matches = self.rounds[-1] + while not all(match.ended for match in current_round_matches): + await asyncio.sleep(1) # Wait for 1 second before checking again + # Get winners for the next round + players = self.get_round_winners() + print(f"Round {self.current_round} finished. {len(players)} players advancing.") + # Tournament has ended + await self.update_brackets() + await self.end_tournament(players[0] if players else None) + + async def create_matches(self, players): + matches = [] + for i in range(0, len(players), 2): + self.games += 1 + if i + 1 < len(players): + # Create a new instance of TournamentMatch for this round + match = TournamentMatch(self.games, players[i], players[i + 1], self) + matches.append(match) + else: + # Create a BYE match where the second player is None + match = TournamentMatch(self.games, players[i], None, self) # BYE match + matches.append(match) + + # Assign the new match instance to the players + await players[i].set_game(match) + if i + 1 < len(players): + await players[i + 1].set_game(match) + + self.rounds.append(matches) + self.matches.extend(matches) + + async def update_brackets(self): + html = self.generate_bracket_html() + for player in self.waiting_players: + await self.send_to_player(player, { + 'type': 'update_brackets', + 'html': html + }) + + def generate_bracket_html(self): + context = { + 'tournament_rounds': self.get_tournament_data() + } + return render_to_string('pong/tournament_brackets.html', context) + + def get_tournament_data(self): + return [ + [ + { + 'player1': match.player1.user.username if match.player1 else 'BOT', + 'player2': match.player2.user.username if match.player2 else 'BOT', + 'winner': match.game_state['player1_name'] if match.game_state['player1_score'] > match.game_state['player2_score'] else match.game_state['player2_name'] if match.ended else None, + 'score1': match.game_state['player1_score'], + 'score2': match.game_state['player2_score'] + } + for match in round_matches + ] + for round_matches in self.rounds + ] + + async def start_round_matches(self): + print(f"Starting TOURNAMENT round #{self.current_round}") + for match in self.rounds[-1]: + if match.player1 and match.player2: + 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 + await match_maker.notify_players(match.player1, match.player2, match.game_id, False) + match.game_state['player1_score'] = 3 + match.game_state['player2_score'] = 0 + await match.end_game() + #asyncio.create_task(match.start_game()) + + def get_round_winners(self): + winners = [] + for match in self.rounds[-1]: + if match.ended: + winner = match.player1 if match.game_state['player1_score'] > match.game_state['player2_score'] else match.player2 + if winner: + winners.append(winner) + return winners + + async def end_tournament(self, winner): + self.tournament_state = "ended" + winner_username = winner.user.username if winner else "No winner" + print(f"The TOURNAMENT winner is {winner_username}") + for player in self.waiting_players: + await self.send_to_player(player, { + 'type': 'tournament_end', + 'winner': winner_username + }) + # Reset tournament state + self.waiting_players = [] + self.matches = [] + self.rounds = [] + self.current_round = 0 + self.games = 0 + + async def handle_match_end(self, match): + await self.update_brackets() + +# Instance of the class +tournament_match_maker = TournamentMatchMaker() \ No newline at end of file diff --git a/pong/game/utils.py b/pong/game/utils.py index 7ef52e9..d30ae3f 100644 --- a/pong/game/utils.py +++ b/pong/game/utils.py @@ -4,236 +4,22 @@ from django.shortcuts import get_object_or_404 from django.db.models import Max, Sum, F from datetime import timedelta from channels.db import database_sync_to_async -#from asgiref.sync import database_sync_to_async -""" async def endfortheouche(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament): +def handle_game_data(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament): try: - print("here endfortheouche §!!!") - # Vérification de l'existence des joueurs et création si nécessaire - player_1 = await get_or_create_player(p1) - player_2 = await get_or_create_player(p2) - - print("ok") - - print("############# BEFORE MATCH") - await create_match(player_1, player_2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament) - print("############# AFTER DONE") - - await update_player_statistics(p1) - print("############# END STAT P1") - await update_player_statistics(p2) - except Exception as e: - print(f"Error in endfortheouche: {e}") - -@database_sync_to_async -def get_player_by_name(name): - print(f"Checking if player '{name}' exists") - exists = Player.objects.filter(name=name).exists() - print(f"Player exists: {exists}") - return exists - - -@database_sync_to_async -def get_player(name): - return Player.objects.get(name=name) - - -async def get_or_create_player(name): - print("here !!") - print(f"Checking existence for player: {name}") - player_exists = await get_player_by_name(name) - print(f"END search in database!! (Player exists: {player_exists})") - if not player_exists: - print("Player does not exist, creating player...") - player = await create_player(name) - print(f"Player created: {player}") - return player - else: - print("Player exists, fetching player...") - player = await get_player(name) - print(f"Player fetched: {player}") - return player - - -@database_sync_to_async -def create_player( - name, - total_match=0, - total_win=0, - p_win= None, - m_score_match= None, - m_score_adv_match= None, - best_score=0, - m_nbr_ball_touch= None, - total_duration= None, - m_duration= None, - num_participated_tournaments=0, - num_won_tournaments=0 -): - print("create player !!!") - - player = Player( - name=name, - total_match=total_match, - total_win=total_win, - p_win=p_win, - m_score_match=m_score_match, - m_score_adv_match=m_score_adv_match, - best_score=best_score, - m_nbr_ball_touch=m_nbr_ball_touch, - total_duration=total_duration, - m_duration=m_duration, - num_participated_tournaments=num_participated_tournaments, - num_won_tournaments=num_won_tournaments - ) - player.save() - return player - -@database_sync_to_async -def create_tournoi(name, nbr_player, date, winner): - tournoi = Tournoi(name=name, nbr_player=nbr_player, date=date, winner=winner) - tournoi.save() - return tournoi - -@database_sync_to_async -def create_match(player1, player2, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration, is_tournoi, tournoi): - match = Match( - player1=player1, - player2=player2, - score_player1=score_player1, - score_player2=score_player2, - nbr_ball_touch_p1=nbr_ball_touch_p1, - nbr_ball_touch_p2=nbr_ball_touch_p2, - duration=duration, - is_tournoi=is_tournoi, - tournoi=tournoi - ) - - if score_player1 > score_player2: - match.winner = match.player1 - elif score_player2 > score_player1: - match.winner = match.player2 - else: - match.winner = None - - match.save() - return match - -@database_sync_to_async -def update_player_statistics(player_name): - print("############# BEG STAT P") - player = get_object_or_404(Player, name=player_name) - - # Filtrer les matchs où le joueur est joueur 1 ou joueur 2 - print("############# HERE") - matches_as_player1 = Match.objects.filter(player1=player) - matches_as_player2 = Match.objects.filter(player2=player) - print("############# ACTUALLY, IT'S GOOD") - - # Calculer les statistiques - total_match = matches_as_player1.count() + matches_as_player2.count() - - if total_match == 0: - # Eviter la division par zéro - player.total_match = total_match - player.total_win = 0 - player.p_win = 0 - player.m_score_match = 0 - player.m_score_adv_match = 0 - player.best_score = 0 - player.m_nbr_ball_touch = 0 - player.total_duration = 0 - player.m_duration = 0 - player.num_participated_tournaments = 0 - player.num_won_tournaments = 0 - player.save() - return - - won_matches = Match.objects.filter(winner=player) - #part_tourn_as_p1 = Tournoi.objects.filter(matches__is_tournoi=True, matches__matches_as_player1=player) - #part_tourn_as_p2 = Tournoi.objects.filter(matches__is_tournoi=True, matches__matches_as_player2=player) - #won_tourn = Tournoi.objects.filter(winner=player) - - total_score = matches_as_player1.aggregate(Sum('score_player1'))['score_player1__sum'] or 0 - total_score += matches_as_player2.aggregate(Sum('score_player2'))['score_player2__sum'] or 0 - - total_score_adv = matches_as_player1.aggregate(Sum('score_player2'))['score_player2__sum'] or 0 - total_score_adv += matches_as_player2.aggregate(Sum('score_player1'))['score_player1__sum'] or 0 - - total_win = won_matches.count() - p_win = (total_win / total_match) * 100 - - m_score_match = total_score / total_match - m_score_adv_match = total_score_adv / total_match - - nbr_ball_touch = matches_as_player1.aggregate(Sum('nbr_ball_touch_p1'))['nbr_ball_touch_p1__sum'] or 0 - nbr_ball_touch += matches_as_player2.aggregate(Sum('nbr_ball_touch_p2'))['nbr_ball_touch_p2__sum'] or 0 - m_nbr_ball_touch = nbr_ball_touch / total_match - - total_duration = matches_as_player1.aggregate(Sum('duration'))['duration__sum'] or 0 - total_duration += matches_as_player2.aggregate(Sum('duration'))['duration__sum'] or 0 - m_duration = total_duration / total_match - - #total_tourn_p = part_tourn_as_p1.count() + part_tourn_as_p2.count() - #total_win_tourn = won_tourn.count() - #p_win_tourn = (total_win_tourn / total_tourn_p) * 100 if total_tourn_p else 0 - - best_score_as_player1 = matches_as_player1.aggregate(Max('score_player1'))['score_player1__max'] or 0 - best_score_as_player2 = matches_as_player2.aggregate(Max('score_player2'))['score_player2__max'] or 0 - best_score = max(best_score_as_player1, best_score_as_player2) - - # Mettre à jour les champs du joueur - player.total_match = total_match - player.total_win = total_win - player.p_win = p_win - player.m_score_match = m_score_match - player.m_score_adv_match = m_score_adv_match - player.best_score = best_score - player.m_nbr_ball_touch = m_nbr_ball_touch - player.total_duration = total_duration - player.m_duration = m_duration - # player.num_participated_tournaments = total_tourn_p - #player.num_won_tournaments = total_win_tourn - - player.save() - print("CHAKU IS THE BEST") - -def get_player_p_win(player_name): - # Rechercher le joueur par son nom - player = get_object_or_404(Player, name=player_name) - # Retourner la valeur de p_win - return player.p_win - """ - - - - - - -######## try synchrone version ######## -def endfortheouche(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament): - try: - print("here endfortheouche §!!!") - # Vérification de l'existence des joueurs et création si nécessaire player_1 = get_or_create_player(p1) player_2 = get_or_create_player(p2) - print("ok") - - print("############# BEFORE MATCH") create_match(player_1, player_2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament) - print("############# AFTER DONE") update_player_statistics(p1) - print("############# END STAT P1") update_player_statistics(p2) + except Exception as e: print(f"Error in endfortheouche: {e}") def get_player_by_name(name): - print(f"Checking if player '{name}' exists") exists = Player.objects.filter(name=name).exists() - print(f"Player exists: {exists}") return exists @@ -242,19 +28,12 @@ def get_player(name): def get_or_create_player(name): - print("here !!") - print(f"Checking existence for player: {name}") player_exists = get_player_by_name(name) - print(f"END search in database!! (Player exists: {player_exists})") if not player_exists: - print("Player does not exist, creating player...") player = create_player(name) - print(f"Player created: {player}") return player else: - print("Player exists, fetching player...") player = get_player(name) - print(f"Player fetched: {player}") return player @@ -272,7 +51,6 @@ def create_player( num_participated_tournaments=0, num_won_tournaments=0 ): - print("create player !!!") player = Player( name=name, @@ -320,20 +98,15 @@ def create_match(player1, player2, score_player1, score_player2, nbr_ball_touch_ return match def update_player_statistics(player_name): - print("############# BEG STAT P") player = get_object_or_404(Player, name=player_name) - # Filtrer les matchs où le joueur est joueur 1 ou joueur 2 - print("############# HERE") matches_as_player1 = Match.objects.filter(player1=player) matches_as_player2 = Match.objects.filter(player2=player) - print("############# ACTUALLY, IT'S GOOD") - # Calculer les statistiques total_match = matches_as_player1.count() + matches_as_player2.count() + # avoid dividing by 0 if total_match == 0: - # Eviter la division par zéro player.total_match = total_match player.total_win = 0 player.p_win = 0 @@ -381,7 +154,6 @@ def update_player_statistics(player_name): best_score_as_player2 = matches_as_player2.aggregate(Max('score_player2'))['score_player2__max'] or 0 best_score = max(best_score_as_player1, best_score_as_player2) - # Mettre à jour les champs du joueur player.total_match = total_match player.total_win = total_win player.p_win = p_win @@ -395,11 +167,8 @@ def update_player_statistics(player_name): #player.num_won_tournaments = total_win_tourn player.save() - print("CHAKU IS THE BEST") def get_player_p_win(player_name): - # Rechercher le joueur par son nom player = get_object_or_404(Player, name=player_name) - # Retourner la valeur de p_win return player.p_win diff --git a/pong/game/views.py b/pong/game/views.py index 8bd8097..8ff352e 100644 --- a/pong/game/views.py +++ b/pong/game/views.py @@ -1,10 +1,6 @@ # /pong/game/views.py from django.shortcuts import render - -def index(request): - return render(request, 'index.html') - from django.core.exceptions import ObjectDoesNotExist from .models import Player, Tournoi, Match from .utils import create_player, create_tournoi, create_match @@ -12,12 +8,14 @@ from django.http import JsonResponse from django.contrib.auth.models import User from django.contrib.auth import authenticate from django.views.decorators.csrf import csrf_exempt -""" from .serializers import MatchSerializer """ from rest_framework import viewsets import json import uuid +def index(request): + return render(request, 'index.html') + @csrf_exempt def check_user_exists(request): if request.method == 'POST': @@ -69,11 +67,6 @@ def get_or_create_token(user): break return user.auth_token - -####################### THEOUCHE PART ############################ - -from django.http import JsonResponse - def match_list_json(request): matches = Match.objects.all() data = { @@ -86,10 +79,8 @@ def match_list_json(request): return JsonResponse(data) def player_list_json(request): - # Récupère tous les joueurs players = Player.objects.all() - # Crée un dictionnaire avec les informations des joueurs data = { 'players': list(players.values( 'id', 'name', 'total_match', 'total_win', 'p_win', @@ -98,31 +89,19 @@ def player_list_json(request): 'num_participated_tournaments', 'num_won_tournaments' )) } - - # Renvoie les données en JSON return JsonResponse(data) def tournoi_list_json(request): - # Récupère tous les joueurs tournois = Tournoi.objects.all() - # Crée un dictionnaire avec les informations des joueurs data = { 'tournois': list(tournois.values( 'id', 'name', 'nbr_player', 'date', 'winner' )) } - - # Renvoie les données en JSON return JsonResponse(data) -####################### THEOUCHE PART ############################ - - - -####################### jcheca PART ############################ - from web3 import Web3 provider = Web3.HTTPProvider("https://sepolia.infura.io/v3/60e51df7c97c4f4c8ab41605a4eb9907") diff --git a/pong/settings.py b/pong/settings.py index 1f8d1ce..446ec08 100644 --- a/pong/settings.py +++ b/pong/settings.py @@ -35,8 +35,8 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'channels', # Add Django Channels - 'pong.game', # Your game app + 'channels', + 'pong.game', 'rest_framework' ] @@ -68,7 +68,7 @@ TEMPLATES = [ }, ] -ASGI_APPLICATION = 'pong.asgi.application' # Add ASGI application +ASGI_APPLICATION = 'pong.asgi.application' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases @@ -136,7 +136,7 @@ CHANNEL_LAYERS = { }, } -LOGGING = { +'''LOGGING = { 'version': 1, # The version of the logging configuration schema 'disable_existing_loggers': False, # Allows existing loggers to keep logging 'formatters': { # Defines how log messages will be formatted @@ -169,4 +169,4 @@ LOGGING = { 'propagate': True, # If True, messages will be passed to the parent loggers as well }, }, -} +}''' diff --git a/pong/static/burger.js b/pong/static/burger.js new file mode 100644 index 0000000..a98343c --- /dev/null +++ b/pong/static/burger.js @@ -0,0 +1,397 @@ +document.addEventListener('DOMContentLoaded', () => { + + const menuButton = document.querySelector('.burger-menu'); + const dropdownMenu = document.getElementById('dropdown-menu'); + + const playerList = document.getElementById('player-list'); + const matchList = document.getElementById('match-list'); + const tournoiList = document.getElementById('tournoi-list'); + const blockchainList = document.getElementById('blockchain-list'); + + const logo = document.querySelector('.logo'); + + menuButton.addEventListener('click', toggleMenu); + + function toggleMenu() { + console.log('Menu toggled'); + if (dropdownMenu.style.display === "block") { + dropdownMenu.style.display = "none"; + hideAllTables(); + } else { + dropdownMenu.style.display = "block"; + } + } + + function hideAllTables(){ + if (playerList) playerList.style.display = 'none'; + if (matchList) matchList.style.display = 'none'; + if (tournoiList) tournoiList.style.display = 'none'; + if (blockchainList) blockchainList.style.display = 'none'; + logo.style.display = 'block'; + } + + const links = document.querySelectorAll('#dropdown-menu a'); + + links.forEach(link => { + link.addEventListener('click', (event) => { + event.preventDefault(); + const tableId = link.getAttribute('data-table'); + showTable(tableId); + }); + }); + + function showTable(tableId) { + hideAllTables(); + logo.style.display = 'none'; + + if (tableId === 'player-list') { + playerList.style.display = 'block'; + fetchPlayers(); + } else if (tableId === 'match-list') { + matchList.style.display = 'block'; + fetchMatches(); + } else if (tableId === 'tournoi-list') { + tournoiList.style.display = 'block'; + fetchTournois(); + } else if (tableId === 'blockchain-list') { + console.log('Opening external page in a new tab'); + window.open('https://sepolia.etherscan.io/address/0x078d04eb6fb97cd863361fc86000647dc876441b', '_blank'); + } + + if (dropdownMenu) { + dropdownMenu.style.display = 'none'; + } + } + + function fetchMatches() { + console.log('Fetching matches...'); + fetch('/api/match_list/') + .then(response => response.json()) + .then(data => { + if (data.matches) { + displayMatches(data.matches); + } + }) + .catch(error => console.error('Error fetching match data:', error)); + } + + function fetchPlayers(){ + console.log('Fetching players...'); + fetch('/api/player_list/') + .then(response => response.json()) + .then(data => { + if (data.players) { + displayPlayers(data.players); + } + }) + .catch(error => console.error('Error fetching match data:', error)); + } + + function fetchTournois(){ + console.log('Fetching tournois...'); + fetch('/api/tournoi_list/') + .then(response => response.json()) + .then(data => { + if (data.tournois) { + displayTournois(data.tournois); + } + }) + .catch(error => console.error('Error fetching match data:', error)); + } + + function displayMatches(matches) { + console.log('Displaying matches:'); + const matchListBody = document.querySelector('#match-list tbody'); + matchListBody.innerHTML = ''; + + if (matches.length != 0) { + matches.forEach(match => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${match.id} + ${match.player1__name} + ${match.player2__name} + ${match.score_player1} + ${match.score_player2} + ${match.winner__name} + ${match.nbr_ball_touch_p1} + ${match.nbr_ball_touch_p2} + ${match.duration} + ${match.date} + ${match.is_tournoi} + ${match.tournoi__name} + `; + matchListBody.appendChild(row); + }); + } else { + row.innerHTML = ` + No matches found. + `; + matchListBody.appendChild(row); + } + } + + function displayPlayers(players) { + console.log('Displaying players:'); + const playersListBody = document.querySelector('#player-list tbody'); + playersListBody.innerHTML = ''; + + if (players.length != 0) { + players.forEach(player => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${player.id} + ${player.name} + ${player.total_match} + ${player.total_win} + ${player.p_win} + ${player.m_score_match} + ${player.m_score_adv_match} + ${player.best_score} + ${player.m_nbr_ball_touch} + ${player.total_duration} + ${player.m_duration} + ${player.num_participated_tournaments} + ${player.num_won_tournaments} + `; + playersListBody.appendChild(row); + }); + } else { + row.innerHTML = ` + No matches found. + ` + playersListBody.appendChild(row); + } + } + + function displayTournois(tournois) { + console.log('Displaying tournois:'); + const tournoisListBody = document.querySelector('#tournoi-list tbody'); + tournoisListBody.innerHTML = ''; + + if (tournois.length != 0) { + tournois.forEach(tournoi => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${tournoi.id} + ${tournoi.name} + ${tournoi.nbr_player} + ${tournoi.date} + ${tournoi.winner.name} + `; + tournoisListBody.appendChild(row); + }); + } else { + row.innerHTML = ` + No matches found. + ` + tournoisListBody.appendChild(row); + } + + } + + document.getElementById('search-player').addEventListener('input', filterPlayers); + document.getElementById('search-match-player').addEventListener('input', filterMatches); + document.getElementById('search-match-date').addEventListener('input', filterMatches); + + function filterPlayers() { + const searchValue = document.getElementById('search-player').value.toLowerCase(); + const playersListBody = document.querySelector('#player-list tbody'); + const rows = playersListBody.getElementsByTagName('tr'); + + for (let i = 0; i < rows.length; i++) { + const nameCell = rows[i].getElementsByTagName('td')[1]; + if (nameCell) { + const nameValue = nameCell.textContent || nameCell.innerText; + if (nameValue.toLowerCase().indexOf(searchValue) > -1 ) { + rows[i].style.display = ''; + } else { + rows[i].style.display = 'none'; + } + } + } + } + + function filterMatches() { + const playerSearchValue = document.getElementById('search-match-player').value.toLowerCase(); + const dateSearchValue = document.getElementById('search-match-date').value; + const matchListBody = document.querySelector('#match-list tbody'); + const rows = matchListBody.getElementsByTagName('tr'); + + for (let i = 0; i < rows.length; i++) { + const player1Cell = rows[i].getElementsByTagName('td')[1]; + const player2Cell = rows[i].getElementsByTagName('td')[2]; + const dateCell = rows[i].getElementsByTagName('td')[9]; + + let playerMatch = true; + if (playerSearchValue) { + const player1Value = player1Cell.textContent || player1Cell.innerText; + const player2Value = player2Cell.textContent || player2Cell.innerText; + playerMatch = player1Value.toLowerCase().indexOf(playerSearchValue) > -1 || + player2Value.toLowerCase().indexOf(playerSearchValue) > -1; + } + + let dateMatch = true; + if (dateSearchValue) { + const dateValue = dateCell.textContent || dateCell.innerText; + dateMatch = dateValue.startsWith(dateSearchValue); + } + + if (playerMatch && dateMatch) { + rows[i].style.display = ''; + } else { + rows[i].style.display = 'none'; + } + } + } + + document.getElementById('generate-player-chart').addEventListener('click', generatePlayerChart); + document.getElementById('generate-match-chart').addEventListener('click', generateMatchLinePlot); + + function generatePlayerChart() { + + if (document.getElementById('player-chart').style.display === 'block'){ + document.getElementById('player-chart').style.display = 'none'; + return ; + } + + if (logo.style.display === 'block'){ + logo.style.display = 'none'; + } + const rows = document.querySelectorAll('#player-list tbody tr'); + const playerNames = []; + const totalMatches = []; + const totalWins = []; + + rows.forEach(row => { + const cells = row.getElementsByTagName('td'); + playerNames.push(cells[1].innerText); + totalMatches.push(parseInt(cells[2].innerText)); + totalWins.push(parseInt(cells[3].innerText)); + }); + + const ctx = document.getElementById('player-chart').getContext('2d'); + document.getElementById('player-chart').style.display = 'block'; + new Chart(ctx, { + type: 'bar', + data: { + labels: playerNames, + datasets: [ + { label: 'Total Matches', data: totalMatches, backgroundColor: 'rgba(75, 192, 192, 0.6)' }, + { label: 'Total Wins', data: totalWins, backgroundColor: 'rgba(54, 162, 235, 0.6)' }, + ] + }, + options: { + scales: { + x: { + ticks: { + color: '#00ffff' + } + }, + y: { + beginAtZero: true, + ticks: { + color: '#00ffff' + } + } + } + } + }); + } + + function generateMatchLinePlot() { + + if (document.getElementById('match-chart').style.display === 'block'){ + document.getElementById('match-chart').style.display = 'none'; + return ; + } + + const rows = document.querySelectorAll('#match-list tbody tr'); + const playerWins = {}; + const dates = []; + + rows.forEach(row => { + const cells = row.getElementsByTagName('td'); + const winner = cells[5].innerText; + const date = cells[9].innerText; + + if (!dates.includes(date)) { + dates.push(date); + } + + if (!playerWins[winner]) { + playerWins[winner] = []; + } + + let existingEntry = playerWins[winner].find(entry => entry.date === date); + + if (existingEntry) { + existingEntry.wins += 1; + } + + else { + let lastWinCount = playerWins[winner].length > 0 ? playerWins[winner][playerWins[winner].length - 1].wins : 0; + playerWins[winner].push({ date: date, wins: lastWinCount + 1 }); + } + }); + + const datasets = Object.keys(playerWins).map(player => { + return { + label: player, + data: playerWins[player].map(entry => ({ x: entry.date, y: entry.wins })), + fill: false, + borderColor: getRandomColor(), + tension: 0.1 + }; + }); + + const ctx = document.getElementById('match-chart').getContext('2d'); + document.getElementById('match-chart').style.display = 'block'; + new Chart(ctx, { + type: 'line', + data: { + labels: dates.sort(), + datasets: datasets + }, + options: { + scales: { + x: { + type: 'time', + time: { + parser: 'YYYY-MM-DD', + unit: 'day', + tooltipFormat: 'll' + }, + title: { + display: true, + text: 'Date' + }, + ticks: { + color: '#00ffff' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Nombre de Victoires Cumulées' + }, + ticks: { + color: '#00ffff' + } + } + } + } + }); + } + + function getRandomColor() { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + } + +}); \ No newline at end of file diff --git a/pong/static/game.js b/pong/static/game.js index 6305385..1b5a53c 100644 --- a/pong/static/game.js +++ b/pong/static/game.js @@ -28,30 +28,20 @@ document.addEventListener('DOMContentLoaded', () => { const loginButton2 = document.getElementById('login2'); const gameContainer = document.getElementById('game1'); - - const menuButton = document.querySelector('.burger-menu'); - const dropdownMenu = document.getElementById('dropdown-menu'); - - const playerList = document.getElementById('player-list'); - const matchList = document.getElementById('match-list'); - const tournoiList = document.getElementById('tournoi-list'); - const blockchainList = document.getElementById('blockchain-list'); + const tournamentContainer = document.getElementById('tournament-bracket'); const pongElements = document.getElementById('pong-elements'); const logo = document.querySelector('.logo'); + const postFormButtons = document.getElementById('post-form-buttons'); const localGameButton = document.getElementById('local-game'); const quickMatchButton = document.getElementById('quick-match'); const tournamentButton = document.getElementById('tournament'); - /* const modal = document.getElementById("myModal"); - const btn = document.getElementById("myBtn"); - const span = document.getElementsByClassName("close")[0]; - const jsonContent = document.getElementById("jsonContent"); */ - let socket; let token; let gameState; + let saveData = null; // Auto-focus and key handling for AUTH-FORM nicknameInput.focus(); @@ -74,16 +64,15 @@ document.addEventListener('DOMContentLoaded', () => { quickMatchButton.addEventListener('click', startQuickMatch); tournamentButton.addEventListener('click', startTournament); - async function handleCheckNickname() { const nickname = nicknameInput.value.trim(); if (nickname) { + window.firstPlayerName = nickname; try { const exists = await checkUserExists(nickname); if (exists) { authForm.style.display = 'none'; loginForm.style.display = 'block'; - // Auto-focus and key handling for LOGIN-FORM loginPasswordInput.focus(); loginPasswordInput.addEventListener('keypress', function (event) { if (event.key === 'Enter') { @@ -94,7 +83,6 @@ document.addEventListener('DOMContentLoaded', () => { } else { authForm.style.display = 'none'; registerForm.style.display = 'block'; - // Auto-focus and key handling for REGISTER-FORM passwordInput.focus(); passwordInput.addEventListener('keypress', function (event) { if (event.key === 'Enter') { @@ -196,8 +184,6 @@ document.addEventListener('DOMContentLoaded', () => { return data.authenticated; } - // functions to handle the second player - async function handleCheckNickname2() { const nickname2 = nicknameInput2.value.trim(); if (nickname2) { @@ -206,7 +192,6 @@ document.addEventListener('DOMContentLoaded', () => { if (exists) { authForm2.style.display = 'none'; loginForm2.style.display = 'block'; - // Auto-focus and key handling for LOGIN-FORM2 loginPasswordInput2.focus(); loginPasswordInput2.addEventListener('keypress', function (event) { if (event.key === 'Enter') { @@ -217,7 +202,6 @@ document.addEventListener('DOMContentLoaded', () => { } else { authForm2.style.display = 'none'; registerForm2.style.display = 'block'; - // Auto-focus and key handling for REGISTER-FORM2 passwordInput2.focus(); passwordInput2.addEventListener('keypress', function (event) { if (event.key === 'Enter') { @@ -333,25 +317,43 @@ document.addEventListener('DOMContentLoaded', () => { } function startLocalGame2() { + nickname = nicknameInput.value.trim(); + nickname2 = nicknameInput2.value.trim(); + saveData = { + type: 'local', + player1_name: nickname, + player2_name: nickname2 + }; gameContainer.style.display = 'flex'; logo.style.display = 'none'; pongElements.style.display = 'none'; - //menuButton.style.display = 'none'; formBlock.style.display = 'none'; startWebSocketConnection(token, 2); } function startQuickMatch() { + saveData = { + type: 'quick' + } gameContainer.style.display = 'flex'; logo.style.display = 'none'; pongElements.style.display = 'none'; - //menuButton.style.display = 'none'; formBlock.style.display = 'none'; + document.getElementById('player1-name').textContent = "player 1"; + document.getElementById('player2-name').textContent = "player 2"; + document.getElementById('game-text').textContent = ""; + document.getElementById('player1-score').textContent = 0; + document.getElementById('player2-score').textContent = 0; + startWebSocketConnection(token, 1); } function startTournament() { - console.log("For now, do nothing, hurry up and work Senor chaku !!!!") + tournamentContainer.style.display = 'flex'; + logo.style.display = 'none'; + pongElements.style.display = 'none'; + formBlock.style.display = 'none'; + startWebSocketConnection(token, 42); } function startWebSocketConnection(token, players) { @@ -360,11 +362,14 @@ document.addEventListener('DOMContentLoaded', () => { socket.onopen = function (event) { console.log('WebSocket connection established'); if (players === 1) { - console.log("Sending token for 1 player game"); + console.log("Sending token for a quick match game"); socket.send(JSON.stringify({ type: 'authenticate', token: token })); - } else { - console.log("Sending tokens for 2 player game"); + } else if (players === 2) { + console.log("Sending tokens for a local game"); socket.send(JSON.stringify({ type: 'authenticate2', token_1: token, token_2: token2 })); + } else { + console.log("Sending token for a tournament game") + socket.send(JSON.stringify({ type: 'authenticate3', token: token })); } }; @@ -376,15 +381,36 @@ document.addEventListener('DOMContentLoaded', () => { console.log('Entered the WAITING ROOM'); } else if (data.type === 'game_start') { console.log('Game started:', data.game_id, '(', data.player1, 'vs', data.player2, ')'); - startGame(data.game_id, data.player1, data.player2); + gameContainer.style.display = 'flex'; + document.addEventListener('keydown', handleKeyDown); } else if (data.type === 'game_state_update') { updateGameState(data.game_state); } else if (data.type === 'player_disconnected') { - console.log("Player disconnected:", data.player); + console.log('Player disconnected:', data.player); } else if (data.type === 'game_ended') { - console.log("Game ended:", data.game_id); + console.log('Game ended:', data.game_id); } else if (data.type === 'error') { console.error(data.message); + } else if (data.type === 'update_tournament_waiting_room') { + // Update the HTML content of the tournament bracket + document.getElementById('tournament-bracket').innerHTML = data.html; + // Reattach the event listener to the "Start Tournament" button + const startButton = document.getElementById('start-tournament-btn'); + if (startButton) { + startButton.addEventListener('click', function() { + if (typeof socket !== 'undefined' && socket.readyState === WebSocket.OPEN) { + console.log('Start TOURNAMENT sent..'); + socket.send(JSON.stringify({type: 'start_tournament'})); + } else { + console.error('WebSocket is not open or undefined'); + } + }); + } + } else if (data.type === 'update_brackets') { + // Update the HTML content of the tournament bracket + document.getElementById('tournament-bracket').innerHTML = data.html; + } else if (data.type === 'tournament_end') { + console.log('Tournament ended, the winner is:', data.winner); } else { console.log('Message from server:', data.type, data.message); } @@ -399,24 +425,14 @@ document.addEventListener('DOMContentLoaded', () => { }; } - function startGame(gameCode, player1_name, player2_name) { - document.getElementById('gameCode').textContent = `Game Code: ${gameCode}`; - document.getElementById('player1-name').textContent = `${player1_name}`; - document.getElementById('player2-name').textContent = `${player2_name}`; - document.addEventListener('keydown', handleKeyDown); - - } - function handleKeyDown(event) { if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'w' || event.key === 's') { - //console.log('Key press: ', event.key); sendKeyPress(event.key.toLowerCase()); } } function sendKeyPress(key) { if (socket.readyState === WebSocket.OPEN) { - //console.log('Key sent: ', key); socket.send(JSON.stringify({ type: 'key_press', key })); } } @@ -424,230 +440,25 @@ document.addEventListener('DOMContentLoaded', () => { function updateGameState(newState) { gameState = newState; renderGame(); + //checkForWinner(); } function renderGame() { - const player1Pad = document.getElementById('player1-pad'); - player1Pad.style.top = `${gameState.player1_position}px`; + document.getElementById('player1-name').textContent = `${gameState.player1_name}`; + document.getElementById('player2-name').textContent = `${gameState.player2_name}`; - const player2Pad = document.getElementById('player2-pad'); - player2Pad.style.top = `${gameState.player2_position}px`; + document.getElementById('player1-pad').style.top = `${gameState.player1_position}px`; + document.getElementById('player2-pad').style.top = `${gameState.player2_position}px`; - const ball = document.getElementById('ball'); - ball.style.left = `${gameState.ball_position.x}px`; - ball.style.top = `${gameState.ball_position.y}px`; + document.getElementById('ball').style.left = `${gameState.ball_position.x}px`; + document.getElementById('ball').style.top = `${gameState.ball_position.y}px`; - const player1Score = document.getElementById('player1-score'); - player1Score.textContent = gameState.player1_score; + document.getElementById('player1-score').textContent = gameState.player1_score; + document.getElementById('player2-score').textContent = gameState.player2_score; - const player2Score = document.getElementById('player2-score'); - player2Score.textContent = gameState.player2_score; + document.getElementById('game-text').textContent = gameState.game_text; } - - ////////////////////////////// BEG BURGER BUTTON //////////////////////////////// - - menuButton.addEventListener('click', toggleMenu); - - function toggleMenu() { - console.log('Menu toggled'); - if (dropdownMenu.style.display === "block") { - dropdownMenu.style.display = "none"; - hideAllTables(); - } else { - dropdownMenu.style.display = "block"; - } - } - - function hideAllTables(){ - if (playerList) playerList.style.display = 'none'; - if (matchList) matchList.style.display = 'none'; - if (tournoiList) tournoiList.style.display = 'none'; - if (blockchainList) blockchainList.style.display = 'none'; - } - - const links = document.querySelectorAll('#dropdown-menu a'); - - links.forEach(link => { - link.addEventListener('click', (event) => { - event.preventDefault(); // Empêche le comportement par défaut du lien - const tableId = link.getAttribute('data-table'); - //console.log("Here !!!!!!!!!!!! NNNNNNNN"); - showTable(tableId); - }); - }); - - function showTable(tableId) { - // Masquer tous les tableaux - console.log('Entering showTable', tableId); - hideAllTables(); - - // Afficher le tableau sélectionné - if (tableId === 'player-list') { - console.log('Showing player list 2'); - //if (playerList) { - playerList.style.display = 'block'; - fetchPlayers(); - //} - } else if (tableId === 'match-list') { - console.log('Showing match list 2'); - //if (matchList) - matchList.style.display = 'block'; - fetchMatches(); - } else if (tableId === 'tournoi-list') { - console.log('Showing tournoi list 2'); - //if (tournoiList) - tournoiList.style.display = 'block'; - fetchTournois(); - } else if (tableId === 'blockchain-list') { - console.log('Opening external page in a new tab'); - window.open('https://sepolia.etherscan.io/address/0x078d04eb6fb97cd863361fc86000647dc876441b', '_blank'); - /* fetch('/web3/') - .then(response => response.json()) - .then(data => { - console.log('ok here !!'); - jsonContent.textContent = JSON.stringify(data, null, 2); - }); */ - } - - // Masquer le menu après la sélection - if (dropdownMenu) { - dropdownMenu.style.display = 'none'; - } - } - - function fetchMatches() { - console.log('Fetching matches...'); - fetch('/api/match_list/') - .then(response => response.json()) - .then(data => { - if (data.matches) { - displayMatches(data.matches); - } - }) - .catch(error => console.error('Error fetching match data:', error)); - } - - function fetchPlayers(){ - console.log('Fetching players...'); - fetch('/api/player_list/') - .then(response => response.json()) - .then(data => { - if (data.players) { - displayPlayers(data.players); - } - }) - .catch(error => console.error('Error fetching match data:', error)); - } - - function fetchTournois(){ - console.log('Fetching tournois...'); - fetch('/api/tournoi_list/') - .then(response => response.json()) - .then(data => { - if (data.tournois) { - displayTournois(data.tournois); - } - }) - .catch(error => console.error('Error fetching match data:', error)); - } - - function displayMatches(matches) { - console.log('Displaying matches:'); - const matchListBody = document.querySelector('#match-list tbody'); - matchListBody.innerHTML = ''; - - if (matches.length != 0) { - matches.forEach(match => { - const row = document.createElement('tr'); - row.innerHTML = ` - ${match.id} - ${match.player1__name} - ${match.player2__name} - ${match.score_player1} - ${match.score_player2} - ${match.winner__name} - ${match.nbr_ball_touch_p1} - ${match.nbr_ball_touch_p2} - ${match.duration} - ${match.date} - ${match.is_tournoi} - ${match.tournoi__name} - `; - matchListBody.appendChild(row); - }); - } else { - row.innerHTML = ` - No matches found. - `; - matchListBody.appendChild(row); - } - } - - function displayPlayers(players) { - console.log('Displaying players:'); - const playersListBody = document.querySelector('#player-list tbody'); - playersListBody.innerHTML = ''; - - if (players.length != 0) { - players.forEach(player => { - const row = document.createElement('tr'); - row.innerHTML = ` - ${player.id} - ${player.name} - ${player.total_match} - ${player.total_win} - ${player.p_win} - ${player.m_score_match} - ${player.m_score_adv_match} - ${player.best_score} - ${player.m_nbr_ball_touch} - ${player.total_duration} - ${player.m_duration} - ${player.num_participated_tournaments} - ${player.num_won_tournaments} - `; - playersListBody.appendChild(row); - }); - } else { - row.innerHTML = ` - No matches found. - ` - playersListBody.appendChild(row); - } - } - - function displayTournois(tournois) { - console.log('Displaying tournois:'); - const tournoisListBody = document.querySelector('#tournoi-list tbody'); - tournoisListBody.innerHTML = ''; - - if (tournois.length != 0) { - tournois.forEach(tournoi => { - const row = document.createElement('tr'); - row.innerHTML = ` - ${tournoi.id} - ${tournoi.name} - ${tournoi.nbr_player} - ${tournoi.date} - ${tournoi.winner.name} - `; - tournoisListBody.appendChild(row); - }); - } else { - row.innerHTML = ` - No matches found. - ` - tournoisListBody.appendChild(row); - } - - } - - ////////////////////////////// END BURGER BUTTON //////////////////////////////// - - - ////////////////////////////// BEG STARS //////////////////////////////// - const starsContainer = document.getElementById('stars'); for (let i = 0; i < 500; i++) { const star = document.createElement('div'); @@ -660,149 +471,43 @@ document.addEventListener('DOMContentLoaded', () => { starsContainer.appendChild(star); } - ////////////////////////////// END STARS //////////////////////////////// + const homeButton = document.getElementById('home'); + const replayButton = document.getElementById('retry'); + const gameControls = document.getElementById('game-controls'); + homeButton.addEventListener('click', () => { + gameContainer.style.display = 'none'; + gameControls.style.display = 'none'; + + logo.style.display = 'block' + + formBlock.style.display = 'block'; + postFormButtons.style.display = 'flex'; - /* btn.onclick = function() { - fetch('/web3/') - .then(response => response.json()) - .then(data => { - console.log('ok here !!'); - jsonContent.textContent = JSON.stringify(data, null, 2); - modal.style.display = "block"; - }); + setupFirstPlayer(); + }); + + function setupFirstPlayer() { + const firstPlayerName = window.firstPlayerName; + document.getElementById('player1-name').textContent = firstPlayerName; } - span.onclick = function() { - modal.style.display = "none"; - } + replayButton.addEventListener('click', () => { + document.getElementById('player1-name').textContent = saveData.player1_name; + document.getElementById('player2-name').textContent = saveData.player2_name; + startLocalGame2(); + }); - window.onclick = function(event) { - if (event.target == modal) { - modal.style.display = "none"; - } - } */ - - ////////////////////////////// BEG LANGAGE //////////////////////////////// - const translations = { - fr: { - welcome: "BIENVENUE DANS LE PONG 42", - labelNickname: "Entrez votre surnom:", - labelPassword: "Entrez votre mot de passe:", - labelConfirmPassword: "Confirmez votre mot de passe:", - labelLoginPassword: "Entrez votre mot de passe:" - }, - en: { - welcome: "WELCOME TO PONG 42", - labelNickname: "Enter your nickname:", - labelPassword: "Enter your password:", - labelConfirmPassword: "Confirm your password:", - labelLoginPassword: "Enter your password:" - }, - it: { - welcome: "BENVENUTO A PONG 42", - labelNickname: "Inserisci il tuo soprannome:", - labelPassword: "Inserisci la tua password:", - labelConfirmPassword: "Conferma la tua password:", - labelLoginPassword: "Inserisci la tua password:" - }, - es: { - welcome: "BIENVENIDO A PONG 42", - labelNickname: "Introduce tu apodo:", - labelPassword: "Introduce tu contraseña:", - labelConfirmPassword: "Confirma tu contraseña:", - labelLoginPassword: "Introduce tu contraseña:" - }, - de: { - welcome: "WILLKOMMEN BEI PONG 42", - labelNickname: "Geben Sie Ihren Spitznamen ein:", - labelPassword: "Geben Sie Ihr Passwort ein:", - labelConfirmPassword: "Bestätigen Sie Ihr Passwort:", - labelLoginPassword: "Geben Sie Ihr Passwort ein:" - } - }; - - function setCookie(name, value, days) { - const d = new Date(); - d.setTime(d.getTime() + (days*24*60*60*1000)); - const expires = "expires=" + d.toUTCString(); - document.cookie = name + "=" + value + ";" + expires + ";path=/"; - } - - function getCookie(name) { - const cname = name + "="; - const decodedCookie = decodeURIComponent(document.cookie); - const ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1); - } - if (c.indexOf(cname) === 0) { - return c.substring(cname.length, c.length); + function checkForWinner() { + if (gameState.player1_score === 3 || gameState.player2_score === 3) { + gameControls.style.display = 'flex'; + homeButton.style.display = 'block'; + replayButton.style.display = 'none'; + console.log(saveData.type); + if (saveData.type === 'local'){ + replayButton.style.display = 'block'; } } - return ""; } - function changeLanguage(lang) { - setCookie('preferredLanguage', lang, 365); // Store the language preference for 1 year - document.getElementById('welcome').innerText = translations[lang].welcome; - document.getElementById('label-nickname').innerText = translations[lang].labelNickname; - document.getElementById('label-password').innerText = translations[lang].labelPassword; - document.getElementById('label-confirm-password').innerText = translations[lang].labelConfirmPassword; - document.getElementById('label-login-password').innerText = translations[lang].labelLoginPassword; - } - - // Function to set the language based on the cookie - function setLanguageFromCookie() { - const preferredLanguage = getCookie('preferredLanguage'); - if (preferredLanguage && translations[preferredLanguage]) { - changeLanguage(preferredLanguage); - } else { - changeLanguage('fr'); // Default to French if no cookie is found - } - } - - document.getElementById('lang-fr').addEventListener('click', () => changeLanguage('fr')); - document.getElementById('lang-en').addEventListener('click', () => changeLanguage('en')); - document.getElementById('lang-it').addEventListener('click', () => changeLanguage('it')); - document.getElementById('lang-es').addEventListener('click', () => changeLanguage('es')); - document.getElementById('lang-de').addEventListener('click', () => changeLanguage('de')); - - // Set the language when the page loads - window.onload = setLanguageFromCookie; - - ////////////////////////////// END LANGAGE //////////////////////////////// - - - - ////////////////////////////// BEG SETTING //////////////////////////////// - - document.getElementById('settings-btn').addEventListener('click', function() { - document.getElementById('settings-menu').style.display = 'block'; - }); - - document.getElementById('close-settings').addEventListener('click', function() { - document.getElementById('settings-menu').style.display = 'none'; - }); - - // Change the color of the text - document.getElementById('color-picker').addEventListener('input', function() { - document.body.style.color = this.value; - document.querySelectorAll('button').forEach(function(button) { - button.style.backgroundColor = this.value; // Change la couleur de fond des boutons - }, this); - }); - - document.getElementById('font-selector').addEventListener('change', function() { - document.body.style.fontFamily = this.value; - }); - - document.getElementById('font-size-slider').addEventListener('input', function() { - document.body.style.fontSize = this.value + 'px'; - }); - - ////////////////////////////// END SETTING //////////////////////////////// - }); diff --git a/pong/static/index.html b/pong/static/index.html index d261bcf..aa89490 100644 --- a/pong/static/index.html +++ b/pong/static/index.html @@ -9,16 +9,7 @@ - - - +
@@ -111,21 +102,30 @@
+ + + + + + + + + + diff --git a/pong/static/language.js b/pong/static/language.js new file mode 100644 index 0000000..dd5b518 --- /dev/null +++ b/pong/static/language.js @@ -0,0 +1,113 @@ +document.addEventListener('DOMContentLoaded', () => { + const translations = { + fr: { + welcome: "BIENVENUE DANS LE PONG 42", + labelNickname: "Entrez votre surnom:", + labelPassword: "Entrez votre mot de passe:", + labelConfirmPassword: "Confirmez votre mot de passe:", + labelLoginPassword: "Entrez votre mot de passe:" + }, + en: { + welcome: "WELCOME TO PONG 42", + labelNickname: "Enter your nickname:", + labelPassword: "Enter your password:", + labelConfirmPassword: "Confirm your password:", + labelLoginPassword: "Enter your password:" + }, + it: { + welcome: "BENVENUTO A PONG 42", + labelNickname: "Inserisci il tuo soprannome:", + labelPassword: "Inserisci la tua password:", + labelConfirmPassword: "Conferma la tua password:", + labelLoginPassword: "Inserisci la tua password:" + }, + es: { + welcome: "BIENVENIDO A PONG 42", + labelNickname: "Introduce tu apodo:", + labelPassword: "Introduce tu contraseña:", + labelConfirmPassword: "Confirma tu contraseña:", + labelLoginPassword: "Introduce tu contraseña:" + }, + de: { + welcome: "WILLKOMMEN BEI PONG 42", + labelNickname: "Geben Sie Ihren Spitznamen ein:", + labelPassword: "Geben Sie Ihr Passwort ein:", + labelConfirmPassword: "Bestätigen Sie Ihr Passwort:", + labelLoginPassword: "Geben Sie Ihr Passwort ein:" + } + }; + + function setCookie(name, value, days) { + const d = new Date(); + d.setTime(d.getTime() + (days*24*60*60*1000)); + const expires = "expires=" + d.toUTCString(); + document.cookie = name + "=" + value + ";" + expires + ";path=/"; + } + + function getCookie(name) { + const cname = name + "="; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(';'); + for(let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') { + c = c.substring(1); + } + if (c.indexOf(cname) === 0) { + return c.substring(cname.length, c.length); + } + } + return ""; + } + + function changeLanguage(lang) { + setCookie('preferredLanguage', lang, 365); + document.getElementById('welcome').innerText = translations[lang].welcome; + document.getElementById('label-nickname').innerText = translations[lang].labelNickname; + document.getElementById('label-password').innerText = translations[lang].labelPassword; + document.getElementById('label-confirm-password').innerText = translations[lang].labelConfirmPassword; + document.getElementById('label-login-password').innerText = translations[lang].labelLoginPassword; + } + + function setLanguageFromCookie() { + const preferredLanguage = getCookie('preferredLanguage'); + if (preferredLanguage && translations[preferredLanguage]) { + changeLanguage(preferredLanguage); + } else { + changeLanguage('fr'); // Default to French if no cookie is found + } + } + + document.getElementById('lang-fr').addEventListener('click', () => changeLanguage('fr')); + document.getElementById('lang-en').addEventListener('click', () => changeLanguage('en')); + document.getElementById('lang-it').addEventListener('click', () => changeLanguage('it')); + document.getElementById('lang-es').addEventListener('click', () => changeLanguage('es')); + document.getElementById('lang-de').addEventListener('click', () => changeLanguage('de')); + + window.onload = setLanguageFromCookie; + + document.getElementById('settings-btn').addEventListener('click', function() { + document.getElementById('settings-menu').style.display = 'block'; + }); + + document.getElementById('close-settings').addEventListener('click', function() { + document.getElementById('settings-menu').style.display = 'none'; + }); + + + document.getElementById('color-picker').addEventListener('input', function() { + document.body.style.color = this.value; + document.querySelectorAll('button').forEach(function(button) { + button.style.backgroundColor = this.value; + }, this); + }); + + document.getElementById('font-selector').addEventListener('change', function() { + document.body.style.fontFamily = this.value; + }); + + document.getElementById('font-size-slider').addEventListener('input', function() { + document.body.style.fontSize = this.value + 'px'; + }); + +}); \ No newline at end of file diff --git a/pong/static/styles.css b/pong/static/styles.css index 450ac4e..38d78f0 100644 --- a/pong/static/styles.css +++ b/pong/static/styles.css @@ -1,5 +1,4 @@ /* General styles */ -body, html { font-family: Arial, sans-serif; @@ -12,7 +11,7 @@ html { align-items: center; justify-content: center; height: 100%; - overflow: hidden; + overflow: auto; } @@ -59,20 +58,10 @@ button:hover { align-items: center; } -.game-code { - font-size: 24px; - position: absolute; - top: 10px; -} - -#gameCode { - left: 10px; -} - .name { font-size: 24px; position: absolute; - top: 30px; + top: 20px; } #player1-name { @@ -144,6 +133,13 @@ button:hover { position: absolute; } +#game-text { + font-size: 64px; + color: #00ffff; + position: absolute; + top: 150px; +} + .logo { position: absolute; top: 20px; @@ -259,13 +255,14 @@ button:hover { position: relative; z-index: 10; max-width: 80%; + overflow: auto; } .navbar { - position: absolute; - top: 30px; - right: 10px; + position: absolute; + top: 30px; + right: 10px; padding: 10px; } @@ -273,48 +270,45 @@ button:hover { font-size: 24px; background: none; border: none; - color: #00ffff; + color: #00ffff; cursor: pointer; transition: color 0.3s ease; } .burger-menu:hover { - color: #ffffff; + color: #ffffff; } - .dropdown-content { display: none; position: absolute; - top: 100%; - right: 0; - margin-top: 10px; - background-color: #1a1a2e; - color: #ffffff; + top: 100%; + right: 0; + margin-top: 10px; + background-color: #1a1a2e; + color: #ffffff; box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.3); border-radius: 5px; z-index: 1; - width: max-content; + width: max-content; } .dropdown-content.show { display: block; } - .dropdown-content a { - color: #ffffff; + color: #ffffff; padding: 12px 16px; text-decoration: none; display: block; - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - transition: background-color 0.3s ease; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + transition: background-color 0.3s ease; } - .dropdown-content a:hover { - background-color: #333; - color: #00ffff; + background-color: #333; + color: #00ffff; } .language-switcher { @@ -374,8 +368,8 @@ button:hover { width: 100%; height: 100%; overflow: auto; - background-color: rgb(0,0,0); - background-color: rgba(0,0,0,0.4); + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.4); padding-top: 60px; } @@ -406,4 +400,73 @@ button:hover { pre { white-space: pre-wrap; word-wrap: break-word; +} +/* +.tournament-waiting-room { + background-color: rgba(0, 0, 0, 0.6); + padding: 20px; + border-radius: 15px; + color: #00ffff; + width: 50%; + margin: auto; + text-align: center; + box-shadow: 0 0 30px #00ffff, inset 0 0 20px #00ffff; + +} + +.tournament-waiting-room h2 { + font-family: 'Arial', sans-serif; + font-size: 2em; + margin-bottom: 15px; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); +} + +.tournament-waiting-room p { + font-family: 'Verdana', sans-serif; + font-size: 1.2em; + margin-bottom: 20px; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6); +} + +.tournament-waiting-room ul { + list-style-type: none; + padding: 0; +} +*/ +body { + color: rgb(0, 255, 255); /* Valeur par défaut */ + font-family: Arial, sans-serif; + font-size: 16px; +} + +canvas { + max-width: 100%; + height: auto; + margin-top: 20px; +} + +#game-controls { + display: flex; + justify-content: center; + gap: 20px; + margin-bottom: 10px; +} + +#game-controls button { + background-color: #00ffff; + color: #000033; + border: none; + padding: 1rem 2rem; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + border-radius: 10px; + text-transform: uppercase; + letter-spacing: 2px; +} + +#game-controls button:hover { + background-color: #000033; + color: #00ffff; + box-shadow: 0 0 20px #00ffff; } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ac9363c..15069d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django +django psycopg2 python-dotenv channels