merge chakib

This commit is contained in:
Theouche 2024-07-29 18:30:40 +02:00
commit cb14984e03
9 changed files with 200 additions and 72 deletions

5
.gitignore vendored
View File

@ -1,8 +1,3 @@
.env
# virtualenv
venv/ venv/
__pycache__/ __pycache__/
data/ data/

View File

@ -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

View File

@ -2,12 +2,12 @@ COMPOSE_FILE=docker-compose.yaml
COMPOSE=docker compose -f $(COMPOSE_FILE) COMPOSE=docker compose -f $(COMPOSE_FILE)
CONTAINER=$(c) CONTAINER=$(c)
up: up: down
sudo mkdir -p $$PWD/data/db sudo mkdir -p data/db
$(COMPOSE) build $(COMPOSE) build
$(COMPOSE) up $(CONTAINER) $(COMPOSE) up $(CONTAINER)
build: build:
$(COMPOSE) build $(CONTAINER) $(COMPOSE) build $(CONTAINER)
start: start:
@ -21,7 +21,7 @@ down:
destroy: destroy:
$(COMPOSE) down -v --rmi all $(COMPOSE) down -v --rmi all
sudo rm -rf $$PWD/data/db sudo rm -rf data
#sudo lsof -i :5432 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true #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 lsof -i :80 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true
@ -33,6 +33,9 @@ ps:
re: destroy up re: destroy up
db-shell:
$(COMPOSE) exec db psql -U 42student players_db
help: help:
@echo "Usage:" @echo "Usage:"
@echo " make build [c=service] # Build images" @echo " make build [c=service] # Build images"

View File

@ -4,20 +4,23 @@ import json
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import User from django.contrib.auth.models import User
from channels.db import database_sync_to_async 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): class GameConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
await self.accept() await self.accept()
self.game = None
print("User connected") print("User connected")
async def receive(self, text_data): async def receive(self, text_data):
data = json.loads(text_data) data = json.loads(text_data)
#print(f"MESSAGE RECEIVED: {data['type']}")
if data['type'] == 'authenticate': if data['type'] == 'authenticate':
await self.authenticate(data['token']) await self.authenticate(data['token'])
elif data['type'] == 'key_press': 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): async def authenticate(self, token):
user = await self.get_user_from_token(token) user = await self.get_user_from_token(token)
@ -43,5 +46,10 @@ class GameConsumer(AsyncWebsocketConsumer):
await match_maker.add_player(self) await match_maker.add_player(self)
async def disconnect(self, close_code): async def disconnect(self, close_code):
if self.game:
await self.game.end_game(disconnected_player=self)
await match_maker.remove_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

View File

@ -9,78 +9,134 @@ class Game:
self.game_id = game_id self.game_id = game_id
self.player1 = player1 self.player1 = player1
self.player2 = player2 self.player2 = player2
self.botgame = player2 is None
self.game_state = { self.game_state = {
'player1_name': player1.user.username, 'player1_name': player1.user.username,
'player2_name': player2.user.username, 'player2_name': player2.user.username if player2 else 'BOT',
'player1_position': 200, # middle of the game field 'player1_position': 150,
'player2_position': 200, 'player2_position': 150,
'ball_position': {'x': 400, 'y': 300}, # middle of the game field 'ball_position': {'x': 390, 'y': 190},
'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}, 'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])},
'player1_score': 0, 'player1_score': 0,
'player2_score': 0 'player2_score': 0
} }
self.speed = 1
self.game_loop_task = None self.game_loop_task = None
self.ended = False
async def start_game(self): 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()) self.game_loop_task = asyncio.create_task(self.game_loop())
async def game_loop(self): async def game_loop(self):
while True: while True:
if self.botgame:
await self.update_bot_position()
self.update_game_state() self.update_game_state()
await self.send_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): def update_game_state(self):
# Update ball position # Update ball position
self.game_state['ball_position']['x'] += self.game_state['ball_velocity']['x'] self.game_state['ball_position']['x'] += self.game_state['ball_velocity']['x']
self.game_state['ball_position']['y'] += self.game_state['ball_velocity']['y'] self.game_state['ball_position']['y'] += self.game_state['ball_velocity']['y']
# Check for collisions with top and bottom walls # 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 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 # 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.game_state['player2_score'] += 1
self.reset_ball() 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.game_state['player1_score'] += 1
self.reset_ball() 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): 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.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): async def send_game_state(self):
if self.ended:
return
message = json.dumps({ message = json.dumps({
'type': 'game_state_update', 'type': 'game_state_update',
'game_state': self.game_state 'game_state': self.game_state
}) })
await self.player1.send(message) 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): async def handle_key_press(self, player, key):
if self.ended:
return
if player == self.player1: if player == self.player1:
if key == 'arrowup' and self.game_state['player1_position'] > 0: if key == 'arrowup':
self.game_state['player1_position'] -= 10 self.game_state['player1_position'] = max(self.game_state['player1_position'] - 25, 0)
elif key == 'arrowdown' and self.game_state['player1_position'] < 550: elif key == 'arrowdown':
self.game_state['player1_position'] += 10 self.game_state['player1_position'] = min(self.game_state['player1_position'] + 25, 300)
elif player == self.player2: elif not self.botgame and player == self.player2:
if key == 'arrowup' and self.game_state['player2_position'] > 0: if key == 'arrowup':
self.game_state['player2_position'] -= 10 self.game_state['player2_position'] = max(self.game_state['player2_position'] - 25, 0)
elif key == 'arrowdown' and self.game_state['player2_position'] < 550: elif key == 'arrowdown':
self.game_state['player2_position'] += 10 self.game_state['player2_position'] = min(self.game_state['player2_position'] + 25, 300)
async def end_game(self): async def end_game(self, disconnected_player=None):
if self.game_loop_task: if not self.ended:
self.game_loop_task.cancel() self.ended = True
# Add any cleanup code here 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)

