mirror of
https://github.com/AudebertAdrien/ft_transcendence.git
synced 2025-12-16 14:07:49 +01:00
fix error
This commit is contained in:
commit
a8ed6f438e
@ -1,8 +0,0 @@
|
|||||||
docker-compose.yaml
|
|
||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
.env
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
makefile
|
|
||||||
venv
|
|
||||||
2
.env
2
.env
@ -11,5 +11,5 @@ POSTGRES_PASSWORD=qwerty#42
|
|||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
PROJECT_PATH=${PWD}/helloworld
|
PROJECT_PATH=${PWD}/pong
|
||||||
POSTGRES_DATA_PATH=${PWD}/data/db
|
POSTGRES_DATA_PATH=${PWD}/data/db
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@ -1,6 +1,10 @@
|
|||||||
FROM python:3.12.4
|
FROM python:latest
|
||||||
|
|
||||||
WORKDIR /ft_transcendence
|
# Set environment variables
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
|
WORKDIR /transcendence
|
||||||
|
|
||||||
RUN apt update && apt upgrade -y
|
RUN apt update && apt upgrade -y
|
||||||
|
|
||||||
@ -11,4 +15,4 @@ RUN python3 -m venv venv
|
|||||||
RUN venv/bin/pip3 install --upgrade pip
|
RUN venv/bin/pip3 install --upgrade pip
|
||||||
RUN venv/bin/pip3 install --no-cache-dir -r requirements.txt
|
RUN venv/bin/pip3 install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 80
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:16.3
|
image: postgres:latest
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
@ -15,9 +15,9 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
environment:
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
@ -26,24 +26,27 @@ services:
|
|||||||
image: backend
|
image: backend
|
||||||
container_name: backend
|
container_name: backend
|
||||||
restart: always
|
restart: always
|
||||||
command: /bin/sh -c "sleep 5 && venv/bin/python3 manage.py migrate --noinput && venv/bin/python3 manage.py runserver 0.0.0.0:8000"
|
command: /bin/sh -c "sleep 5 &&
|
||||||
#&& venv/bin/python manage.py collectstatic --noinput
|
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:
|
volumes:
|
||||||
- helloword_project:/ft_transcendence/helloworld
|
- pong:/transcendence/pong
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "80:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
DB_HOST: db
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
DB_PORT: 5432
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
DB_NAME: ${POSTGRES_DB}
|
||||||
DB_HOST: ${DB_HOST}
|
DB_USER: ${POSTGRES_USER}
|
||||||
DB_PORT: ${DB_PORT}
|
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8000 || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@ -55,7 +58,7 @@ volumes:
|
|||||||
type: none
|
type: none
|
||||||
device: ${POSTGRES_DATA_PATH}
|
device: ${POSTGRES_DATA_PATH}
|
||||||
o: bind
|
o: bind
|
||||||
helloword_project:
|
pong:
|
||||||
driver: local
|
driver: local
|
||||||
driver_opts:
|
driver_opts:
|
||||||
type: none
|
type: none
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Django settings
|
# Django settings
|
||||||
SECRET_KEY=
|
SECRET_KEY=
|
||||||
DEBUG=True
|
DEBUG=True
|
||||||
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
|
DJANGO_ALLOWED_HOSTS=['*']
|
||||||
|
|
||||||
# PostgreSQL settings
|
# PostgreSQL settings
|
||||||
POSTGRES_DB=
|
POSTGRES_DB=
|
||||||
@ -11,5 +11,5 @@ POSTGRES_PASSWORD=
|
|||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
PROJECT_PATH=${PWD}/helloworld
|
PROJECT_PATH=${PWD}/pong
|
||||||
POSTGRES_DATA_PATH=${PWD}/data/db
|
POSTGRES_DATA_PATH=${PWD}/data/db
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
"""helloworld URL Configuration
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path
|
|
||||||
from helloworld import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
|
|
||||||
# Hello, world!
|
|
||||||
path('', views.index, name='index')
|
|
||||||
]
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("Hello, CHAKIB est une trompette ou pas!")
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for helloworld project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'helloworld.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
||||||
4
makefile
4
makefile
@ -23,9 +23,7 @@ destroy:
|
|||||||
$(COMPOSE) down -v --rmi all
|
$(COMPOSE) down -v --rmi all
|
||||||
sudo rm -rf $$PWD/data/db
|
sudo rm -rf $$PWD/data/db
|
||||||
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
|
||||||
kill-5432:
|
|
||||||
sudo lsof -i :5432 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true
|
|
||||||
|
|
||||||
logs:
|
logs:
|
||||||
$(COMPOSE) logs -f $(CONTAINER)
|
$(COMPOSE) logs -f $(CONTAINER)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'helloworld.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pong.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
|||||||
30
pong/asgi.py
Normal file
30
pong/asgi.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# /pong/asgi.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
ASGI config for pong project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import django
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pong.settings')
|
||||||
|
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
|
||||||
|
|
||||||
|
application = ProtocolTypeRouter({
|
||||||
|
"http": get_asgi_application(),
|
||||||
|
"websocket": AuthMiddlewareStack(
|
||||||
|
URLRouter(
|
||||||
|
pong.game.routing.websocket_urlpatterns
|
||||||
|
)
|
||||||
|
),
|
||||||
|
})
|
||||||
0
pong/game/__init__.py
Normal file
0
pong/game/__init__.py
Normal file
47
pong/game/consumers.py
Normal file
47
pong/game/consumers.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# /pong/game/consumers.py
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class GameConsumer(AsyncWebsocketConsumer):
|
||||||
|
async def connect(self):
|
||||||
|
await self.accept()
|
||||||
|
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'])
|
||||||
|
|
||||||
|
async def authenticate(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} authenticated")
|
||||||
|
await self.join_waiting_room()
|
||||||
|
else:
|
||||||
|
await self.send(text_data=json.dumps({'type': 'error', 'message': 'Authentication failed'}))
|
||||||
|
print("Authentication failed")
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def get_user_from_token(self, token):
|
||||||
|
try:
|
||||||
|
user = User.objects.filter(auth_token=token).first()
|
||||||
|
return user
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def join_waiting_room(self):
|
||||||
|
await self.send(text_data=json.dumps({'type': 'waiting_room'}))
|
||||||
|
await match_maker.add_player(self)
|
||||||
|
|
||||||
|
async def disconnect(self, close_code):
|
||||||
|
await match_maker.remove_player(self)
|
||||||
|
print(f"User {self.user} disconnected")
|
||||||
86
pong/game/game.py
Normal file
86
pong/game/game.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# /pong/game/game.py
|
||||||
|
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, game_id, player1, player2):
|
||||||
|
self.game_id = game_id
|
||||||
|
self.player1 = player1
|
||||||
|
self.player2 = player2
|
||||||
|
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
|
||||||
|
'ball_velocity': {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])},
|
||||||
|
'player1_score': 0,
|
||||||
|
'player2_score': 0
|
||||||
|
}
|
||||||
|
self.game_loop_task = None
|
||||||
|
|
||||||
|
async def start_game(self):
|
||||||
|
print(f"- Game {self.game_id} START")
|
||||||
|
self.game_loop_task = asyncio.create_task(self.game_loop())
|
||||||
|
|
||||||
|
async def game_loop(self):
|
||||||
|
while True:
|
||||||
|
self.update_game_state()
|
||||||
|
await self.send_game_state()
|
||||||
|
await asyncio.sleep(1/60) # 60 FPS
|
||||||
|
|
||||||
|
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:
|
||||||
|
self.game_state['ball_velocity']['y'] *= -1
|
||||||
|
|
||||||
|
# Check for scoring
|
||||||
|
if self.game_state['ball_position']['x'] <= 0:
|
||||||
|
self.game_state['player2_score'] += 1
|
||||||
|
self.reset_ball()
|
||||||
|
elif self.game_state['ball_position']['x'] >= 800:
|
||||||
|
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_velocity'] = {'x': random.choice([-5, 5]), 'y': random.choice([-5, 5])}
|
||||||
|
|
||||||
|
async def send_game_state(self):
|
||||||
|
message = json.dumps({
|
||||||
|
'type': 'game_state_update',
|
||||||
|
'game_state': self.game_state
|
||||||
|
})
|
||||||
|
await self.player1.send(message)
|
||||||
|
await self.player2.send(message)
|
||||||
|
|
||||||
|
async def handle_key_press(self, player, key):
|
||||||
|
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
|
||||||
|
|
||||||
|
async def end_game(self):
|
||||||
|
if self.game_loop_task:
|
||||||
|
self.game_loop_task.cancel()
|
||||||
|
# Add any cleanup code here
|
||||||
65
pong/game/matchmaking.py
Normal file
65
pong/game/matchmaking.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# /pong/game/matchmaking.py
|
||||||
|
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
from .game import Game
|
||||||
|
|
||||||
|
class MatchMaker:
|
||||||
|
def __init__(self):
|
||||||
|
self.waiting_players = []
|
||||||
|
self.active_games = {}
|
||||||
|
self.matching_task = None
|
||||||
|
|
||||||
|
async def add_player(self, player):
|
||||||
|
if player not in self.waiting_players:
|
||||||
|
self.waiting_players.append(player)
|
||||||
|
print(f"User {player.user.username} joins the WAITING ROOM")
|
||||||
|
if not self.matching_task or self.matching_task.done():
|
||||||
|
self.matching_task = asyncio.create_task(self.match_loop())
|
||||||
|
|
||||||
|
async def remove_player(self, player):
|
||||||
|
if player in self.waiting_players:
|
||||||
|
self.waiting_players.remove(player)
|
||||||
|
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
# No players to match, wait for a short time before checking again
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
async def create_game(self, player1, player2):
|
||||||
|
game_id = len(self.active_games) + 1
|
||||||
|
print(f"- Creating game: {game_id}")
|
||||||
|
new_game = Game(game_id, player1, player2)
|
||||||
|
self.active_games[game_id] = new_game
|
||||||
|
await self.notify_players(player1, player2, game_id)
|
||||||
|
asyncio.create_task(new_game.start_game())
|
||||||
|
return game_id
|
||||||
|
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Instance of the class
|
||||||
|
match_maker = MatchMaker()
|
||||||
6
pong/game/models.py
Normal file
6
pong/game/models.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# /pong/game/models.py
|
||||||
|
|
||||||
|
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))
|
||||||
8
pong/game/routing.py
Normal file
8
pong/game/routing.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# /pong/game/routing.py
|
||||||
|
|
||||||
|
from django.urls import re_path
|
||||||
|
from . import consumers
|
||||||
|
|
||||||
|
websocket_urlpatterns = [
|
||||||
|
re_path(r'ws/game/$', consumers.GameConsumer.as_asgi()),
|
||||||
|
]
|
||||||
11
pong/game/urls.py
Normal file
11
pong/game/urls.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# /pong/game/urls.py
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
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'),
|
||||||
|
]
|
||||||
64
pong/game/views.py
Normal file
64
pong/game/views.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# /pong/game/views.py
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
return render(request, 'index.html')
|
||||||
|
|
||||||
|
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
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def register_user(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
data = json.loads(request.body)
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
if not User.objects.filter(username=username).exists():
|
||||||
|
user = User.objects.create_user(username=username, password=password)
|
||||||
|
token = get_or_create_token(user)
|
||||||
|
return JsonResponse({'registered': True, 'token': token})
|
||||||
|
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':
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
username = data.get('username', '')
|
||||||
|
password = data.get('password', '')
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
if user is not None:
|
||||||
|
token = get_or_create_token(user)
|
||||||
|
return JsonResponse({'authenticated': True, 'token': token, 'user_id': user.id})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'authenticated': False}, status=401)
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
else:
|
||||||
|
return JsonResponse({'error': 'Method not allowed'}, status=405)
|
||||||
|
|
||||||
|
def get_or_create_token(user):
|
||||||
|
if not user.auth_token:
|
||||||
|
while True:
|
||||||
|
token = str(uuid.uuid4())
|
||||||
|
if not User.objects.filter(auth_token=token).exists():
|
||||||
|
user.auth_token = token
|
||||||
|
user.save()
|
||||||
|
break
|
||||||
|
return user.auth_token
|
||||||
@ -1,34 +1,28 @@
|
|||||||
|
# /pong/settings.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Django settings for helloworld project.
|
Django settings for pong project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 2.2.3.
|
Generated by 'django-admin startproject' using Django 3.2.
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/2.2/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/2.2/ref/settings/
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'matxp6k!wbkmdlk)97)ew2qr%&9nr=n#v_-+v#yel4^r&czf7q'
|
SECRET_KEY = '12345678'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# A list of strings representing the host/domain names that this Django site can serve.
|
ALLOWED_HOSTS = ['*']
|
||||||
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@ -40,6 +34,8 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'channels', # Add Django Channels
|
||||||
|
'pong.game', # Your game app
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@ -52,12 +48,12 @@ MIDDLEWARE = [
|
|||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'helloworld.urls'
|
ROOT_URLCONF = 'pong.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [os.path.join(BASE_DIR, 'pong', 'static')], # Ensure templates are found
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@ -70,25 +66,24 @@ TEMPLATES = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'helloworld.wsgi.application'
|
ASGI_APPLICATION = 'pong.asgi.application' # Add ASGI application
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': os.getenv('POSTGRES_DB', 'default_db_name'),
|
'NAME': os.getenv('DB_NAME'),
|
||||||
'USER': os.getenv('POSTGRES_USER', 'default_user'),
|
'USER': os.getenv('DB_USER'),
|
||||||
'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'default_password'),
|
'PASSWORD': os.getenv('DB_PASSWORD'),
|
||||||
'HOST': os.getenv('DB_HOST', 'db'),
|
'HOST': os.getenv('DB_HOST'),
|
||||||
'PORT': os.getenv('DB_PORT', '5432'),
|
'PORT': '5432',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
@ -105,26 +100,36 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
# If you set this to False, Django will make some optimizations so as not
|
|
||||||
# to load the internationalization machinery.
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
# If you set this to False, Django will not format dates, numbers and
|
|
||||||
# calendars according to the current locale
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'pong/static')]
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# Channels
|
||||||
|
# Define the channel layers for WebSockets
|
||||||
|
CHANNEL_LAYERS = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'channels.layers.InMemoryChannelLayer',
|
||||||
|
},
|
||||||
|
}
|
||||||
201
pong/static/game.js
Normal file
201
pong/static/game.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const checkNicknameButton = document.getElementById('check-nickname');
|
||||||
|
const registerButton = document.getElementById('register');
|
||||||
|
const loginButton = document.getElementById('login');
|
||||||
|
const authForm = document.getElementById('auth-form');
|
||||||
|
const gameContainer = document.getElementById('game1');
|
||||||
|
const nicknameInput = document.getElementById('nickname');
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
const confirmPasswordInput = document.getElementById('confirm-password');
|
||||||
|
const loginPasswordInput = document.getElementById('login-password');
|
||||||
|
const loginForm = document.getElementById('login-form');
|
||||||
|
const registerForm = document.getElementById('register-form');
|
||||||
|
|
||||||
|
let socket;
|
||||||
|
let token;
|
||||||
|
let gameState;
|
||||||
|
|
||||||
|
checkNicknameButton.addEventListener('click', handleCheckNickname);
|
||||||
|
registerButton.addEventListener('click', handleRegister);
|
||||||
|
loginButton.addEventListener('click', handleLogin);
|
||||||
|
|
||||||
|
async function handleCheckNickname() {
|
||||||
|
const nickname = nicknameInput.value.trim();
|
||||||
|
if (nickname) {
|
||||||
|
try {
|
||||||
|
const exists = await checkUserExists(nickname);
|
||||||
|
if (exists) {
|
||||||
|
authForm.style.display = 'none';
|
||||||
|
loginForm.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
authForm.style.display = 'none';
|
||||||
|
registerForm.style.display = 'block';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking user existence:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Please enter a nickname.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkUserExists(username) {
|
||||||
|
const response = await fetch('/api/check_user_exists/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
return data.exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
const nickname = nicknameInput.value.trim();
|
||||||
|
const password = passwordInput.value.trim();
|
||||||
|
const confirmPassword = confirmPasswordInput.value.trim();
|
||||||
|
|
||||||
|
if (password === confirmPassword) {
|
||||||
|
try {
|
||||||
|
const result = await registerUser(nickname, password);
|
||||||
|
if (result) {
|
||||||
|
registerForm.style.display = 'none';
|
||||||
|
gameContainer.style.display = 'flex';
|
||||||
|
startGame();
|
||||||
|
} else {
|
||||||
|
alert('Registration failed. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error registering user:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Passwords do not match.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerUser(username, password) {
|
||||||
|
const response = await fetch('/api/register_user/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.registered) {
|
||||||
|
token = data.token;
|
||||||
|
}
|
||||||
|
return data.registered;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
const nickname = nicknameInput.value.trim();
|
||||||
|
const password = loginPasswordInput.value.trim();
|
||||||
|
try {
|
||||||
|
const result = await authenticateUser(nickname, password);
|
||||||
|
if (result) {
|
||||||
|
loginForm.style.display = 'none';
|
||||||
|
gameContainer.style.display = 'flex';
|
||||||
|
startWebSocketConnection(token);
|
||||||
|
} else {
|
||||||
|
alert('Authentication failed. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error authenticating user:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function authenticateUser(username, password) {
|
||||||
|
const response = await fetch('/api/authenticate_user/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.authenticated) {
|
||||||
|
token = data.token;
|
||||||
|
}
|
||||||
|
return data.authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startWebSocketConnection(token) {
|
||||||
|
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 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = function (event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.type === 'authenticated') {
|
||||||
|
console.log('Authentication successful');
|
||||||
|
} else if (data.type === '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 === 'error') {
|
||||||
|
console.error(data.message);
|
||||||
|
} else {
|
||||||
|
console.log('Message from server:', data.type, data.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = function (event) {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function (error) {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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') {
|
||||||
|
sendKeyPress(event.key.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendKeyPress(key) {
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(JSON.stringify({ type: 'key_press', key }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGameState(newState) {
|
||||||
|
gameState = newState;
|
||||||
|
renderGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGame() {
|
||||||
|
const player1Pad = document.getElementById('player1-pad');
|
||||||
|
player1Pad.style.top = `${gameState.player1_position}px`;
|
||||||
|
|
||||||
|
const player2Pad = document.getElementById('player2-pad');
|
||||||
|
player2Pad.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`;
|
||||||
|
|
||||||
|
const player1Score = document.getElementById('player1-score');
|
||||||
|
player1Score.textContent = gameState.player1_score;
|
||||||
|
|
||||||
|
const player2Score = document.getElementById('player2-score');
|
||||||
|
player2Score.textContent = gameState.player2_score;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
42
pong/static/index.html
Normal file
42
pong/static/index.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pong Game</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'styles.css' %}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="auth-form">
|
||||||
|
<label for="nickname">Enter your nickname:</label>
|
||||||
|
<input type="text" id="nickname" name="nickname">
|
||||||
|
<button id="check-nickname">Check Nickname</button>
|
||||||
|
</div>
|
||||||
|
<div id="register-form" style="display: none;">
|
||||||
|
<label for="password">Enter your password:</label>
|
||||||
|
<input type="password" id="password" name="password">
|
||||||
|
<label for="confirm-password">Confirm your password:</label>
|
||||||
|
<input type="password" id="confirm-password" name="confirm-password">
|
||||||
|
<button id="register">Register</button>
|
||||||
|
</div>
|
||||||
|
<div id="login-form" style="display: none;">
|
||||||
|
<label for="login-password">Enter your password:</label>
|
||||||
|
<input type="password" id="login-password" name="login-password">
|
||||||
|
<button id="login">Login</button>
|
||||||
|
</div>
|
||||||
|
<div id="game1" style="display: none;">
|
||||||
|
<div id="gameCode" class="game-code">Game Code: </div>
|
||||||
|
<div id="player1-name" class="name">Player 1</div>
|
||||||
|
<div id="player2-name" class="name">Player 2</div>
|
||||||
|
<div id="game2">
|
||||||
|
<div id="player1-score" class="score">0</div>
|
||||||
|
<div id="player2-score" class="score">0</div>
|
||||||
|
<div id="player1-pad" class="pad"></div>
|
||||||
|
<div id="player2-pad" class="pad"></div>
|
||||||
|
<div id="ball"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{% static 'game.js' %}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
113
pong/static/styles.css
Normal file
113
pong/static/styles.css
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/* General styles */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #000000;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 10px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px 0 20px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game1 {
|
||||||
|
width: 810px;
|
||||||
|
height: 500px;
|
||||||
|
position: relative;
|
||||||
|
background-color: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-code {
|
||||||
|
font-size: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameCode {
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player1-name {
|
||||||
|
left: 10px; /* Adjust player score position */
|
||||||
|
}
|
||||||
|
|
||||||
|
#player2-name {
|
||||||
|
right: 10px; /* Adjust bot score position */
|
||||||
|
}
|
||||||
|
|
||||||
|
#game2 {
|
||||||
|
top: 60px;
|
||||||
|
width: 800px;
|
||||||
|
height: 400px;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid red; /* Add red border */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
font-size: 24px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player1-score {
|
||||||
|
left: 50px; /* Adjust player score position */
|
||||||
|
}
|
||||||
|
|
||||||
|
#player2-score {
|
||||||
|
right: 50px; /* Adjust bot score position */
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad {
|
||||||
|
width: 10px;
|
||||||
|
height: 100px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player1-pad {
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player2-pad {
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ball {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #ff0000;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
15
pong/urls.py
Normal file
15
pong/urls.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# /pong/urls.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('api/', include('pong.game.urls')),
|
||||||
|
path('', include('pong.game.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
@ -1,3 +1,5 @@
|
|||||||
Django
|
Django
|
||||||
psycopg2
|
psycopg2
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
channels
|
||||||
|
daphne
|
||||||
Loading…
x
Reference in New Issue
Block a user