diff --git a/.env b/.env index 41d604b..66aac01 100644 --- a/.env +++ b/.env @@ -8,8 +8,24 @@ POSTGRES_DB=players_db POSTGRES_USER=42student POSTGRES_PASSWORD=qwerty +# Django settings DB_HOST=db DB_PORT=5432 +PWD_PATH=${PWD} +PROJECT_PATH=${PWD_PATH}/pong + +# ElasticSearch settings +STACK_VERSION=8.14.3 +CLUSTER_NAME=docker-cluster +LICENSE=basic + +ELASTIC_USERNAME=elastic +ELASTIC_PASSWORD=elastic_pass + +# Kibana settings +KIBANA_PORT=5601 +KIBANA_USERNAME=kibana_system +KIBANA_PASSWORD=kibana_pass + +ENCRYPTION_KEY=c34d38b3a14956121ff2170e5030b471551370178f43e5626eec58b04a30fae2 -PROJECT_PATH=/home/mchiboub/42cursus/transcendence/pong -POSTGRES_DATA_PATH=/home/mchiboub/42cursus/transcendence/data/db \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6ccca4f..5500a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ venv/ __pycache__/ -data/ .env -makefile \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 10f5083..42ff94d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ RUN python3 -m venv venv RUN venv/bin/pip3 install --upgrade pip RUN venv/bin/pip3 install --no-cache-dir -r requirements.txt -EXPOSE 80 +EXPOSE 8080 diff --git a/config/logstash.conf b/config/logstash.conf new file mode 100644 index 0000000..329a522 --- /dev/null +++ b/config/logstash.conf @@ -0,0 +1,28 @@ +input { + file { + path => "/usr/share/logstash/logs/django.log" + start_position => "beginning" + sincedb_path => "/dev/null" + codec => "json" + } +} + +filter { + json { + source => "message" + target => "json_message" + } +} + +output { + elasticsearch { + hosts => ["https://es01:9200"] + user => "elastic" + password => "${ELASTIC_PASSWORD}" + ssl_enabled => true + ssl_certificate_authorities => "/usr/share/logstash/certs/ca/ca.crt" + ssl_verification_mode => "full" + index => "django-logs-%{+YYYY.MM.dd}" + } + stdout { codec => rubydebug } + } diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 351dbe4..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,70 +0,0 @@ -services: - db: - image: postgres:latest - container_name: postgres - restart: always - volumes: - - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - app-network - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - - backend: - build: - context: . - dockerfile: Dockerfile - image: backend - container_name: backend - restart: always - command: /bin/sh -c "sleep 5 && - venv/bin/python manage.py makemigrations --noinput && - venv/bin/python manage.py migrate --noinput && - venv/bin/python manage.py collectstatic --noinput && - venv/bin/daphne -b 0.0.0.0 -p 80 pong.asgi:application" - volumes: - - pong:/transcendence/pong - ports: - - "80:80" - depends_on: - - db - networks: - - app-network - environment: - DB_HOST: db - DB_PORT: 5432 - DB_NAME: ${POSTGRES_DB} - DB_USER: ${POSTGRES_USER} - DB_PASSWORD: ${POSTGRES_PASSWORD} - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"] - interval: 10s - timeout: 5s - retries: 5 - -volumes: - postgres_data: - driver: local - driver_opts: - type: none - device: ${POSTGRES_DATA_PATH} - o: bind - pong: - driver: local - driver_opts: - type: none - device: ${PROJECT_PATH} - o: bind - -networks: - app-network: - driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..062be5a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,236 @@ +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: . + dockerfile: Dockerfile + image: backend + container_name: backend + restart: always + command: /bin/sh -c "sleep 5 && + venv/bin/python manage.py makemigrations --noinput && + venv/bin/python manage.py migrate --noinput && + venv/bin/python manage.py collectstatic --noinput && + venv/bin/daphne -b 0.0.0.0 -p 8080 pong.asgi:application" + volumes: + - pong:/transcendence/pong + - pong_django_logs:/transcendence/logs + ports: + - 8080:8080 + networks: + - app-network + environment: + DB_HOST: db + DB_PORT: 5432 + DB_NAME: ${POSTGRES_DB} + DB_USER: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + depends_on: + - db + healthcheck: + test: ["CMD-SHELL", "curl", "http://localhost:8080"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + db: + image: postgres:latest + container_name: postgres + restart: always + volumes: + - pong_pg_data:/var/lib/postgresql/data + ports: + - "5432:5432" + networks: + - app-network + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + 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 + driver_opts: + type: none + device: ${PROJECT_PATH} + o: bind + pong_django_logs: + driver: local + 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: + name: app-network + driver: bridge diff --git a/env_template b/env_template index a22175d..165b010 100644 --- a/env_template +++ b/env_template @@ -1,15 +1,30 @@ # Django settings -SECRET_KEY= +SECRET_KEY="FollowTheWhiteRabbit" DEBUG=True DJANGO_ALLOWED_HOSTS=['*'] # PostgreSQL settings -POSTGRES_DB=players_db +POSTGRES_DB=players_db POSTGRES_USER=42student -POSTGRES_PASSWORD=test +POSTGRES_PASSWORD= +# Django settings DB_HOST=db DB_PORT=5432 +PWD_PATH=${PWD} +PROJECT_PATH=${PWD_PATH}/pong -PROJECT_PATH=${PWD}/pong -POSTGRES_DATA_PATH=${PWD}/data/db +# ElasticSearch settings +STACK_VERSION=8.14.3 +CLUSTER_NAME=docker-cluster +LICENSE=basic + +ELASTIC_USERNAME=elastic +ELASTIC_PASSWORD= + +# Kibana settings +KIBANA_PORT=5601 +KIBANA_USERNAME=kibana_system +KIBANA_PASSWORD= + +ENCRYPTION_KEY=c34d38b3a14956121ff2170e5030b471551370178f43e5626eec58b04a30fae2 diff --git a/makefile b/makefile index f84c03f..e7fbc31 100644 --- a/makefile +++ b/makefile @@ -1,13 +1,12 @@ -COMPOSE_FILE=docker-compose.yaml +COMPOSE_FILE=docker-compose.yml COMPOSE=docker compose -f $(COMPOSE_FILE) CONTAINER=$(c) up: down - sudo mkdir -p data/db $(COMPOSE) build - $(COMPOSE) up $(CONTAINER) + $(COMPOSE) up -d $(CONTAINER) || true -build: +build: $(COMPOSE) build $(CONTAINER) start: @@ -21,9 +20,13 @@ down: destroy: $(COMPOSE) down -v --rmi all - sudo rm -rf data - #sudo lsof -i :5432 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true - #sudo lsof -i :80 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + +kill-pid: + sudo lsof -i :5432 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :5601 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :9200 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :8080 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :5044 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true logs: $(COMPOSE) logs -f $(CONTAINER) @@ -34,6 +37,8 @@ ps: db-shell: $(COMPOSE) exec db psql -U 42student players_db +re: destroy up + help: @echo "Usage:" @echo " make build [c=service] # Build images" @@ -42,7 +47,9 @@ help: @echo " make down [c=service] # Stop and remove containers" @echo " make destroy # Stop and remove containers and volumes" @echo " make stop [c=service] # Stop containers" - @echo " make restart [c=service] # Restart containers" @echo " make logs [c=service] # Tail logs of containers" @echo " make ps # List containers" @echo " make help # Show this help" + +.PHONY: up build start stop down destroy logs ps db-shell help + diff --git a/manage.py b/manage.py old mode 100755 new mode 100644 index d01a08d..252b26c --- 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 b473ece..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): @@ -16,11 +18,17 @@ class GameConsumer(AsyncWebsocketConsumer): 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']) - 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) @@ -45,11 +53,54 @@ class GameConsumer(AsyncWebsocketConsumer): 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") + + @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 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 5ceecb8..0f30c73 100644 --- a/pong/game/game.py +++ b/pong/game/game.py @@ -3,69 +3,44 @@ import json import asyncio import random -from .utils import endfortheouche - -class BotPlayer: - def __init__(self, game_state, speed): - self.game_state = game_state - self.speed = speed - - async def update_bot_position(self): - print("Update bot position started") - try: - target_y = self.predict_ball_position() - #print(f"Target Y: {target_y}") - player2_position = self.game_state['player2_position'] - #print(f"Player2 Position: {player2_position}") - - if player2_position < target_y < player2_position + 80: - print("Bot is already aligned with the ball, no move needed.") - elif player2_position < target_y: - new_position = min(player2_position + (5 * self.speed), 300) - print(f"Moving bot down to {new_position}") - self.game_state['player2_position'] = new_position - elif player2_position + 80 > target_y: - new_position = max(player2_position - (5 * self.speed), 0) - print(f"Moving bot up to {new_position}") - self.game_state['player2_position'] = new_position - - #await asyncio.sleep(1) # Rafraîchir toutes les secondes - except Exception as e: - print(f"Error in BotPlayer.update_bot_position: {e}") - - - def predict_ball_position(self): - # Prédire la future position de la balle en tenant compte de sa vitesse - ball_y = self.game_state['ball_position']['y'] - ball_speed_y = self.game_state['ball_velocity']['y'] - # Prédiction simple, peut être améliorée pour plus de précision - future_ball_y = ball_y + ball_speed_y * 1 # prédire pour la prochaine seconde - - # Gérer les rebonds sur les limites - if future_ball_y < 0: - future_ball_y = -future_ball_y - elif future_ball_y > 300: - future_ball_y = 600 - future_ball_y - - return future_ball_y - +from datetime import datetime +from .utils import handle_game_data, getlen +from asgiref.sync import sync_to_async +from .models import Tournoi class Game: - def __init__(self, game_id, player1, player2): + def __init__(self, game_id, player1, player2, localgame): self.game_id = game_id self.player1 = player1 self.player2 = player2 - self.botgame = player2 is None - self.game_state = { - 'player1_name': player1.user.username, - 'player2_name': player2.user.username if player2 else 'BOT', - 'player1_position': 150, - 'player2_position': 150, - 'ball_position': {'x': 390, 'y': 190}, - 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, - 'player1_score': 0, - 'player2_score': 0 - } + self.localgame = localgame + if self.localgame: + self.botgame = False + self.game_state = { + 'player1_name': player1.user.username, + 'player2_name': player1.user2.username, + 'player1_position': 150, + 'player2_position': 150, + 'ball_position': {'x': 390, 'y': 190}, + 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, + 'player1_score': 0, + 'player2_score': 0, + 'game_text': '' + } + else: + # Set botgame to True if either player1 or player2 is None + self.botgame = player1 is None or player2 is None + self.game_state = { + 'player1_name': player1.user.username if player1 else 'BOT', + 'player2_name': player2.user.username if player2 else 'BOT', + 'player1_position': 150, + 'player2_position': 150, + 'ball_position': {'x': 390, 'y': 190}, + 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, + 'player1_score': 0, + 'player2_score': 0, + 'game-text': '' + } self.speed = 1 self.game_loop_task = None self.ended = False @@ -73,37 +48,69 @@ class Game: self.p2_mov = 0 self.bt1 = 0 self.bt2 = 0 - - if self.botgame: - self.bot_player = BotPlayer(self.game_state, self.speed) + 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()) + print(f" Begin MATCH at: {self.start_time}") async def game_loop(self): - #print("Here, ok") - while True: + print(" In the game loop..") + x = 0 + while not self.ended: if self.botgame: - #print('still ok') - #await self.bot_player.update_bot_position() - await self.update_bot_position() - #print('is it ok ?? ') + x += 1 + if x == 60: + await self.update_bot_position() + x = 0 await self.handle_pad_movement() - self.update_game_state() + 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() - def update_game_state(self): + 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: + return # Update ball position self.game_state['ball_position']['x'] += self.game_state['ball_velocity']['x'] self.game_state['ball_position']['y'] += self.game_state['ball_velocity']['y'] @@ -123,16 +130,20 @@ class Game: self.game_state['ball_velocity']['x'] *= -1 self.bt2 += 1 self.update_ball_velocity() - # Check for scoring + # 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'] >= 5: - self.end_game() + if self.game_state['player2_score'] > 2: + 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'] >= 5: - self.end_game() + if self.game_state['player1_score'] > 2: + self.game_state['game_text'] = f"{self.game_state['player1_name']} WINS!" + await self.send_game_state() + await self.end_game() self.reset_ball() def reset_ball(self): @@ -164,30 +175,35 @@ class Game: }) await self.player1.send(message) if not self.botgame: - await self.player2.send(message) + if not self.localgame: + await self.player2.send(message) async def handle_key_press(self, player, key): if self.ended: return - if player == self.player1: - print(f"Key press: {key}") - if key == 'arrowup': - self.p1_mov = -1 - #self.game_state['player1_position'] = max(self.game_state['player1_position'] - 25, 0) - elif key == 'arrowdown': - self.p1_mov = 1 - #self.game_state['player1_position'] = min(self.game_state['player1_position'] + 25, 300) - elif not self.botgame and player == self.player2: + if self.localgame: + if key == 'arrowup': + self.p2_mov = -1 + elif key == 'arrowdown': + self.p2_mov = 1 + elif key == 'w': + self.p1_mov = -1 + elif key == 's': + self.p1_mov = 1 + elif player == self.player1: + if key == 'arrowup': + self.p1_mov = -1 + elif key == 'arrowdown': + self.p1_mov = 1 + elif player == self.player2: if key == 'arrowup': self.p2_mov = -1 - #self.game_state['player2_position'] = max(self.game_state['player2_position'] - 25, 0) elif key == 'arrowdown': self.p2_mov = 1 - #self.game_state['player2_position'] = min(self.game_state['player2_position'] + 25, 300) async def handle_pad_movement(self): - #print(f"P1 mov: {self.p1_mov}") - #print(f"P2 mov: {self.p2_mov}") + if self.ended: + return if self.p1_mov == -1: self.game_state['player1_position'] = max(self.game_state['player1_position'] - (5 * self.speed), 0) elif self.p1_mov == 1: @@ -202,7 +218,11 @@ class Game: 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 + # Notify that one player left the game if disconnected_player: remaining_player = self.player2 if disconnected_player == self.player1 else self.player1 @@ -212,7 +232,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', @@ -220,23 +241,13 @@ class Game: }) await self.player1.send(end_message) if not self.botgame: - await self.player2.send(end_message) - await endfortheouche(self.game_state['player1_name'], self.game_state['player2_name'], + if not self.localgame: + await self.player2.send(end_message) + if hasattr(self, 'tournament'): + 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, 42, False, None) - -### pour Theo ### -# nickname player1 -# nickname player2 -# score p1 -# score p2 -# winner -# ball touch p1 -# ball touch p2 -# match time -# match type: 'quick match' - -# match type: 'tournament' -# -> tournament id - -#endfortheouche(p1, p2, s_p1, s_p2, bt_p1, bt_p2, dur, is_tournoi, name_tournament) + self.bt1, self.bt2, duration, True, self.tournament.tournoi_reg) + else: + 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 28b8dda..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): @@ -35,12 +36,12 @@ class MatchMaker: player1 = self.waiting_players.pop(0) player2 = self.waiting_players.pop(0) print(f"*** MATCH FOUND: {player1.user.username} vs {player2.user.username}") - await self.create_game(player1, player2) + await self.create_game(player1, player2, False) else: await asyncio.sleep(1) self.timer += 1 # Waiting for more than 30s -> BOT game - if self.timer >= 30 and self.waiting_players: + if self.timer >= 15 and self.waiting_players: player1 = self.waiting_players.pop(0) print(f"*** MATCH FOUND: {player1.user.username} vs BOT") self.botgame = True @@ -49,26 +50,30 @@ class MatchMaker: if not self.waiting_players: break - async def create_game(self, player1, player2): + async def create_game(self, player1, player2, localgame): game_id = len(self.active_games) + 1 - print(f"- Creating game: #{game_id}") - new_game = Game(game_id, player1, player2) + if localgame: + print(f"- Creating LOCAL game: #{game_id}") + else: + print(f"- Creating MATCH game: #{game_id}") + new_game = Game(game_id, player1, player2, localgame) self.active_games[game_id] = new_game await player1.set_game(new_game) - await player2.set_game(new_game) - await self.notify_players(player1, player2, game_id) + if not localgame: + await player2.set_game(new_game) + await self.notify_players(player1, player2, game_id, localgame) asyncio.create_task(new_game.start_game()) async def create_bot_game(self, player1): game_id = len(self.active_games) + 1 print(f"- Creating BOT game: #{game_id}") - new_game = Game(game_id, player1, None) + new_game = Game(game_id, player1, None, False) self.active_games[game_id] = new_game await player1.set_game(new_game) - await self.notify_players(player1, None, game_id) + await self.notify_players(player1, None, game_id, False) asyncio.create_task(new_game.start_game()) - async def notify_players(self, player1, player2, game_id): + async def notify_players(self, player1, player2, game_id, localgame): if player2: await player1.send(json.dumps({ 'type': 'game_start', @@ -83,21 +88,20 @@ class MatchMaker: 'player2': player2.user.username })) else: - await player1.send(json.dumps({ - 'type': 'game_start', - 'game_id': game_id, - 'player1': player1.user.username, - '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 + if localgame: + await player1.send(json.dumps({ + 'type': 'game_start', + 'game_id': game_id, + 'player1': player1.user.username, + 'player2': player1.user2.username + })) + else: + await player1.send(json.dumps({ + 'type': 'game_start', + 'game_id': game_id, + 'player1': player1.user.username, + 'player2': 'BOT' + })) # Instance of the class match_maker = MatchMaker() - -# to get what you want use get_player_p_win(player_name) !! (voir utils.py) -# \ No newline at end of file diff --git a/pong/game/migrations/0003_alter_tournoi_date.py b/pong/game/migrations/0003_alter_tournoi_date.py new file mode 100644 index 0000000..6bfda6c --- /dev/null +++ b/pong/game/migrations/0003_alter_tournoi_date.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.1 on 2024-09-10 14:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('game', '0002_alter_match_winner'), + ] + + operations = [ + migrations.AlterField( + model_name='tournoi', + name='date', + field=models.DateField(auto_now_add=True), + ), + ] diff --git a/pong/game/models.py b/pong/game/models.py index 2586446..4f63102 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) @@ -26,7 +25,7 @@ class Player(models.Model): class Tournoi(models.Model): name = models.CharField(max_length=200) nbr_player = models.PositiveSmallIntegerField() - date = models.DateField() + date = models.DateField(auto_now_add=True) winner = models.ForeignKey('Player', on_delete=models.SET_NULL, null=True) def __str__(self): @@ -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/match_list.html b/pong/game/templates/pong/match_list.html deleted file mode 100644 index 72c1315..0000000 --- a/pong/game/templates/pong/match_list.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Matches List - - -

