Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

54 implement tournament #258

Merged
merged 65 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
4816bac
replace User by get_user_model call in Pong model
Neffi42 Oct 1, 2024
aa7d0f1
remove join tournament button in pong.html
Neffi42 Oct 1, 2024
3507789
create Tournament page
Neffi42 Oct 1, 2024
80f00d6
create PongTournament model
Neffi42 Oct 1, 2024
f816f61
Merge branch '54-implement-tournament' into 155-create-tournament-model
Neffi42 Oct 2, 2024
8bb6c01
create menu.js
Neffi42 Oct 2, 2024
91d0211
add required on join form & change redirection to tournament page
Neffi42 Oct 2, 2024
783e0ba
create tournament selection page
Neffi42 Oct 2, 2024
055a852
Merge pull request #187 from TheSecretOrganization/155-create-tournam…
Neffi42 Oct 2, 2024
fc5f1b7
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 3, 2024
3f8dfbf
Merge branch '54-implement-tournament' of github.com:TheSecretOrganiz…
Neffi42 Oct 7, 2024
86acc11
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 9, 2024
ad7665d
Merge branch 'main' into 200-create-tournament-page
Neffi42 Oct 9, 2024
0234e49
delete menus in tournament page
Neffi42 Oct 9, 2024
c2ee7c1
create pong_tournament page
Neffi42 Oct 9, 2024
c01a76f
rename Pong consumer to PongGame
Neffi42 Oct 9, 2024
a7da603
create join message in tournament
Neffi42 Oct 10, 2024
8bbf6af
add lock system to tournament
Neffi42 Oct 10, 2024
43db95d
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 10, 2024
ba3e57c
Merge branch '54-implement-tournament' into 200-create-tournament-page
Neffi42 Oct 10, 2024
67225ad
rename variable
Neffi42 Oct 10, 2024
2a178ed
Merge pull request #214 from TheSecretOrganization/200-create-tournam…
Neffi42 Oct 10, 2024
dad5bde
rename Consumer to Game
Neffi42 Oct 9, 2024
bfaaa5a
Merge pull request #218 from TheSecretOrganization/200-create-tournam…
Neffi42 Oct 14, 2024
41fe753
Merge branch '54-implement-tournament' into 156-create-matcmaking-system
Neffi42 Oct 14, 2024
0c1dbf8
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
54850f3
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
c427a16
create onPageChange event in pong_tournament.html
Neffi42 Oct 14, 2024
f344ceb
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
2271a5c
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
7e4d85f
add check for creator comming back
Neffi42 Oct 14, 2024
85c83a4
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
ca93d61
add toast when locking tournament alone
Neffi42 Oct 14, 2024
367a184
update tournament page
Neffi42 Oct 14, 2024
9fe34bf
wip
Neffi42 Oct 14, 2024
110eaa8
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 14, 2024
767853e
create redirection between game and tournament
Neffi42 Oct 15, 2024
bcaf842
add conditionnals on wsConnect callbacks
Neffi42 Oct 15, 2024
4a4a141
watcher prototype
Neffi42 Oct 15, 2024
4ea461f
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 15, 2024
e230aeb
create tournament.py and rename Game to Pong
Neffi42 Oct 15, 2024
d3135fd
refactor tournament.py
Neffi42 Oct 15, 2024
ff9dd85
rename PongGame to Pong
Neffi42 Oct 15, 2024
c34d4fc
fix import
Neffi42 Oct 15, 2024
ec523a8
fix tournaments route
Neffi42 Oct 15, 2024
20d39d4
wip
Neffi42 Oct 15, 2024
e3b7783
fix missing tournament_name
Neffi42 Oct 16, 2024
96cabdb
create ready btn
Neffi42 Oct 16, 2024
f70f6b5
update Pong model to add uuid field
Neffi42 Oct 16, 2024
1929f57
create handleHistory
Neffi42 Oct 16, 2024
b20571d
fix ready
Neffi42 Oct 16, 2024
14abd25
update migrations
Neffi42 Oct 16, 2024
06a05e0
update migrations
Neffi42 Oct 16, 2024
06e3b27
tournament done
Neffi42 Oct 16, 2024
bab5ee2
admin page done
Neffi42 Oct 16, 2024
bb171ae
protect disconection
Neffi42 Oct 16, 2024
dda30bf
create models serialization
Neffi42 Oct 16, 2024
ecafd17
create tournament_json view
Neffi42 Oct 16, 2024
c2e6928
add results_only context
Neffi42 Oct 16, 2024
99bf21e
create result page
Neffi42 Oct 16, 2024
91bfe82
tournament done
Neffi42 Oct 16, 2024
5f485fa
Merge branch 'main' into 54-implement-tournament
Neffi42 Oct 16, 2024
598c5d8
sonarcloud
Neffi42 Oct 16, 2024
2e74d34
corrected test
Neffi42 Oct 16, 2024
8391db1
sonarcloud
Neffi42 Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions django/src/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
path('friends/', include('friends.urls')),
path('auth/', include('ft_auth.urls')),
path('pages/', include('pages.urls')),
path('games/', include('games.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
18 changes: 13 additions & 5 deletions django/src/games/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from django.contrib import admin
from .models import Pong
from .models import Pong, PongTournament


@admin.register(Pong)
class PongAdmin(admin.ModelAdmin):
list_display = ('user1', 'user2', 'score1', 'score2', 'created_at')
search_fields = ('user1__username', 'user2__username')
list_filter = ('created_at',)
class PongGameAdmin(admin.ModelAdmin):
list_display = ("user1", "user2", "score1", "score2", "created_at", "uuid")
search_fields = ("user1__username", "user2__username")
list_filter = ("created_at",)


@admin.register(PongTournament)
class PongTournamentAdmin(admin.ModelAdmin):
list_display = ("name", "winner", "created_at", "updated_at")
search_fields = ("name", "winner__username")
list_filter = ("created_at", "updated_at", "winner")
16 changes: 15 additions & 1 deletion django/src/games/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.1 on 2024-09-23 14:42
# Generated by Django 5.1 on 2024-10-16 15:39

import django.db.models.deletion
from django.conf import settings
Expand All @@ -20,9 +20,23 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('score1', models.PositiveSmallIntegerField()),
('score2', models.PositiveSmallIntegerField()),
('uuid', models.CharField(max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user1', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user1', to=settings.AUTH_USER_MODEL)),
('user2', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user2', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='PongTournament',
Neffi42 marked this conversation as resolved.
Show resolved Hide resolved
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('games', models.ManyToManyField(related_name='tournaments', to='games.pong')),
('participants', models.ManyToManyField(related_name='tournaments', to=settings.AUTH_USER_MODEL)),
('winner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='winner', to=settings.AUTH_USER_MODEL)),
],
),
]
68 changes: 65 additions & 3 deletions django/src/games/models.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,79 @@
from django.db import models
from ft_auth.models import User
from django.contrib.auth import get_user_model


class Pong(models.Model):
user1 = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name="user1"
get_user_model(),
on_delete=models.SET_NULL,
null=True,
related_name="user1",
)
user2 = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name="user2"
get_user_model(),
on_delete=models.SET_NULL,
null=True,
related_name="user2",
)
score1 = models.PositiveSmallIntegerField()
score2 = models.PositiveSmallIntegerField()
uuid = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.user1.username} vs {self.user2.username} - {self.score1}:{self.score2}"

