From 18c6580ae3129df6ee00d70679cb3bfa2e314328 Mon Sep 17 00:00:00 2001 From: CHIBOUB Chakib Date: Sun, 28 Jul 2024 23:09:09 +0200 Subject: [PATCH] Server now can handle multiple games at once, and closes game routine when someone disconnects --- pong/game/consumers.py | 16 +++++++++--- pong/game/game.py | 53 ++++++++++++++++++++++++++-------------- pong/game/matchmaking.py | 28 ++++++++++++--------- pong/static/game.js | 4 +++ 4 files changed, 67 insertions(+), 34 deletions(-) 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 f6eef12..d7244f9 100644 --- a/pong/game/game.py +++ b/pong/game/game.py @@ -22,9 +22,10 @@ class Game: } 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): @@ -35,7 +36,6 @@ class Game: await self.send_game_state() await asyncio.sleep(1/60) # Around 60 FPS - # The amazing AI BOT player 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: @@ -69,7 +69,6 @@ class Game: self.reset_ball() elif self.game_state['ball_position']['x'] >= 790: self.game_state['player1_score'] += 1 - print(f"*** Ball in position ({self.game_state['ball_position']['x']},{self.game_state['ball_position']['y']}) with a speed ratio of {self.speed}") self.reset_ball() def reset_ball(self): @@ -93,6 +92,8 @@ class Game: 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 @@ -102,26 +103,40 @@ class Game: await self.player2.send(message) async def handle_key_press(self, player, key): + if self.ended: + return if player == self.player1: if key == 'arrowup': - self.game_state['player1_position'] -= 25 - if self.game_state['player1_position'] < 0: - self.game_state['player1_position'] = 0 + self.game_state['player1_position'] = max(self.game_state['player1_position'] - 25, 0) elif key == 'arrowdown': - self.game_state['player1_position'] += 25 - if self.game_state['player1_position'] > 300: - self.game_state['player1_position'] = 300 + 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'] -= 25 - if self.game_state['player2_position'] < 0: - self.game_state['player2_position'] = 0 + self.game_state['player2_position'] = max(self.game_state['player2_position'] - 25, 0) elif key == 'arrowdown': - self.game_state['player2_position'] += 25 - if self.game_state['player2_position'] > 300: - self.game_state['player2_position'] = 300 + 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) diff --git a/pong/game/matchmaking.py b/pong/game/matchmaking.py index 5c41298..ab6b21e 100644 --- a/pong/game/matchmaking.py +++ b/pong/game/matchmaking.py @@ -22,42 +22,52 @@ 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}") + print(f"*** MATCH FOUND: {player1.user.username} vs {player2.user.username}") await self.create_game(player1, player2) else: await asyncio.sleep(1) self.timer += 1 # Waiting for more than 30s -> BOT game - if self.timer >= 30: + if self.timer >= 30 and self.waiting_players: player1 = self.waiting_players.pop(0) - print(f"MATCH FOUND: {player1.user.username} vs BOT") + 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()) async def create_bot_game(self, player1): game_id = len(self.active_games) + 1 - print(f"- Creating BOT game: {game_id}") + 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): if player2: await player1.send(json.dumps({ @@ -82,11 +92,7 @@ class MatchMaker: async def handle_key_press(self, player, key): for game in self.active_games.values(): - if not self.botgame: - if player in [game.player1, game.player2]: - await game.handle_key_press(player, key) - break - else: + if player in [game.player1, game.player2]: await game.handle_key_press(player, key) break diff --git a/pong/static/game.js b/pong/static/game.js index 6f83af0..7338ecc 100644 --- a/pong/static/game.js +++ b/pong/static/game.js @@ -170,6 +170,10 @@ document.addEventListener('DOMContentLoaded', () => { 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 {