Matches List

- - - - - - - - - - - - - - - - - - - {% for match in matches %} - - - - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
IDPlayer 1Player 2Score Player 1Score Player 2WinnerBall Touches Player 1Ball Touches Player 2DurationDateIs TournamentTournament
{{ 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 }}
No matches found.
- - diff --git a/pong/game/templates/pong/player_list.html b/pong/game/templates/pong/player_list.html deleted file mode 100644 index b9fbb42..0000000 --- a/pong/game/templates/pong/player_list.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - Players List - - -

Players List

- - - - - - - - - - - - - - - - - - - - {% for player in players %} - - - - - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
IDNameTotal MatchesTotal WinsWin PercentageAverage Match ScoreAverage Opponent ScoreBest ScoreAverage Ball TouchesTotal DurationAverage DurationParticipated TournamentsWon Tournaments
{{ 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 }}
No players found.
- - diff --git a/pong/game/templates/pong/tournament_brackets.html b/pong/game/templates/pong/tournament_brackets.html new file mode 100644 index 0000000..ca65553 --- /dev/null +++ b/pong/game/templates/pong/tournament_brackets.html @@ -0,0 +1,60 @@ + + + + + + Tournament Brackets + + + +
+ {% for round in tournament_rounds %} +
+ {% for match in round %} +
+
+ {{ match.player1 }} +
+
+ {{ match.player2|default:"BYE" }} +
+
+ {% endfor %} +
+ {% endfor %} +
+ + \ 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/templates/pong/tournoi_list.html b/pong/game/templates/pong/tournoi_list.html deleted file mode 100644 index e5718f5..0000000 --- a/pong/game/templates/pong/tournoi_list.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Tournaments List - - -