def serialize(self):
return {
"user1": (
{"id": self.user1.id, "username": self.user1.username}
if self.user1
else None
),
"user2": (
{"id": self.user2.id, "username": self.user2.username}
if self.user2
else None
),
"score1": self.score1,
"score2": self.score2,
"uuid": self.uuid,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
}

class PongTournament(models.Model):
name = models.CharField(max_length=255, unique=True)
winner = models.ForeignKey(
get_user_model(),
on_delete=models.SET_NULL,
null=True,
related_name="winner",
)
participants = models.ManyToManyField(
get_user_model(), related_name="tournaments"
)
games = models.ManyToManyField(Pong, related_name="tournaments")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"Tournament: {self.name} - Winner: {self.winner}"

def serialize(self):
return {
"name": self.name,
"winner": (
{"id": self.winner.id, "username": self.winner.username}
if self.winner
else None
),
"participants": [
{"id": participant.id, "username": participant.username}
for participant in self.participants.all()
],
"games": [game.serialize() for game in self.games.all()],
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
}
131 changes: 94 additions & 37 deletions django/src/games/pong.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

logger = logging.getLogger(__name__)

class Consumer(AsyncWebsocketConsumer):

class Pong(AsyncWebsocketConsumer):
win_goal = 5

async def connect(self):
Expand All @@ -32,23 +33,35 @@ async def connect(self):
await self.close()
return

