diff --git a/makefile b/makefile index e795e69..f1b37a8 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ CONTAINER=$(c) up: down $(COMPOSE) build - $(COMPOSE) up -d $(CONTAINER) || true + $(COMPOSE) up $(CONTAINER) build: $(COMPOSE) build $(CONTAINER) diff --git a/pong/game/consumers.py b/pong/game/consumers.py index a5e3853..a71eff0 100644 --- a/pong/game/consumers.py +++ b/pong/game/consumers.py @@ -26,6 +26,9 @@ class GameConsumer(AsyncWebsocketConsumer): 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("Start TOURNAMENT received..") + await tournament_match_maker.start_tournament() async def authenticate(self, token): user = await self.get_user_from_token(token) @@ -82,11 +85,11 @@ class GameConsumer(AsyncWebsocketConsumer): if user: self.user = user await self.send(text_data=json.dumps({'type': 'authenticated'})) - print(f"User {self.user} 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("Authentication failed") + print("Tournament authentication failed") async def join_tournament_waiting_room(self): await tournament_match_maker.add_player(self) diff --git a/pong/game/game.py b/pong/game/game.py index 515dccd..e619da5 100644 --- a/pong/game/game.py +++ b/pong/game/game.py @@ -7,7 +7,6 @@ from datetime import datetime from .utils import handle_game_data from asgiref.sync import sync_to_async - class Game: def __init__(self, game_id, player1, player2, localgame): self.game_id = game_id @@ -47,12 +46,11 @@ class Game: self.p2_mov = 0 self.bt1 = 0 self.bt2 = 0 - self.start_time = None + self.start_time = datetime.now() async def start_game(self): print(f"- Game #{self.game_id} STARTED") self.game_loop_task = asyncio.create_task(self.game_loop()) - self.start_time = datetime.now() print(f" Begin MATCH at: {self.start_time}") async def game_loop(self): @@ -213,7 +211,7 @@ class Game: print(f"- Game #{self.game_id} ENDED") end_time = datetime.now() - duration = (end_time - self.start_time).total_seconds() / 60 + duration = (end_time - self.start_time).total_seconds() / 60 # Notify that one player left the game if disconnected_player: diff --git a/pong/game/templates/pong/tournament_brackets.html b/pong/game/templates/pong/tournament_brackets.html new file mode 100644 index 0000000..85719a3 --- /dev/null +++ b/pong/game/templates/pong/tournament_brackets.html @@ -0,0 +1,85 @@ + + + + + + Tournament Brackets + + + +
+ {% for round in tournament_rounds %} +
+

Round {{ forloop.counter }}

+ {% for match in round %} +
+
+ {{ match.player1 }} + {% if match.score1 is not None %}{{ match.score1 }}{% endif %} +
+
+ {{ match.player2|default:"BYE" }} + {% if match.score2 is not None %}{{ match.score2 }}{% endif %} +
+
+ {% if not forloop.last %}
{% endif %} + {% 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 index dc09859..cb77338 100644 --- a/pong/game/templates/pong/tournament_waiting_room.html +++ b/pong/game/templates/pong/tournament_waiting_room.html @@ -1,10 +1,20 @@ - +

Tournament Waiting Room

-

Players currently waiting: {{ players|length }}

+ +

Players currently waiting: {{ players_count }}

+

Minimum players needed to start: {{ min_players_to_start }}

+ +

Players:

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

Waiting for more players to join...

+ {% endif %}
\ No newline at end of file diff --git a/pong/game/tournament.py b/pong/game/tournament.py index 60dd111..1f039fb 100644 --- a/pong/game/tournament.py +++ b/pong/game/tournament.py @@ -1,14 +1,42 @@ # /pong/game/tournament.py import json +import asyncio from django.template.loader import render_to_string +import random +from .matchmaking import match_maker +from .game import Game + +class TournamentMatch(Game): + def __init__(self, game_id, player1, player2, tournament): + # Initialize the parent Game class with the provided parameters + super().__init__(game_id, player1, player2, False) + # Store the current game instance in active games + match_maker.active_games[game_id] = self + # Set the game for the players + player1.set_game(self) + if player2: + player2.set_game(self) + # Store the tournament instance + self.tournament = tournament + + async def end_game(self, disconnected_player=None): + # Call the parent class's end_game method + await super().end_game(disconnected_player) + # Handle the end of the match in the tournament context + await self.tournament.handle_match_end(self) + class TournamentMatchMaker: def __init__(self): self.waiting_players = [] + self.matches = [] + self.rounds = [] + self.current_round = 0 + self.tournament_state = "waiting" # Can be "waiting", "in_progress", or "ended" async def add_player(self, player): - if player not in self.waiting_players: + if self.tournament_state == "waiting" and player not in self.waiting_players: self.waiting_players.append(player) print(f"User {player.user.username} joins the TOURNAMENT WAITING ROOM") await self.update_waiting_room() @@ -21,16 +49,111 @@ class TournamentMatchMaker: async def update_waiting_room(self): html = self.generate_waiting_room_html() for player in self.waiting_players: - await player.send(json.dumps({ - 'type': 'update_waiting_room', + await self.send_to_player(player, { + 'type': 'update_tournament_waiting_room', 'html': html - })) + }) def generate_waiting_room_html(self): context = { - 'players': [player.user.username for player in self.waiting_players] + 'players': [player.user.username for player in self.waiting_players], + 'tournament_state': self.tournament_state, + 'players_count': len(self.waiting_players), + 'min_players_to_start': 2 # You can adjust this number as needed } return render_to_string('pong/tournament_waiting_room.html', context) + async def start_tournament(self): + if len(self.waiting_players) < 2: + return False + + self.tournament_state = "in_progress" + random.shuffle(self.waiting_players) + self.current_round = 1 + self.create_matches(self.waiting_players) + await self.update_brackets() + await self.start_round_matches() + return True + + def create_matches(self, players): + matches = [] + for i in range(0, len(players), 2): + if i + 1 < len(players): + matches.append(TournamentMatch(len(self.matches) + 1, players[i], players[i + 1], self)) + else: + matches.append(TournamentMatch(len(self.matches) + 1, players[i], None, self)) # Bye + 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): + 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.game_state['player1_score'] = 3 + match.game_state['player2_score'] = 0 + await match.end_game() + + async def handle_match_end(self, match): + await self.update_brackets() + if all(m.ended for m in self.rounds[-1]): + await self.advance_tournament() + + async def advance_tournament(self): + winners = [match.player1 if match.game_state['player1_score'] > match.game_state['player2_score'] else match.player2 for match in self.rounds[-1] if match.player1 and match.player2] + if len(winners) > 1: + self.current_round += 1 + self.create_matches(winners) + await self.update_brackets() + await self.start_round_matches() + else: + await self.end_tournament(winners[0]) + + async def end_tournament(self, winner): + self.tournament_state = "ended" + for player in self.waiting_players: + await self.send_to_player(player, { + 'type': 'tournament_end', + 'winner': winner.user.username + }) + self.waiting_players = [] + self.matches = [] + self.rounds = [] + self.current_round = 0 + + async def send_to_player(self, player, data): + await player.send(json.dumps(data)) + # Instance of the class tournament_match_maker = TournamentMatchMaker() \ No newline at end of file diff --git a/pong/static/game.js b/pong/static/game.js index f4e7216..1d79dcf 100644 --- a/pong/static/game.js +++ b/pong/static/game.js @@ -383,6 +383,7 @@ 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, ')'); + gameContainer.style.display = 'flex'; document.addEventListener('keydown', handleKeyDown); } else if (data.type === 'game_state_update') { updateGameState(data.game_state); @@ -392,7 +393,26 @@ document.addEventListener('DOMContentLoaded', () => { console.log("Game ended:", data.game_id); } else if (data.type === 'error') { console.error(data.message); - } else if (data.type === 'update_waiting_room') { + // Assuming you're inside some WebSocket message handling function + } 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 { + console.error('Start button not found'); + } + } else if (data.type === 'update_brackets') { document.getElementById('tournament-bracket').innerHTML = data.html; } else { console.log('Message from server:', data.type, data.message); @@ -481,7 +501,6 @@ document.addEventListener('DOMContentLoaded', () => { startLocalGame2(); }); - function checkForWinner() { if (gameState.player1_score === 3 || gameState.player2_score === 3) { gameControls.style.display = 'flex';