diff --git a/indianpong/indianpong/settings.py b/indianpong/indianpong/settings.py index 10b3be1..b944cbf 100644 --- a/indianpong/indianpong/settings.py +++ b/indianpong/indianpong/settings.py @@ -27,6 +27,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = environ.get("DEBUG", default=True) +BASE_URL = environ.get("BASE_URL", default="http://localhost:8000") + ALLOWED_HOSTS = ['indianpong.com','indianpong.onrender.com', 'http://127.0.0.1:8000', 'localhost', '127.0.0.1']#environ.get("ALLOWED_HOSTS", default="").split(" ") CSRF_TRUSTED_ORIGINS = [ diff --git a/indianpong/pong/consumer_chat.py b/indianpong/pong/consumer_chat.py index 6bcc49f..0bc045f 100644 --- a/indianpong/pong/consumer_chat.py +++ b/indianpong/pong/consumer_chat.py @@ -51,20 +51,17 @@ async def receive(self, text_data): # if accept it create game object and send link in form: /remote-game/invite/game_id to both # send message to room group that user accepted the game make it in client side accepted = data["accepted"] - group_name = f"{accepted}_{accepter}" accepted = await UserProfile.objects.aget(username=accepted) accepter = await UserProfile.objects.aget(username=self.user.username) + group_name = f"{accepted}-{accepter}" # create game object game = await Game.objects.acreate(group_name=group_name, player1=accepted, player2=accepter) - message = f"/remote-game/invite/{game.id}" #? Maybe do these in client side - m = await Message.objects.acreate(content=message, user=accepted, room_id=self.room_name) #? await self.channel_layer.group_send( self.room_group_name, { "type": "accept.game", - "message": message, + "game_id": game.id, "user": accepted.username, - "created_date": m.get_short_date(), #? blocking? } ) @@ -131,15 +128,13 @@ async def invite_game(self, event): })) async def accept_game(self, event): - message = event["message"] + game_id = event["game_id"] user = event["user"] - created_date = event["created_date"] # Send message to WebSocket await self.send(text_data=json.dumps({ "type": "accept.game", - "message": message, + "game_id": game_id, "user": user, - "created_date": created_date, })) async def decline_game(self, event): diff --git a/indianpong/pong/consumer_pong.py b/indianpong/pong/consumer_pong.py index a753ea9..3bc8ffd 100644 --- a/indianpong/pong/consumer_pong.py +++ b/indianpong/pong/consumer_pong.py @@ -1,654 +1,655 @@ -import asyncio -import json -from channels.generic.websocket import AsyncWebsocketConsumer -from channels.db import database_sync_to_async -from asgiref.sync import sync_to_async, async_to_sync -from pong.utils import AsyncLockedDict -from django.core.cache import cache -from .utils import add_to_cache, remove_from_cache -#from .models import Game, Tournament, UserProfile -from pong.game import * -import datetime - -USER_CHANNEL_NAME = AsyncLockedDict() # key: username, value: channel_name -GAMES = AsyncLockedDict() # key: game_id, value: PongGame object -USER_STATUS = AsyncLockedDict() # key: username, value: game_id or lobby - - -class PongConsumer(AsyncWebsocketConsumer): - - async def connect(self): - - self.game_type = self.scope['url_route']['kwargs']['game_type'] # tournament or peer-to-peer or invite - self.game_id = self.scope['url_route']['kwargs']['game_id'] # game_id or new - self.user = self.scope['user'] - - await self.accept() - - # Add the user to the 'lobby' group - await self.channel_layer.group_add("lobby", self.channel_name) - - # Set the user's channel name - await USER_CHANNEL_NAME.set(self.user.username, self.channel_name) - # Add user username to lobby cache - await USER_STATUS.set(self.user.username, "lobby") - # Get the list of online users usernames - lobby_users_usernames = await USER_STATUS.get_keys_with_value('lobby') - lobby_users_usernames.remove(self.user.username) - await self.send(text_data=json.dumps({ - 'type': 'inlobby', - 'user': self.user.username, - 'users': lobby_users_usernames, - })) - await self.channel_layer.group_send("lobby", { - 'type': 'user.inlobby', - 'user': self.user.username, - }) - if self.game_type == 'tournament': - await self.tournament_match_handler() - elif self.game_type == 'invite': - await self.from_chat_handler() - - async def disconnect(self, close_code): - game_id = await USER_STATUS.get(self.user.username) - if game_id != 'lobby': - game = await GAMES.get(game_id) - if game != None: - other_player_channel_name = await USER_CHANNEL_NAME.get(game.otherPlayer(self.user.username)) - await self.record_for_disconnected(game_id, game) - await self.exit_handler(game_id, game) - await self.channel_layer.send(other_player_channel_name, { - 'type': 'game.disconnect', - 'game_id': game_id, - 'disconnected': self.user.username, - }) - - # Remove the user from the 'lobby' group - await self.channel_layer.group_discard("lobby", self.channel_name) - - # Remove the user's channel name - await USER_CHANNEL_NAME.delete(self.user.username) - - # Remove user username from lobby cache - await USER_STATUS.delete(self.user.username) - - # Set the user's status to offline - await self.channel_layer.group_send("lobby", { - 'type': 'user.outlobby', - 'user': self.user.username, - }) - - # Close the websocket connection - await self.close(close_code) - - async def receive(self, text_data): - data = json.loads(text_data) - action = data.get('action') - - if action == 'invite': - matchmaking = data.get('matchmaking') - invitee_username = data.get('invitee_username') - if matchmaking == 'true': - invitee_username = await self.matchmaking_handler() - if invitee_username == None: - await self.send(text_data=json.dumps({ - "error": "No suitable opponent found.", - })) - return - if await self.check_is_user_inlobby(invitee_username): - invitee_channel_name = await USER_CHANNEL_NAME.get(invitee_username) - if invitee_channel_name: - await self.channel_layer.send(invitee_channel_name, { - 'type': 'game.invite', - 'inviter': self.user.username, - 'invitee': invitee_username, - }) - - elif action == 'accept': - inviter_username = data.get('inviter_username') - await self.accept_handler(inviter_username) - - elif action == 'decline': - inviter_username = data.get('inviter_username') - await self.decline_handler(inviter_username) - - - elif action == 'start.request': - opponent_username = data.get('opponent') - game_id = data.get('game_id') - vote = data.get('vote') - await self.start_handler(game_id, opponent_username, vote) - - elif action == 'leave.game': - game_id = data.get('game_id') - left = data.get('left') - opponent = data.get('opponent') - await self.leave_handler(game_id, left, opponent) - - elif action == 'restart': #? not sure is needed or not - invitee_username = data.get('invitee_username') - invitee_channel_name = await USER_CHANNEL_NAME.get(invitee_username) - if invitee_channel_name: - await self.channel_layer.send(invitee_channel_name, { - 'type': 'game.restart', - 'inviter': self.user.username, - }) - elif action == 'exit': - game_id = data.get('game_id') - game = await GAMES.get(game_id) - if await self.check_is_users_ingame(game_id, game): - await self.exit_handler(game_id, game) - - #TODO Maybe remove this - elif action == 'pause.game': - game_id = data.get('game_id') - game = await GAMES.get(game_id) - if (game != None): - game.pauseGame() - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.pause", - "game_id": game_id, - } - ) - - #TODO Maybe remove this - elif action == 'resume.game': - game_id = data.get('game_id') - game = await GAMES.get(game_id) - if (game != None): - game.resumeGame() - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.resume", - "game_id": game_id, - } - ) - - elif action == "ball": #? Needs validation - # Make a move in a game and get the ball coordinates - # we send a message to the clients with the ball coordinates - game_id = data["game_id"] - # Move and Get ball coordinates - game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache - if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this - if (game.status == Status.PLAYING): - x, y, player1_score, player2_score = game.moveBall() - # Send a message to the game group with the game id, the move coordinates - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.ball", - "game_id": game_id, - "x": x, - "y": y, - "player1_score": player1_score, - "player2_score": player2_score, - } - ) - elif (game.status == Status.ENDED and not game.no_more): - await self.end_handler(game_id, game) - game.no_more = True - - - elif action == "paddle": #? Needs validation - # Make a move in a game - game_id = data["game_id"] - dir = data["direction"] - # Move and Get paddle coordinate - game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache - if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this - y = game.movePaddle(self.user.username, dir) - # Send a message to the game group with the game id, the paddle coordinate, and the player's username - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.paddle", - "game_id": game_id, - "y": y, - "player": self.user.username, - } - ) - elif action == "ability": - game_id = data["game_id"] - ability = data["abilities"] - game = await GAMES.get(game_id) - if (game != None): - if (game.status == Status.PLAYING): - game.activateAbility(self.user.username, ability) - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.ability", - "game_id": game_id, - "player": self.user.username, - "ability": ability, - } - ) - - ### HANDLERS ### - async def tournament_match_handler(self): - game_id, player1, player2, group_name, tournament_id = await self.match_details() - if await self.check_is_user_inlobby(player1) and await self.check_is_user_inlobby(player2): - player1_channel_name = await USER_CHANNEL_NAME.get(player1) - player2_channel_name = await USER_CHANNEL_NAME.get(player2) - - await self.channel_layer.group_add(group_name, player1_channel_name) - await self.channel_layer.group_add(group_name, player2_channel_name) - - await GAMES.set(game_id, PongGame(player1, player2, tournament_id)) - - await self.channel_layer.group_send(group_name, { - 'type': 'tournament.match', - 'tournament_id': tournament_id, - 'game_id': game_id, - 'player1': player1, - 'player2': player2, - }) - - async def from_chat_handler(self): - game_id, player1, player2, group_name, tournament_id = await self.match_details() - if await self.check_is_user_inlobby(player1) and await self.check_is_user_inlobby(player2): - player1_channel_name = await USER_CHANNEL_NAME.get(player1) - player2_channel_name = await USER_CHANNEL_NAME.get(player2) - - await self.channel_layer.group_add(group_name, player1_channel_name) - await self.channel_layer.group_add(group_name, player2_channel_name) - - await GAMES.set(game_id, PongGame(player1, player2)) - - await self.channel_layer.group_send(group_name, { - 'type': 'chat.game', - 'game_id': game_id, - 'player1': player1, - 'player2': player2, - }) - - async def accept_handler(self, inviter_username): - inviter_channel_name = await USER_CHANNEL_NAME.get(inviter_username) - group_name = f"{inviter_username}-{self.user.username}" - await self.channel_layer.group_add(group_name, self.channel_name) - await self.channel_layer.group_add(group_name, inviter_channel_name) - - # Create a new game instance and save it to the database - game = await self.create_game(group_name, inviter_username, self.user.username) - # Create a new game instance and save it to the cache - await GAMES.set(game.id, PongGame(inviter_username, self.user.username)) - - await self.channel_layer.group_send(group_name, { - 'type': 'game.accept', - 'accepter': self.user.username, - 'accepted': inviter_username, - 'game_id': game.id, - }) - - async def decline_handler(self, inviter_username): - inviter_channel_name = await USER_CHANNEL_NAME.get(inviter_username) - await self.channel_layer.send(inviter_channel_name, { - 'type': 'game.decline', - 'decliner': self.user.username, - 'declined': inviter_username, - }) - - async def start_handler(self, game_id, opponent_username, vote): - # Get the current game status and update it with the vote count - game = await GAMES.get(game_id) - current = game.status.value + int(vote) - await GAMES.set_field_value(game_id, Status(current), "status") - - # Check both players voted to start the game - if Status(current) == Status.PLAYING: # both players voted to start the game - await USER_STATUS.set(self.user.username, game_id) - await USER_STATUS.set(opponent_username, game_id) - cache.set(f"playing_{self.user.username}", True) - cache.set(f"playing_{opponent_username}", True) - - """ # Send message to lobby #? Maybe unnecesary bcs playing_username cache - await self.channel_layer.group_send('lobby', { - 'type': 'users.ingame', - 'game_type': self.game_type, - 'players': [self.user.username, opponent_username], - }) """ - await self.channel_layer.group_send(game.group_name, { - 'type': 'game.start', - 'game_id': game_id, - 'vote': current, - }) - - async def leave_handler(self, game_id, left, opponent): - # Get scores - game = await GAMES.get(game_id) - left_score = game.getScore(left) # blocking? - opponent_score = MAX_SCORE # set max score automaticaly - duration = game.getDuration() - # Record the game - await self.record_stats_elo_wallet(game_id, opponent_score, left_score, opponent, left, duration) - await USER_STATUS.set(game.player1.username, 'lobby') #? - await USER_STATUS.set(game.player2.username, 'lobby') #? - - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.leave", - "game_id": game_id, - "left": self.user.username, - "left_score": left_score, - "opponent_score": opponent_score, - "winner": opponent, - "loser": left, - } - ) - #await self.exit_handler(game_id, game, opponent) #! Invalid channel name error - - async def exit_handler(self, game_id, game): - # Discard both from the game group - opponent = game.otherPlayer(self.user.username) - opponent_channel_name = await USER_CHANNEL_NAME.get(opponent) - await self.channel_layer.group_discard(game.group_name, self.channel_name) - await self.channel_layer.group_discard(game.group_name, opponent_channel_name) - cache.set(f"playing_{self.user.username}", False) - cache.set(f"playing_{opponent}", False) - # delete the game from the cache - await GAMES.delete(game_id) - - async def end_handler(self, game_id, game): - # Get scores - player1_score = game.player1.score - player2_score = game.player2.score - duration = game.getDuration() - winner, loser, winner_score, loser_score = game.getWinnerLoserandScores() - # Set the game winner, scores and save it to the database - await self.record_stats_elo_wallet(game_id, winner_score, loser_score, winner, loser, duration) - await USER_STATUS.set(game.player1.username, 'lobby') #? - await USER_STATUS.set(game.player2.username, 'lobby') #? - - #? Maybe unnecesary - await self.channel_layer.group_send( - game.group_name, - { - "type": "game.end", - "game_id": game_id, - "player1_score": player1_score, - "player2_score": player2_score, - "winner": winner, - "loser": loser, - } - ) - - - ## SENDERS ## - async def user_inlobby(self, event): - user = event['user'] - await self.send(text_data=json.dumps({ - 'type': 'user.inlobby', - 'user': user, - })) - - async def user_outlobby(self, event): - user = event['user'] - await self.send(text_data=json.dumps({ - 'type': 'user.outlobby', - 'user': user, - })) - - async def game_disconnect(self, event): - game_id = event['game_id'] - disconnected = event['disconnected'] - await self.send(text_data=json.dumps({ - 'type': 'game.disconnect', - 'game_id': game_id, - 'disconnected': disconnected, - })) - - async def game_invite(self, event): - inviter = event['inviter'] - invitee = event['invitee'] - await self.send(text_data=json.dumps({ - 'type': 'game.invite', - 'inviter': inviter, - 'invitee': invitee, - })) - - async def tournament_match(self, event): - tournament_id = event['tournament_id'] - game_id = event['game_id'] - player1 = event['player1'] - player2 = event['player2'] - await self.send(text_data=json.dumps({ - 'type': 'tournament.match', - 'tournament_id': tournament_id, - 'game_id': game_id, - 'player1': player1, - 'player2': player2, - })) - - async def chat_game(self, event): - game_id = event['game_id'] - player1 = event['player1'] - player2 = event['player2'] - await self.send(text_data=json.dumps({ - 'type': 'tournament.match', - 'game_id': game_id, - 'player1': player1, - 'player2': player2, - })) - - async def game_accept(self, event): - accepter = event['accepter'] - accepted = event['accepted'] - game_id = event['game_id'] - await self.send(text_data=json.dumps({ - 'type': 'game.accept', - 'accepter': accepter, - 'accepted': accepted, - 'game_id': game_id, - })) - - async def game_decline(self, event): - decliner = event['decliner'] - declined = event['declined'] - await self.send(text_data=json.dumps({ - 'type': 'game.decline', - 'decliner': decliner, - 'declined': declined, - })) - - async def game_start(self, event): - game_id = event['game_id'] - vote = event['vote'] - await self.send(text_data=json.dumps({ - 'type': 'game.start', - 'game_id': game_id, - 'vote': vote, - })) - - #? Maybe unnecesary - async def users_ingame(self, event): - game_type = event['game_type'] - players = event['players'] - await self.send(text_data=json.dumps({ - 'type': 'users.ingame', - 'game_type': game_type, - 'players': players, - })) - - async def game_leave(self, event): - game_id = event['game_id'] - left = event['left'] - left_score = event['left_score'] - opponent_score = event['opponent_score'] - winner = event['winner'] - loser = event['loser'] - await self.send(text_data=json.dumps({ - 'type': 'game.leave', - 'game_id': game_id, - 'left': left, - 'left_score': left_score, - 'opponent_score': opponent_score, - 'winner': winner, - 'loser': loser, - })) - - async def game_end(self, event): - game_id = event['game_id'] - player1_score = event['player1_score'] - player2_score = event['player2_score'] - winner = event['winner'] - loser = event['loser'] - - await self.send(text_data=json.dumps({ - 'type': 'game.end', - 'game_id': game_id, - 'player1_score': player1_score, - 'player2_score': player2_score, - 'winner': winner, - 'loser': loser, - })) - - async def game_restart(self, event): - inviter = event['inviter'] - await self.send(text_data=json.dumps({ - 'type': 'game.restart', - 'inviter': inviter, - })) - - async def game_pause(self, event): - game_id = event['game_id'] - await self.send(text_data=json.dumps({ - 'type': 'game.pause', - 'game_id': game_id, - })) - - async def game_resume(self, event): - game_id = event['game_id'] - await self.send(text_data=json.dumps({ - 'type': 'game.resume', - 'game_id': game_id, - })) - - async def game_ball(self, event): - game_id = event['game_id'] - x = event['x'] - y = event['y'] - player1_score = event['player1_score'] - player2_score = event['player2_score'] - await self.send(text_data=json.dumps({ - 'type': 'game.ball', - 'game_id': game_id, - 'x': x, - 'y': y, - 'player1_score': player1_score, - 'player2_score': player2_score, - })) - - async def game_paddle(self, event): - game_id = event['game_id'] - y = event['y'] - player = event['player'] - await self.send(text_data=json.dumps({ - 'type': 'game.paddle', - 'game_id': game_id, - 'y': y, - 'player': player, - })) - - async def game_ability(self, event): - game_id = event['game_id'] - player = event['player'] - ability = event['ability'] - await self.send(text_data=json.dumps({ - 'type': 'game.ability', - 'game_id': game_id, - 'player': player, - 'ability': ability, - })) - - # Helper methods to interact with the database # - async def create_game(self, group_name, player1, player2): - from .models import Game, UserProfile - # Create a new game instance with the given players and an group_name - accepted = await UserProfile.objects.aget(username=player1) - accepter = await UserProfile.objects.aget(username=player2) - game = await Game.objects.acreate(group_name=group_name, player1=accepted, player2=accepter) - return game - - - @database_sync_to_async - def match_details(self): - from .models import Game - game = Game.objects.get(id=self.game_id) - game_id = game.id - player1 = game.player1.username - player2 = game.player2.username - group_name = game.group_name - tournament_id = game.tournament_id - return game_id, player1, player2, group_name, tournament_id - - @database_sync_to_async - def record_stats_elo_wallet(self, game_id, winner_score, loser_score, winner, loser, game_duration): - from .models import Game, UserProfile - from .update import update_wallet_elo, update_stats_pong, update_tournament - - game = Game.objects.get(id=game_id) - game.winner_score = winner_score - game.loser_score = loser_score - game.winner =UserProfile.objects.get(username=winner) - game.loser = UserProfile.objects.get(username=loser) - game.game_duration = datetime.timedelta(seconds=game_duration) - game.save() - - update_wallet_elo(game.winner, game.loser) - update_stats_pong(game.winner, game.loser, winner_score, loser_score, game_duration, "remote") - - # İf the game is a tournament game - if game.tournament_id: #? Check - update_tournament(game) - - - async def record_for_disconnected(self, game_id, game): - duration = game.getDuration() - if game.player1.username == self.user.username: - await self.record_stats_elo_wallet(game_id, game.player1.score, MAX_SCORE, game.player2.username, game.player1.username, duration) - else: - await self.record_stats_elo_wallet(game_id, MAX_SCORE, game.player2.score, game.player1.username, game.player2.username, duration) - - async def check_is_user_inlobby(self, username): - answer = await USER_STATUS.get(username) == 'lobby' - if not answer: - await self.send(text_data=json.dumps({ - "error": "User is not in the lobby.", - })) - return answer - - async def check_is_users_ingame(self, game_id, game): - answer = await USER_STATUS.get(game.player1.username) == game_id and await USER_STATUS.get(game.player2.username) == game_id - return answer - - async def matchmaking_handler(self): - from .models import UserProfile - # Get the current user's elo_point - current_user = await UserProfile.objects.aget(username=self.user.username) - current_user_elo = current_user.elo_point - # Get a list of online users - lobby_users_usernames = await USER_STATUS.get_keys_with_value('lobby') - lobby_users_usernames.remove(self.user.username) #TODO if user not in lobby - - return await self.get_similar_users(lobby_users_usernames, current_user_elo) - - - - @database_sync_to_async - def get_similar_users(self, lobby_users_usernames, current_user_elo): - from .models import UserProfile - users = UserProfile.objects.filter(username__in=lobby_users_usernames, elo_point__gte=current_user_elo-100, elo_point__lte=current_user_elo+100).all() - similar_users = [user.username for user in users] - if similar_users: - invitee_username = random.choice(similar_users) - else: - invitee_username = random.choice(lobby_users_usernames) if lobby_users_usernames else None - - return invitee_username - - +import asyncio +import json +from channels.generic.websocket import AsyncWebsocketConsumer +from channels.db import database_sync_to_async +from asgiref.sync import sync_to_async, async_to_sync +from pong.utils import AsyncLockedDict +from django.core.cache import cache +from .utils import add_to_cache, remove_from_cache +#from .models import Game, Tournament, UserProfile +from pong.game import * +import datetime + +USER_CHANNEL_NAME = AsyncLockedDict() # key: username, value: channel_name +GAMES = AsyncLockedDict() # key: game_id, value: PongGame object +USER_STATUS = AsyncLockedDict() # key: username, value: game_id or lobby + + +class PongConsumer(AsyncWebsocketConsumer): + + async def connect(self): + + self.game_type = self.scope['url_route']['kwargs']['game_type'] # tournament or peer-to-peer or invite + self.game_id = self.scope['url_route']['kwargs']['game_id'] # game_id or new + self.user = self.scope['user'] + + await self.accept() + + # Add the user to the 'lobby' group + await self.channel_layer.group_add("lobby", self.channel_name) + + # Set the user's channel name + await USER_CHANNEL_NAME.set(self.user.username, self.channel_name) + # Add user username to lobby cache + await USER_STATUS.set(self.user.username, "lobby") + # Get the list of online users usernames + lobby_users_usernames = await USER_STATUS.get_keys_with_value('lobby') + lobby_users_usernames.remove(self.user.username) + await self.send(text_data=json.dumps({ + 'type': 'inlobby', + 'user': self.user.username, + 'users': lobby_users_usernames, + })) + await self.channel_layer.group_send("lobby", { + 'type': 'user.inlobby', + 'user': self.user.username, + }) + if self.game_type == 'tournament': + await self.tournament_match_handler() + elif self.game_type == 'invite': + await self.from_chat_handler() + + async def disconnect(self, close_code): + game_id = await USER_STATUS.get(self.user.username) + if game_id != 'lobby': + game = await GAMES.get(game_id) + if game != None: + other_player_channel_name = await USER_CHANNEL_NAME.get(game.otherPlayer(self.user.username)) + await self.record_for_disconnected(game_id, game) + await self.exit_handler(game_id, game) + await self.channel_layer.send(other_player_channel_name, { + 'type': 'game.disconnect', + 'game_id': game_id, + 'disconnected': self.user.username, + }) + + # Remove the user from the 'lobby' group + await self.channel_layer.group_discard("lobby", self.channel_name) + + # Remove the user's channel name + await USER_CHANNEL_NAME.delete(self.user.username) + + # Remove user username from lobby cache + await USER_STATUS.delete(self.user.username) + + # Set the user's status to offline + await self.channel_layer.group_send("lobby", { + 'type': 'user.outlobby', + 'user': self.user.username, + }) + + # Close the websocket connection + await self.close(close_code) + + async def receive(self, text_data): + data = json.loads(text_data) + action = data.get('action') + + if action == 'invite': + matchmaking = data.get('matchmaking') + invitee_username = data.get('invitee_username') + if matchmaking == 'true': + invitee_username = await self.matchmaking_handler() + if invitee_username == None: + await self.send(text_data=json.dumps({ + "error": "No suitable opponent found.", + })) + return + if await self.check_is_user_inlobby(invitee_username): + invitee_channel_name = await USER_CHANNEL_NAME.get(invitee_username) + if invitee_channel_name: + await self.channel_layer.send(invitee_channel_name, { + 'type': 'game.invite', + 'inviter': self.user.username, + 'invitee': invitee_username, + }) + + elif action == 'accept': + inviter_username = data.get('inviter_username') + await self.accept_handler(inviter_username) + + elif action == 'decline': + inviter_username = data.get('inviter_username') + await self.decline_handler(inviter_username) + + + elif action == 'start.request': + opponent_username = data.get('opponent') + game_id = data.get('game_id') + vote = data.get('vote') + await self.start_handler(game_id, opponent_username, vote) + + elif action == 'leave.game': + game_id = data.get('game_id') + left = data.get('left') + opponent = data.get('opponent') + await self.leave_handler(game_id, left, opponent) + + elif action == 'restart': #? not sure is needed or not + invitee_username = data.get('invitee_username') + invitee_channel_name = await USER_CHANNEL_NAME.get(invitee_username) + if invitee_channel_name: + await self.channel_layer.send(invitee_channel_name, { + 'type': 'game.restart', + 'inviter': self.user.username, + }) + elif action == 'exit': + game_id = data.get('game_id') + game = await GAMES.get(game_id) + if await self.check_is_users_ingame(game_id, game): + await self.exit_handler(game_id, game) + + #TODO Maybe remove this + elif action == 'pause.game': + game_id = data.get('game_id') + game = await GAMES.get(game_id) + if (game != None): + game.pauseGame() + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.pause", + "game_id": game_id, + } + ) + + #TODO Maybe remove this + elif action == 'resume.game': + game_id = data.get('game_id') + game = await GAMES.get(game_id) + if (game != None): + game.resumeGame() + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.resume", + "game_id": game_id, + } + ) + + elif action == "ball": #? Needs validation + # Make a move in a game and get the ball coordinates + # we send a message to the clients with the ball coordinates + game_id = data["game_id"] + # Move and Get ball coordinates + game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache + if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this + if (game.status == Status.PLAYING): + x, y, player1_score, player2_score = game.moveBall() + # Send a message to the game group with the game id, the move coordinates + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.ball", + "game_id": game_id, + "x": x, + "y": y, + "player1_score": player1_score, + "player2_score": player2_score, + } + ) + elif (game.status == Status.ENDED and not game.no_more): + await self.end_handler(game_id, game) + game.no_more = True + + elif action == "paddle": #? Needs validation + # Make a move in a game + game_id = data["game_id"] + dir = data["direction"] + # Move and Get paddle coordinate + game = await GAMES.get(game_id) #? When games status is ended, game_id is deleted from GAMES cache + if (game != None): #? So game becomes None. Is this check enough? or moving delete to end solve without this + y = game.movePaddle(self.user.username, dir) + # Send a message to the game group with the game id, the paddle coordinate, and the player's username + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.paddle", + "game_id": game_id, + "y": y, + "player": self.user.username, + } + ) + elif action == "ability": + game_id = data["game_id"] + ability = data["abilities"] + game = await GAMES.get(game_id) + if (game != None): + if (game.status == Status.PLAYING): + game.activateAbility(self.user.username, ability) + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.ability", + "game_id": game_id, + "player": self.user.username, + "ability": ability, + } + ) + + ### HANDLERS ### + async def tournament_match_handler(self): + game_id, player1, player2, group_name, tournament_id = await self.match_details() + if await self.check_is_user_inlobby(player1) and await self.check_is_user_inlobby(player2): + player1_channel_name = await USER_CHANNEL_NAME.get(player1) + player2_channel_name = await USER_CHANNEL_NAME.get(player2) + + await self.channel_layer.group_add(group_name, player1_channel_name) + await self.channel_layer.group_add(group_name, player2_channel_name) + + await GAMES.set(game_id, PongGame(player1, player2, tournament_id)) + + await self.channel_layer.group_send(group_name, { + 'type': 'tournament.match', + 'tournament_id': tournament_id, + 'game_id': game_id, + 'player1': player1, + 'player2': player2, + }) + + async def from_chat_handler(self): + game_id, player1, player2, group_name, tournament_id = await self.match_details() + if await self.check_is_user_inlobby(player1) and await self.check_is_user_inlobby(player2): + player1_channel_name = await USER_CHANNEL_NAME.get(player1) + player2_channel_name = await USER_CHANNEL_NAME.get(player2) + + await self.channel_layer.group_add(group_name, player1_channel_name) + await self.channel_layer.group_add(group_name, player2_channel_name) + + await GAMES.set(game_id, PongGame(player1, player2)) + + await self.channel_layer.group_send(group_name, { + 'type': 'chat.game', + 'game_id': game_id, + 'player1': player1, + 'player2': player2, + }) + + async def accept_handler(self, inviter_username): + inviter_channel_name = await USER_CHANNEL_NAME.get(inviter_username) + group_name = f"{inviter_username}-{self.user.username}" + await self.channel_layer.group_add(group_name, self.channel_name) + await self.channel_layer.group_add(group_name, inviter_channel_name) + + # Create a new game instance and save it to the database + game = await self.create_game(group_name, inviter_username, self.user.username) + # Create a new game instance and save it to the cache + await GAMES.set(game.id, PongGame(inviter_username, self.user.username)) + + await self.channel_layer.group_send(group_name, { + 'type': 'game.accept', + 'accepter': self.user.username, + 'accepted': inviter_username, + 'game_id': game.id, + }) + + async def decline_handler(self, inviter_username): + inviter_channel_name = await USER_CHANNEL_NAME.get(inviter_username) + await self.channel_layer.send(inviter_channel_name, { + 'type': 'game.decline', + 'decliner': self.user.username, + 'declined': inviter_username, + }) + + async def start_handler(self, game_id, opponent_username, vote): + # Get the current game status and update it with the vote count + game = await GAMES.get(game_id) + current = game.status.value + int(vote) + await GAMES.set_field_value(game_id, Status(current), "status") + + # Check both players voted to start the game + if Status(current) == Status.PLAYING: # both players voted to start the game + await USER_STATUS.set(self.user.username, game_id) + await USER_STATUS.set(opponent_username, game_id) + cache.set(f"playing_{self.user.username}", True) + cache.set(f"playing_{opponent_username}", True) + + """ # Send message to lobby #? Maybe unnecesary bcs playing_username cache + await self.channel_layer.group_send('lobby', { + 'type': 'users.ingame', + 'game_type': self.game_type, + 'players': [self.user.username, opponent_username], + }) """ + await self.channel_layer.group_send(game.group_name, { + 'type': 'game.start', + 'game_id': game_id, + 'vote': current, + }) + + async def leave_handler(self, game_id, left, opponent): + # Get scores + game = await GAMES.get(game_id) + left_score = game.getScore(left) # blocking? + opponent_score = MAX_SCORE # set max score automaticaly + duration = game.getDuration() + # Record the game + await self.record_stats_elo_wallet(game_id, opponent_score, left_score, opponent, left, duration) + await USER_STATUS.set(game.player1.username, 'lobby') #? + await USER_STATUS.set(game.player2.username, 'lobby') #? + + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.leave", + "game_id": game_id, + "left": self.user.username, + "left_score": left_score, + "opponent_score": opponent_score, + "winner": opponent, + "loser": left, + } + ) + #await self.exit_handler(game_id, game, opponent) #! Invalid channel name error + + async def exit_handler(self, game_id, game): + # Discard both from the game group + opponent = game.otherPlayer(self.user.username) + opponent_channel_name = await USER_CHANNEL_NAME.get(opponent) + await self.channel_layer.group_discard(game.group_name, self.channel_name) + await self.channel_layer.group_discard(game.group_name, opponent_channel_name) + cache.set(f"playing_{self.user.username}", False) + cache.set(f"playing_{opponent}", False) + # delete the game from the cache + await GAMES.delete(game_id) + + async def end_handler(self, game_id, game): + # Get scores + player1_score = game.player1.score + player2_score = game.player2.score + duration = game.getDuration() + winner, loser, winner_score, loser_score = game.getWinnerLoserandScores() + # Set the game winner, scores and save it to the database + await self.record_stats_elo_wallet(game_id, winner_score, loser_score, winner, loser, duration) + await USER_STATUS.set(game.player1.username, 'lobby') #? + await USER_STATUS.set(game.player2.username, 'lobby') #? + + #? Maybe unnecesary + await self.channel_layer.group_send( + game.group_name, + { + "type": "game.end", + "game_id": game_id, + "player1_score": player1_score, + "player2_score": player2_score, + "winner": winner, + "loser": loser, + } + ) + + + ## SENDERS ## + async def user_inlobby(self, event): + user = event['user'] + await self.send(text_data=json.dumps({ + 'type': 'user.inlobby', + 'user': user, + })) + + async def user_outlobby(self, event): + user = event['user'] + await self.send(text_data=json.dumps({ + 'type': 'user.outlobby', + 'user': user, + })) + + async def game_disconnect(self, event): + game_id = event['game_id'] + disconnected = event['disconnected'] + await self.send(text_data=json.dumps({ + 'type': 'game.disconnect', + 'game_id': game_id, + 'disconnected': disconnected, + })) + + async def game_invite(self, event): + inviter = event['inviter'] + invitee = event['invitee'] + await self.send(text_data=json.dumps({ + 'type': 'game.invite', + 'inviter': inviter, + 'invitee': invitee, + })) + + async def tournament_match(self, event): + tournament_id = event['tournament_id'] + game_id = event['game_id'] + player1 = event['player1'] + player2 = event['player2'] + await self.send(text_data=json.dumps({ + 'type': 'tournament.match', + 'tournament_id': tournament_id, + 'game_id': game_id, + 'player1': player1, + 'player2': player2, + })) + + async def chat_game(self, event): + game_id = event['game_id'] + player1 = event['player1'] + player2 = event['player2'] + await self.send(text_data=json.dumps({ + 'type': 'chat.game', + 'game_id': game_id, + 'player1': player1, + 'player2': player2, + })) + + async def game_accept(self, event): + accepter = event['accepter'] + accepted = event['accepted'] + game_id = event['game_id'] + await self.send(text_data=json.dumps({ + 'type': 'game.accept', + 'accepter': accepter, + 'accepted': accepted, + 'game_id': game_id, + })) + + async def game_decline(self, event): + decliner = event['decliner'] + declined = event['declined'] + await self.send(text_data=json.dumps({ + 'type': 'game.decline', + 'decliner': decliner, + 'declined': declined, + })) + + async def game_start(self, event): + game_id = event['game_id'] + vote = event['vote'] + await self.send(text_data=json.dumps({ + 'type': 'game.start', + 'game_id': game_id, + 'vote': vote, + })) + + #? Maybe unnecesary + async def users_ingame(self, event): + game_type = event['game_type'] + players = event['players'] + await self.send(text_data=json.dumps({ + 'type': 'users.ingame', + 'game_type': game_type, + 'players': players, + })) + + async def game_leave(self, event): + game_id = event['game_id'] + left = event['left'] + left_score = event['left_score'] + opponent_score = event['opponent_score'] + winner = event['winner'] + loser = event['loser'] + await self.send(text_data=json.dumps({ + 'type': 'game.leave', + 'game_id': game_id, + 'left': left, + 'left_score': left_score, + 'opponent_score': opponent_score, + 'winner': winner, + 'loser': loser, + })) + + async def game_end(self, event): + game_id = event['game_id'] + player1_score = event['player1_score'] + player2_score = event['player2_score'] + winner = event['winner'] + loser = event['loser'] + + await self.send(text_data=json.dumps({ + 'type': 'game.end', + 'game_id': game_id, + 'player1_score': player1_score, + 'player2_score': player2_score, + 'winner': winner, + 'loser': loser, + })) + + async def game_restart(self, event): + inviter = event['inviter'] + await self.send(text_data=json.dumps({ + 'type': 'game.restart', + 'inviter': inviter, + })) + + async def game_pause(self, event): + game_id = event['game_id'] + await self.send(text_data=json.dumps({ + 'type': 'game.pause', + 'game_id': game_id, + })) + + async def game_resume(self, event): + game_id = event['game_id'] + await self.send(text_data=json.dumps({ + 'type': 'game.resume', + 'game_id': game_id, + })) + + async def game_ball(self, event): + game_id = event['game_id'] + x = event['x'] + y = event['y'] + player1_score = event['player1_score'] + player2_score = event['player2_score'] + await self.send(text_data=json.dumps({ + 'type': 'game.ball', + 'game_id': game_id, + 'x': x, + 'y': y, + 'player1_score': player1_score, + 'player2_score': player2_score, + })) + + async def game_paddle(self, event): + game_id = event['game_id'] + y = event['y'] + player = event['player'] + await self.send(text_data=json.dumps({ + 'type': 'game.paddle', + 'game_id': game_id, + 'y': y, + 'player': player, + })) + + async def game_ability(self, event): + game_id = event['game_id'] + player = event['player'] + ability = event['ability'] + await self.send(text_data=json.dumps({ + 'type': 'game.ability', + 'game_id': game_id, + 'player': player, + 'ability': ability, + })) + + # Helper methods to interact with the database # + async def create_game(self, group_name, player1, player2): + from .models import Game, UserProfile + # Create a new game instance with the given players and an group_name + accepted = await UserProfile.objects.aget(username=player1) + accepter = await UserProfile.objects.aget(username=player2) + game = await Game.objects.acreate(group_name=group_name, player1=accepted, player2=accepter) + return game + + + @database_sync_to_async + def match_details(self): + from .models import Game + game = Game.objects.get(id=self.game_id) + game_id = game.id + player1 = game.player1.username + player2 = game.player2.username + group_name = game.group_name + tournament_id = game.tournament_id + return game_id, player1, player2, group_name, tournament_id + + + @database_sync_to_async + def record_stats_elo_wallet(self, game_id, winner_score, loser_score, winner, loser, game_duration): + from .models import Game, UserProfile + from .update import update_wallet_elo, update_stats_pong, update_tournament + + game = Game.objects.get(id=game_id) + if game.winner == None: + game.winner_score = winner_score + game.loser_score = loser_score + game.winner =UserProfile.objects.get(username=winner) + game.loser = UserProfile.objects.get(username=loser) + game.game_duration = datetime.timedelta(seconds=game_duration) + game.save() + + update_wallet_elo(game.winner, game.loser) + update_stats_pong(game.winner, game.loser, winner_score, loser_score, game_duration, "remote") + + # İf the game is a tournament game + if game.tournament_id: #? Check + update_tournament(game) + + + async def record_for_disconnected(self, game_id, game): + duration = game.getDuration() + if game.player1.username == self.user.username: + await self.record_stats_elo_wallet(game_id, game.player1.score, MAX_SCORE, game.player2.username, game.player1.username, duration) + else: + await self.record_stats_elo_wallet(game_id, MAX_SCORE, game.player2.score, game.player1.username, game.player2.username, duration) + + async def check_is_user_inlobby(self, username): + answer = await USER_STATUS.get(username) == 'lobby' + if not answer: + await self.send(text_data=json.dumps({ + "error": "User is not in the lobby.", + })) + return answer + + async def check_is_users_ingame(self, game_id, game): + answer = await USER_STATUS.get(game.player1.username) == game_id and await USER_STATUS.get(game.player2.username) == game_id + return answer + + async def matchmaking_handler(self): + from .models import UserProfile + # Get the current user's elo_point + current_user = await UserProfile.objects.aget(username=self.user.username) + current_user_elo = current_user.elo_point + # Get a list of online users + lobby_users_usernames = await USER_STATUS.get_keys_with_value('lobby') + lobby_users_usernames.remove(self.user.username) #TODO if user not in lobby + + return await self.get_similar_users(lobby_users_usernames, current_user_elo) + + + + @database_sync_to_async + def get_similar_users(self, lobby_users_usernames, current_user_elo): + from .models import UserProfile + users = UserProfile.objects.filter(username__in=lobby_users_usernames, elo_point__gte=current_user_elo-100, elo_point__lte=current_user_elo+100).all() + similar_users = [user.username for user in users] + if similar_users: + invitee_username = random.choice(similar_users) + else: + invitee_username = random.choice(lobby_users_usernames) if lobby_users_usernames else None + + return invitee_username + + diff --git a/indianpong/pong/game.py b/indianpong/pong/game.py index 9aefdd7..96ed0c0 100644 --- a/indianpong/pong/game.py +++ b/indianpong/pong/game.py @@ -195,6 +195,8 @@ def getScore(self, username): return self.player2.score def getDuration(self): + if self.start_time == 0: + return 0 if self.end_time == 0: self.end_time = time.time() return self.end_time - self.start_time diff --git a/indianpong/pong/models.py b/indianpong/pong/models.py index 3374d9c..e45d6d7 100644 --- a/indianpong/pong/models.py +++ b/indianpong/pong/models.py @@ -13,6 +13,7 @@ from .utils import create_random_svg, get_upload_to from indianpong.settings import EMAIL_HOST_USER, STATICFILES_DIRS from django.utils import timezone +from django.conf import settings import uuid from datetime import timedelta from .game import MAX_SCORE @@ -365,7 +366,7 @@ def message_for_match(self, game): except Room.DoesNotExist: room = Room.objects.create(first_user=game.player1, second_user=game.player2) # Create message for the room with game link - message = f"http://localhost:8000/remote-game/tournament/{game.id}" + message = f"{settings.BASE_URL}/remote-game/tournament/{game.id}" Message.objects.create(user=game.player1, room=room, content=message) diff --git a/indianpong/pong/rps.py b/indianpong/pong/rps.py index 1b0ea88..5cace21 100644 --- a/indianpong/pong/rps.py +++ b/indianpong/pong/rps.py @@ -1,124 +1,126 @@ -from enum import Enum -import time - -class Choices(Enum): - ROCK = 0 - PAPER = 1 - SCISSORS = 2 - -class Abilities(Enum): - LIKEACHEATER = 3, - GODOFTHINGS = 4 - -class RoundResult(Enum): - DRAW = 0 - PLAYER1_WIN = 1 - PLAYER2_WIN = 2 - -KV_CHOICES = {'rock': Choices.ROCK, 'paper': Choices.PAPER, 'scissors': Choices.SCISSORS, 'godthings': Abilities.GODOFTHINGS, 'cheater': Abilities.LIKEACHEATER} - -class Shaker: - def __init__(self, username): - self.username = username - self.score = 0 - self.choices = [] - -class RPS: - def __init__(self, player1, player2): - self.shaker1 = Shaker(player1) - self.shaker2 = Shaker(player2) - self.max_score = 3 - self.group_name = f'rps_{player1}_{player2}' - self.start_time = 0 - self.end_time = 0 - - def play(self, username, choice): - if (self.start_time == 0): - self.start_time = time.time() - if username == self.shaker1.username: - self.shaker1.choices.append(KV_CHOICES[choice]) - elif username == self.shaker2.username: - self.shaker2.choices.append(KV_CHOICES[choice]) - - def ability_result(self, choice1, choice2): - ab1 = choice1 == Abilities.LIKEACHEATER or choice1 == Abilities.GODOFTHINGS - ab2 = choice2 == Abilities.LIKEACHEATER or choice2 == Abilities.GODOFTHINGS - if ab1 and ab2: #both played this it's draw - return 0 - elif ab1 and choice1 == Abilities.LIKEACHEATER: #stole opponent score if greater than 0 - if self.shaker2.score > 0: - self.shaker2.score -= 1 - self.shaker1.score += 1 - return 1 - elif ab2 and choice2 == Abilities.LIKEACHEATER: #won round instantly - if self.shaker1.score > 0: - self.shaker1.score -= 1 - self.shaker2.score += 1 - return 2 - elif ab1 and choice1 == Abilities.GODOFTHINGS: - self.shaker1.score += 1 - return 1 - elif ab2 and choice2 == Abilities.GODOFTHINGS: - self.shaker2.score += 1 - return 2 - else: - return 3 - - - def round_result(self): - shaker1_choice = self.shaker1.choices.pop() - shaker2_choice = self.shaker2.choices.pop() - result = self.ability_result(shaker1_choice, shaker2_choice) - if result == 0: - return RoundResult.DRAW.name - elif result == 1: - return RoundResult.PLAYER1_WIN.name - elif result == 2: - return RoundResult.PLAYER2_WIN.name - result = (shaker1_choice.value - shaker2_choice.value) % 3 - if result == 0: - return RoundResult.DRAW.name - elif result == 1: - self.shaker1.score += 1 - return RoundResult.PLAYER1_WIN.name - else: - self.shaker2.score += 1 - return RoundResult.PLAYER2_WIN.name - - def check_is_over(self): - if self.shaker1.score == self.max_score or self.shaker2.score == self.max_score: - self.end_time = time.time() - return True - return False - - def get_winner_loser(self): - if self.shaker1.score > self.shaker2.score: - return self.shaker1.username, self.shaker2.username - else: - return self.shaker2.username, self.shaker1.username - - def otherPlayer(self, username): - if username == self.shaker1.username: - return self.shaker2.username - else: - return self.shaker1.username - - def get_scores(self): - return self.shaker1.score, self.shaker2.score - - def getDuration(self): - if self.end_time == 0: - self.end_time = time.time() - return self.end_time - self.start_time - - def getWinnerLoserandScores(self): - if self.shaker1.score > self.shaker2.score: - return self.shaker1.username, self.shaker2.username, self.shaker1.score, self.shaker2.score - else: - return self.shaker2.username, self.shaker1.username, self.shaker2.score, self.shaker1.score - - def both_played(self): - return len(self.shaker1.choices) == len(self.shaker2.choices) == 1 - - def getChoices(self): - return self.shaker1.choices[0].name, self.shaker2.choices[0].name +from enum import Enum +import time + +class Choices(Enum): + ROCK = 0 + PAPER = 1 + SCISSORS = 2 + +class Abilities(Enum): + LIKEACHEATER = 3, + GODOFTHINGS = 4 + +class RoundResult(Enum): + DRAW = 0 + PLAYER1_WIN = 1 + PLAYER2_WIN = 2 + +KV_CHOICES = {'rock': Choices.ROCK, 'paper': Choices.PAPER, 'scissors': Choices.SCISSORS, 'godthings': Abilities.GODOFTHINGS, 'cheater': Abilities.LIKEACHEATER} + +class Shaker: + def __init__(self, username): + self.username = username + self.score = 0 + self.choices = [] + +class RPS: + def __init__(self, player1, player2): + self.shaker1 = Shaker(player1) + self.shaker2 = Shaker(player2) + self.max_score = 3 + self.group_name = f'rps_{player1}_{player2}' + self.start_time = 0 + self.end_time = 0 + + def play(self, username, choice): + if (self.start_time == 0): + self.start_time = time.time() + if username == self.shaker1.username: + self.shaker1.choices.append(KV_CHOICES[choice]) + elif username == self.shaker2.username: + self.shaker2.choices.append(KV_CHOICES[choice]) + + def ability_result(self, choice1, choice2): + ab1 = choice1 == Abilities.LIKEACHEATER or choice1 == Abilities.GODOFTHINGS + ab2 = choice2 == Abilities.LIKEACHEATER or choice2 == Abilities.GODOFTHINGS + if ab1 and ab2: #both played this it's draw + return 0 + elif ab1 and choice1 == Abilities.LIKEACHEATER: #stole opponent score if greater than 0 + if self.shaker2.score > 0: + self.shaker2.score -= 1 + self.shaker1.score += 1 + return 1 + elif ab2 and choice2 == Abilities.LIKEACHEATER: #won round instantly + if self.shaker1.score > 0: + self.shaker1.score -= 1 + self.shaker2.score += 1 + return 2 + elif ab1 and choice1 == Abilities.GODOFTHINGS: + self.shaker1.score += 1 + return 1 + elif ab2 and choice2 == Abilities.GODOFTHINGS: + self.shaker2.score += 1 + return 2 + else: + return 3 + + + def round_result(self): + shaker1_choice = self.shaker1.choices.pop() + shaker2_choice = self.shaker2.choices.pop() + result = self.ability_result(shaker1_choice, shaker2_choice) + if result == 0: + return RoundResult.DRAW.name + elif result == 1: + return RoundResult.PLAYER1_WIN.name + elif result == 2: + return RoundResult.PLAYER2_WIN.name + result = (shaker1_choice.value - shaker2_choice.value) % 3 + if result == 0: + return RoundResult.DRAW.name + elif result == 1: + self.shaker1.score += 1 + return RoundResult.PLAYER1_WIN.name + else: + self.shaker2.score += 1 + return RoundResult.PLAYER2_WIN.name + + def check_is_over(self): + if self.shaker1.score == self.max_score or self.shaker2.score == self.max_score: + self.end_time = time.time() + return True + return False + + def get_winner_loser(self): + if self.shaker1.score > self.shaker2.score: + return self.shaker1.username, self.shaker2.username + else: + return self.shaker2.username, self.shaker1.username + + def otherPlayer(self, username): + if username == self.shaker1.username: + return self.shaker2.username + else: + return self.shaker1.username + + def get_scores(self): + return self.shaker1.score, self.shaker2.score + + def getDuration(self): + if self.start_time == 0: + return 0 + if self.end_time == 0: + self.end_time = time.time() + return self.end_time - self.start_time + + def getWinnerLoserandScores(self): + if self.shaker1.score > self.shaker2.score: + return self.shaker1.username, self.shaker2.username, self.shaker1.score, self.shaker2.score + else: + return self.shaker2.username, self.shaker1.username, self.shaker2.score, self.shaker1.score + + def both_played(self): + return len(self.shaker1.choices) == len(self.shaker2.choices) == 1 + + def getChoices(self): + return self.shaker1.choices[0].name, self.shaker2.choices[0].name diff --git a/indianpong/pong/templates/base.html b/indianpong/pong/templates/base.html index 72bb3d6..c8fa5c8 100644 --- a/indianpong/pong/templates/base.html +++ b/indianpong/pong/templates/base.html @@ -123,6 +123,27 @@