self.valid = True
await self.accept()
await self.channel_layer.group_add(self.room_id, self.channel_name)
logger.info(f"User {self.user.id} successfully connected to room {self.room_id}.")
self.valid = True
logger.info(
f"User {self.user.id} successfully connected to room {self.room_id}."
)
await self.send_message("group", "game_join", {"user": self.user.id})
await self.send_message("client", "game_pad", {"game_pad": self.pad_n})
if self.tournament_name != "0":
await self.send_message(
"client",
"tournament_name",
{"tournament_name": self.tournament_name},
)

async def initialize_game(self):
query_params = parse_qs(self.scope["query_string"].decode())
self.room_id = self.check_missing_param(query_params, "room_id")
self.mode = self.check_missing_param(query_params, "mode")
player_needed = self.check_missing_param(query_params, "player_needed")
self.room_id = self.check_missing_param(query_params, "room_id")
self.host = self.check_missing_param(query_params, "host") == "True"
self.tournament_name = self.check_missing_param(
query_params, "tournament_name"
)
self.host = await self.redis.get(self.room_id) == None
self.pad_n = "pad_1" if self.host else "pad_2"
players = await self.redis.lrange(f'pong_{self.room_id}_id', 0, -1)
players = await self.redis.lrange(f"pong_{self.room_id}_id", 0, -1)