Tournaments List

- - - - - - - - - - - - {% for tournoi in tournois %} - - - - - - - - {% empty %} - - - - {% endfor %} - -
IDNameNumber of PlayersDateWinner
{{ tournoi.id }}{{ tournoi.name }}{{ tournoi.nbr_player }}{{ tournoi.date }}{{ tournoi.winner.name }}
No tournaments found.
- - diff --git a/pong/game/tournament.py b/pong/game/tournament.py new file mode 100644 index 0000000..b0c6c1b --- /dev/null +++ b/pong/game/tournament.py @@ -0,0 +1,222 @@ +# /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 +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", + "Shibuya Incident", "Cunning Game", "Elite of the Stars", "Chaku's Disciples" +] + +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 + # 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) + if self.game_id in match_maker.active_games: + 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" + self.name = random.choice(TOURNAMENT_NAMES) + self.final_name = "" + self.tournoi_reg = None + + async def add_player(self, player): + if self.tournament_state == "waiting" and player not in self.waiting_players: + self.waiting_players.append(player) + if player: + print(f"User {player.user.username} joins the TOURNAMENT WAITING ROOM") + else: + print("BOT 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 if player else 'BYE' for player in self.waiting_players], + 'tournament_state': self.tournament_state, + 'players_count': len(self.waiting_players), + 'min_players_to_start': 3 # 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): + if player: + 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 + 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) + self.tournoi_reg = await sync_to_async(create_tournament)(self.final_name, len(self.waiting_players)) + 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 + if players[i]: + await players[i].set_game(match) + if i + 1 < len(players): + if players[i + 1]: + 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 'BYE', + 'player2': match.player2.user.username if match.player2 else 'BYE', + '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) + #asyncio.create_task(match.start_game()) + match.game_state['player1_score'] = 3 + match.game_state['player2_score'] = 0 + await match.end_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 + }) + + await sync_to_async(update_tournament)(self.final_name, 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/urls.py b/pong/game/urls.py index 8da5f37..df150f0 100644 --- a/pong/game/urls.py +++ b/pong/game/urls.py @@ -2,11 +2,8 @@ from django.urls import path, include from . import views -from .views import player_list, tournoi_list, match_list from rest_framework.routers import DefaultRouter -from .views import match_list_json -from .views import player_list_json - +from .views import match_list_json, player_list_json, tournoi_list_json urlpatterns = [ @@ -14,9 +11,8 @@ urlpatterns = [ path('check_user_exists/', views.check_user_exists, name='check_user_exists'), path('register_user/', views.register_user, name='register_user'), path('authenticate_user/', views.authenticate_user, name='authenticate_user'), - path('players/', player_list, name='player_list'), - path('matches/', match_list, name='match_list'), - path('tournois/', tournoi_list, name='tournoi_list'), + path('web3/', views.read_data, name='read_data'), path('api/match_list/', match_list_json, name='match_list_json'), path('api/player_list/', player_list_json, name='player_list_json'), + path('api/tournoi_list/', tournoi_list_json, name='tournoi_list_json') ] diff --git a/pong/game/utils.py b/pong/game/utils.py index e260e81..16921d0 100644 --- a/pong/game/utils.py +++ b/pong/game/utils.py @@ -5,33 +5,38 @@ from django.db.models import Max, Sum, F from datetime import timedelta from channels.db import database_sync_to_async -async def endfortheouche(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament): - # Check if player p1 exists, if not create - if not await database_sync_to_async(Player.objects.filter(name=p1).exists)(): - player_1 = await create_player(p1) - print("############# PLAYER DONE") +def handle_game_data(p1, p2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament): + try: + player_1 = get_or_create_player(p1) + player_2 = get_or_create_player(p2) + + create_match(player_1, player_2, s_p1, s_p2, bt_p1, bt_2, dur, is_tournoi, name_tournament) + + update_player_statistics(p1) + update_player_statistics(p2) + + except Exception as e: + print(f"Error in endfortheouche: {e}") + +def get_player_by_name(name): + exists = Player.objects.filter(name=name).exists() + return exists + + +def get_player(name): + return Player.objects.get(name=name) + + +def get_or_create_player(name): + player_exists = get_player_by_name(name) + if not player_exists: + player = create_player(name) + return player else: - player_1 = await database_sync_to_async(Player.objects.get)(name=p1) + player = get_player(name) + return player - # Check if player p2 exists, if not create - if not await database_sync_to_async(Player.objects.filter(name=p2).exists)(): - player_2 = await create_player(p2) - print("############# PLAYER DONE") - else: - player_2 = await database_sync_to_async(Player.objects.get)(name=p2) - - # create Match - 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") - # Update data p1 et p2 - - await uptdate_player_statistics(p1) - print("############# END STAT P1") - await uptdate_player_statistics(p2) - -@database_sync_to_async def create_player( name, total_match=0, @@ -46,33 +51,29 @@ def create_player( num_participated_tournaments=0, num_won_tournaments=0 ): - if Player.objects.filter(name=name).exists(): - raise ValueError(f"A player with the name '{name}' already exists.") - + 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 + 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, @@ -96,22 +97,16 @@ def create_match(player1, player2, score_player1, score_player2, nbr_ball_touch_ match.save() return match -@database_sync_to_async -def uptdate_player_statistics(player_name): - print("############# BEG STAT P") +def update_player_statistics(player_name): 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 @@ -127,9 +122,9 @@ def uptdate_player_statistics(player_name): 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) """ + #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 @@ -151,15 +146,14 @@ def uptdate_player_statistics(player_name): 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 - """ + #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 @@ -169,50 +163,34 @@ def uptdate_player_statistics(player_name): 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.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 - -""" def complete_match(match_id, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration): - try: - match = Match.objects.get(id=match_id) - except Match.DoesNotExist: - raise ValidationError(f"Match with id {match_id} does not exist") - - match.score_player1 = score_player1 - match.score_player2 = score_player2 - match.nbr_ball_touch_p1 = nbr_ball_touch_p1 - match.nbr_ball_touch_p2 = nbr_ball_touch_p2 - match.duration = duration - - 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 """ - -""" def complete_tournoi(tournoi_id, player): - try: - tournoi = Tournoi.objects.get(id = tournoi_id) - except Tournoi.DoesNotExist: - raise ValidationError(f"Tournoi with id {tournoi_id} does not exist") - - tournoi.winner = player +def create_tournament(name, nbr_player): + print("tournoi created!!!") + tournoi=Tournoi(name=name, nbr_player=nbr_player, winner=None) + tournoi.save() + print(f"tournoi name : {tournoi.name} *******!*!*!*!**!*!**!*!*!*!*!*!*!*!*!*") + return tournoi + +def update_tournament(name_tournoi, winner_name): + tournoi = get_object_or_404(Tournoi, name=name_tournoi) + winner_p = get_object_or_404(Player, name=winner_name) + print(f"in update tourna - tournoi name : {tournoi.name} *******!*!*!*!**!*!**!*!*!*!*!*!*!*!*!*") + print(f"in update tourna - winner is : {winner_p.name} *******!*!*!*!**!*!**!*!*!*!*!*!*!*!*!*") + + tournoi.winner = winner_p + print(f"in update tourna - TOURNOI winner is : {tournoi.winner.name} *******!*!*!*!**!*!**!*!*!*!*!*!*!*!*!*") tournoi.save() - return tournoi """ - + + +def getlen(): + return Tournoi.objects.count() diff --git a/pong/game/views.py b/pong/game/views.py index a6cc709..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,24 @@ 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': + data = json.loads(request.body) + username = data.get('username') + if User.objects.filter(username=username).exists(): + return JsonResponse({'exists': True}) + return JsonResponse({'exists': False}) + return JsonResponse({'error': 'Invalid request method'}, status=400) + @csrf_exempt def register_user(request): if request.method == 'POST': @@ -31,16 +39,6 @@ def register_user(request): return JsonResponse({'registered': False, 'error': 'User already exists'}) return JsonResponse({'error': 'Invalid request method'}, status=400) -@csrf_exempt -def check_user_exists(request): - if request.method == 'POST': - data = json.loads(request.body) - username = data.get('username') - if User.objects.filter(username=username).exists(): - return JsonResponse({'exists': True}) - return JsonResponse({'exists': False}) - return JsonResponse({'error': 'Invalid request method'}, status=400) - @csrf_exempt def authenticate_user(request): if request.method == 'POST': @@ -69,25 +67,8 @@ def get_or_create_token(user): break return user.auth_token - -####################### THEOUCHE PART ############################ - -def player_list(request): - players = Player.objects.all() - return render(request, 'pong/player_list.html', {'players': players}) - -def match_list(request): - matches = Match.objects.select_related('player1', 'player2', 'winner', 'tournoi').all() - return render(request, 'pong/match_list.html', {'matches': matches}) - -def tournoi_list(request): - tournois = Tournoi.objects.select_related('winner').all() - return render(request, 'pong/tournoi_list.html', {'tournois': tournois}) - -from django.http import JsonResponse - def match_list_json(request): - matches = Match.objects.select_related('player1', 'player2', 'winner', 'tournoi').all() + matches = Match.objects.all() data = { 'matches': list(matches.values( 'id', 'player1__name', 'player2__name', 'score_player1', 'score_player2', @@ -98,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', @@ -110,9 +89,81 @@ def player_list_json(request): 'num_participated_tournaments', 'num_won_tournaments' )) } + return JsonResponse(data) + +def tournoi_list_json(request): + tournois = Tournoi.objects.all() - # Renvoie les données en JSON + data = { + 'tournois': list(tournois.values( + 'id', 'name', 'nbr_player', 'date', 'winner' + )) + } return JsonResponse(data) -####################### THEOUCHE PART ############################ +from web3 import Web3 + +provider = Web3.HTTPProvider("https://sepolia.infura.io/v3/60e51df7c97c4f4c8ab41605a4eb9907") +web3 = Web3(provider) +eth_gas_price = web3.eth.gas_price/1000000000 +print(eth_gas_price) + +contract_address = "0x078D04Eb6fb97Cd863361FC86000647DC876441B" +contract_abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"uint256","name":"_timecode","type":"uint256"},{"internalType":"uint256","name":"_participantCount","type":"uint256"},{"internalType":"string[]","name":"_playerPseudonyms","type":"string[]"},{"internalType":"string[]","name":"_finalOrder","type":"string[]"}],"name":"addTournament","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllTournaments","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"timecode","type":"uint256"},{"internalType":"uint256","name":"participantCount","type":"uint256"},{"internalType":"string[]","name":"playerPseudonyms","type":"string[]"},{"internalType":"string[]","name":"finalOrder","type":"string[]"}],"internalType":"struct PongTournament.Tournament[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"getTournament","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"timecode","type":"uint256"},{"internalType":"uint256","name":"participantCount","type":"uint256"},{"internalType":"string[]","name":"playerPseudonyms","type":"string[]"},{"internalType":"string[]","name":"finalOrder","type":"string[]"}],"internalType":"struct PongTournament.Tournament","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tournamentCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tournaments","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"timecode","type":"uint256"},{"internalType":"uint256","name":"participantCount","type":"uint256"}],"stateMutability":"view","type":"function"}] + +contract = web3.eth.contract(address=contract_address, abi=contract_abi) + +def read_data(request): + # Créer une instance du contrat + + # Appeler une fonction du contrat pour obtenir tous les tournois + tournaments = contract.functions.getAllTournaments().call() + + # Afficher les résultats + json_data = [] + for tournament in tournaments: + tournament_data = [] + for item in tournament: + print(f"{item}") + tournament_data.append(item) + json_data.append(tournament_data) + + # Retourner le JSON comme réponse HTTP + # print(f"Tournament ID: {tournament[0]}") + # print(f"Name: {tournament[1]}") + # print(f"Timecode: {tournament[2]}") + # print(f"Participant Count: {tournament[3]}") + # print(f"Player Pseudonyms: {', '.join(tournament[4])}") + # print(f"Final Order: {', '.join(tournament[5])}") + print("-----------------------------") + return JsonResponse(json_data, safe=False) + + +def write_data(request): + # addTournament(string,uint256,uint256,string[],string[]) + + # # Configuration de la transaction pour la fonction store + # account = "0x66CeBE2A1F7dae0F6AdBAad2c15A56A9121abfEf" + # private_key = "beb16ee3434ec5abec8b799549846cc04443c967b8d3643b943e2e969e7d25be" + + # nonce = web3.eth.get_transaction_count(account) + # transaction = contract.functions.addTournament("test",1721830559,6,["aaudeber", "tlorne", "ocassany", "yestello", "jcheca", "toto"],["toto", "jcheca", "yestello", "tlorne", "ocassany", "aaudeber"]).build_transaction({ + # 'chainId': 11155111, # ID de la chaîne Sepolia + # 'gas': 2000000, + # 'gasPrice': web3.to_wei(eth_gas_price, 'gwei'), + # 'nonce': nonce + # }) + + # # Signature de la transaction + # signed_txn = web3.eth.account.sign_transaction(transaction, private_key) + + # # Envoi de la transaction + # tx_hash = web3.eth.send_raw_transaction(signed_txn.rawTransaction) + # print("Transaction hash:", web3.to_hex(tx_hash)) + + # # Attente de la confirmation de la transaction + # tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash) + # print("Transaction receipt:", tx_receipt) + print("-----------------------------") + diff --git a/pong/settings.py b/pong/settings.py index 83bd985..b44ccdf 100644 --- a/pong/settings.py +++ b/pong/settings.py @@ -6,8 +6,9 @@ Django settings for pong project. Generated by 'django-admin startproject' using Django 3.2. """ -from pathlib import Path import os +import logging.config +from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -34,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' ] @@ -67,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 @@ -134,3 +135,38 @@ CHANNEL_LAYERS = { 'BACKEND': 'channels.layers.InMemoryChannelLayer', }, } + +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 + 'json': { + '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', + # Formatter that outputs logs in JSON format, which is ideal for ingestion by Logstash. + }, + 'default': { + 'format': '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s', + # This is a basic text formatter with timestamp, log level, logger name, line number, and the actual message. + }, + }, + 'handlers': { # Handlers determine where the log messages are sent + 'file': { + 'level': 'INFO', # Minimum log level to be handled (INFO and above) + 'class': 'logging.FileHandler', + 'filename': os.path.join(BASE_DIR, 'logs/django.log'), # The file where logs will be saved + 'formatter': 'json', # Uses the JSON formatter defined above + }, + 'console': { + 'level': 'DEBUG', # Minimum log level to be handled (DEBUG and above) + 'class': 'logging.StreamHandler', + 'formatter': 'default', # Uses the default text formatter + }, + }, + 'loggers': { # Loggers are the actual log streams that get configured + 'django': { # The Django logger catches all messages sent by the Django framework + 'handlers': ['file', 'console'], # Sends logs to both the file and the console + 'level': 'DEBUG', # Minimum log level to be logged + '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..aa2139a --- /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 = ''; + const row = document.createElement('tr'); + + if (matches.length != 0) { + matches.forEach(match => { + 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 = ''; + const row = document.createElement('tr'); + + if (players.length != 0) { + players.forEach(player => { + 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 = ''; + const row = document.createElement('tr'); + + if (tournois.length != 0) { + tournois.forEach(tournoi => { + row.innerHTML = ` + ${tournoi.id} + ${tournoi.name} + ${tournoi.nbr_player} + ${tournoi.date} + ${tournoi.winner ? tournoi.winner.name : 'None'} + `; + 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/flags/de.svg b/pong/static/flags/de.svg new file mode 100755 index 0000000..61e64d0 --- /dev/null +++ b/pong/static/flags/de.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pong/static/flags/es.svg b/pong/static/flags/es.svg new file mode 100755 index 0000000..ce2e1a8 --- /dev/null +++ b/pong/static/flags/es.svg @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pong/static/flags/fr.svg b/pong/static/flags/fr.svg new file mode 100755 index 0000000..240e789 --- /dev/null +++ b/pong/static/flags/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pong/static/flags/it.svg b/pong/static/flags/it.svg new file mode 100755 index 0000000..038afd9 --- /dev/null +++ b/pong/static/flags/it.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pong/static/flags/us.svg b/pong/static/flags/us.svg new file mode 100755 index 0000000..8a8fb5c --- /dev/null +++ b/pong/static/flags/us.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pong/static/game.js b/pong/static/game.js index 380d21b..8dcc898 100644 --- a/pong/static/game.js +++ b/pong/static/game.js @@ -1,31 +1,47 @@ document.addEventListener('DOMContentLoaded', () => { - console.log('DOMContentLoaded event fired'); - const checkNicknameButton = document.getElementById('check-nickname'); - const registerButton = document.getElementById('register'); - const loginButton = document.getElementById('login'); + const formBlock = document.getElementById('block-form'); + const authForm = document.getElementById('auth-form'); - const gameContainer = document.getElementById('game1'); const nicknameInput = document.getElementById('nickname'); + const checkNicknameButton = document.getElementById('check-nickname'); + + const registerForm = document.getElementById('register-form'); const passwordInput = document.getElementById('password'); const confirmPasswordInput = document.getElementById('confirm-password'); - const loginPasswordInput = document.getElementById('login-password'); + const registerButton = document.getElementById('register'); + const loginForm = document.getElementById('login-form'); - const registerForm = document.getElementById('register-form'); - const formBlock = document.getElementById('block-form'); - //const viewSelector = document.getElementById('view-selector'); - //const viewPlayersButton = document.getElementById('view-players'); - //const viewMatchesButton = document.getElementById('view-matches'); - const menuButton = document.querySelector('.burger-menu'); - const playerList = document.getElementById('player-list'); - const matchList = document.getElementById('match-list'); - const dropdownMenu = document.getElementById('dropdown-menu'); + const loginPasswordInput = document.getElementById('login-password'); + const loginButton = document.getElementById('login'); + + const authForm2 = document.getElementById('auth-form2'); + const nicknameInput2 = document.getElementById('nickname2'); + const checkNicknameButton2 = document.getElementById('check-nickname2'); + + const registerForm2 = document.getElementById('register-form2'); + const passwordInput2 = document.getElementById('password2'); + const confirmPasswordInput2 = document.getElementById('confirm-password2'); + const registerButton2 = document.getElementById('register2'); + + const loginForm2 = document.getElementById('login-form2'); + const loginPasswordInput2 = document.getElementById('login-password2'); + const loginButton2 = document.getElementById('login2'); + + const gameContainer = document.getElementById('game1'); + 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'); + let socket; let token; let gameState; + let saveData = null; // Auto-focus and key handling for AUTH-FORM nicknameInput.focus(); @@ -40,15 +56,23 @@ document.addEventListener('DOMContentLoaded', () => { registerButton.addEventListener('click', handleRegister); loginButton.addEventListener('click', handleLogin); + checkNicknameButton2.addEventListener('click', handleCheckNickname2); + registerButton2.addEventListener('click', handleRegister2); + loginButton2.addEventListener('click', handleLogin2); + + localGameButton.addEventListener('click', startLocalGame); + 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') { @@ -59,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') { @@ -103,11 +126,7 @@ document.addEventListener('DOMContentLoaded', () => { const result = await registerUser(nickname, password); if (result) { registerForm.style.display = 'none'; - gameContainer.style.display = 'flex'; - formBlock.style.display = 'none'; - logo.style.display = 'none'; - pongElements.style.display = 'none'; - startWebSocketConnection(token); + document.getElementById("post-form-buttons").style.display = 'block'; } else { alert('Registration failed. Please try again.'); } @@ -141,11 +160,7 @@ document.addEventListener('DOMContentLoaded', () => { const result = await authenticateUser(nickname, password); if (result) { loginForm.style.display = 'none'; - gameContainer.style.display = 'flex'; - formBlock.style.display = 'none'; - logo.style.display = 'none'; - pongElements.style.display = 'none'; - startWebSocketConnection(token); + document.getElementById("post-form-buttons").style.display = 'block'; } else { alert('Authentication failed. Please try again.'); } @@ -169,12 +184,196 @@ document.addEventListener('DOMContentLoaded', () => { return data.authenticated; } - function startWebSocketConnection(token) { + async function handleCheckNickname2() { + const nickname2 = nicknameInput2.value.trim(); + if (nickname2) { + try { + const exists = await checkUserExists2(nickname2); + if (exists) { + authForm2.style.display = 'none'; + loginForm2.style.display = 'block'; + loginPasswordInput2.focus(); + loginPasswordInput2.addEventListener('keypress', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + loginButton2.click(); + } + }); + } else { + authForm2.style.display = 'none'; + registerForm2.style.display = 'block'; + passwordInput2.focus(); + passwordInput2.addEventListener('keypress', function (event) { + if (event.key === 'Enter') { + confirmPasswordInput2.focus(); + confirmPasswordInput2.addEventListener('keypress', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + registerButton2.click(); + } + }); + } + }); + } + } catch (error) { + console.error('Error checking user existence:', error); + } + } else { + alert('Please enter a nickname.'); + } + } + + async function checkUserExists2(username) { + const response = await fetch('/check_user_exists/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username }) + }); + const data = await response.json(); + return data.exists; + } + + async function handleRegister2() { + const nickname2 = nicknameInput2.value.trim(); + const password2 = passwordInput2.value.trim(); + const confirmPassword2 = confirmPasswordInput2.value.trim(); + + if (password2 === confirmPassword2) { + try { + const result = await registerUser2(nickname2, password2); + if (result) { + registerForm2.style.display = 'none'; + startLocalGame2(); + } else { + alert('Registration failed. Please try again.'); + } + } catch (error) { + console.error('Error registering user:', error); + } + } else { + alert('Passwords do not match.'); + } + } + + async function registerUser2(username, password) { + const response = await fetch('/register_user/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + const data = await response.json(); + if (data.registered) { + token2 = data.token; + } + return data.registered; + } + + async function handleLogin2() { + const nickname2 = nicknameInput2.value.trim(); + const password2 = loginPasswordInput2.value.trim(); + try { + const result = await authenticateUser2(nickname2, password2); + if (result) { + loginForm2.style.display = 'none'; + startLocalGame2(); + } else { + alert('Authentication failed. Please try again.'); + } + } catch (error) { + console.error('Error authenticating user:', error); + } + } + + async function authenticateUser2(username, password) { + const response = await fetch('/authenticate_user/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + const data = await response.json(); + if (data.authenticated) { + token2 = data.token; + } + return data.authenticated; + } + + function startLocalGame() { + console.log("starting a Local Game.."); + document.getElementById("post-form-buttons").style.display = 'none'; + authForm2.style.display = 'block'; + nicknameInput2.focus(); + nicknameInput2.addEventListener('keypress', function (event) { + if (event.key === 'Enter') { + event.preventDefault(); + checkNicknameButton2.click(); + } + }); + } + + 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'; + formBlock.style.display = 'none'; + startWebSocketConnection(token, 2); + } + + function startQuickMatch() { + saveData = { + type: 'quick' + } + gameContainer.style.display = 'flex'; + logo.style.display = 'none'; + pongElements.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() { + saveData = { + type: 'tournoi' + } + tournamentContainer.style.display = 'flex'; + logo.style.display = 'none'; + pongElements.style.display = 'none'; + formBlock.style.display = 'none'; + startWebSocketConnection(token, 42); + } + + function startWebSocketConnection(token, players) { socket = new WebSocket(`ws://${window.location.host}/ws/game/`); socket.onopen = function (event) { console.log('WebSocket connection established'); - socket.send(JSON.stringify({ type: 'authenticate', token: token })); + if (players === 1) { + console.log("Sending token for a quick match game"); + socket.send(JSON.stringify({ type: 'authenticate', token: token })); + } 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 })); + } }; socket.onmessage = function (event) { @@ -185,15 +384,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); } @@ -208,24 +428,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') { - console.log('Key press: ', event.key); + if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'w' || event.key === 's') { 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 })); } } @@ -233,165 +443,76 @@ 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; } - // viewSelector.addEventListener('change', function() { - // const selectedView = this.value; - - // Masquer les deux listes par défaut - // playerList.style.display = 'none'; - // matchList.style.display = 'none'; - - // Afficher la liste sélectionnée - // if (selectedView === 'player-list') { - // playerList.style.display = 'block'; - // fetchPlayers(); - //} else if (selectedView === 'match-list') { - // matchList.style.display = 'block'; - // fetchMatches(); - //} - //}) - - console.log('Here'); - - function toggleMenu() { - console.log('Menu toggled'); - if (dropdownMenu.style.display === "block") { - dropdownMenu.style.display = "none"; - } else { - dropdownMenu.style.display = "block"; - } + const starsContainer = document.getElementById('stars'); + for (let i = 0; i < 500; i++) { + const star = document.createElement('div'); + star.className = 'star'; + star.style.width = `${Math.random() * 3}px`; + star.style.height = star.style.width; + star.style.left = `${Math.random() * 100}%`; + star.style.top = `${Math.random() * 100}%`; + star.style.animationDuration = `${Math.random() * 2 + 1}s`; + starsContainer.appendChild(star); } - function showTable(tableId) { - // Masquer tous les tableaux - if (playerList) { - playerList.style.display = 'none'; - } - if (matchList) { - matchList.style.display = 'none'; - } + const homeButton = document.getElementById('home'); + const replayButton = document.getElementById('retry'); + const gameControls = document.getElementById('game-controls'); - // Afficher le tableau sélectionné - if (tableId === 'player-list') { - if (playerList) { - playerList.style.display = 'block'; - } - fetchPlayers(); - } else if (tableId === 'match-list') { - if (matchList) { - matchList.style.display = 'block'; - } - fetchMatches(); - } + homeButton.addEventListener('click', () => { + gameContainer.style.display = 'none'; + gameControls.style.display = 'none'; - // Masquer le menu après la sélection - if (dropdownMenu) { - dropdownMenu.style.display = 'none'; - } - } + logo.style.display = 'block' - // Ajouter les gestionnaires d'événements - if (menuButton) { - menuButton.addEventListener('click', toggleMenu); - } - - 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'); - showTable(tableId); - }); + formBlock.style.display = 'block'; + postFormButtons.style.display = 'flex'; + + setupFirstPlayer(); }); - function fetchMatches() { - fetch('/api/match_list/') - .then(response => response.json()) - .then(data => { - if (data.matches) { - displayMatches(data.matches); + function setupFirstPlayer() { + const firstPlayerName = window.firstPlayerName; + document.getElementById('player1-name').textContent = firstPlayerName; + } + + replayButton.addEventListener('click', () => { + document.getElementById('player1-name').textContent = saveData.player1_name; + document.getElementById('player2-name').textContent = saveData.player2_name; + startLocalGame2(); + }); + + function checkForWinner() { + if (gameState.player1_score === 3 || gameState.player2_score === 3) { + if (saveData.type != "tournoi"){ + gameControls.style.display = 'flex'; + homeButton.style.display = 'block'; + replayButton.style.display = 'none'; + console.log(saveData.type); + if (saveData.type === 'local'){ + replayButton.style.display = 'block'; } - }) - .catch(error => console.error('Error fetching match data:', error)); - } - - function fetchPlayers(){ - 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 displayMatches(matches) { - const matchListBody = document.querySelector('#match-list tbody'); - matchListBody.innerHTML = ''; - - 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); - }); - } - - function displayPlayers(players) { - const playersListBody = document.querySelector('#player-list tbody'); - playersListBody.innerHTML = ''; - - 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); - }); + } + } } }); diff --git a/pong/static/index.html b/pong/static/index.html index 9aae525..aa89490 100644 --- a/pong/static/index.html +++ b/pong/static/index.html @@ -1,18 +1,24 @@ {% load static %} - + Pong Game - + + - +
+ Français + English + Italiano + Español + Deutsch +
@@ -22,26 +28,67 @@
+ + +
-

BIENVENUE DANS LE PONG 42

+

BIENVENUE DANS LE PONG 42

- +
+ + + +
@@ -50,24 +97,35 @@ + + + + -