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
-
-
-
- ID
- Player 1
- Player 2
- Score Player 1
- Score Player 2
- Winner
- Ball Touches Player 1
- Ball Touches Player 2
- Duration
- Date
- Is Tournament
- Tournament
-
-
-
- {% for match in matches %}
-
- {{ 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 }}
-
- {% empty %}
-
- No matches found.
-
- {% endfor %}
-
-
-
-
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
-
-
-
- ID
- Name
- Total Matches
- Total Wins
- Win Percentage
- Average Match Score
- Average Opponent Score
- Best Score
- Average Ball Touches
- Total Duration
- Average Duration
- Participated Tournaments
- Won Tournaments
-
-
-
- {% for player in players %}
-
- {{ 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 }}
-
- {% empty %}
-
- No players found.
-
- {% endfor %}
-
-
-
-
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:
+
+ {% for player in players %}
+ {{ player }}
+ {% endfor %}
+
+
+ {% if players_count >= min_players_to_start %}
+
Start Tournament
+ {% 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
-
-
-
- ID
- Name
- Number of Players
- Date
- Winner
-
-
-
- {% for tournoi in tournois %}
-
- {{ tournoi.id }}
- {{ tournoi.name }}
- {{ tournoi.nbr_player }}
- {{ tournoi.date }}
- {{ tournoi.winner.name }}
-
- {% empty %}
-
- No tournaments found.
-
- {% endfor %}
-
-
-
-
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
-
+
-
+
-
+
@@ -22,26 +28,67 @@
+ ⚙️ Réglages
+
+
@@ -50,24 +97,35 @@
+
+
+
+ Home
+ Rejouer
+
+
-
Game Code :
-
Player 1
-
Player 2
+
+
-
+
Players
+
@@ -131,45 +172,36 @@
- {% for player in players %}
-
- {{ 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 }}
-
- {% empty %}
-
- No players found.
-
- {% endfor %}
-
+
+
Générer le graphique
+
+
+
Tournois
+
+
+
+ ID
+ Name
+ Nbr_players
+ Date
+ Winner
+
+
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/pong/static/language.js b/pong/static/language.js
new file mode 100644
index 0000000..dd5b518
--- /dev/null
+++ b/pong/static/language.js
@@ -0,0 +1,113 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const translations = {
+ fr: {
+ welcome: "BIENVENUE DANS LE PONG 42",
+ labelNickname: "Entrez votre surnom:",
+ labelPassword: "Entrez votre mot de passe:",
+ labelConfirmPassword: "Confirmez votre mot de passe:",
+ labelLoginPassword: "Entrez votre mot de passe:"
+ },
+ en: {
+ welcome: "WELCOME TO PONG 42",
+ labelNickname: "Enter your nickname:",
+ labelPassword: "Enter your password:",
+ labelConfirmPassword: "Confirm your password:",
+ labelLoginPassword: "Enter your password:"
+ },
+ it: {
+ welcome: "BENVENUTO A PONG 42",
+ labelNickname: "Inserisci il tuo soprannome:",
+ labelPassword: "Inserisci la tua password:",
+ labelConfirmPassword: "Conferma la tua password:",
+ labelLoginPassword: "Inserisci la tua password:"
+ },
+ es: {
+ welcome: "BIENVENIDO A PONG 42",
+ labelNickname: "Introduce tu apodo:",
+ labelPassword: "Introduce tu contraseña:",
+ labelConfirmPassword: "Confirma tu contraseña:",
+ labelLoginPassword: "Introduce tu contraseña:"
+ },
+ de: {
+ welcome: "WILLKOMMEN BEI PONG 42",
+ labelNickname: "Geben Sie Ihren Spitznamen ein:",
+ labelPassword: "Geben Sie Ihr Passwort ein:",
+ labelConfirmPassword: "Bestätigen Sie Ihr Passwort:",
+ labelLoginPassword: "Geben Sie Ihr Passwort ein:"
+ }
+ };
+
+ function setCookie(name, value, days) {
+ const d = new Date();
+ d.setTime(d.getTime() + (days*24*60*60*1000));
+ const expires = "expires=" + d.toUTCString();
+ document.cookie = name + "=" + value + ";" + expires + ";path=/";
+ }
+
+ function getCookie(name) {
+ const cname = name + "=";
+ const decodedCookie = decodeURIComponent(document.cookie);
+ const ca = decodedCookie.split(';');
+ for(let i = 0; i < ca.length; i++) {
+ let c = ca[i];
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(cname) === 0) {
+ return c.substring(cname.length, c.length);
+ }
+ }
+ return "";
+ }
+
+ function changeLanguage(lang) {
+ setCookie('preferredLanguage', lang, 365);
+ document.getElementById('welcome').innerText = translations[lang].welcome;
+ document.getElementById('label-nickname').innerText = translations[lang].labelNickname;
+ document.getElementById('label-password').innerText = translations[lang].labelPassword;
+ document.getElementById('label-confirm-password').innerText = translations[lang].labelConfirmPassword;
+ document.getElementById('label-login-password').innerText = translations[lang].labelLoginPassword;
+ }
+
+ function setLanguageFromCookie() {
+ const preferredLanguage = getCookie('preferredLanguage');
+ if (preferredLanguage && translations[preferredLanguage]) {
+ changeLanguage(preferredLanguage);
+ } else {
+ changeLanguage('fr'); // Default to French if no cookie is found
+ }
+ }
+
+ document.getElementById('lang-fr').addEventListener('click', () => changeLanguage('fr'));
+ document.getElementById('lang-en').addEventListener('click', () => changeLanguage('en'));
+ document.getElementById('lang-it').addEventListener('click', () => changeLanguage('it'));
+ document.getElementById('lang-es').addEventListener('click', () => changeLanguage('es'));
+ document.getElementById('lang-de').addEventListener('click', () => changeLanguage('de'));
+
+ window.onload = setLanguageFromCookie;
+
+ document.getElementById('settings-btn').addEventListener('click', function() {
+ document.getElementById('settings-menu').style.display = 'block';
+ });
+
+ document.getElementById('close-settings').addEventListener('click', function() {
+ document.getElementById('settings-menu').style.display = 'none';
+ });
+
+
+ document.getElementById('color-picker').addEventListener('input', function() {
+ document.body.style.color = this.value;
+ document.querySelectorAll('button').forEach(function(button) {
+ button.style.backgroundColor = this.value;
+ }, this);
+ });
+
+ document.getElementById('font-selector').addEventListener('change', function() {
+ document.body.style.fontFamily = this.value;
+ });
+
+ document.getElementById('font-size-slider').addEventListener('input', function() {
+ document.body.style.fontSize = this.value + 'px';
+ });
+
+});
\ No newline at end of file
diff --git a/pong/static/styles.css b/pong/static/styles.css
index 19a2666..38d78f0 100644
--- a/pong/static/styles.css
+++ b/pong/static/styles.css
@@ -1,5 +1,4 @@
/* General styles */
-body,
html {
font-family: Arial, sans-serif;
@@ -12,10 +11,10 @@ html {
align-items: center;
justify-content: center;
height: 100%;
- overflow: hidden;
-
+ overflow: auto;
}
+
label {
margin: 10px 0 5px;
}
@@ -59,20 +58,10 @@ button:hover {
align-items: center;
}
-.game-code {
- font-size: 24px;
- position: absolute;
- top: 10px;
-}
-
-#gameCode {
- left: 10px;
-}
-
.name {
font-size: 24px;
position: absolute;
- top: 30px;
+ top: 20px;
}
#player1-name {
@@ -144,6 +133,13 @@ button:hover {
position: absolute;
}
+#game-text {
+ font-size: 64px;
+ color: #00ffff;
+ position: absolute;
+ top: 150px;
+}
+
.logo {
position: absolute;
top: 20px;
@@ -259,12 +255,14 @@ button:hover {
position: relative;
z-index: 10;
max-width: 80%;
+ overflow: auto;
}
+
.navbar {
position: absolute;
- top: 0;
- right: 0;
+ top: 30px;
+ right: 10px;
padding: 10px;
}
@@ -272,28 +270,203 @@ button:hover {
font-size: 24px;
background: none;
border: none;
- color: white;
+ color: #00ffff;
cursor: pointer;
+ transition: color 0.3s ease;
+}
+
+.burger-menu:hover {
+ color: #ffffff;
}
.dropdown-content {
display: none;
position: absolute;
+ top: 100%;
right: 0;
- background-color: #f9f9f9;
- box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ margin-top: 10px;
+ background-color: #1a1a2e;
+ color: #ffffff;
+ box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.3);
+ border-radius: 5px;
z-index: 1;
+ width: max-content;
}
-.dropdown-content a {
- color: black;
- padding: 12px 16px;
- text-decoration: none;
+.dropdown-content.show {
display: block;
}
-.dropdown-content a:hover {
- background-color: #f1f1f1;
+.dropdown-content a {
+ color: #ffffff;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ transition: background-color 0.3s ease;
}
-.show {display: block;}
\ No newline at end of file
+.dropdown-content a:hover {
+ background-color: #333;
+ color: #00ffff;
+}
+
+.language-switcher {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ display: flex;
+ gap: 10px;
+}
+
+.language-switcher img {
+ width: 30px;
+ height: 20px;
+ cursor: pointer;
+}
+
+#settings-btn {
+ position: fixed;
+ bottom: 10px;
+ right: 10px;
+ cursor: pointer;
+ z-index: 1000;
+ border: none;
+ border-radius: 50%;
+ padding: 10px;
+ font-size: 18px;
+}
+
+#settings-menu {
+ position: fixed;
+ bottom: 50px;
+ right: 10px;
+ padding: 20px;
+ background-color: rgb(66, 63, 63);
+ border: 1px solid #ccc;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ width: 250px;
+ z-index: 1000;
+}
+
+#close-settings {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 16px;
+}
+
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgb(0, 0, 0);
+ background-color: rgba(0, 0, 0, 0.4);
+ padding-top: 60px;
+}
+
+.modal-content {
+ background-color: #fefefe;
+ margin: 5% auto;
+ padding: 20px;
+ border: 5px solid #888;
+ width: 80%;
+ max-height: 70vh;
+ overflow-y: auto;
+}
+
+.close {
+ color: #aaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.close:hover,
+.close:focus {
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+/*
+.tournament-waiting-room {
+ background-color: rgba(0, 0, 0, 0.6);
+ padding: 20px;
+ border-radius: 15px;
+ color: #00ffff;
+ width: 50%;
+ margin: auto;
+ text-align: center;
+ box-shadow: 0 0 30px #00ffff, inset 0 0 20px #00ffff;
+
+}
+
+.tournament-waiting-room h2 {
+ font-family: 'Arial', sans-serif;
+ font-size: 2em;
+ margin-bottom: 15px;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
+}
+
+.tournament-waiting-room p {
+ font-family: 'Verdana', sans-serif;
+ font-size: 1.2em;
+ margin-bottom: 20px;
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);
+}
+
+.tournament-waiting-room ul {
+ list-style-type: none;
+ padding: 0;
+}
+*/
+body {
+ color: rgb(0, 255, 255); /* Valeur par défaut */
+ font-family: Arial, sans-serif;
+ font-size: 16px;
+}
+
+canvas {
+ max-width: 100%;
+ height: auto;
+ margin-top: 20px;
+}
+
+#game-controls {
+ display: flex;
+ justify-content: center;
+ gap: 20px;
+ margin-bottom: 10px;
+}
+
+#game-controls button {
+ background-color: #00ffff;
+ color: #000033;
+ border: none;
+ padding: 1rem 2rem;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border-radius: 10px;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+}
+
+#game-controls button:hover {
+ background-color: #000033;
+ color: #00ffff;
+ box-shadow: 0 0 20px #00ffff;
+}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 3c23d90..15069d9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,8 @@
-Django
+django
psycopg2
python-dotenv
channels
daphne
-djangorestframework
\ No newline at end of file
+djangorestframework
+web3
+python-json-logger==2.0.7