if self.host:
await self.redis.set(self.room_id, 1)
self.info = self.Info(
creator=self.user.id,
room_id=self.room_id,
Expand All @@ -57,13 +70,14 @@ async def initialize_game(self):
self.ball = self.Ball()
self.pad_1 = self.Pad(True)
self.pad_2 = self.Pad(False)
logger.info(f"User {self.user.id} is the host for room {self.room_id}.")
await self.redis.set(self.room_id, 1)
logger.info(
f"User {self.user.id} is the host for room {self.room_id}."
)
else:
if not await self.redis.get(self.room_id):
raise ConnectionRefusedError("Invalid room")
if str(self.user.id).encode("utf-8") in players:
raise ConnectionRefusedError("User already connected to the room")
raise ConnectionRefusedError(
"User already connected to the room"
)
if len(players) == 2:
raise ConnectionRefusedError("Room is full")

Expand All @@ -72,21 +86,33 @@ async def initialize_game(self):
async def disconnect(self, close_code):
self.connected = False

if self.host:
players = await self.redis.lrange(f'pong_{self.room_id}_id', 0, -1)
if hasattr(self, "host") and self.host:
await self.channel_layer.group_send(
f"{self.room_id}_watcher",
{"type": "watcher_stop", "id": self.room_id},
)
players = await self.redis.lrange(f"pong_{self.room_id}_id", 0, -1)

if hasattr(self, "game_task"):
self.game_task.cancel()

await self.redis.delete(self.room_id)
await self.redis.delete(f'pong_{self.room_id}_id')
await self.redis.delete(f"pong_{self.room_id}_id")
await self.redis.delete(f"{self.room_id}_watcher")
logger.info(f"Room {self.room_id} has been closed by the host.")

if self.mode == "online" and len(players) == 2:
if not hasattr(self, "winner"):
self.punish_coward(self.info.creator)
await self.save_pong_to_db(self.winner)

if hasattr(self, "valid"):
await self.send_message("group", "game_stop", {"user": self.user.id})
await self.channel_layer.group_discard(self.room_id, self.channel_name)
await self.send_message(
"group", "game_stop", {"user": self.user.id}
)
await self.channel_layer.group_discard(
self.room_id, self.channel_name
)

await self.redis.close()
logger.info(f"User {self.scope['user'].id} has disconnected.")
Expand All @@ -101,7 +127,9 @@ async def receive(self, text_data):
if "content" not in data:
raise AttributeError("Missing 'content'")

logger.debug(f"Received '{msg_type}' message from user {self.scope['user'].id}.")
logger.debug(
f"Received '{msg_type}' message from user {self.scope['user'].id}."
)
match msg_type:
case "game_stop":
await self.close()
Expand All @@ -112,7 +140,9 @@ async def receive(self, text_data):
case _:
raise ValueError("Unknown 'type' in data")
except Exception as e:
logger.warning(f"Invalid message received from user {self.scope['user'].id}: {str(e)}")
logger.warning(
f"Invalid message received from user {self.scope['user'].id}: {str(e)}"
)
await self.send_error("Invalid message")

async def game_join(self, event):
Expand Down Expand Up @@ -142,9 +172,13 @@ async def game_move(self, event):
raise AttributeError("Missing 'pad_n'")
if event["content"].get("direction") == None:
raise AttributeError("Missing 'direction'")
await self.move_pad(event["content"]["pad_n"], event["content"]["direction"])
await self.move_pad(
event["content"]["pad_n"], event["content"]["direction"]
)
except AssertionError as e:
logger.warning(f"Invalid game_move received in {self.room_id}: {str(e)}")
logger.warning(
f"Invalid game_move received in {self.room_id}: {str(e)}"
)

async def game_stop(self, event):
if not self.connected:
Expand All @@ -171,30 +205,46 @@ async def loop(self):
while True:
try:
self.ball.move()
self.handle_collisions()
if self.info.score[0] == self.win_goal or self.info.score[1] == self.win_goal:
await self.send_message("group", "game_stop", {"winner": self.get_winner()})
await self.handle_collisions()
if (
self.info.score[0] == self.win_goal
or self.info.score[1] == self.win_goal
):
await self.send_message(
"group", "game_stop", {"winner": self.get_winner()}
)
break
await self.send_game_state()
await asyncio.sleep(fps)
except Exception as e:
logger.error(f"Unexpected error in the loop from game {self.room_id}: {str(e)}")
logger.error(
f"Unexpected error in the loop from game {self.room_id}: {str(e)}"
)
break

def get_winner(self):
if self.mode == "online":
return self.info.players[0] if self.info.score[0] > self.info.score[1] else self.info.players[1]
return (
self.info.players[0]
if self.info.score[0] > self.info.score[1]
else self.info.players[1]
)
else:
return "Pad 1" if self.info.score[0] > self.info.score[1] else "Pad 2"
return (
"Pad 1" if self.info.score[0] > self.info.score[1] else "Pad 2"
)

async def move_pad(self, pad_number, direction):
pad = self.pad_1 if pad_number == "pad_1" else self.pad_2
step = pad.step if direction == "down" else -pad.step
pad.y = min(max(pad.y + step, 0), 1 - pad.height)
await self.send_game_state()

def handle_collisions(self):
if self.ball.y - self.ball.radius / 2 <= 0 or self.ball.y + self.ball.radius / 2 >= 1:
async def handle_collisions(self):
if (
self.ball.y - self.ball.radius / 2 <= 0
or self.ball.y + self.ball.radius / 2 >= 1
):
self.ball.revert_velocity(1)
elif self.check_pad_collision():
self.ball.revert_velocity(0)
Expand All @@ -203,9 +253,9 @@ def handle_collisions(self):
else:
self.ball.x = 1 - self.pad_2.width - self.ball.radius / 2
elif self.ball.x + self.ball.radius / 2 <= self.pad_1.width:
self.score_point(1)
await self.score_point(1)
elif self.ball.x - self.ball.radius / 2 >= 1 - self.pad_2.width:
self.score_point(0)
await self.score_point(0)

def check_pad_collision(self):
if self.ball.x - self.ball.radius <= self.pad_1.width:
Expand All @@ -216,7 +266,7 @@ def check_pad_collision(self):
return True
return False

def score_point(self, winner: int):
async def score_point(self, winner: int):
self.info.score[winner] += 1
self.reset_game()

Expand Down Expand Up @@ -248,19 +298,26 @@ async def send_game_state(self):
)

async def save_pong_to_db(self, winner):
Pong = apps.get_model('games', 'Pong')
pong = apps.get_model("games", "Pong")
try:
await sync_to_async(Pong.objects.create)(
user1=await sync_to_async(get_user_model().objects.get)(id=self.info.players[0]),
user2=await sync_to_async(get_user_model().objects.get)(id=self.info.players[1]),
await sync_to_async(pong.objects.create)(
user1=await sync_to_async(get_user_model().objects.get)(
id=self.info.players[0]
),
user2=await sync_to_async(get_user_model().objects.get)(
id=self.info.players[1]
),
score1=self.info.score[0],
score2=self.info.score[1]
score2=self.info.score[1],
uuid=self.room_id,
)
logger.debug(f"Game {self.room_id} saved to db.")
except Exception as e:
logger.error(f"Could not save game {self.room_id} to db: {str(e)}")

def check_missing_param(self, query_params: dict[Any, List], param_name: str):
def check_missing_param(
self, query_params: dict[Any, List], param_name: str
):
param = query_params.get(param_name, [None])[0]
if param == None:
raise AttributeError(f"Missing query parameter: {param_name}")
Expand Down
Loading
Loading