{{context.baseInfoSubHeaderText3}}

return cookieValue ? cookieValue.pop() : ''; } + + + function showToast(content, status, iconClass) { + const liveToast = document.getElementById('liveToast'); + var toastContent = document.querySelector('#liveToast .fw-semibold'); + var toastIcon = document.querySelector('.toast-body .i-class i'); + + + toastIcon.className = iconClass; + liveToast.classList.remove('text-bg-danger'); + liveToast.className = 'toast'; + liveToast.classList.add(status); + + toastContent.textContent = content; + const toast = new bootstrap.Toast(liveToast); + toast.show(); + setTimeout(function() { + toast.hide(); + }, 8000); + } + diff --git a/indianpong/pong/templates/play-ai.html b/indianpong/pong/templates/play-ai.html index 99a0e49..c859b7c 100644 --- a/indianpong/pong/templates/play-ai.html +++ b/indianpong/pong/templates/play-ai.html @@ -21,11 +21,6 @@ -
- - 200 - -
diff --git a/indianpong/pong/templates/room.html b/indianpong/pong/templates/room.html index 58adbc6..c6ab83d 100644 --- a/indianpong/pong/templates/room.html +++ b/indianpong/pong/templates/room.html @@ -1,170 +1,178 @@ -{% extends 'base.html' %} - -{% load static %} -{% load status %} - -{% block title %} -{{context.chatPageTittle}} -{% endblock %} - -{% block stylesheet %}{% endblock %} -{% block app %} - -{% load custom_filters %} - -
-
-
-
- - - - -
- - -
-
- -
- {% if room.first_user == request.user %} - - -
- {{ room.second_user }} -
- {% else %} - - -
- {{ room.first_user }} -
- {% endif %} -
-
- - - - {% if not user_friends_status|get_item:room.second_user.username %} - - - {% else %} - - - {% endif %} - {% if user_blocked_status|get_item:room.second_user.username or user_blocked_status|get_item:room.first_user.username %} - - - {% else %} - - - {% endif %} - - -
-
- -
- -
- - - -
-
-
-
- -
- {{ room_name|json_script:"room-name" }} - {{ request.user.username|json_script:"user" }} - - -
-
-
- -
-
- {% csrf_token %} +{% extends 'base.html' %} + +{% load static %} +{% load status %} + +{% block title %} +{{context.chatPageTittle}} +{% endblock %} + +{% block stylesheet %}{% endblock %} +{% block app %} + +{% load custom_filters %} + +
+
+
+
+ + + + +
+ + +
+
+ +
+ {% if room.first_user == request.user %} + + +
+ {{ room.second_user }} +
+ {% else %} + + +
+ {{ room.first_user }} +
+ {% endif %} +
+
+ + + + {% if not user_friends_status|get_item:room.second_user.username %} + + + {% else %} + + + {% endif %} + {% if user_blocked_status|get_item:room.second_user.username or user_blocked_status|get_item:room.first_user.username %} + + + {% else %} + + + {% endif %} + + +
+
+ +
+ +
+ + + +
+
+
+
+ +
+ {{ room_name|json_script:"room-name" }} + {{ request.user.username|json_script:"user" }} + + +
+
+
+ +
+
+ {% csrf_token %} {% endblock %} \ No newline at end of file diff --git a/indianpong/pong/views.py b/indianpong/pong/views.py index deea949..b56d1d5 100644 --- a/indianpong/pong/views.py +++ b/indianpong/pong/views.py @@ -8,9 +8,8 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponseRedirect from django.http import Http404 +from django.conf import settings from django.utils import timezone -from django.core import serializers -from django.template import loader from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger import ssl @@ -126,7 +125,7 @@ def auth(request): auth_url = "https://api.intra.42.fr/oauth/authorize" fields = { "client_id": "u-s4t2ud-4b7a045a7cc7dd977eeafae807bd4947670f273cb30e1dd674f6bfa490ba6c45", # environ.get("FT_CLIENT_ID"), - "redirect_uri": "http://localhost:8000/auth_callback", # This should be parameterized + "redirect_uri": f"{settings.BASE_URL}/auth_callback", # This should be parameterized "scope": "public", # "state": state_req, # This will generate a 50-character long random string "response_type": "code", @@ -151,9 +150,9 @@ def auth_callback(request): data = { "grant_type": "authorization_code", "client_id": "u-s4t2ud-4b7a045a7cc7dd977eeafae807bd4947670f273cb30e1dd674f6bfa490ba6c45", # environ.get("FT_CLIENT_ID"), - "client_secret": "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", # environ.get("FT_CLIENT_SECRET"), + "client_secret": "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", # environ.get("FT_CLIENT_SECRET"), "code": code, - "redirect_uri": "http://localhost:8000/auth_callback", + "redirect_uri": f"{settings.BASE_URL}/auth_callback", } encoded_data = urllib.parse.urlencode(data).encode("utf-8") req = urllib.request.Request( @@ -273,7 +272,9 @@ def profile_view(request, username): context = langs.get_langs(lang) game_records = Game.objects.filter( Q(player1=profile) | Q(player2=profile), - game_kind='pong' + game_kind='pong', + ).exclude( + game_duration=None ).order_by("-created_at") game_records_rps = Game.objects.filter( Q(player1=profile) | Q(player2=profile), @@ -856,11 +857,15 @@ def remote_game(request, game_type, game_id): if game_type == 'peer-to-peer' and game_id != 'new': raise Http404("Invalid game id for peer-to-peer. It should be 'new'.") - if game_type == 'tournament' or game_type == 'invite': + if game_type == 'tournament': game = get_object_or_404(Game, id=game_id) tournament = get_object_or_404(Tournament, id=game.tournament_id) if game.winner is not None: raise Http404("The game is already finished.") + elif game_type == 'invite': + game = get_object_or_404(Game, id=game_id) + if game.winner is not None: + raise Http404("The game is already finished.") lang = request.COOKIES.get('selectedLanguage', 'en') context = langs.get_langs(lang) diff --git a/indianpong/static/js/chat.js b/indianpong/static/js/chat.js index f08e96b..7f1da44 100644 --- a/indianpong/static/js/chat.js +++ b/indianpong/static/js/chat.js @@ -122,24 +122,34 @@ const langMessages = {
` return message - } +} - function messageOthers(data) { - var message = ` -
  • -
    -
    -
    -
    +function messageOthers(data) { + var message = ` +
  • +
    +
    +
    +

    ${data.message}

    -
    ${data.created_date}
    -
    +
    ${data.created_date}
    -
  • ` - return message + + ` + return message +} + +document.addEventListener('click', function(e) { + if (e.target.tagName === 'A' && e.target.hasAttribute('data-url')) { + var url = e.target.getAttribute('data-url'); + if (url.includes('/remote-game/tournament/')) { + e.preventDefault(); + swapApp(url); + } } +}); chatsocket.onmessage = function (e) { const cookie = document.cookie.split('; ').find(row => row.startsWith('selectedLanguage=')); @@ -159,7 +169,7 @@ const langMessages = { conversation.scrollTop = conversation.scrollHeight; }, 0); break; - case 'invite': + case 'invite.game': if (user === data.inviter) { showToast(`${langMessages[lang]['inviteyou']}`, 'text-bg-info', 'bi bi-bug-fill'); } else { @@ -168,7 +178,7 @@ const langMessages = { declineButton.style.display = 'block'; } break; - case 'accept': + case 'accept.game': if (user === data.accepted) { showToast(`${langMessages[lang]['acceptyou']}`, 'text-bg-success', 'bi bi-bug-fill'); } else { @@ -179,7 +189,7 @@ const langMessages = { swapApp(`/remote-game/invite/${data.game_id}`); break; - case 'decline': + case 'decline.game': if (user === data.declined) { showToast(`${langMessages[lang]['declineyou']}`, 'text-bg-success', 'bi bi-bug-fill'); } else { @@ -246,14 +256,26 @@ const langMessages = { } inviteButton.onclick = function (e) { - console.log("invite button clicked"); + //console.log("invite button clicked"); chatsocket.send(JSON.stringify({ - "action": "invite", - "inviter": user, - "invited": userNameOnChat, + "action": "invite.game", })) } + acceptButton.onclick = function (e) { + chatsocket.send(JSON.stringify({ + "action": "accept.game", + "accepted": userNameOnChat, + "accepter": user, + })) + } + + declineButton.onclick = function (e) { + chatsocket.send(JSON.stringify({ + "action": "decline.game", + })) + } + followButton.onclick = function (e) { const cookie = document.cookie.split('; ').find(row => row.startsWith('selectedLanguage=')); const lang = cookie ? cookie.split('=')[1] : 'en'; diff --git a/indianpong/static/js/game/play-ai.js b/indianpong/static/js/game/play-ai.js index cfd13a2..e4f1526 100644 --- a/indianpong/static/js/game/play-ai.js +++ b/indianpong/static/js/game/play-ai.js @@ -8,7 +8,7 @@ var start_time; var score1 = 0; var score2 = 0; -const MAX_SCORE = 10; +const MAX_SCORE = 3; // Player Abilities var likeaCheaterCount = 0; @@ -521,14 +521,14 @@ document.addEventListener("keyup", function(event) { // Ai Player -let reactionDelaySlider = document.getElementById('reactionDelay'); +/* let reactionDelaySlider = document.getElementById('reactionDelay'); let delayValueSpan = document.getElementById('delayValue'); reactionDelaySlider.oninput = function() { reactionDelay = this.value / ball.speed; delayValueSpan.innerText = Math.round(reactionDelay); // Display the current value of the slider let value = (this.value-this.min)/(this.max-this.min)*100 -} -let reactionDelay = Math.round(reactionDelaySlider.value / ball.speed); +} */ +let reactionDelay = 1000/(ball.speed*2)//Math.round(reactionDelaySlider.value / ball.speed); let lastBallPosition = { x: ball.x, y: ball.y }; let ballDirection = { x: 0, y: 0 }; let predictedY = paddle2.y; diff --git a/indianpong/static/js/game/sockPong.js b/indianpong/static/js/game/sockPong.js index 43b09ec..6cabb10 100644 --- a/indianpong/static/js/game/sockPong.js +++ b/indianpong/static/js/game/sockPong.js @@ -1,805 +1,810 @@ -export function RemotePong() { - -const cookie = document.cookie.split('; ').find(row => row.startsWith('selectedLanguage=')); -const lang = cookie ? cookie.split('=')[1] : 'en'; - -function showToast(content, status, iconClass) { - const liveToast = document.getElementById('liveToast'); - var toastContent = document.querySelector('#liveToast .fw-semibold'); - var toastIcon = document.querySelector('.toast-body .i-class i'); - - toastIcon.className = iconClass; - liveToast.classList.remove('text-bg-danger'); - liveToast.className = 'toast'; - liveToast.classList.add(status); - - toastContent.textContent = content; - const toast = new bootstrap.Toast(liveToast); - toast.show(); - setTimeout(function() { - toast.hide(); - }, 8000); -} - - // Extract game_id and game_type from the URL -const pathArray = window.location.pathname.split('/'); -const gameType = pathArray[2]; // Assuming game_type is the third segment of the URL -const gameId = pathArray[3]; // Assuming game_id is the fourth segment of the URL - - -// Connect to the WebSocket server using the extracted game_id and game_type -const matchsocket = new WebSocket(`wss://${window.location.host}/ws/remote-game/${gameType}/${gameId}/`); //? Maybe we need to pass game type and game id here - - -const canvas = document.getElementById('pongCanvas'); -var ctx = canvas.getContext("2d"); -canvas.width = 800; -canvas.height = 600; - -const checkbox = document.getElementById('flexSwitchCheckDefault'); -const selectedGameModeLabel = document.getElementById('selectedGameMode'); - -let gameMode = "Vanilla"; - -// Custom items -const leftArea = document.getElementById('left-area-display'); -const paddleColor = document.querySelector('.left-card').dataset.paddlecolor; -const playgroundColor = document.querySelector('.left-card').dataset.playgroundcolor; -canvas.style.borderColor = playgroundColor; -const giantMan = document.querySelector('.left-card').dataset.giantman; -const likeaCheater = document.querySelector('.left-card').dataset.likeacheater; -const fastandFurious = document.querySelector('.left-card').dataset.fastandfurious; -const rageofFire = document.querySelector('.left-card').dataset.rageoffire; -const frozenBall = document.querySelector('.left-card').dataset.frozenball; -const tournament = document.querySelector('.left-card').dataset.tournament; - -let likeaCheaterCount = 0; -let fastandFuriousCount = 0; -let frozenBallCount = 0; - -let isFrozenBallActive = false; - -// Paddle objects -var paddleWidth = 10; -var paddleHeight = 100; -var paddleY = (canvas.height - paddleHeight) / 2; -var paddle1 = {x: 0, y: paddleY, width: paddleWidth, height: paddleHeight}; -var paddle2 = {x: canvas.width - paddleWidth, y: paddleY, width: paddleWidth, height: paddleHeight}; - -// Ball object -var ball = {x: canvas.width / 2, y: canvas.height / 2, radius: 10}; - -// maybe merge with my object -var player1 = {username: '', score: 0}; -var player2 = {username: '', score: 0}; - -var my = { - username: '', opponent_username: '', game_id: '', tournament_id: '', -}; - -let upPressed = false; -let downPressed = false; - -//Button -//const startButton = document.getElementById('startButton'); -const leaveButton = document.getElementById('leaveButton'); -leaveButton.style.display = 'none'; -const gameOverScreen = document.getElementById('gameOverScreen'); -const matchmakingButton = document.getElementById('matchmakingButton'); -//const restartButton = document.getElementById('restartButton'); -//startButton.style.display = 'none'; -//restartButton.style.display = 'none'; - -// Envai -var textWidth1 = ctx.measureText(player1.username + ": " + player1.score).width; -var textWidth2 = ctx.measureText(player2.username + ": " + player2.score).width; - -/// Draw everything -function render() { - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Create a radial gradient for the background - var gradient = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 10, canvas.width / 2, canvas.height / 2, 300); - gradient.addColorStop(0, 'lightgrey'); - gradient.addColorStop(1, 'darkgrey'); - ctx.fillStyle = gradient; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Draw the middle dotted line - ctx.beginPath(); - ctx.setLineDash([5, 15]); - ctx.moveTo(canvas.width / 2, 0); - ctx.lineTo(canvas.width / 2, canvas.height); - ctx.strokeStyle = "black"; - ctx.stroke(); - - // Draw the middle dotted circle - ctx.beginPath(); - ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, Math.PI * 2, false); - ctx.setLineDash([5, 15]); - ctx.stroke(); - - - // Add shadow to the paddles - ctx.shadowColor = 'black'; - ctx.shadowBlur = 10; - ctx.shadowOffsetX = 5; - ctx.shadowOffsetY = 5; - - ctx.fillStyle = paddleColor; - ctx.fillRect(paddle1.x, paddle1.y, paddle1.width, paddle1.height); - // If paddle2 is on the right, draw the shadow to the left - ctx.shadowOffsetX = -5; - ctx.shadowOffsetY = 5; - ctx.fillRect(paddle2.x, paddle2.y, paddle2.width, paddle2.height); - - // Add shiny effect to the ball - ctx.beginPath(); - ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, false); - var gradient = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.radius); - gradient.addColorStop(0, 'white'); - gradient.addColorStop(0.1, 'gold'); - gradient.addColorStop(1, 'darkorange'); - ctx.fillStyle = gradient; - ctx.fill(); - ctx.closePath(); - - // Reset shadow properties - ctx.shadowColor = 'transparent'; - ctx.shadowBlur = 0; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - - ctx.font = "14px Roboto"; - ctx.fillStyle = 'white'; - ctx.fillText(player1.username + ": " + player1.score, 50, 20); - ctx.fillText(player2.username + ": " + player2.score, canvas.width - textWidth2 - 80, 20); -} - -var requestId; - -// The main game loop -var startGame = function () { - - - BallRequest(); - updatePaddlePosition(); - render(); - // Request to do this again ASAP - requestId = requestAnimationFrame(startGame); -}; - -// When you want to stop the game loop -var stopGame = function() { - cancelAnimationFrame(requestId); -}; - -function gameUtilsReset() { - likeaCheaterCount = 0; - fastandFuriousCount = 0; - frozenBallCount = 0; - isFrozenBallActive = false; - - if (giantMan == "true" && (gameMode === "Abilities" || tournament === "abilities")) - sendAbility("giantMan"); - if (rageofFire == "true" && (gameMode === "Abilities" || tournament === "abilities")) - sendAbility("rageofFire"); -} - -// Cross-browser support for requestAnimationFrame -var w = window; -requestAnimationFrame = - w.requestAnimationFrame || - w.webkitRequestAnimationFrame || - w.msRequestAnimationFrame || - w.mozRequestAnimationFrame; - -function paddleMove(player, y) { - if (player === player1.username) { - paddle1.y = y; - } else if (player === player2.username) { - paddle2.y = y; - } -} - -function ballMove(x, y) { - ball.x = x; - ball.y = y; -} - -function scoreUpdate(player1_score, player2_score) { - player1.score = player1_score; - player2.score = player2_score; -} - -matchsocket.onopen = function (e) { - // Show some greeting message - console.log('WebSocket connection established'); -} - -matchsocket.onclose = function (e) { - //clearInterval(BallRequest); - stopGame(); - console.error('WebSocket connection closed'); -} - -matchsocket.onerror = function (e) { - console.error('Error: ' + e.data); - //clearInterval(BallRequest); - stopGame(); -} - -matchsocket.onmessage = function (e) { - const data = JSON.parse(e.data); - //console.log(data); - switch (data.type) { - case 'inlobby': - // Self send message - console.log('In lobby', data.user); - if (my.username === '') { - my.username = data.user; - } - // Take online users usernames and display them - addUsersToTable(data.users); - console.log('Online users', data.users); - break; - - case 'user.inlobby': - // Send others i joined the lobby - console.log('User in lobby', data.user); - // Add user to online users list - if (data.user !== my.username) { - addUsersToTable([data.user]); - } - if (lang === 'tr') - showToast(data.user + ' katıldı!', 'text-bg-success', 'bi bi-check-circle-fill') - else if (lang === 'hi') - showToast(data.user + ' शामिल हो गया!', 'text-bg-success', 'bi bi-check-circle-fill') - else if (lang === 'pt') - showToast(data.user + ' entrou!', 'text-bg-success', 'bi bi-check-circle-fill') - else - showToast(data.user + ' joined!', 'text-bg-success', 'bi bi-check-circle-fill') - break; - - case 'user.outlobby': - // Send others user left the lobby - console.log('User out lobby', data.user); - // Remove user from online users list - removeUserFromTable(data.user); - if (lang === 'tr') - showToast(data.user + ' ayrıldı!', 'text-bg-danger', 'bi bi-check-circle-fill') - else if (lang === 'hi') - showToast(data.user + ' चला गया!', 'text-bg-danger', 'bi bi-check-circle-fill') - else if (lang === 'pt') - showToast(data.user + ' saiu!', 'text-bg-danger', 'bi bi-check-circle-fill') - else - showToast(data.user + ' left!', 'text-bg-danger', 'bi bi-check-circle-fill') - break; - - case 'game.disconnected': - //clearInterval(BallRequest); - stopGame(); - gameOverScreen.style.display = 'block'; - showToast(`${data.disconnected} disconnected You are automatically winner`, 'text-bg-danger', 'bi bi-check-circle-fill') - console.log('Player disconnected', data.disconnected); - - case 'game.invite': - // Tell user that he/she is invited to a game - console.log('Game invite', data.inviter); - console.log('data: ', data.invitee + " " + my.username) - // Display the modal for accepting or declining the invitation - - hideInviteButtons(data.inviter, data.invitee); - - const acceptButton = document.getElementById(`acceptButton${data.inviter}`); - const declineButton = document.getElementById(`declineButton${data.inviter}`); - if (data.invitee === my.username) { - if (lang === 'tr') - showToast(`${data.inviter} tarafından bir oyun daveti aldınız`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`${data.inviter} द्वारा आपको एक खेल आमंत्रण मिला है`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Você recebeu um convite de jogo de ${data.inviter}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`You received a game invitation from ${data.inviter}`, 'text-bg-success', 'bi bi-check-circle-fill') - acceptButton.style.display = 'flex'; - declineButton.style.display = 'flex'; - } - - acceptButton.onclick = function () { - accept(data.inviter); - acceptButton.style.display = 'none'; - declineButton.style.display = 'none'; - }; - - declineButton.onclick = function () { - decline(data.inviter); - acceptButton.style.display = 'none'; - declineButton.style.display = 'none'; - }; - - console.log(`Invited Group: ${data.inviter} vs ${data.invitee}`); - break; - - case 'tournament.match': - player1.username = data.player1; - player2.username = data.player2; - my.game_id = data.game_id; - my.tournament_id = data.tournament_id; - leftArea.style.display = 'none'; - if (lang === 'tr') - showToast(`Turnuva maçı başladı! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`टूर्नामेंट मैच शुरू हो गया! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Jogo de torneio começou! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`Tournament match started! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - - render(); - showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill') - - document.addEventListener("keydown", SpaceKeyDown); - - console.log(`Tournament Id: ${data.tournament_id}, Match Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); - break; - - case 'chat.game': - player1.username = data.player1; - player2.username = data.player2; - my.game_id = data.game_id; - if (lang === 'tr') - showToast(`Chat maçı başladı! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`टूर्नामेंट मैच शुरू हो गया! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Jogo de torneio começou! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`Chat match started! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - - render(); - showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill') - - document.addEventListener("keydown", SpaceKeyDown); - - console.log(`Match Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); - break; - - case 'game.accept': - player1.username = data.accepted; - player2.username = data.accepter; - my.game_id = data.game_id; - if (data.accepter === my.username) { - if (lang === 'tr') - showToast(`Davetiniz ${data.accepted} tarafından kabul edildi`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`आपका निमंत्रण ${data.accepted} द्वारा स्वीकृत किया गया`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Seu convite foi aceito por ${data.accepted}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`You accepted the game invitation from ${data.accepted}`, 'text-bg-success', 'bi bi-check-circle-fill'); - my.opponent_username = data.accepted; // if gerekir mi? - } - else if (data.accepted === my.username) { - if (lang === 'tr') - showToast(`Davetiniz ${data.accepter} tarafından kabul edildi`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`आपका निमंत्रण ${data.accepter} द्वारा स्वीकृत किया गया`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Seu convite foi aceito por ${data.accepter}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`Your invitation is accepted by ${data.accepter}`, 'text-bg-success', 'bi bi-check-circle-fill'); - my.opponent_username = data.accepter; // if gerekir mi? - } - render(); - showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); - - - document.addEventListener("keydown", SpaceKeyDown); - - console.log(`Accepted Game Id: ${data.game_id} => ${data.accepted} vs ${data.accepter}`); - break; - - case 'game.decline': - if (data.declined === my.username) { - if (lang === 'tr') - showToast(`Davetiniz ${data.decliner} tarafından reddedildi`, 'text-bg-danger', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`आपका निमंत्रण ${data.decliner} द्वारा अस्वीकार किया गया`, 'text-bg-danger', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Seu convite foi recusado por ${data.decliner}`, 'text-bg-danger', 'bi bi-check-circle-fill'); - else - showToast(`Your invitation is declined by ${data.decliner}`, 'text-bg-danger', 'bi bi-check-circle-fill'); - } - console.log(`Declined Game => ${data.declined} vs ${data.decliner}`); - break; - - case 'game.start': - // if they vote for Start, start the game otherwise update votes - // Start the game - checkbox.disabled = true; - leftArea.style.display = 'none'; - if (data.vote == 2) { - if (lang === 'tr') - showToast(`3 saniye içinde ${player1.username} ve ${player2.username} arasında oyun başlıyor`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`3 सेकंड में ${player1.username} और ${player2.username} के बीच खेल शुरू हो रहा है`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`Jogo começando em 3 segundos entre ${player1.username} e ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`Game starting in 3 sec between ${player1.username} and ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); - - leaveButton.style.display = 'block'; - gameUtilsReset(); - - // make invitationMessage disappear after 3 seconds - - leaveButton.onclick = function () { - leaveGame(); - leaveButton.style.display = 'none'; - } - - // Control paddle1 with w, s keys - document.addEventListener("keydown", function(event) { - if (event.key === "w" || event.key === "W" || event.key === "ArrowUp") { - upPressed = true; - } else if (event.key === "s" || event.key === "S"|| event.key === "ArrowDown") { - downPressed = true; - } - if (event.key === '1' && likeaCheaterCount < 1 && likeaCheater == "true" && (gameMode === "Abilities" || tournament === "abilities")) { - sendAbility("likeaCheater"); - showToast('You used like a cheater!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); - likeaCheaterCount += 1; - } - else if (event.key === '2' && fastandFuriousCount < 1 && fastandFurious == "true" && isFrozenBallActive == false && (gameMode === "Abilities" || tournament === "abilities")) { - sendAbility("fastandFurious"); - showToast('You used fast and furious!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); - fastandFuriousCount += 1; - } - else if (event.key === '3' && frozenBallCount < 1 && frozenBall == "true" && (gameMode === "Abilities" || tournament === "abilities")) { - sendAbility("frozenBall"); - showToast('You used frozen ball!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); - isFrozenBallActive = true; - setTimeout(function () { - isFrozenBallActive = false; - }, 3000); - frozenBallCount += 1; - } - }); - - document.addEventListener("keyup", function(event) { - if (event.key === "w" || event.key === "W" || event.key === "ArrowUp") { - upPressed = false; - } else if (event.key === "s" || event.key === "S"|| event.key === "ArrowDown") { - downPressed = false; - } - }); - - setTimeout(function () { - startGame(); - }, 3000); - // Ask ball coordinates every 16 milliseconds - //setInterval(BallRequest, 16); - - console.log(`Started Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); - } - else if (data.vote == 1) { - console.log(`Waiting for Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); - } - else if (data.vote == 0) { - console.log(`None of the players hit space Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); - } - break; - - case 'game.leave': - //clearInterval(BallRequest); - leftArea.style.display = 'block'; - stopGame(); - var left_score = data.left_score; - var opponent_score = data.opponent_score; - var winner = data.winner; - var loser = data.loser; - document.getElementById('winnerText').innerText = winner; - document.getElementById('loserText').innerText = loser; - gameOverScreen.style.display = 'block'; - // Show some left game message with scores etc. - //showToast(`${data.left} left the game. Winner is ${winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); - // maybe put restart - leaveButton.style.display = 'none'; - console.log(`Left Game Id: ${data.game_id}`); - break; - - case 'game.end': - //clearInterval(BallRequest); - leftArea.style.display = 'block'; - stopGame(); - checkbox.disabled = false; - player1.score = data.player1_score; - player2.score = data.player2_score; - - var winner = data.winner; - var loser = data.loser; - console.log('Winner: ' + winner + ' Loser: ' + loser + ' Player1 score: ' + player1.score + ' Player2 score: ' + player2.score); - document.getElementById('winnerText').innerText = winner; - document.getElementById('loserText').innerText = loser; - gameOverScreen.style.display = 'block'; - // Show some game ended message with scores etc - if (lang === 'tr') - showToast(`Oyun bitti. Kazanan ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'hi') - showToast(`खेल समाप्त हो गया। विजेता ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else if (lang === 'pt') - showToast(`O jogo acabou. Vencedor é ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); - else - showToast(`Game is ended. Winner is ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); - // maybe put restart - - console.log(`Ended Game Id: ${data.game_id} => ${data.winner} won`); - break; - - case 'game.pause': - // Pause the game - console.log('Game id: ' + data.game_id + ' paused'); - break; - - case 'game.resume': - // Resume the game - console.log('Game id: ' + data.game_id + ' resumed'); - break; - - case 'game.ball': - //get ball position and update it - ballMove(data.x, data.y) - scoreUpdate(data.player1_score, data.player2_score) - //console.log(`Moving Ball Id: ${data.game_id} for ball: ${data.x} ${data.y}`); - break; - - case 'game.paddle': - //get paddle position and update it - paddleMove(data.player, data.y) - //console.log(`Moving Paddle Id: ${data.game_id} for ${data.player}: ${data.y}`); - break; - - case 'game.ability': - console.log(data.ability + ' is used!') - if (data.ability == 'giantMan' && (gameMode === "Abilities" || tournament === "abilities")) { - if (data.player == player1.username) - paddle1.height = 115 - else if (data.player == player2.username) - paddle2.height = 115 - - break; - - }}; -} - -matchsocket.sendJSON = function (data) { - matchsocket.send(JSON.stringify(data)); -} - -function addUsersToTable(usersArray) { - var tableBody = document.querySelector('.custom-table tbody'); - - usersArray.forEach(function(username) { - var row = document.createElement('tr'); - var usernameCell = document.createElement('td'); - usernameCell.textContent = username; - var actionsCell = document.createElement('td'); - - // Create buttons - var btnGroup = document.createElement('div'); - btnGroup.className = "btn-group"; - btnGroup.style.display = "flex"; - btnGroup.style.justifyContent = "center"; - - var inviteButton = document.createElement('button'); - inviteButton.type = "button"; - inviteButton.className = "invite-button"; - inviteButton.id = "inviteButton" + username; - inviteButton.innerHTML = ''; - - var acceptButton = document.createElement('button'); - acceptButton.type = "button"; - acceptButton.className = "accept-button"; - acceptButton.id = "acceptButton" + username; - acceptButton.innerHTML = ''; - - var declineButton = document.createElement('button'); - declineButton.type = "button"; - declineButton.className = "decline-button"; - declineButton.id = "declineButton" + username; - declineButton.innerHTML = ''; - - // Add buttons to button group - btnGroup.appendChild(inviteButton); - btnGroup.appendChild(acceptButton); - btnGroup.appendChild(declineButton); - - actionsCell.appendChild(btnGroup); - row.appendChild(usernameCell); - row.appendChild(actionsCell); - tableBody.appendChild(row); - - // Add event listener to invite button - inviteButton.addEventListener('click', function() { - invite('false', username); - }); - }); -} - -function removeUserFromTable(username) { - var tableBody = document.querySelector('.custom-table tbody'); - var rows = tableBody.querySelectorAll('tr'); - - rows.forEach(function(row) { - var usernameCell = row.querySelector('td:first-child'); - if (usernameCell.textContent.trim() === username.trim()) { - tableBody.removeChild(row); - } - }); -} - - - -function invite(matchmaking = 'false', username) { - // Get necessary data and call socket.sendJSON - if (lang === 'tr') - showToast(`Oyuna ${username} davet ettiniz`, 'text-bg-success', 'bi bi-check-circle-fill') - else if (lang === 'hi') - showToast(`आपने ${username} को एक खेल के लिए आमंत्रित किया`, 'text-bg-success', 'bi bi-check-circle-fill') - else if (lang === 'pt') - showToast(`Você convidou ${username} para um jogo`, 'text-bg-success', 'bi bi-check-circle-fill') - else - showToast(`You invited ${username} to a game`, 'text-bg-success', 'bi bi-check-circle-fill') - matchsocket.sendJSON({ - action: 'invite', - matchmaking: matchmaking, - invitee_username: username, - }); -} - -matchmakingButton.onclick = function () { - invite('true', ''); -} - -//---------------------------------------------- - -function hideInviteButtons(username1, username2) { - // Get the invite buttons for the two users - const inviteButton1 = document.getElementById(`inviteButton${username1}`); - const inviteButton2 = document.getElementById(`inviteButton${username2}`); - - // Hide the invite buttons - if (inviteButton1) inviteButton1.style.display = 'none'; - if (inviteButton2) inviteButton2.style.display = 'none'; -} - -function exitGame() { - console.log("id: " + my.game_id); - matchsocket.sendJSON({ - action: 'exit', - game_id: my.game_id, - }); - - swapApp('/pong-game-find') -} - -function accept(inviter) { - // Get necessary data and call socket.sendJSON - matchsocket.sendJSON({ - action: 'accept', - inviter_username: inviter, - }); -} - -function decline(inviter) { - // Get necessary data and call socket.sendJSON - matchsocket.sendJSON({ - action: 'decline', - inviter_username: inviter, - }); -} - -// Vote count ll be 1 at start if -function startRequest(player1, player2) { - matchsocket.sendJSON({ - action: 'start.request', - game_id: my.game_id, - opponent: my.opponent_username, - vote: 1, - }); -} - -function SpaceKeyDown(event) { - if (event.code === "Space") { - startRequest(my.username, my.opponent_username); - // Remove the event listener after it has been triggered once - document.removeEventListener("keydown", SpaceKeyDown); - } -} - - -function leaveGame() { - matchsocket.sendJSON({ - action: 'leave.game', - game_id: my.game_id, - left: my.username, - opponent: my.opponent_username, - }); -} - -// send this in keydown event -function PaddleRequest(direction) { - // Get necessary data and call socket.sendJSON - matchsocket.sendJSON({ - action: 'paddle', - game_id: my.game_id, - direction: direction - }); -} - -function updatePaddlePosition() { - if (upPressed) { - PaddleRequest('up'); - } else if (downPressed) { - PaddleRequest('down'); - } -} - -function sendAbility(ability) { - matchsocket.sendJSON({ - action: 'ability', - abilities: ability, - game_id: my.game_id, - }); -} - - - -// send this in setInterval(update, 16) this ll be game state -function BallRequest() { - // Get necessary data and call socket.sendJSON - matchsocket.sendJSON({ - action: 'ball', - game_id: my.game_id, - }); -} - -document.getElementById('exitButton').addEventListener('click', exitGame); - -checkbox.addEventListener('change', function() { - // Checkbox'un durumuna göre etiketin innerHTML değerini değiştirme - if (checkbox.checked) { - gameMode = "Abilities"; - if (lang === 'tr') - selectedGameModeLabel.innerHTML = "Yetenekler"; - else if (lang === 'hi') - selectedGameModeLabel.innerHTML = "क्षमताएँ"; - else if (lang === 'pt') - selectedGameModeLabel.innerHTML = "Habilidades"; - else - selectedGameModeLabel.innerHTML = "Abilities"; - } else { - gameMode = "Vanilla"; - if (lang === 'tr') - selectedGameModeLabel.innerHTML = "Vanilya"; - else if (lang === 'hi') - selectedGameModeLabel.innerHTML = "वैनिला"; - else if (lang === 'pt') - selectedGameModeLabel.innerHTML = "Baunilha"; - else - selectedGameModeLabel.innerHTML = "Vanilla"; - } -}); - -const gameModeSelect = document.getElementById("gameModeSelect"); - -gameModeSelect.addEventListener("mouseenter", function() { - document.querySelector(".game-mode-info").style.display = "block"; -}); - -gameModeSelect.addEventListener("mouseleave", function() { - document.querySelector(".game-mode-info").style.display = "none"; -}); +export function RemotePong() { + +const cookie = document.cookie.split('; ').find(row => row.startsWith('selectedLanguage=')); +const lang = cookie ? cookie.split('=')[1] : 'en'; + +function showToast(content, status, iconClass) { + const liveToast = document.getElementById('liveToast'); + var toastContent = document.querySelector('#liveToast .fw-semibold'); + var toastIcon = document.querySelector('.toast-body .i-class i'); + + toastIcon.className = iconClass; + liveToast.classList.remove('text-bg-danger'); + liveToast.className = 'toast'; + liveToast.classList.add(status); + + toastContent.textContent = content; + const toast = new bootstrap.Toast(liveToast); + toast.show(); + setTimeout(function() { + toast.hide(); + }, 8000); +} + + // Extract game_id and game_type from the URL +const pathArray = window.location.pathname.split('/'); +const gameType = pathArray[2]; // Assuming game_type is the third segment of the URL +const gameId = pathArray[3]; // Assuming game_id is the fourth segment of the URL + + +// Connect to the WebSocket server using the extracted game_id and game_type +const matchsocket = new WebSocket(`ws://${window.location.host}/ws/remote-game/${gameType}/${gameId}/`); //? Maybe we need to pass game type and game id here + + +const canvas = document.getElementById('pongCanvas'); +var ctx = canvas.getContext("2d"); +canvas.width = 800; +canvas.height = 600; + +const checkbox = document.getElementById('flexSwitchCheckDefault'); +const selectedGameModeLabel = document.getElementById('selectedGameMode'); + +let gameMode = "Vanilla"; + +// Custom items +const leftArea = document.getElementById('left-area-display'); +const paddleColor = document.querySelector('.left-card').dataset.paddlecolor; +const playgroundColor = document.querySelector('.left-card').dataset.playgroundcolor; +canvas.style.borderColor = playgroundColor; +const giantMan = document.querySelector('.left-card').dataset.giantman; +const likeaCheater = document.querySelector('.left-card').dataset.likeacheater; +const fastandFurious = document.querySelector('.left-card').dataset.fastandfurious; +const rageofFire = document.querySelector('.left-card').dataset.rageoffire; +const frozenBall = document.querySelector('.left-card').dataset.frozenball; +const tournament = document.querySelector('.left-card').dataset.tournament; + +let likeaCheaterCount = 0; +let fastandFuriousCount = 0; +let frozenBallCount = 0; + +let isFrozenBallActive = false; + +// Paddle objects +var paddleWidth = 10; +var paddleHeight = 100; +var paddleY = (canvas.height - paddleHeight) / 2; +var paddle1 = {x: 0, y: paddleY, width: paddleWidth, height: paddleHeight}; +var paddle2 = {x: canvas.width - paddleWidth, y: paddleY, width: paddleWidth, height: paddleHeight}; + +// Ball object +var ball = {x: canvas.width / 2, y: canvas.height / 2, radius: 10}; + +// maybe merge with my object +var player1 = {username: '', score: 0}; +var player2 = {username: '', score: 0}; + +var my = { + username: '', opponent_username: '', game_id: '', tournament_id: '', +}; + +let upPressed = false; +let downPressed = false; + +//Button +//const startButton = document.getElementById('startButton'); +const leaveButton = document.getElementById('leaveButton'); +leaveButton.style.display = 'none'; +const gameOverScreen = document.getElementById('gameOverScreen'); +const matchmakingButton = document.getElementById('matchmakingButton'); +//const restartButton = document.getElementById('restartButton'); +//startButton.style.display = 'none'; +//restartButton.style.display = 'none'; + +// Envai +var textWidth1 = ctx.measureText(player1.username + ": " + player1.score).width; +var textWidth2 = ctx.measureText(player2.username + ": " + player2.score).width; + +/// Draw everything +function render() { + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Create a radial gradient for the background + var gradient = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 10, canvas.width / 2, canvas.height / 2, 300); + gradient.addColorStop(0, 'lightgrey'); + gradient.addColorStop(1, 'darkgrey'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw the middle dotted line + ctx.beginPath(); + ctx.setLineDash([5, 15]); + ctx.moveTo(canvas.width / 2, 0); + ctx.lineTo(canvas.width / 2, canvas.height); + ctx.strokeStyle = "black"; + ctx.stroke(); + + // Draw the middle dotted circle + ctx.beginPath(); + ctx.arc(canvas.width / 2, canvas.height / 2, 50, 0, Math.PI * 2, false); + ctx.setLineDash([5, 15]); + ctx.stroke(); + + + // Add shadow to the paddles + ctx.shadowColor = 'black'; + ctx.shadowBlur = 10; + ctx.shadowOffsetX = 5; + ctx.shadowOffsetY = 5; + + ctx.fillStyle = paddleColor; + ctx.fillRect(paddle1.x, paddle1.y, paddle1.width, paddle1.height); + // If paddle2 is on the right, draw the shadow to the left + ctx.shadowOffsetX = -5; + ctx.shadowOffsetY = 5; + ctx.fillRect(paddle2.x, paddle2.y, paddle2.width, paddle2.height); + + // Add shiny effect to the ball + ctx.beginPath(); + ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, false); + var gradient = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.radius); + gradient.addColorStop(0, 'white'); + gradient.addColorStop(0.1, 'gold'); + gradient.addColorStop(1, 'darkorange'); + ctx.fillStyle = gradient; + ctx.fill(); + ctx.closePath(); + + // Reset shadow properties + ctx.shadowColor = 'transparent'; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + + ctx.font = "14px Roboto"; + ctx.fillStyle = 'white'; + ctx.fillText(player1.username + ": " + player1.score, 50, 20); + ctx.fillText(player2.username + ": " + player2.score, canvas.width - textWidth2 - 80, 20); +} + +var requestId; + +// The main game loop +var startGame = function () { + + + BallRequest(); + updatePaddlePosition(); + render(); + // Request to do this again ASAP + requestId = requestAnimationFrame(startGame); +}; + +// When you want to stop the game loop +var stopGame = function() { + cancelAnimationFrame(requestId); +}; + +function gameUtilsReset() { + likeaCheaterCount = 0; + fastandFuriousCount = 0; + frozenBallCount = 0; + isFrozenBallActive = false; + + if (giantMan == "true" && (gameMode === "Abilities" || tournament === "abilities")) + sendAbility("giantMan"); + if (rageofFire == "true" && (gameMode === "Abilities" || tournament === "abilities")) + sendAbility("rageofFire"); +} + +// Cross-browser support for requestAnimationFrame +var w = window; +requestAnimationFrame = + w.requestAnimationFrame || + w.webkitRequestAnimationFrame || + w.msRequestAnimationFrame || + w.mozRequestAnimationFrame; + +function paddleMove(player, y) { + if (player === player1.username) { + paddle1.y = y; + } else if (player === player2.username) { + paddle2.y = y; + } +} + +function ballMove(x, y) { + ball.x = x; + ball.y = y; +} + +function scoreUpdate(player1_score, player2_score) { + player1.score = player1_score; + player2.score = player2_score; +} + +matchsocket.onopen = function (e) { + // Show some greeting message + console.log('WebSocket connection established'); +} + +matchsocket.onclose = function (e) { + //clearInterval(BallRequest); + stopGame(); + console.error('WebSocket connection closed'); +} + +matchsocket.onerror = function (e) { + console.error('Error: ' + e.data); + //clearInterval(BallRequest); + stopGame(); +} + +matchsocket.onmessage = function (e) { + const data = JSON.parse(e.data); + //console.log(data); + switch (data.type) { + case 'inlobby': + // Self send message + console.log('In lobby', data.user); + if (my.username === '') { + my.username = data.user; + } + // Take online users usernames and display them + addUsersToTable(data.users); + console.log('Online users', data.users); + break; + + case 'user.inlobby': + // Send others i joined the lobby + console.log('User in lobby', data.user); + // Add user to online users list + if (data.user !== my.username) { + addUsersToTable([data.user]); + } + if (lang === 'tr') + showToast(data.user + ' katıldı!', 'text-bg-success', 'bi bi-check-circle-fill') + else if (lang === 'hi') + showToast(data.user + ' शामिल हो गया!', 'text-bg-success', 'bi bi-check-circle-fill') + else if (lang === 'pt') + showToast(data.user + ' entrou!', 'text-bg-success', 'bi bi-check-circle-fill') + else + showToast(data.user + ' joined!', 'text-bg-success', 'bi bi-check-circle-fill') + break; + + case 'user.outlobby': + // Send others user left the lobby + console.log('User out lobby', data.user); + // Remove user from online users list + removeUserFromTable(data.user); + if (lang === 'tr') + showToast(data.user + ' ayrıldı!', 'text-bg-danger', 'bi bi-check-circle-fill') + else if (lang === 'hi') + showToast(data.user + ' चला गया!', 'text-bg-danger', 'bi bi-check-circle-fill') + else if (lang === 'pt') + showToast(data.user + ' saiu!', 'text-bg-danger', 'bi bi-check-circle-fill') + else + showToast(data.user + ' left!', 'text-bg-danger', 'bi bi-check-circle-fill') + break; + + case 'game.disconnected': + //clearInterval(BallRequest); + stopGame(); + gameOverScreen.style.display = 'block'; + showToast(`${data.disconnected} disconnected You are automatically winner`, 'text-bg-danger', 'bi bi-check-circle-fill') + console.log('Player disconnected', data.disconnected); + + case 'game.invite': + // Tell user that he/she is invited to a game + console.log('Game invite', data.inviter); + console.log('data: ', data.invitee + " " + my.username) + // Display the modal for accepting or declining the invitation + + hideInviteButtons(data.inviter, data.invitee); + + const acceptButton = document.getElementById(`acceptButton${data.inviter}`); + const declineButton = document.getElementById(`declineButton${data.inviter}`); + if (data.invitee === my.username) { + if (lang === 'tr') + showToast(`${data.inviter} tarafından bir oyun daveti aldınız`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`${data.inviter} द्वारा आपको एक खेल आमंत्रण मिला है`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Você recebeu um convite de jogo de ${data.inviter}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`You received a game invitation from ${data.inviter}`, 'text-bg-success', 'bi bi-check-circle-fill') + acceptButton.style.display = 'flex'; + declineButton.style.display = 'flex'; + } + + acceptButton.onclick = function () { + accept(data.inviter); + acceptButton.style.display = 'none'; + declineButton.style.display = 'none'; + }; + + declineButton.onclick = function () { + decline(data.inviter); + acceptButton.style.display = 'none'; + declineButton.style.display = 'none'; + }; + + console.log(`Invited Group: ${data.inviter} vs ${data.invitee}`); + break; + + case 'tournament.match': + checkbox.disabled = true; + leftArea.style.display = 'none'; + player1.username = data.player1; + player2.username = data.player2; + my.game_id = data.game_id; + my.tournament_id = data.tournament_id; + leftArea.style.display = 'none'; + if (lang === 'tr') + showToast(`Turnuva maçı başladı! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`टूर्नामेंट मैच शुरू हो गया! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Jogo de torneio começou! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`Tournament match started! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + + render(); + showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill') + + document.addEventListener("keydown", SpaceKeyDown); + + console.log(`Tournament Id: ${data.tournament_id}, Match Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); + break; + + case 'chat.game': + checkbox.disabled = true; + leftArea.style.display = 'none'; + player1.username = data.player1; + player2.username = data.player2; + my.game_id = data.game_id; + my.opponent_username = data.player1 === my.username ? data.player2 : data.player1; + if (lang === 'tr') + showToast(`Chat maçı başladı! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`टूर्नामेंट मैच शुरू हो गया! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Jogo de torneio começou! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`Chat match started! ${player1.username} vs ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + + render(); + showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill') + + document.addEventListener("keydown", SpaceKeyDown); + + console.log(`Match Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); + break; + + case 'game.accept': + player1.username = data.accepted; + player2.username = data.accepter; + my.game_id = data.game_id; + if (data.accepter === my.username) { + if (lang === 'tr') + showToast(`Davetiniz ${data.accepted} tarafından kabul edildi`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`आपका निमंत्रण ${data.accepted} द्वारा स्वीकृत किया गया`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Seu convite foi aceito por ${data.accepted}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`You accepted the game invitation from ${data.accepted}`, 'text-bg-success', 'bi bi-check-circle-fill'); + my.opponent_username = data.accepted; // if gerekir mi? + } + else if (data.accepted === my.username) { + if (lang === 'tr') + showToast(`Davetiniz ${data.accepter} tarafından kabul edildi`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`आपका निमंत्रण ${data.accepter} द्वारा स्वीकृत किया गया`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Seu convite foi aceito por ${data.accepter}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`Your invitation is accepted by ${data.accepter}`, 'text-bg-success', 'bi bi-check-circle-fill'); + my.opponent_username = data.accepter; // if gerekir mi? + } + render(); + showToast('Press Space to start the game', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); + + + document.addEventListener("keydown", SpaceKeyDown); + + console.log(`Accepted Game Id: ${data.game_id} => ${data.accepted} vs ${data.accepter}`); + break; + + case 'game.decline': + if (data.declined === my.username) { + if (lang === 'tr') + showToast(`Davetiniz ${data.decliner} tarafından reddedildi`, 'text-bg-danger', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`आपका निमंत्रण ${data.decliner} द्वारा अस्वीकार किया गया`, 'text-bg-danger', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Seu convite foi recusado por ${data.decliner}`, 'text-bg-danger', 'bi bi-check-circle-fill'); + else + showToast(`Your invitation is declined by ${data.decliner}`, 'text-bg-danger', 'bi bi-check-circle-fill'); + } + console.log(`Declined Game => ${data.declined} vs ${data.decliner}`); + break; + + case 'game.start': + // if they vote for Start, start the game otherwise update votes + // Start the game + checkbox.disabled = true; + leftArea.style.display = 'none'; + if (data.vote == 2) { + if (lang === 'tr') + showToast(`3 saniye içinde ${player1.username} ve ${player2.username} arasında oyun başlıyor`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`3 सेकंड में ${player1.username} और ${player2.username} के बीच खेल शुरू हो रहा है`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`Jogo começando em 3 segundos entre ${player1.username} e ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`Game starting in 3 sec between ${player1.username} and ${player2.username}`, 'text-bg-success', 'bi bi-check-circle-fill'); + + leaveButton.style.display = 'block'; + gameUtilsReset(); + + // make invitationMessage disappear after 3 seconds + + leaveButton.onclick = function () { + leaveGame(); + leaveButton.style.display = 'none'; + } + + // Control paddle1 with w, s keys + document.addEventListener("keydown", function(event) { + if (event.key === "w" || event.key === "W" || event.key === "ArrowUp") { + upPressed = true; + } else if (event.key === "s" || event.key === "S"|| event.key === "ArrowDown") { + downPressed = true; + } + if (event.key === '1' && likeaCheaterCount < 1 && likeaCheater == "true" && (gameMode === "Abilities" || tournament === "abilities")) { + sendAbility("likeaCheater"); + showToast('You used like a cheater!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); + likeaCheaterCount += 1; + } + else if (event.key === '2' && fastandFuriousCount < 1 && fastandFurious == "true" && isFrozenBallActive == false && (gameMode === "Abilities" || tournament === "abilities")) { + sendAbility("fastandFurious"); + showToast('You used fast and furious!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); + fastandFuriousCount += 1; + } + else if (event.key === '3' && frozenBallCount < 1 && frozenBall == "true" && (gameMode === "Abilities" || tournament === "abilities")) { + sendAbility("frozenBall"); + showToast('You used frozen ball!', 'text-bg-primary', 'bi bi-exclamation-triangle-fill'); + isFrozenBallActive = true; + setTimeout(function () { + isFrozenBallActive = false; + }, 3000); + frozenBallCount += 1; + } + }); + + document.addEventListener("keyup", function(event) { + if (event.key === "w" || event.key === "W" || event.key === "ArrowUp") { + upPressed = false; + } else if (event.key === "s" || event.key === "S"|| event.key === "ArrowDown") { + downPressed = false; + } + }); + + setTimeout(function () { + startGame(); + }, 3000); + // Ask ball coordinates every 16 milliseconds + //setInterval(BallRequest, 16); + + console.log(`Started Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); + } + else if (data.vote == 1) { + console.log(`Waiting for Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); + } + else if (data.vote == 0) { + console.log(`None of the players hit space Game Id: ${data.game_id} => ${data.player1} vs ${data.player2}`); + } + break; + + case 'game.leave': + //clearInterval(BallRequest); + leftArea.style.display = 'block'; + stopGame(); + var left_score = data.left_score; + var opponent_score = data.opponent_score; + var winner = data.winner; + var loser = data.loser; + document.getElementById('winnerText').innerText = winner; + document.getElementById('loserText').innerText = loser; + gameOverScreen.style.display = 'block'; + // Show some left game message with scores etc. + //showToast(`${data.left} left the game. Winner is ${winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); + // maybe put restart + leaveButton.style.display = 'none'; + console.log(`Left Game Id: ${data.game_id}`); + break; + + case 'game.end': + //clearInterval(BallRequest); + leftArea.style.display = 'block'; + stopGame(); + checkbox.disabled = false; + player1.score = data.player1_score; + player2.score = data.player2_score; + + var winner = data.winner; + var loser = data.loser; + console.log('Winner: ' + winner + ' Loser: ' + loser + ' Player1 score: ' + player1.score + ' Player2 score: ' + player2.score); + document.getElementById('winnerText').innerText = winner; + document.getElementById('loserText').innerText = loser; + gameOverScreen.style.display = 'block'; + // Show some game ended message with scores etc + if (lang === 'tr') + showToast(`Oyun bitti. Kazanan ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'hi') + showToast(`खेल समाप्त हो गया। विजेता ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else if (lang === 'pt') + showToast(`O jogo acabou. Vencedor é ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); + else + showToast(`Game is ended. Winner is ${data.winner}`, 'text-bg-success', 'bi bi-check-circle-fill'); + // maybe put restart + + console.log(`Ended Game Id: ${data.game_id} => ${data.winner} won`); + break; + + case 'game.pause': + // Pause the game + console.log('Game id: ' + data.game_id + ' paused'); + break; + + case 'game.resume': + // Resume the game + console.log('Game id: ' + data.game_id + ' resumed'); + break; + + case 'game.ball': + //get ball position and update it + ballMove(data.x, data.y) + scoreUpdate(data.player1_score, data.player2_score) + //console.log(`Moving Ball Id: ${data.game_id} for ball: ${data.x} ${data.y}`); + break; + + case 'game.paddle': + //get paddle position and update it + paddleMove(data.player, data.y) + //console.log(`Moving Paddle Id: ${data.game_id} for ${data.player}: ${data.y}`); + break; + + case 'game.ability': + console.log(data.ability + ' is used!') + if (data.ability == 'giantMan' && (gameMode === "Abilities" || tournament === "abilities")) { + if (data.player == player1.username) + paddle1.height = 115 + else if (data.player == player2.username) + paddle2.height = 115 + + break; + + }}; +} + +matchsocket.sendJSON = function (data) { + matchsocket.send(JSON.stringify(data)); +} + +function addUsersToTable(usersArray) { + var tableBody = document.querySelector('.custom-table tbody'); + + usersArray.forEach(function(username) { + var row = document.createElement('tr'); + var usernameCell = document.createElement('td'); + usernameCell.textContent = username; + var actionsCell = document.createElement('td'); + + // Create buttons + var btnGroup = document.createElement('div'); + btnGroup.className = "btn-group"; + btnGroup.style.display = "flex"; + btnGroup.style.justifyContent = "center"; + + var inviteButton = document.createElement('button'); + inviteButton.type = "button"; + inviteButton.className = "invite-button"; + inviteButton.id = "inviteButton" + username; + inviteButton.innerHTML = ''; + + var acceptButton = document.createElement('button'); + acceptButton.type = "button"; + acceptButton.className = "accept-button"; + acceptButton.id = "acceptButton" + username; + acceptButton.innerHTML = ''; + + var declineButton = document.createElement('button'); + declineButton.type = "button"; + declineButton.className = "decline-button"; + declineButton.id = "declineButton" + username; + declineButton.innerHTML = ''; + + // Add buttons to button group + btnGroup.appendChild(inviteButton); + btnGroup.appendChild(acceptButton); + btnGroup.appendChild(declineButton); + + actionsCell.appendChild(btnGroup); + row.appendChild(usernameCell); + row.appendChild(actionsCell); + tableBody.appendChild(row); + + // Add event listener to invite button + inviteButton.addEventListener('click', function() { + invite('false', username); + }); + }); +} + +function removeUserFromTable(username) { + var tableBody = document.querySelector('.custom-table tbody'); + var rows = tableBody.querySelectorAll('tr'); + + rows.forEach(function(row) { + var usernameCell = row.querySelector('td:first-child'); + if (usernameCell.textContent.trim() === username.trim()) { + tableBody.removeChild(row); + } + }); +} + + + +function invite(matchmaking = 'false', username) { + // Get necessary data and call socket.sendJSON + if (lang === 'tr') + showToast(`Oyuna ${username} davet ettiniz`, 'text-bg-success', 'bi bi-check-circle-fill') + else if (lang === 'hi') + showToast(`आपने ${username} को एक खेल के लिए आमंत्रित किया`, 'text-bg-success', 'bi bi-check-circle-fill') + else if (lang === 'pt') + showToast(`Você convidou ${username} para um jogo`, 'text-bg-success', 'bi bi-check-circle-fill') + else + showToast(`You invited ${username} to a game`, 'text-bg-success', 'bi bi-check-circle-fill') + matchsocket.sendJSON({ + action: 'invite', + matchmaking: matchmaking, + invitee_username: username, + }); +} + +matchmakingButton.onclick = function () { + invite('true', ''); +} + +//---------------------------------------------- + +function hideInviteButtons(username1, username2) { + // Get the invite buttons for the two users + const inviteButton1 = document.getElementById(`inviteButton${username1}`); + const inviteButton2 = document.getElementById(`inviteButton${username2}`); + + // Hide the invite buttons + if (inviteButton1) inviteButton1.style.display = 'none'; + if (inviteButton2) inviteButton2.style.display = 'none'; +} + +function exitGame() { + console.log("id: " + my.game_id); + matchsocket.sendJSON({ + action: 'exit', + game_id: my.game_id, + }); + + swapApp('/pong-game-find') +} + +function accept(inviter) { + // Get necessary data and call socket.sendJSON + matchsocket.sendJSON({ + action: 'accept', + inviter_username: inviter, + }); +} + +function decline(inviter) { + // Get necessary data and call socket.sendJSON + matchsocket.sendJSON({ + action: 'decline', + inviter_username: inviter, + }); +} + +// Vote count ll be 1 at start if +function startRequest(player1, player2) { + matchsocket.sendJSON({ + action: 'start.request', + game_id: my.game_id, + opponent: my.opponent_username, + vote: 1, + }); +} + +function SpaceKeyDown(event) { + if (event.code === "Space") { + startRequest(my.username, my.opponent_username); + // Remove the event listener after it has been triggered once + document.removeEventListener("keydown", SpaceKeyDown); + } +} + + +function leaveGame() { + matchsocket.sendJSON({ + action: 'leave.game', + game_id: my.game_id, + left: my.username, + opponent: my.opponent_username, + }); +} + +// send this in keydown event +function PaddleRequest(direction) { + // Get necessary data and call socket.sendJSON + matchsocket.sendJSON({ + action: 'paddle', + game_id: my.game_id, + direction: direction + }); +} + +function updatePaddlePosition() { + if (upPressed) { + PaddleRequest('up'); + } else if (downPressed) { + PaddleRequest('down'); + } +} + +function sendAbility(ability) { + matchsocket.sendJSON({ + action: 'ability', + abilities: ability, + game_id: my.game_id, + }); +} + + + +// send this in setInterval(update, 16) this ll be game state +function BallRequest() { + // Get necessary data and call socket.sendJSON + matchsocket.sendJSON({ + action: 'ball', + game_id: my.game_id, + }); +} + +document.getElementById('exitButton').addEventListener('click', exitGame); + +checkbox.addEventListener('change', function() { + // Checkbox'un durumuna göre etiketin innerHTML değerini değiştirme + if (checkbox.checked) { + gameMode = "Abilities"; + if (lang === 'tr') + selectedGameModeLabel.innerHTML = "Yetenekler"; + else if (lang === 'hi') + selectedGameModeLabel.innerHTML = "क्षमताएँ"; + else if (lang === 'pt') + selectedGameModeLabel.innerHTML = "Habilidades"; + else + selectedGameModeLabel.innerHTML = "Abilities"; + } else { + gameMode = "Vanilla"; + if (lang === 'tr') + selectedGameModeLabel.innerHTML = "Vanilya"; + else if (lang === 'hi') + selectedGameModeLabel.innerHTML = "वैनिला"; + else if (lang === 'pt') + selectedGameModeLabel.innerHTML = "Baunilha"; + else + selectedGameModeLabel.innerHTML = "Vanilla"; + } +}); + +const gameModeSelect = document.getElementById("gameModeSelect"); + +gameModeSelect.addEventListener("mouseenter", function() { + document.querySelector(".game-mode-info").style.display = "block"; +}); + +gameModeSelect.addEventListener("mouseleave", function() { + document.querySelector(".game-mode-info").style.display = "none"; +}); } \ No newline at end of file diff --git a/indianpong/static/js/signup.js b/indianpong/static/js/signup.js index b8121b7..e52cadf 100644 --- a/indianpong/static/js/signup.js +++ b/indianpong/static/js/signup.js @@ -71,25 +71,6 @@ export function initializeSignup() { } } -function showToast(content, status, iconClass) { - const liveToast = document.getElementById('liveToast'); - var toastContent = document.querySelector('#liveToast .fw-semibold'); - var toastIcon = document.querySelector('.toast-body .i-class i'); - - - toastIcon.className = iconClass; - liveToast.classList.remove('text-bg-danger'); - liveToast.className = 'toast'; - liveToast.classList.add(status); - - toastContent.textContent = content; - const toast = new bootstrap.Toast(liveToast); - toast.show(); - setTimeout(function() { - toast.hide(); - }, 8000); -} - export function makeRegister(check) { if (!check) return;