diff --git a/.env b/.env
index 8abdf3a..cab57c9 100644
--- a/.env
+++ b/.env
@@ -11,11 +11,11 @@ POSTGRES_PASSWORD=qwerty
DB_HOST=db
DB_PORT=5432
-#PROJECT_PATH=${PWD}/pong
-#POSTGRES_DATA_PATH=${PWD}/data/db
-#ES_DATA_PATH=${PWD}/data/es
-#KIBA_DATA_PATH=${PWD}/data/kiba
-#LSTASH_DATA_PATH=${PWD}/data/lstash
+PROJECT_PATH=${PWD}/pong
+POSTGRES_DATA_PATH=${PWD}/data/db
+ES_DATA_PATH=${PWD}/data/es
+KIBA_DATA_PATH=${PWD}/data/kiba
+LSTASH_DATA_PATH=${PWD}/data/lstash
# ElasticSearch settings
STACK_VERSION=8.14.3
@@ -23,7 +23,7 @@ CLUSTER_NAME=docker-cluster
LICENSE=basic
ELASTIC_PASSWORD=qwerty42
-ES_PORT=127.0.0.1:9200
+ES_PORT=9200
# Kibana settings
KIBANA_PASSWORD=qwerty42
diff --git a/.gitignore b/.gitignore
index 495985a..3adaaf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,3 @@
-.env
-
-# virtualenv
venv/
-
__pycache__/
-
data/
diff --git a/README.md b/README.md
deleted file mode 100644
index 93dbd37..0000000
--- a/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-### Installing Docker and Docker Compose on Ubuntu
-
-## ADVICE
-- The bash script included in the docker-compose.yml file only works with alphanumeric characters
diff --git a/docker-compose.yaml b/docker-compose.yaml
index e317179..ddc7844 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -25,11 +25,6 @@ services:
DB_NAME: ${POSTGRES_DB}
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
- #healthcheck:
- #test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
- #interval: 20s
- #timeout: 5s
- #retries: 5
db:
image: postgres:latest
@@ -45,11 +40,6 @@ services:
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}
@@ -57,6 +47,8 @@ services:
- pong_logstash_data_01:/usr/share/elasticsearch/data
ports:
- ${ES_PORT}:9200
+ networks:
+ - app-network
environment:
- node.name=es01
- cluster.name=${CLUSTER_NAME}
@@ -78,6 +70,12 @@ services:
- pong_logstash_data_01:/usr/share/logstash/data/logstash.conf
ports:
- "5044:5044"
+ networks:
+ - app-network
+ environment:
+ - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
+ - ELASTICSEARCH_USERNAME=kibana_system
+ - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
kibana:
image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
@@ -88,9 +86,11 @@ services:
user: root
ports:
- 5601:5601
+ networks:
+ - app-network
environment:
- SERVERNAME=kibana
- - ELASTICSEARCH_HOSTS=https://es01:9200
+ - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
mem_limit: ${KB_MEM_LIMIT}
@@ -103,14 +103,34 @@ services:
volumes:
pong:
driver: local
+ driver_opts:
+ type: none
+ device: ${PROJECT_PATH}
+ o: bind
pong_pg_data:
driver: local
+ driver_opts:
+ type: none
+ device: ${POSTGRES_DATA_PATH}
+ o: bind
pong_es_data_01:
driver: local
+ driver_opts:
+ type: none
+ device: ${ES_DATA_PATH}
+ o: bind
pong_kibana:
driver: local
+ driver_opts:
+ type: none
+ device: ${KIBA_DATA_PATH}
+ o: bind
pong_logstash_data_01:
driver: local
+ driver_opts:
+ type: none
+ device: ${LSTASH_DATA_PATH}
+ o: bind
networks:
app-network:
diff --git a/logstash.conf b/logstash.conf
index 88ba485..beca20b 100644
--- a/logstash.conf
+++ b/logstash.conf
@@ -3,21 +3,24 @@ input {
}
filter {
- # Adjust the grok pattern according to the PostgreSQL log format
- # Example log format: "2024-07-30 10:20:30 UTC LOG: statement: SELECT * FROM table"
grok {
- match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{GREEDYDATA:log_message}" }
+ match => {
+ "message" => '%{IP:client_ip} - - \[%{HTTPDATE:timestamp}\] "%{WORD:http_method} %{URIPATH:request_path}" %{NUMBER:http_status_code} %{NUMBER:response_size}'
+ }
+ # Optional: add a tag to the event for easier identification
+ add_tag => ["parsed_log"]
}
- # Optionally, parse and format the extracted timestamp field
+ # Optionally, convert the timestamp to the Logstash @timestamp
date {
- match => [ "timestamp", "ISO8601" ]
+ match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
target => "@timestamp"
- # Optional: specify timezone if needed
- # timezone => "UTC"
}
}
output {
- stdout { codec => rubydebug }
+ elasticsearch {
+ hosts => ["http://es01:9200"]
+ index => "logstash-%{+YYYY.MM.dd}"
+ }
}
diff --git a/makefile b/makefile
index f62a24f..9b3edb8 100644
--- a/makefile
+++ b/makefile
@@ -21,9 +21,9 @@ down:
destroy:
$(COMPOSE) down -v --rmi all
- sudo rm -rf $$PWD/data/db
- 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
+ #sudo rm -rf $$PWD/data/db
+ #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
logs:
$(COMPOSE) logs -f $(CONTAINER)
diff --git a/pong/game/consumers.py b/pong/game/consumers.py
index 8495636..b473ece 100644
--- a/pong/game/consumers.py
+++ b/pong/game/consumers.py
@@ -4,20 +4,23 @@ import json
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 # Import the match_maker instance
+from .matchmaking import match_maker
class GameConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
+ self.game = None
print("User connected")
async def receive(self, text_data):
data = json.loads(text_data)
- #print(f"MESSAGE RECEIVED: {data['type']}")
if data['type'] == 'authenticate':
await self.authenticate(data['token'])
elif data['type'] == 'key_press':
- await match_maker.handle_key_press(self, data['key'])
+ if self.game:
+ await self.game.handle_key_press(self, data['key'])
+ else:
+ await match_maker.handle_key_press(self, data['key'])
async def authenticate(self, token):
user = await self.get_user_from_token(token)
@@ -43,5 +46,10 @@ class GameConsumer(AsyncWebsocketConsumer):
await 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)
- print(f"User {self.user} disconnected")
+ print(f"User {self.user.username if hasattr(self, 'user') else 'Unknown'} disconnected")
+
+ async def set_game(self, game):
+ self.game = game
diff --git a/pong/game/game.py b/pong/game/game.py
index 2b2af26..fc234ec 100644
--- a/pong/game/game.py
+++ b/pong/game/game.py
@@ -9,78 +9,135 @@ class Game:
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,
- 'player1_position': 200, # middle of the game field
- 'player2_position': 200,
- 'ball_position': {'x': 400, 'y': 300}, # middle of the game field
+ '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.speed = 1
self.game_loop_task = None
+ self.ended = False
async def start_game(self):
- print(f"- Game {self.game_id} START")
+ print(f"- Game #{self.game_id} STARTED")
self.game_loop_task = asyncio.create_task(self.game_loop())
async def game_loop(self):
while True:
+ if self.botgame:
+ await self.update_bot_position()
self.update_game_state()
await self.send_game_state()
- await asyncio.sleep(1/60) # 60 FPS
+ 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)
def update_game_state(self):
# 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']
-
# Check for collisions with top and bottom walls
- if self.game_state['ball_position']['y'] <= 0 or self.game_state['ball_position']['y'] >= 600:
+ if self.game_state['ball_position']['y'] <= 10 or self.game_state['ball_position']['y'] >= 390:
self.game_state['ball_velocity']['y'] *= -1
-
+ # Check for collisions with paddles
+ if self.game_state['ball_position']['x'] <= 20 and \
+ self.game_state['player1_position'] - 10 <= self.game_state['ball_position']['y'] <= self.game_state['player1_position'] + 90:
+ if self.game_state['ball_velocity']['x'] < 0:
+ self.game_state['ball_velocity']['x'] *= -1
+ self.update_ball_velocity()
+ elif self.game_state['ball_position']['x'] >= 760 and \
+ self.game_state['player2_position'] - 10 <= self.game_state['ball_position']['y'] <= self.game_state['player2_position'] + 90:
+ if self.game_state['ball_velocity']['x'] > 0:
+ self.game_state['ball_velocity']['x'] *= -1
+ self.update_ball_velocity()
# Check for scoring
- if self.game_state['ball_position']['x'] <= 0:
+ if self.game_state['ball_position']['x'] <= 10:
self.game_state['player2_score'] += 1
self.reset_ball()
- elif self.game_state['ball_position']['x'] >= 800:
+ elif self.game_state['ball_position']['x'] >= 790:
self.game_state['player1_score'] += 1
self.reset_ball()
- # Check for collisions with paddles
- if self.game_state['ball_position']['x'] <= 20 and \
- self.game_state['player1_position'] - 50 <= self.game_state['ball_position']['y'] <= self.game_state['player1_position'] + 50:
- self.game_state['ball_velocity']['x'] *= -1
- elif self.game_state['ball_position']['x'] >= 780 and \
- self.game_state['player2_position'] - 50 <= self.game_state['ball_position']['y'] <= self.game_state['player2_position'] + 50:
- self.game_state['ball_velocity']['x'] *= -1
-
def reset_ball(self):
- self.game_state['ball_position'] = {'x': 400, 'y': 300}
+ self.game_state['ball_position'] = {'x': 390, 'y': 190}
self.game_state['ball_velocity'] = {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}
+ self.speed = 1
+
+ def update_ball_velocity(self):
+ self.speed += 0.05
+ if self.speed > 2:
+ self.speed = 2
+ self.game_state['ball_velocity']['x'] *= self.speed
+ if self.game_state['ball_velocity']['x'] < -10:
+ self.game_state['ball_velocity']['x'] = -10
+ elif self.game_state['ball_velocity']['x'] > 10:
+ self.game_state['ball_velocity']['x'] = 10
+ self.game_state['ball_velocity']['y'] *= self.speed
+ if self.game_state['ball_velocity']['y'] < -10:
+ self.game_state['ball_velocity']['y'] = -10
+ elif self.game_state['ball_velocity']['y'] > 10:
+ self.game_state['ball_velocity']['y'] = 10
async def send_game_state(self):
+ if self.ended:
+ return
message = json.dumps({
'type': 'game_state_update',
'game_state': self.game_state
})
await self.player1.send(message)
- await self.player2.send(message)
+ if not self.botgame:
+ await self.player2.send(message)
async def handle_key_press(self, player, key):
+ if self.ended:
+ return
if player == self.player1:
- if key == 'arrowup' and self.game_state['player1_position'] > 0:
- self.game_state['player1_position'] -= 10
- elif key == 'arrowdown' and self.game_state['player1_position'] < 550:
- self.game_state['player1_position'] += 10
- elif player == self.player2:
- if key == 'arrowup' and self.game_state['player2_position'] > 0:
- self.game_state['player2_position'] -= 10
- elif key == 'arrowdown' and self.game_state['player2_position'] < 550:
- self.game_state['player2_position'] += 10
+ if key == 'arrowup':
+ self.game_state['player1_position'] = max(self.game_state['player1_position'] - 25, 0)
+ elif key == 'arrowdown':
+ self.game_state['player1_position'] = min(self.game_state['player1_position'] + 25, 300)
+ elif not self.botgame and player == self.player2:
+ if key == 'arrowup':
+ self.game_state['player2_position'] = max(self.game_state['player2_position'] - 25, 0)
+ elif key == 'arrowdown':
+ self.game_state['player2_position'] = min(self.game_state['player2_position'] + 25, 300)
- async def end_game(self):
- if self.game_loop_task:
- self.game_loop_task.cancel()
- # Add any cleanup code here
\ No newline at end of file
+ async def end_game(self, disconnected_player=None):
+ if not self.ended:
+ self.ended = True
+ if self.game_loop_task:
+ self.game_loop_task.cancel()
+ print(f"- Game #{self.game_id} ENDED")
+ # Notify that one player left the game
+ if disconnected_player:
+ remaining_player = self.player2 if disconnected_player == self.player1 else self.player1
+ disconnected_name = disconnected_player.user.username
+ message = json.dumps({
+ 'type': 'player_disconnected',
+ 'player': disconnected_name
+ })
+ if not self.botgame:
+ await remaining_player.send(message)
+ # Notify both players that the game has ended
+ end_message = json.dumps({
+ 'type': 'game_ended',
+ 'game_id': self.game_id
+ })
+ await self.player1.send(end_message)
+ if not self.botgame:
+ await self.player2.send(end_message)
+ #endfortheouche(p1, p2, s_p1, s_p2, winner, bt_p1, bt_p2, dur, is_tournoi, name_tournament)
diff --git a/pong/game/matchmaking.py b/pong/game/matchmaking.py
index bd96aa2..ab6b21e 100644
--- a/pong/game/matchmaking.py
+++ b/pong/game/matchmaking.py
@@ -9,6 +9,8 @@ class MatchMaker:
self.waiting_players = []
self.active_games = {}
self.matching_task = None
+ self.timer = 0
+ self.botgame = False
async def add_player(self, player):
if player not in self.waiting_players:
@@ -20,46 +22,79 @@ class MatchMaker:
async def remove_player(self, player):
if player in self.waiting_players:
self.waiting_players.remove(player)
+
+ 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]
+ break
async def match_loop(self):
while True:
if len(self.waiting_players) >= 2:
player1 = self.waiting_players.pop(0)
player2 = self.waiting_players.pop(0)
- print(f"MATCH FOUND: {player1.user.username} vs {player2.user.username}")
- game_id = await self.create_game(player1, player2)
+ print(f"*** MATCH FOUND: {player1.user.username} vs {player2.user.username}")
+ await self.create_game(player1, player2)
else:
- # No players to match, wait for a short time before checking again
await asyncio.sleep(1)
+ self.timer += 1
+ # Waiting for more than 30s -> BOT game
+ if self.timer >= 30 and self.waiting_players:
+ player1 = self.waiting_players.pop(0)
+ print(f"*** MATCH FOUND: {player1.user.username} vs BOT")
+ self.botgame = True
+ self.timer = 0
+ await self.create_bot_game(player1)
+ if not self.waiting_players:
+ break
async def create_game(self, player1, player2):
game_id = len(self.active_games) + 1
- print(f"- Creating game: {game_id}")
+ print(f"- Creating game: #{game_id}")
new_game = Game(game_id, player1, player2)
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)
asyncio.create_task(new_game.start_game())
- return game_id
+
+ 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)
+ self.active_games[game_id] = new_game
+ await player1.set_game(new_game)
+ await self.notify_players(player1, None, game_id)
+ asyncio.create_task(new_game.start_game())
async def notify_players(self, player1, player2, game_id):
- await player1.send(json.dumps({
- 'type': 'game_start',
- 'game_id': game_id,
- 'player1': player1.user.username,
- 'player2': player2.user.username
- }))
- await player2.send(json.dumps({
- 'type': 'game_start',
- 'game_id': game_id,
- 'player1': player1.user.username,
- 'player2': player2.user.username
- }))
+ if player2:
+ await player1.send(json.dumps({
+ 'type': 'game_start',
+ 'game_id': game_id,
+ 'player1': player1.user.username,
+ 'player2': player2.user.username
+ }))
+ await player2.send(json.dumps({
+ 'type': 'game_start',
+ 'game_id': game_id,
+ 'player1': player1.user.username,
+ '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
+ break
# Instance of the class
match_maker = MatchMaker()
diff --git a/pong/game/migrations/0002_player_best_score_player_m_duration_and_more.py b/pong/game/migrations/0002_player_best_score_player_m_duration_and_more.py
new file mode 100644
index 0000000..df55fa5
--- /dev/null
+++ b/pong/game/migrations/0002_player_best_score_player_m_duration_and_more.py
@@ -0,0 +1,68 @@
+# Generated by Django 5.0.7 on 2024-07-24 16:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('game', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='player',
+ name='best_score',
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='m_duration',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='m_nbr_ball_touch',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='m_score_adv_match',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='m_score_match',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='num_participated_tournaments',
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='num_won_tournaments',
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='p_win',
+ field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='total_duration',
+ field=models.DurationField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='total_match',
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='player',
+ name='total_win',
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ ]
diff --git a/pong/game/models.py b/pong/game/models.py
index 6068904..483e955 100644
--- a/pong/game/models.py
+++ b/pong/game/models.py
@@ -8,6 +8,17 @@ User.add_to_class('auth_token', models.CharField(max_length=100, null=True, blan
class Player(models.Model):
name = models.CharField(max_length=100)
+ total_match = models.PositiveSmallIntegerField(default=0)
+ total_win = models.PositiveSmallIntegerField(default=0)
+ p_win = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
+ m_score_match = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
+ m_score_adv_match = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
+ best_score = models.PositiveSmallIntegerField(default=0)
+ m_nbr_ball_touch = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
+ total_duration = models.DurationField(null=True, blank=True)
+ m_duration = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
+ num_participated_tournaments = models.PositiveSmallIntegerField(default=0)
+ num_won_tournaments = models.PositiveSmallIntegerField(default=0)
def __str__(self):
return self.name
diff --git a/pong/game/templates/pong/match_list.html b/pong/game/templates/pong/match_list.html
new file mode 100644
index 0000000..72c1315
--- /dev/null
+++ b/pong/game/templates/pong/match_list.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+ 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
new file mode 100644
index 0000000..b9fbb42
--- /dev/null
+++ b/pong/game/templates/pong/player_list.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ 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/tournoi_list.html b/pong/game/templates/pong/tournoi_list.html
new file mode 100644
index 0000000..e5718f5
--- /dev/null
+++ b/pong/game/templates/pong/tournoi_list.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ 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/urls.py b/pong/game/urls.py
index 96488b0..570f027 100644
--- a/pong/game/urls.py
+++ b/pong/game/urls.py
@@ -2,10 +2,14 @@
from django.urls import path
from . import views
+from .views import player_list, tournoi_list, match_list
urlpatterns = [
path('', views.index, name='index'),
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'),
]
diff --git a/pong/game/utils.py b/pong/game/utils.py
index 4985d59..c6655ee 100644
--- a/pong/game/utils.py
+++ b/pong/game/utils.py
@@ -1,33 +1,165 @@
-from myapp.models import Player, Tournoi, Match
+from .models import Player, Tournoi, Match
from django.core.exceptions import ValidationError
+from django.shortcuts import get_object_or_404
+from django.db.models import Max, Sum, F
+from datetime import timedelta
-def create_player(name):
- player = Player(name=name)
+def endfortheouche(p1, p2, s_p1, s_p2, winner, bt_p1, bt_p2, dur, is_tournoi, name_tournament) :
+ #If he doesn't exist, create player p1
+ if not Player.objects.filter(name=p1).exist():
+ create_player(p1)
+
+ #If he doesn't exist, create player p2
+ if not Player.objects.filter(name=p2).exist():
+ create_player(p2)
+
+ #create Match
+ create_match(p1, p2, s_p1, s_p2, bt_p1, bt_p2, dur, is_tournoi, name_tournamenttournoi)
+
+ #Update data p1 et p2
+ uptdate_player_statistics(p1)
+ uptdate_player_statistics(p2)
+
+
+def create_player(
+ name,
+ total_match=0,
+ total_win=0,
+ p_win= None,
+ m_score_match= None,
+ m_score_adv_match= None,
+ best_score=0,
+ m_nbr_ball_touch= None,
+ total_duration= None,
+ m_duration= None,
+ num_participated_tournaments=0,
+ num_won_tournaments=0
+):
+ 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
+ )
player.save()
return player
-def create_tournoi(name, nbr_player, date, winner=None):
+def create_tournoi(name, nbr_player, date, winner):
tournoi = Tournoi(name=name, nbr_player=nbr_player, date=date, winner=winner)
tournoi.save()
return tournoi
-def create_match(player1, player2, score_player1=0, score_player2=0, winner=None, nbr_ball_touch_p1=0, nbr_ball_touch_p2=0, duration=None, is_tournoi=False, tournoi=None):
+def create_match(player1, player2, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration, is_tournoi, tournoi):
match = Match(
player1=player1,
player2=player2,
score_player1=score_player1,
score_player2=score_player2,
- winner=winner,
nbr_ball_touch_p1=nbr_ball_touch_p1,
nbr_ball_touch_p2=nbr_ball_touch_p2,
duration=duration,
is_tournoi=is_tournoi,
tournoi=tournoi
)
+
+ if score_player1 > score_player2:
+ match.winner = match.player1
+ elif score_player2 > score_player1:
+ match.winner = match.player2
+ else:
+ match.winner = None
+
match.save()
return match
-def complete_match(match_id, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration):
+
+def uptdate_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
+ matches_as_player1 = Match.objects.filter(player1=player)
+ matches_as_player2 = Match.objects.filter(player2=player)
+
+ # Calculer les statistiques
+ total_match = matches_as_player1.count() + matches_as_player2.count()
+
+ if total_match == 0:
+ # Eviter la division par zéro
+ player.total_match = total_match
+ player.total_win = 0
+ player.p_win = 0
+ player.m_score_match = 0
+ player.m_score_adv_match = 0
+ player.best_score = 0
+ player.m_nbr_ball_touch = 0
+ player.total_duration = timedelta()
+ player.m_duration = timedelta()
+ player.num_participated_tournaments = 0
+ player.num_won_tournaments = 0
+ player.save()
+ return
+
+ won_matches = Match.objects.filter(winner=player)
+ part_tourn_as_p1 = Tournoi.objects.filter(matches__is_tournoi=True, matches__matches_as_player1=player)
+ part_tourn_as_p2 = Tournoi.objects.filter(matches__is_tournoi=True, matches__matches_as_player2=player)
+ won_tourn = Tournoi.objects.filter(winner=player)
+
+ total_score = matches_as_player1.aggregate(Sum('score_player1'))['score_player1__sum'] or 0
+ total_score += matches_as_player2.aggregate(Sum('score_player2'))['score_player2__sum'] or 0
+
+ total_score_adv = matches_as_player1.aggregate(Sum('score_player2'))['score_player2__sum'] or 0
+ total_score_adv += matches_as_player2.aggregate(Sum('score_player1'))['score_player1__sum'] or 0
+
+ total_win = won_matches.count()
+ p_win = (total_win / total_match) * 100
+
+ m_score_match = total_score / total_match
+ m_score_adv_match = total_score_adv / total_match
+
+ nbr_ball_touch = matches_as_player1.aggregate(Sum('nbr_ball_touch_p1'))['nbr_ball_touch_p1__sum'] or 0
+ nbr_ball_touch += matches_as_player2.aggregate(Sum('nbr_ball_touch_p2'))['nbr_ball_touch_p2__sum'] or 0
+ m_nbr_ball_touch = nbr_ball_touch / total_match
+
+ total_duration = matches_as_player1.aggregate(Sum('duration'))['duration__sum'] or timedelta()
+ total_duration += matches_as_player2.aggregate(Sum('duration'))['duration__sum'] or timedelta()
+ m_duration = total_duration / total_match
+
+ """ total_tourn_p = part_tourn_as_p1.count() + part_tourn_as_p2.count()
+ total_win_tourn = won_tourn.count()
+ p_win_tourn = (total_win_tourn / total_tourn_p) * 100 if total_tourn_p else 0
+ """
+ best_score_as_player1 = matches_as_player1.aggregate(Max('score_player1'))['score_player1__max'] or 0
+ best_score_as_player2 = matches_as_player2.aggregate(Max('score_player2'))['score_player2__max'] or 0
+ best_score = max(best_score_as_player1, best_score_as_player2)
+
+ # Mettre à jour les champs du joueur
+ player.total_match = total_match
+ player.total_win = total_win
+ player.p_win = p_win
+ player.m_score_match = m_score_match
+ player.m_score_adv_match = m_score_adv_match
+ player.best_score = best_score
+ player.m_nbr_ball_touch = m_nbr_ball_touch
+ player.total_duration = total_duration
+ player.m_duration = m_duration
+ """ player.num_participated_tournaments = total_tourn_p
+ player.num_won_tournaments = total_win_tourn """
+
+ player.save()
+
+
+""" 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:
@@ -47,9 +179,9 @@ def complete_match(match_id, score_player1, score_player2, nbr_ball_touch_p1, nb
match.winner = None
match.save()
- return match
+ return match """
-def complete_tournoi(tournoi_id, player):
+""" def complete_tournoi(tournoi_id, player):
try:
tournoi = Tournoi.objects.get(id = tournoi_id)
except Tournoi.DoesNotExist:
@@ -57,49 +189,7 @@ def complete_tournoi(tournoi_id, player):
tournoi.winner = player
tournoi.save()
- return tournoi
-
-def player_statistics(request, player_name):
- player = get_object_or_404(Player, nam = player_name)
-
- #filtre on tab
- matches_as_player1 = Match.objects.filter(player1=player)
- matches_as_player2 = Match.objects.filter(player2=player)
- won_matches = Match.objects.filter(winner=player)
- part_tourn_as_p1 = Tournoi.objects.filter(matches__is_tournoi=True, matches__player1=player)
- part_tourn_as_p2 = Tournoi.objects.filter(matches__is_tournoi=True, matches__player2=player)
- won_tourn = Tournoi.objects.filter(winner=player)
-
- # calulate stat
- total_match = match_as_player1.count() + match_as_player2.count()
- total_score = sum([match.score_player1 for match in matches_as_player1 ]) + sum([match.score_player2 for match in matches_as_player2])
- total_score_adv = sum([match.score_player2 for match in matches_as_player1 ]) + sum([match.score_player1 for match in matches_as_player2])
- total_win = won_matches.count()
- p_win = (total_win / total_match) * 100
- m_score_match = total_score / total_match
- m_score_adv_match = total_score_adv / total_match
- nbr_ball_touch = sum([match.nbr_ball_touch_p1 for match in matches_as_player1]) + sum([match.nbr_ball_touch_p2 for match in matches_as_player2])
- m_nbr_ball_touch = nbr_ball_touch / total_match
- total_duration = sum([match.duration for match in matches_as_player1]) + sum(match.duration for match in matches_as_player2)
- 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
-
- best_score_as_player1 = matches_as_player1.aggregate(Max('score_player1'))['score_player1__max']
- best_score_as_player2 = matches_as_player2.aggregate(Max('score_player2'))['score_player2__max']
- best_score = max(best_score_as_player1, best_score_as_player2)
-
- data = {
- 'player_name': player.name,
- 'number of match played' : total_match
- 'number of win (matches)' : total_win
- 'pourcentage of victory' : p_win
- 'num_participated_tournaments': num_participated_tournaments,
- 'num_won_tournaments': num_won_tournaments
- }
-
- return data
+ return tournoi """
diff --git a/pong/game/views.py b/pong/game/views.py
index 279a317..298fa5b 100644
--- a/pong/game/views.py
+++ b/pong/game/views.py
@@ -2,9 +2,9 @@
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
from django.http import JsonResponse
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
@@ -12,6 +12,11 @@ from django.views.decorators.csrf import csrf_exempt
import json
import uuid
+
+def index(request):
+ return render(request, 'index.html')
+
+
@csrf_exempt
def register_user(request):
if request.method == 'POST':
@@ -62,3 +67,138 @@ def get_or_create_token(user):
user.save()
break
return user.auth_token
+
+
+####################### THEOUCHE PART ############################
+
+
+@csrf_exempt
+def create_player_view(request):
+ if request.method == 'POST':
+ try:
+ data = json.loads(request.body)
+ name = data.get('name')
+
+ # Vérifier que le nom est présent
+ if not name:
+ return JsonResponse({'error': 'Name is required'}, status=400)
+
+ # Appeler la fonction create_player et traiter les exceptions
+ player = create_player(
+ name=name,
+ total_match=data.get('total_match', 0),
+ total_win=data.get('total_win', 0),
+ p_win=data.get('p_win'),
+ m_score_match=data.get('m_score_match'),
+ m_score_adv_match=data.get('m_score_adv_match'),
+ best_score=data.get('best_score', 0),
+ m_nbr_ball_touch=data.get('m_nbr_ball_touch'),
+ total_duration=data.get('total_duration'),
+ m_duration=data.get('m_duration'),
+ num_participated_tournaments=data.get('num_participated_tournaments', 0),
+ num_won_tournaments=data.get('num_won_tournaments', 0)
+ )
+ return JsonResponse({'id': player.id, 'name': player.name})
+
+ except ValueError as e:
+ # Erreur spécifique à la validation
+ return JsonResponse({'error': str(e)}, status=400)
+ except json.JSONDecodeError:
+ # Erreur de décodage JSON
+ return JsonResponse({'error': 'Invalid JSON format'}, status=400)
+ except Exception as e:
+ # Erreur générale
+ return JsonResponse({'error': 'An unexpected error occurred', 'details': str(e)}, status=500)
+
+ # Méthode HTTP non supportée
+ return JsonResponse({'error': 'Invalid HTTP method'}, status=405)
+
+
+
+@csrf_exempt
+def create_tournoi_view(request):
+ if request.method == 'POST':
+ data = json.loads(request.body)
+ name = data.get('name')
+ nbr_player = data.get('nbr_player')
+ date = data.get('date')
+ winner_id = data.get('winner_id')
+ if not (name and nbr_player and date):
+ return JsonResponse({'error': 'Name, number of players, and date are required'}, status=400)
+
+ winner = None
+ if winner_id:
+ try:
+ winner = Player.objects.get(id=winner_id)
+ except Player.DoesNotExist:
+ return JsonResponse({'error': 'Winner not found'}, status=404)
+
+ tournoi = create_tournoi(name, nbr_player, date, winner)
+ return JsonResponse({
+ 'id': tournoi.id,
+ 'name': tournoi.name,
+ 'nbr_player': tournoi.nbr_player,
+ 'date': tournoi.date.isoformat(),
+ 'winner': winner.id if winner else None
+ })
+ return JsonResponse({'error': 'Invalid HTTP method'}, status=405)
+
+@csrf_exempt
+def create_match_view(request):
+ if request.method == 'POST':
+ data = json.loads(request.body)
+ player1_id = data.get('player1_id')
+ player2_id = data.get('player2_id')
+ score_player1 = data.get('score_player1', 0)
+ score_player2 = data.get('score_player2', 0)
+ nbr_ball_touch_p1 = data.get('nbr_ball_touch_p1', 0)
+ nbr_ball_touch_p2 = data.get('nbr_ball_touch_p2', 0)
+ duration = data.get('duration')
+ is_tournoi = data.get('is_tournoi', False)
+ tournoi_id = data.get('tournoi_id')
+
+ if not (player1_id and player2_id and duration):
+ return JsonResponse({'error': 'Player IDs and duration are required'}, status=400)
+
+ try:
+ player1 = Player.objects.get(id=player1_id)
+ player2 = Player.objects.get(id=player2_id)
+ except Player.DoesNotExist:
+ return JsonResponse({'error': 'One or both players not found'}, status=404)
+
+ tournoi = None
+ if tournoi_id:
+ try:
+ tournoi = Tournoi.objects.get(id=tournoi_id)
+ except Tournoi.DoesNotExist:
+ return JsonResponse({'error': 'Tournoi not found'}, status=404)
+
+ match = create_match(player1, player2, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration, is_tournoi, tournoi)
+ return JsonResponse({
+ 'id': match.id,
+ 'player1': match.player1.id,
+ 'player2': match.player2.id,
+ 'score_player1': match.score_player1,
+ 'score_player2': match.score_player2,
+ 'nbr_ball_touch_p1': match.nbr_ball_touch_p1,
+ 'nbr_ball_touch_p2': match.nbr_ball_touch_p2,
+ 'duration': str(match.duration),
+ 'is_tournoi': match.is_tournoi,
+ 'tournoi': match.tournoi.id if match.tournoi else None
+ })
+ return JsonResponse({'error': 'Invalid HTTP method'}, status=405)
+
+
+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})
+
+####################### THEOUCHE PART ############################
diff --git a/pong/static/game.js b/pong/static/game.js
index 4c985aa..b7d38cb 100644
--- a/pong/static/game.js
+++ b/pong/static/game.js
@@ -10,15 +10,106 @@ document.addEventListener('DOMContentLoaded', () => {
const loginPasswordInput = document.getElementById('login-password');
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');
+ const formBlock = document.getElementById('block-form');
+
let socket;
let token;
let gameState;
+ // Auto-focus and key handling for AUTH-FORM
+ nicknameInput.focus();
+ nicknameInput.addEventListener('keypress', function (event) {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ checkNicknameButton.click();
+ }
+ });
+
checkNicknameButton.addEventListener('click', handleCheckNickname);
registerButton.addEventListener('click', handleRegister);
loginButton.addEventListener('click', handleLogin);
+
+ /// THEOUCHE NOT CERTAIN ///
+ async function createPlayer(name, totalMatch = 0, totalWin = 0, pWin = null, mScoreMatch = null, mScoreAdvMatch = null, bestScore = 0, mNbrBallTouch = null, totalDuration = null, mDuration = null, numParticipatedTournaments = 0, numWonTournaments = 0) {
+ try {
+ const response = await fetch('/api/create_player/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ name,
+ total_match: totalMatch,
+ total_win: totalWin,
+ p_win: pWin,
+ m_score_match: mScoreMatch,
+ m_score_adv_match: mScoreAdvMatch,
+ best_score: bestScore,
+ m_nbr_ball_touch: mNbrBallTouch,
+ total_duration: totalDuration,
+ m_duration: mDuration,
+ num_participated_tournaments: numParticipatedTournaments,
+ num_won_tournaments: numWonTournaments
+ })
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || 'Network response was not ok');
+ }
+
+ const data = await response.json();
+ return data;
+
+ } catch (error) {
+ // Afficher l'erreur avec un message plus spécifique
+ console.error('Error creating player:', error.message);
+ alert(`Failed to create player: ${error.message}`);
+ }
+ }
+
+ async function createTournoi(name, nbr_player, date, winner_id) {
+ try {
+ const response = await fetch('/api/create_tournoi/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ name, nbr_player, date, winner_id })
+ });
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error creating tournoi:', error);
+ }
+ }
+
+ async function createMatch(player1_id, player2_id, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration, is_tournoi, tournoi_id) {
+ try {
+ const response = await fetch('/api/create_match/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ player1_id, player2_id, score_player1, score_player2, nbr_ball_touch_p1, nbr_ball_touch_p2, duration, is_tournoi, tournoi_id })
+ });
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error creating match:', error);
+ }
+ }
+
+ /// THEOUCHE NOT CERTAIN ///
+
async function handleCheckNickname() {
const nickname = nicknameInput.value.trim();
if (nickname) {
@@ -27,9 +118,30 @@ document.addEventListener('DOMContentLoaded', () => {
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') {
+ event.preventDefault();
+ loginButton.click();
+ }
+ });
} 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') {
+ confirmPasswordInput.focus();
+ confirmPasswordInput.addEventListener('keypress', function (event) {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ registerButton.click();
+ }
+ });
+ }
+ });
}
} catch (error) {
console.error('Error checking user existence:', error);
@@ -60,8 +172,10 @@ document.addEventListener('DOMContentLoaded', () => {
try {
const result = await registerUser(nickname, password);
if (result) {
+ //await createPlayer(nickname);
registerForm.style.display = 'none';
gameContainer.style.display = 'flex';
+ formBlock.style.display = 'none';
startWebSocketConnection(token);
} else {
alert('Registration failed. Please try again.');
@@ -97,6 +211,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (result) {
loginForm.style.display = 'none';
gameContainer.style.display = 'flex';
+ formBlock.style.display = 'none';
startWebSocketConnection(token);
} else {
alert('Authentication failed. Please try again.');
@@ -134,12 +249,16 @@ document.addEventListener('DOMContentLoaded', () => {
if (data.type === 'authenticated') {
console.log('Authentication successful');
} else if (data.type === 'waiting_room') {
- console.log('Entered the waiting room');
+ 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);
} else if (data.type === 'game_state_update') {
updateGameState(data.game_state);
+ } else if (data.type === 'player_disconnected') {
+ console.log("Player disconnected:", data.player);
+ } else if (data.type === 'game_ended') {
+ console.log("Game ended:", data.game_id);
} else if (data.type === 'error') {
console.error(data.message);
} else {
@@ -161,6 +280,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('player1-name').textContent = `${player1_name}`;
document.getElementById('player2-name').textContent = `${player2_name}`;
document.addEventListener('keydown', handleKeyDown);
+
}
function handleKeyDown(event) {
diff --git a/pong/static/index.html b/pong/static/index.html
index 0ffc170..9bfdf51 100644
--- a/pong/static/index.html
+++ b/pong/static/index.html
@@ -1,32 +1,53 @@
{% load static %}
+
Pong Game
+
+

+
+
+
-