View File

@ -9,6 +9,8 @@ class MatchMaker:
self.waiting_players = [] self.waiting_players = []
self.active_games = {} self.active_games = {}
self.matching_task = None self.matching_task = None
self.timer = 0
self.botgame = False
async def add_player(self, player): async def add_player(self, player):
if player not in self.waiting_players: if player not in self.waiting_players:
@ -20,46 +22,79 @@ class MatchMaker:
async def remove_player(self, player): async def remove_player(self, player):
if player in self.waiting_players: if player in self.waiting_players:
self.waiting_players.remove(player) 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): async def match_loop(self):
while True: while True:
if len(self.waiting_players) >= 2: if len(self.waiting_players) >= 2:
player1 = self.waiting_players.pop(0) player1 = self.waiting_players.pop(0)
player2 = 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}")
game_id = await self.create_game(player1, player2) await self.create_game(player1, player2)
else: else:
# No players to match, wait for a short time before checking again
await asyncio.sleep(1) 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): async def create_game(self, player1, player2):
game_id = len(self.active_games) + 1 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) new_game = Game(game_id, player1, player2)
self.active_games[game_id] = new_game 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) await self.notify_players(player1, player2, game_id)
asyncio.create_task(new_game.start_game()) 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): async def notify_players(self, player1, player2, game_id):
await player1.send(json.dumps({ if player2:
'type': 'game_start', await player1.send(json.dumps({
'game_id': game_id, 'type': 'game_start',
'player1': player1.user.username, 'game_id': game_id,
'player2': player2.user.username 'player1': player1.user.username,
})) 'player2': player2.user.username
await player2.send(json.dumps({ }))
'type': 'game_start', await player2.send(json.dumps({
'game_id': game_id, 'type': 'game_start',
'player1': player1.user.username, 'game_id': game_id,
'player2': player2.user.username '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): async def handle_key_press(self, player, key):
for game in self.active_games.values(): for game in self.active_games.values():
if player in [game.player1, game.player2]: if player in [game.player1, game.player2]:
await game.handle_key_press(player, key) await game.handle_key_press(player, key)
break break
# Instance of the class # Instance of the class
match_maker = MatchMaker() match_maker = MatchMaker()

View File

@ -15,6 +15,15 @@ document.addEventListener('DOMContentLoaded', () => {
let token; let token;
let gameState; 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); checkNicknameButton.addEventListener('click', handleCheckNickname);
registerButton.addEventListener('click', handleRegister); registerButton.addEventListener('click', handleRegister);
loginButton.addEventListener('click', handleLogin); loginButton.addEventListener('click', handleLogin);
@ -107,9 +116,30 @@ document.addEventListener('DOMContentLoaded', () => {
if (exists) { if (exists) {
authForm.style.display = 'none'; authForm.style.display = 'none';
loginForm.style.display = 'block'; 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 { } else {
authForm.style.display = 'none'; authForm.style.display = 'none';
registerForm.style.display = 'block'; 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) { } catch (error) {
console.error('Error checking user existence:', error); console.error('Error checking user existence:', error);
@ -215,12 +245,16 @@ document.addEventListener('DOMContentLoaded', () => {
if (data.type === 'authenticated') { if (data.type === 'authenticated') {
console.log('Authentication successful'); console.log('Authentication successful');
} else if (data.type === 'waiting_room') { } else if (data.type === 'waiting_room') {
console.log('Entered the waiting room'); console.log('Entered the WAITING ROOM');
} else if (data.type === 'game_start') { } else if (data.type === 'game_start') {
console.log('Game started:', data.game_id, '(', data.player1, 'vs', data.player2, ')'); console.log('Game started:', data.game_id, '(', data.player1, 'vs', data.player2, ')');
startGame(data.game_id, data.player1, data.player2); startGame(data.game_id, data.player1, data.player2);
} else if (data.type === 'game_state_update') { } else if (data.type === 'game_state_update') {
updateGameState(data.game_state); 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') { } else if (data.type === 'error') {
console.error(data.message); console.error(data.message);
} else { } else {

View File

@ -37,6 +37,6 @@
<div id="ball"></div> <div id="ball"></div>
</div> </div>
</div> </div>
<script src="{% static 'game.js' %}"></script> <script src="{% static 'game.js' %}"></script>
</body> </body>
</html> </html>

View File

@ -6,7 +6,8 @@ from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), # Disable the admin page
# path('admin/', admin.site.urls),
path('api/', include('pong.game.urls')), path('api/', include('pong.game.urls')),
path('', include('pong.game.urls')), path('', include('pong.game.urls')),
] ]