From ee21ed23692b73e83e4e76e363374161af82ad9e Mon Sep 17 00:00:00 2001 From: Hanmin Date: Wed, 9 Oct 2024 18:29:31 +0200 Subject: [PATCH 01/14] Frontend: Fixed some Typos and added Makefile rules --- Makefile | 19 +++++++++++-------- frontend/src/api/api.js | 2 +- frontend/src/api/auth.js | 4 ++-- frontend/src/api/token.js | 2 +- frontend/src/components/Auth/SignIn.js | 2 +- .../components/Auth/TwoFactorAuthSignIn.js | 1 - frontend/src/components/Auth/Verify.js | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 5cac1878..d0d3ba69 100644 --- a/Makefile +++ b/Makefile @@ -38,22 +38,19 @@ logs: intro ## Show the logs of the project @ $(DC) logs -f logs-backend: intro ## Show the logs of the backend - @ docker logs ft_transcendence-backend-1 + @ docker logs ft_transcendence-backend-1 -f logs-frontend: intro ## Show the logs of the frontend - @ docker logs ft_transcendence-frontend-1 + @ docker logs ft_transcendence-frontend-1 -f logs-harvester: intro ## Show the logs of the harvester - @ docker logs ft_transcendence-harvester-1 + @ docker logs ft_transcendence-harvester-1 -f logs-postgres: intro ## Show the logs of the database - @ docker logs ft_transcendence-postgres-1 + @ docker logs ft_transcendence-postgres-1 -f logs-statcruncher: intro ## Show the logs of the statcruncher - @ docker logs ft_transcendence-statcruncher-1 - -logs-traefik: intro ## Show the logs of the reverse proxy - @ docker logs ft_transcendence-traefik-1 + @ docker logs ft_transcendence-statcruncher-1 -f down: intro ## Stop the project @ $(DC) down @@ -61,4 +58,10 @@ down: intro ## Stop the project clean: down ## Stop the project and remove all the stopped containers / unused networks / dangling images / unused build caches (docker system prune -f) @ docker system prune -f +cleanv: down ## Stop the project and remove all the volumes + @ docker volume prune -af + +fclean: cleanv ## Stop the project and remove all the stopped containers / unused networks / dangling images / unused build caches / volumes + @ docker system prune -af + .PHONY: all up down clean diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 0e66bf24..40850f18 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -44,7 +44,7 @@ export const isRefreshExpired = () => { }; const API = axios.create({ - baseURL: `/api/v1/`, + baseURL: '/api/v1/', }); API.interceptors.request.use( diff --git a/frontend/src/api/auth.js b/frontend/src/api/auth.js index 9bf100c8..b59cfdd4 100644 --- a/frontend/src/api/auth.js +++ b/frontend/src/api/auth.js @@ -2,11 +2,11 @@ import axios from 'axios'; export const ApiLogin = async (username, password, otp) => { const data = otp ? { username, password, otp } : { username, password }; - const response = await axios.post(`/api/v1/auth/login`, data); + const response = await axios.post('/api/v1/auth/login', data); localStorage.setItem('token', response.data.access); localStorage.setItem('refresh', response.data.refresh); }; export const ApiSignup = async formData => { - await axios.post(`/api/v1/auth/register`, formData); + await axios.post('/api/v1/auth/register', formData); }; diff --git a/frontend/src/api/token.js b/frontend/src/api/token.js index e1d9c751..2fc0b8d8 100644 --- a/frontend/src/api/token.js +++ b/frontend/src/api/token.js @@ -14,7 +14,7 @@ const refreshToken = async () => { throw new Error("Refresh token expired"); } - const response = await axios.post(`/api/v1/auth/token/refresh`, { refresh }); + const response = await axios.post('/api/v1/auth/token/refresh', { refresh }); const newToken = response.data.access; localStorage.setItem("token", newToken); diff --git a/frontend/src/components/Auth/SignIn.js b/frontend/src/components/Auth/SignIn.js index 191896e8..fac51ed9 100644 --- a/frontend/src/components/Auth/SignIn.js +++ b/frontend/src/components/Auth/SignIn.js @@ -47,7 +47,7 @@ const SignIn = () => { const handleFortyTwo = event => { event.preventDefault(); - window.location.href = `/api/v1/auth/42/login`; + window.location.href = '/api/v1/auth/42/login'; }; return ( diff --git a/frontend/src/components/Auth/TwoFactorAuthSignIn.js b/frontend/src/components/Auth/TwoFactorAuthSignIn.js index e7eeb9d6..addf0ded 100644 --- a/frontend/src/components/Auth/TwoFactorAuthSignIn.js +++ b/frontend/src/components/Auth/TwoFactorAuthSignIn.js @@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next"; import axios from "axios"; import { useNotification } from "../../context/NotificationContext"; import { ApiLogin } from "../../api/auth"; -import logger from "../../api/logger"; import { useAuth } from "../../context/AuthContext"; import OTPInputComponent from "./OTPInput"; import { FormContainer } from "./styles/Authentication.styled"; diff --git a/frontend/src/components/Auth/Verify.js b/frontend/src/components/Auth/Verify.js index aea74c21..7bd21058 100644 --- a/frontend/src/components/Auth/Verify.js +++ b/frontend/src/components/Auth/Verify.js @@ -18,7 +18,7 @@ const Verify = () => { const code = params.get('code'); if (code) { - axios.post(`/api/v1/verify`, { code }) + axios.post('/api/v1/verify', { code }) .then(() => { window.location.href = '/signin'; }) From 2f82f8e1e6d4d42fe8e6a115872a4f93d98bb282 Mon Sep 17 00:00:00 2001 From: Hanmin Date: Wed, 9 Oct 2024 22:19:44 +0200 Subject: [PATCH 02/14] Todo: Updated Todo --- frontend/todo.md | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/todo.md b/frontend/todo.md index 6d25caec..e6705a51 100644 --- a/frontend/todo.md +++ b/frontend/todo.md @@ -1,4 +1,5 @@ # TODO +- check all tags to add
and
- Game: send invite - Profile: `users/@me/stats` -> win / loss / ratio From 5f4dd0dbd18dd9e22f50a490a247a38aa5111001 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Thu, 10 Oct 2024 08:30:10 +0200 Subject: [PATCH 03/14] img tags: Updated alt attributes --- frontend/src/components/Friends/FriendsList.js | 2 +- frontend/src/components/Friends/RequestsList.js | 2 +- frontend/src/components/Game/GameProfile.js | 4 ++-- frontend/src/components/Game/Tournament/JoinTournament.js | 2 +- frontend/src/components/Navigation/SearchList.js | 2 +- frontend/src/components/Profile/main/ProfilePicture.js | 2 +- frontend/src/components/Settings/Visibility.js | 2 +- frontend/todo.md | 1 - 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Friends/FriendsList.js b/frontend/src/components/Friends/FriendsList.js index 385dc117..e5c4e60d 100644 --- a/frontend/src/components/Friends/FriendsList.js +++ b/frontend/src/components/Friends/FriendsList.js @@ -54,7 +54,7 @@ const FriendsList = ({ friends, setIsRefetch }) => { handleProfile(friend.username)}> - + {friend.displayName} {setActivityDescription(friend.status?.activity)} diff --git a/frontend/src/components/Friends/RequestsList.js b/frontend/src/components/Friends/RequestsList.js index 19dcb0c2..ca918d2f 100644 --- a/frontend/src/components/Friends/RequestsList.js +++ b/frontend/src/components/Friends/RequestsList.js @@ -48,7 +48,7 @@ const RequestsList = ({ requests, setIsRefetch }) => { requests.map((request, key) => ( handleProfile(request.username)}> - + {request.displayName} {request.is === 'sender' ? ( diff --git a/frontend/src/components/Game/GameProfile.js b/frontend/src/components/Game/GameProfile.js index 60c56ca1..52add7b3 100644 --- a/frontend/src/components/Game/GameProfile.js +++ b/frontend/src/components/Game/GameProfile.js @@ -8,7 +8,7 @@ const GameProfile = ({ side, playerSide, player, opponent }) => { {playerSide === side ? ( player ? ( <> - + {player.username} ) : ( @@ -20,7 +20,7 @@ const GameProfile = ({ side, playerSide, player, opponent }) => { ) : ( opponent ? ( <> - + {opponent.username} ) : ( diff --git a/frontend/src/components/Game/Tournament/JoinTournament.js b/frontend/src/components/Game/Tournament/JoinTournament.js index b2a9e40c..032c5672 100644 --- a/frontend/src/components/Game/Tournament/JoinTournament.js +++ b/frontend/src/components/Game/Tournament/JoinTournament.js @@ -91,7 +91,7 @@ const JoinTournament = () => { {activeFriends.length ? ( activeFriends.map((friend) => ( - + {friend.displayName} )) diff --git a/frontend/src/components/Navigation/SearchList.js b/frontend/src/components/Navigation/SearchList.js index c1aab98b..425eebba 100644 --- a/frontend/src/components/Navigation/SearchList.js +++ b/frontend/src/components/Navigation/SearchList.js @@ -17,7 +17,7 @@ const SearchList = ({ results, setInput, setResults }) => { {results && results.length ? results.map((profile) => ( handleSelect(profile.username)}> - + {profile.displayName} )) : ( diff --git a/frontend/src/components/Profile/main/ProfilePicture.js b/frontend/src/components/Profile/main/ProfilePicture.js index 66235646..9b00fe40 100644 --- a/frontend/src/components/Profile/main/ProfilePicture.js +++ b/frontend/src/components/Profile/main/ProfilePicture.js @@ -8,7 +8,7 @@ const ProfilePicture = ({ profileUser }) => {
diff --git a/frontend/src/components/Settings/Visibility.js b/frontend/src/components/Settings/Visibility.js index 4f2e38ef..6699e9d6 100644 --- a/frontend/src/components/Settings/Visibility.js +++ b/frontend/src/components/Settings/Visibility.js @@ -45,7 +45,7 @@ const Visibility = () => { {blockedUsers.map((relation, id) => (
- + {relation.displayName}
handleUnblock(e, relation.relationID)}> diff --git a/frontend/todo.md b/frontend/todo.md index e6705a51..6d25caec 100644 --- a/frontend/todo.md +++ b/frontend/todo.md @@ -1,5 +1,4 @@ # TODO -- check all tags to add
and
- Game: send invite - Profile: `users/@me/stats` -> win / loss / ratio From 5088f38604704ff8ad1714bf214bf499231dacaa Mon Sep 17 00:00:00 2001 From: evan Date: Thu, 10 Oct 2024 15:13:13 +0200 Subject: [PATCH 04/14] core: http-less mode --- docker-compose.yml | 1 + frontend/config/nginx/nginx.conf | 24 +++++++++++++++++++ frontend/package.json | 2 +- frontend/src/api/api.js | 2 +- frontend/src/api/auth.js | 4 ++-- frontend/src/api/logger.js | 2 +- frontend/src/api/token.js | 2 +- frontend/src/components/Auth/SignIn.js | 2 +- .../components/Auth/TwoFactorAuthSignIn.js | 2 +- frontend/src/components/Auth/Verify.js | 2 +- 10 files changed, 34 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d4cdb9fb..76ba33bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,7 @@ services: - certs ports: - "8888:443" + - "8000:80" networks: - transcendence volumes: diff --git a/frontend/config/nginx/nginx.conf b/frontend/config/nginx/nginx.conf index 68008055..3d44c8c4 100644 --- a/frontend/config/nginx/nginx.conf +++ b/frontend/config/nginx/nginx.conf @@ -46,3 +46,27 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } + +server { + listen 80; + server_name localhost; + + location /api/ { + proxy_pass https://backend:8443; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + location /ws/ { + proxy_pass https://backend:8443; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/frontend/package.json b/frontend/package.json index 1013f778..3b966119 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "GENERATE_SOURCEMAP=false react-scripts build", + "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 0e66bf24..d3475f84 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -44,7 +44,7 @@ export const isRefreshExpired = () => { }; const API = axios.create({ - baseURL: `/api/v1/`, + baseURL: process.env.REACT_APP_ENV == 'production' ? '/api/v1' : 'http://localhost:8000/api/v1', }); API.interceptors.request.use( diff --git a/frontend/src/api/auth.js b/frontend/src/api/auth.js index 9bf100c8..bac3d658 100644 --- a/frontend/src/api/auth.js +++ b/frontend/src/api/auth.js @@ -2,11 +2,11 @@ import axios from 'axios'; export const ApiLogin = async (username, password, otp) => { const data = otp ? { username, password, otp } : { username, password }; - const response = await axios.post(`/api/v1/auth/login`, data); + const response = await axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/login' : 'http://localhost:8000/api/v1/auth/login', data); localStorage.setItem('token', response.data.access); localStorage.setItem('refresh', response.data.refresh); }; export const ApiSignup = async formData => { - await axios.post(`/api/v1/auth/register`, formData); + await axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/register' : 'http://localhost:8000/api/v1/auth/register', formData); }; diff --git a/frontend/src/api/logger.js b/frontend/src/api/logger.js index 7a00fca5..1629dba9 100644 --- a/frontend/src/api/logger.js +++ b/frontend/src/api/logger.js @@ -1,5 +1,5 @@ const logger = (message) => { - if (process.env.NODE_ENV === "development") { + if (process.env.REACT_APP_ENV === "development") { console.log(message); } }; diff --git a/frontend/src/api/token.js b/frontend/src/api/token.js index e1d9c751..8b4950e4 100644 --- a/frontend/src/api/token.js +++ b/frontend/src/api/token.js @@ -14,7 +14,7 @@ const refreshToken = async () => { throw new Error("Refresh token expired"); } - const response = await axios.post(`/api/v1/auth/token/refresh`, { refresh }); + const response = await axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/token/refresh' : 'http://localhost:8000/api/v1/auth/token/refresh', { refresh }); const newToken = response.data.access; localStorage.setItem("token", newToken); diff --git a/frontend/src/components/Auth/SignIn.js b/frontend/src/components/Auth/SignIn.js index 191896e8..b4b6709d 100644 --- a/frontend/src/components/Auth/SignIn.js +++ b/frontend/src/components/Auth/SignIn.js @@ -47,7 +47,7 @@ const SignIn = () => { const handleFortyTwo = event => { event.preventDefault(); - window.location.href = `/api/v1/auth/42/login`; + window.location.href = process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/42/login' : 'http://localhost:8000/api/v1/auth/42/login'; }; return ( diff --git a/frontend/src/components/Auth/TwoFactorAuthSignIn.js b/frontend/src/components/Auth/TwoFactorAuthSignIn.js index e7eeb9d6..1d07295a 100644 --- a/frontend/src/components/Auth/TwoFactorAuthSignIn.js +++ b/frontend/src/components/Auth/TwoFactorAuthSignIn.js @@ -30,7 +30,7 @@ const TwoFactorAuthSignIn = ({ username, password, setIsTwoFactorAuth, available }, [otpSent, addNotification, t]); const handlePlatform = platform => { - axios.post('/api/v1/auth/totp/request', { username, password, platform }) + axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/totp/request' : 'http://localhost:8000/api/v1/auth/totp/request', { username, password, platform }) .then(() => { setOtpSent(true); }) diff --git a/frontend/src/components/Auth/Verify.js b/frontend/src/components/Auth/Verify.js index aea74c21..009674ef 100644 --- a/frontend/src/components/Auth/Verify.js +++ b/frontend/src/components/Auth/Verify.js @@ -18,7 +18,7 @@ const Verify = () => { const code = params.get('code'); if (code) { - axios.post(`/api/v1/verify`, { code }) + axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/verify' : 'http://localhost:8000/api/v1/verify', { code }) .then(() => { window.location.href = '/signin'; }) From 2735a0bce77cf5e0c41e7bc48ee281ecf31002df Mon Sep 17 00:00:00 2001 From: evan Date: Thu, 10 Oct 2024 15:14:27 +0200 Subject: [PATCH 05/14] core: oop --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 3b966119..1013f778 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", + "build": "GENERATE_SOURCEMAP=false react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, From 995f242ea9942d2797c081838e5ea2442d093b25 Mon Sep 17 00:00:00 2001 From: evan Date: Thu, 10 Oct 2024 15:33:21 +0200 Subject: [PATCH 06/14] core: support websocket in http mode --- frontend/src/components/Game/Game.js | 2 +- frontend/src/context/RelationContext.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Game/Game.js b/frontend/src/components/Game/Game.js index 37a3f671..16d89423 100644 --- a/frontend/src/components/Game/Game.js +++ b/frontend/src/components/Game/Game.js @@ -30,7 +30,7 @@ const Game = () => { const maxReconnectAttempts = 5; - const { sendMessage, lastMessage, readyState } = useWebSocket('/ws/match', { + const { sendMessage, lastMessage, readyState } = useWebSocket(process.env.REACT_APP_ENV == 'production' ? '/ws/match' : 'ws://localhost:8000/ws/match', { onClose: event => { if (event.code === 1006) handleReconnect() }, shouldReconnect: () => true, }); diff --git a/frontend/src/context/RelationContext.js b/frontend/src/context/RelationContext.js index 41aa4848..9614997c 100644 --- a/frontend/src/context/RelationContext.js +++ b/frontend/src/context/RelationContext.js @@ -6,8 +6,8 @@ import { formatUserData } from "../api/user"; import { GetBlockedUsers, GetFriends, GetRequests } from "../scripts/relation"; import { useNotification } from "./NotificationContext"; -const WS_CHAT_URL = '/ws/chat/?token='; -const WS_STATUS_URL = '/ws/status/?token='; +const WS_CHAT_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/chat/?token=' : 'ws://localhost:8000/ws/chat/?token='; +const WS_STATUS_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/status/?token=' : 'ws://localhost:8000/ws/status/?token=' export const RelationContext = createContext({ conversations: [], From b64e8129acc35649c6a8d2c03e449d92372a6095 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 10:12:53 +0200 Subject: [PATCH 07/14] Devmode: Updated to enable dev mode --- backend/api/consumers.py | 12 ++++++++-- docker-compose.yml | 1 + frontend/config/nginx/nginx.conf | 24 +++++++++++++++++++ frontend/src/api/api.js | 2 +- frontend/src/api/logger.js | 2 +- frontend/src/api/token.js | 2 +- frontend/src/components/Auth/SignIn.js | 2 +- .../components/Auth/TwoFactorAuthSignIn.js | 3 ++- frontend/src/components/Auth/Verify.js | 2 +- frontend/src/components/Game/Game.js | 2 +- frontend/src/context/RelationContext.js | 4 ++-- 11 files changed, 45 insertions(+), 11 deletions(-) diff --git a/backend/api/consumers.py b/backend/api/consumers.py index 12dcd896..478f9572 100644 --- a/backend/api/consumers.py +++ b/backend/api/consumers.py @@ -340,6 +340,7 @@ async def connect(self): }) self.last_heartbeat = time.time() self.heartbeat_task = asyncio.create_task(self.heartbeat_check()) + self.match = None logger.info(f"[{self.__class__.__name__}] Connection accepted, heartbeat check task started") async def disconnect(self, close_code): @@ -375,7 +376,7 @@ async def heartbeat_check(self): await asyncio.sleep(2) # Check every 2 seconds current_time = time.time() if current_time - self.last_heartbeat > 10: # No heartbeat for 10 seconds - logger.warning(f"[{self.__class__.__name__}] No heartbeat received for 10 seconds, closing connection") + logger.warning(f"[{self.__class__.__name__}] User {self.user.userID} missed heartbeat too many times, closing connection") await self.close() break @@ -463,6 +464,9 @@ async def send_match_begin(self, match): } ) logger.info(f"[{self.__class__.__name__}] MATCH_BEGIN sent for match: {match.matchID}") + asyncio.create_task(self.delayed_match_start(match)) + + async def delayed_match_start(self, match): await asyncio.sleep(5) await self.start_match(match) @@ -488,6 +492,10 @@ async def handle_paddle_move(self, data): await self.send_match_update() async def handle_player_quit(self): + if self.match is None: + logger.info(f"[{self.__class__.__name__}] Player {self.user.userID} quit without being in a match") + return + logger.info(f"[{self.__class__.__name__}] Player {self.user.userID} quit the match {self.match.matchID}") if self.match.playerB is not None and self.match.playerA is not None: @@ -562,7 +570,7 @@ def get_opponent_info(self, match, user): opponent = User.objects.get(userID=opponent_id) opponent_data = UserSerializer(opponent).data safe_profile = get_safe_profile(opponent_data, me=False) - opponent_settings = UserSettings.objects.get(userID=opponent_id) + opponent_settings, created = UserSettings.objects.get_or_create(userID=opponent_id) opponent_settings_data = UserSettingsSerializer(opponent_settings).data safe_profile['paddle_skin'] = opponent_settings_data['selectedPaddleSkin'] logger.info(f"[{self.__class__.__name__}] Opponent info retrieved for: {opponent_id}") diff --git a/docker-compose.yml b/docker-compose.yml index d4cdb9fb..76ba33bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,7 @@ services: - certs ports: - "8888:443" + - "8000:80" networks: - transcendence volumes: diff --git a/frontend/config/nginx/nginx.conf b/frontend/config/nginx/nginx.conf index 68008055..3d44c8c4 100644 --- a/frontend/config/nginx/nginx.conf +++ b/frontend/config/nginx/nginx.conf @@ -46,3 +46,27 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } } + +server { + listen 80; + server_name localhost; + + location /api/ { + proxy_pass https://backend:8443; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + location /ws/ { + proxy_pass https://backend:8443; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index 40850f18..d3475f84 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -44,7 +44,7 @@ export const isRefreshExpired = () => { }; const API = axios.create({ - baseURL: '/api/v1/', + baseURL: process.env.REACT_APP_ENV == 'production' ? '/api/v1' : 'http://localhost:8000/api/v1', }); API.interceptors.request.use( diff --git a/frontend/src/api/logger.js b/frontend/src/api/logger.js index 7a00fca5..1629dba9 100644 --- a/frontend/src/api/logger.js +++ b/frontend/src/api/logger.js @@ -1,5 +1,5 @@ const logger = (message) => { - if (process.env.NODE_ENV === "development") { + if (process.env.REACT_APP_ENV === "development") { console.log(message); } }; diff --git a/frontend/src/api/token.js b/frontend/src/api/token.js index 2fc0b8d8..8b4950e4 100644 --- a/frontend/src/api/token.js +++ b/frontend/src/api/token.js @@ -14,7 +14,7 @@ const refreshToken = async () => { throw new Error("Refresh token expired"); } - const response = await axios.post('/api/v1/auth/token/refresh', { refresh }); + const response = await axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/token/refresh' : 'http://localhost:8000/api/v1/auth/token/refresh', { refresh }); const newToken = response.data.access; localStorage.setItem("token", newToken); diff --git a/frontend/src/components/Auth/SignIn.js b/frontend/src/components/Auth/SignIn.js index fac51ed9..b4b6709d 100644 --- a/frontend/src/components/Auth/SignIn.js +++ b/frontend/src/components/Auth/SignIn.js @@ -47,7 +47,7 @@ const SignIn = () => { const handleFortyTwo = event => { event.preventDefault(); - window.location.href = '/api/v1/auth/42/login'; + window.location.href = process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/42/login' : 'http://localhost:8000/api/v1/auth/42/login'; }; return ( diff --git a/frontend/src/components/Auth/TwoFactorAuthSignIn.js b/frontend/src/components/Auth/TwoFactorAuthSignIn.js index addf0ded..1d07295a 100644 --- a/frontend/src/components/Auth/TwoFactorAuthSignIn.js +++ b/frontend/src/components/Auth/TwoFactorAuthSignIn.js @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import axios from "axios"; import { useNotification } from "../../context/NotificationContext"; import { ApiLogin } from "../../api/auth"; +import logger from "../../api/logger"; import { useAuth } from "../../context/AuthContext"; import OTPInputComponent from "./OTPInput"; import { FormContainer } from "./styles/Authentication.styled"; @@ -29,7 +30,7 @@ const TwoFactorAuthSignIn = ({ username, password, setIsTwoFactorAuth, available }, [otpSent, addNotification, t]); const handlePlatform = platform => { - axios.post('/api/v1/auth/totp/request', { username, password, platform }) + axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/totp/request' : 'http://localhost:8000/api/v1/auth/totp/request', { username, password, platform }) .then(() => { setOtpSent(true); }) diff --git a/frontend/src/components/Auth/Verify.js b/frontend/src/components/Auth/Verify.js index 7bd21058..009674ef 100644 --- a/frontend/src/components/Auth/Verify.js +++ b/frontend/src/components/Auth/Verify.js @@ -18,7 +18,7 @@ const Verify = () => { const code = params.get('code'); if (code) { - axios.post('/api/v1/verify', { code }) + axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/verify' : 'http://localhost:8000/api/v1/verify', { code }) .then(() => { window.location.href = '/signin'; }) diff --git a/frontend/src/components/Game/Game.js b/frontend/src/components/Game/Game.js index 37a3f671..16d89423 100644 --- a/frontend/src/components/Game/Game.js +++ b/frontend/src/components/Game/Game.js @@ -30,7 +30,7 @@ const Game = () => { const maxReconnectAttempts = 5; - const { sendMessage, lastMessage, readyState } = useWebSocket('/ws/match', { + const { sendMessage, lastMessage, readyState } = useWebSocket(process.env.REACT_APP_ENV == 'production' ? '/ws/match' : 'ws://localhost:8000/ws/match', { onClose: event => { if (event.code === 1006) handleReconnect() }, shouldReconnect: () => true, }); diff --git a/frontend/src/context/RelationContext.js b/frontend/src/context/RelationContext.js index 41aa4848..9614997c 100644 --- a/frontend/src/context/RelationContext.js +++ b/frontend/src/context/RelationContext.js @@ -6,8 +6,8 @@ import { formatUserData } from "../api/user"; import { GetBlockedUsers, GetFriends, GetRequests } from "../scripts/relation"; import { useNotification } from "./NotificationContext"; -const WS_CHAT_URL = '/ws/chat/?token='; -const WS_STATUS_URL = '/ws/status/?token='; +const WS_CHAT_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/chat/?token=' : 'ws://localhost:8000/ws/chat/?token='; +const WS_STATUS_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/status/?token=' : 'ws://localhost:8000/ws/status/?token=' export const RelationContext = createContext({ conversations: [], From 1d802adea3f08398e42832bb24ac254fe018f24a Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 10:29:27 +0200 Subject: [PATCH 08/14] Dev mode: Fully functional --- frontend/src/api/api.js | 2 +- frontend/src/api/auth.js | 4 ++-- frontend/src/components/Auth/SignIn.js | 2 +- frontend/src/components/Auth/TwoFactorAuthSignIn.js | 3 +-- frontend/src/components/Auth/Verify.js | 2 +- frontend/src/context/RelationContext.js | 4 ++-- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js index d3475f84..423d1ed6 100644 --- a/frontend/src/api/api.js +++ b/frontend/src/api/api.js @@ -44,7 +44,7 @@ export const isRefreshExpired = () => { }; const API = axios.create({ - baseURL: process.env.REACT_APP_ENV == 'production' ? '/api/v1' : 'http://localhost:8000/api/v1', + baseURL: process.env.REACT_APP_ENV === 'production' ? '/api/v1' : 'http://localhost:8000/api/v1', }); API.interceptors.request.use( diff --git a/frontend/src/api/auth.js b/frontend/src/api/auth.js index b59cfdd4..66a0424b 100644 --- a/frontend/src/api/auth.js +++ b/frontend/src/api/auth.js @@ -2,11 +2,11 @@ import axios from 'axios'; export const ApiLogin = async (username, password, otp) => { const data = otp ? { username, password, otp } : { username, password }; - const response = await axios.post('/api/v1/auth/login', data); + const response = await axios.post(process.env.REACT_APP_ENV === 'production' ? '/api/v1/auth/login' : 'http://localhost:8000/api/v1/auth/login', data); localStorage.setItem('token', response.data.access); localStorage.setItem('refresh', response.data.refresh); }; export const ApiSignup = async formData => { - await axios.post('/api/v1/auth/register', formData); + await axios.post(process.env.REACT_APP_ENV === 'production' ? '/api/v1/auth/register' : 'http://localhost:8000/api/v1/auth/register', formData); }; diff --git a/frontend/src/components/Auth/SignIn.js b/frontend/src/components/Auth/SignIn.js index b4b6709d..522fdb02 100644 --- a/frontend/src/components/Auth/SignIn.js +++ b/frontend/src/components/Auth/SignIn.js @@ -47,7 +47,7 @@ const SignIn = () => { const handleFortyTwo = event => { event.preventDefault(); - window.location.href = process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/42/login' : 'http://localhost:8000/api/v1/auth/42/login'; + window.location.href = process.env.REACT_APP_ENV === 'production' ? '/api/v1/auth/42/login' : 'http://localhost:8000/api/v1/auth/42/login'; }; return ( diff --git a/frontend/src/components/Auth/TwoFactorAuthSignIn.js b/frontend/src/components/Auth/TwoFactorAuthSignIn.js index 1d07295a..5d303b79 100644 --- a/frontend/src/components/Auth/TwoFactorAuthSignIn.js +++ b/frontend/src/components/Auth/TwoFactorAuthSignIn.js @@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next"; import axios from "axios"; import { useNotification } from "../../context/NotificationContext"; import { ApiLogin } from "../../api/auth"; -import logger from "../../api/logger"; import { useAuth } from "../../context/AuthContext"; import OTPInputComponent from "./OTPInput"; import { FormContainer } from "./styles/Authentication.styled"; @@ -30,7 +29,7 @@ const TwoFactorAuthSignIn = ({ username, password, setIsTwoFactorAuth, available }, [otpSent, addNotification, t]); const handlePlatform = platform => { - axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/totp/request' : 'http://localhost:8000/api/v1/auth/totp/request', { username, password, platform }) + axios.post(process.env.REACT_APP_ENV === 'production' ? '/api/v1/auth/totp/request' : 'http://localhost:8000/api/v1/auth/totp/request', { username, password, platform }) .then(() => { setOtpSent(true); }) diff --git a/frontend/src/components/Auth/Verify.js b/frontend/src/components/Auth/Verify.js index 009674ef..d6b1c82b 100644 --- a/frontend/src/components/Auth/Verify.js +++ b/frontend/src/components/Auth/Verify.js @@ -18,7 +18,7 @@ const Verify = () => { const code = params.get('code'); if (code) { - axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/verify' : 'http://localhost:8000/api/v1/verify', { code }) + axios.post(process.env.REACT_APP_ENV === 'production' ? '/api/v1/verify' : 'http://localhost:8000/api/v1/verify', { code }) .then(() => { window.location.href = '/signin'; }) diff --git a/frontend/src/context/RelationContext.js b/frontend/src/context/RelationContext.js index 9614997c..a84c3452 100644 --- a/frontend/src/context/RelationContext.js +++ b/frontend/src/context/RelationContext.js @@ -6,8 +6,8 @@ import { formatUserData } from "../api/user"; import { GetBlockedUsers, GetFriends, GetRequests } from "../scripts/relation"; import { useNotification } from "./NotificationContext"; -const WS_CHAT_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/chat/?token=' : 'ws://localhost:8000/ws/chat/?token='; -const WS_STATUS_URL = process.env.REACT_APP_ENV == 'production' ? '/ws/status/?token=' : 'ws://localhost:8000/ws/status/?token=' +const WS_CHAT_URL = process.env.REACT_APP_ENV === 'production' ? '/ws/chat/?token=' : 'ws://localhost:8000/ws/chat/?token='; +const WS_STATUS_URL = process.env.REACT_APP_ENV === 'production' ? '/ws/status/?token=' : 'ws://localhost:8000/ws/status/?token=' export const RelationContext = createContext({ conversations: [], From 6ec1b7c441d2b157211e6811121f72593ce94046 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 10:45:34 +0200 Subject: [PATCH 09/14] Authentication: Adjusted Styling for Nodnarb --- .../src/components/Auth/styles/Authentication.styled.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Auth/styles/Authentication.styled.js b/frontend/src/components/Auth/styles/Authentication.styled.js index 178a74d8..f24ac31f 100644 --- a/frontend/src/components/Auth/styles/Authentication.styled.js +++ b/frontend/src/components/Auth/styles/Authentication.styled.js @@ -22,8 +22,8 @@ export const FormContainer = styled(Form)` align-items: center; gap: 20px; border-radius: 15px; - width: 700px; - padding: 80px 50px; + width: 750px; + padding: 50px 100px; margin-top: 80px; font-family: 'Inter', sans-serif; background: transparent; @@ -46,7 +46,7 @@ export const FormContainer = styled(Form)` & .mb-3 { width: 100%; position: relative; - margin: 10px 0; + margin: 20px 0; & > i { position: absolute; @@ -100,7 +100,7 @@ export const FormContainer = styled(Form)` .mb-3 input:not(:placeholder-shown) ~ span, .mb-3 input:invalid ~ span, .mb-3 input:focus ~ span { - transform: translateX(350px) translateY(-25px); + transform: translateX(400px) translateY(-25px); font-size: 0.8rem; padding: 5px 10px; background: #fff; @@ -136,6 +136,7 @@ export const LanguageDropdownButton = styled.select` font-size: 0.9rem; font-family: 'Orbitron', sans-serif; transition: background 0.3s ease, border 0.3s ease; + margin-bottom: 15px; &:hover { background: rgba(75, 0, 130, 0.6); From 636cb0202c01e402a868c0a1188c77e4b032bfe6 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 11:31:18 +0200 Subject: [PATCH 10/14] i18n: Muted i18n logs --- frontend/src/components/Navigation/LanguageDropdown.js | 2 ++ frontend/src/i18n.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Navigation/LanguageDropdown.js b/frontend/src/components/Navigation/LanguageDropdown.js index 7ca4a4a1..8fc3c9a6 100644 --- a/frontend/src/components/Navigation/LanguageDropdown.js +++ b/frontend/src/components/Navigation/LanguageDropdown.js @@ -2,6 +2,8 @@ import React from "react"; import { LanguageDropdownButton } from "./styles/LanguageDropdown.styled"; const LanguageDropdown = ({ handleChange, language }) => { + if (!language) return null; + return ( Date: Fri, 11 Oct 2024 11:31:37 +0200 Subject: [PATCH 11/14] Game: Fixed paddles movement range and ball fps --- backend/api/consumers.py | 8 ++++---- frontend/src/scripts/game.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/api/consumers.py b/backend/api/consumers.py index 478f9572..10f443be 100644 --- a/backend/api/consumers.py +++ b/backend/api/consumers.py @@ -484,9 +484,9 @@ async def handle_paddle_move(self, data): paddle_speed = 5 # Appropriate paddle speed (adjust as needed) if direction == 'up': - match_state[player_key]['paddle_y'] = min(690, match_state[player_key]['paddle_y'] + paddle_speed) + match_state[player_key]['paddle_y'] = min(682, match_state[player_key]['paddle_y'] + paddle_speed) elif direction == 'down': - match_state[player_key]['paddle_y'] = max(60, match_state[player_key]['paddle_y'] - paddle_speed) + match_state[player_key]['paddle_y'] = max(73, match_state[player_key]['paddle_y'] - paddle_speed) # logger.debug(f"[{self.__class__.__name__}] Paddle moved: {player_key} - {direction}") await self.send_match_update() @@ -611,7 +611,7 @@ async def run_match_loop(self, match_id): PADDLE_WIDTH = 10 PADDLE_HEIGHT = 60 BALL_RADIUS = 25 / 2 - BALL_SPEED = 0.3 + BALL_SPEED = 0.6 MAX_SCORE = 10 while match_id in self.active_matches: @@ -655,7 +655,7 @@ async def run_match_loop(self, match_id): break await self.send_match_update() - await asyncio.sleep(1 / 120) # 120 FPS + await asyncio.sleep(1 / 60) # 120 FPS async def send_paddle_hit(self, player, ball): await self.channel_layer.group_send( diff --git a/frontend/src/scripts/game.js b/frontend/src/scripts/game.js index 69441663..b453fb8d 100644 --- a/frontend/src/scripts/game.js +++ b/frontend/src/scripts/game.js @@ -54,7 +54,7 @@ const AddToScene = (scene, terrain, paddle1, paddle2, ball) => { const GameCanvas = (canvas, paddle1, paddle2, ball, terrain, hit) => { const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(40, terrain.WIDTH / terrain.HEIGHT, 0.1, 1000); - camera.position.set(0, 0, 20.7); + camera.position.set(0, 0, 20.8); camera.lookAt(0, 0, 0); const lights = Lightning(scene, terrain); From 8533ed2f39eb3f5241c55c779e379de871710d68 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 16:42:25 +0200 Subject: [PATCH 12/14] Game: Local Game implemented --- frontend/src/api/token.js | 2 +- frontend/src/app/Router.js | 4 +- frontend/src/components/Game/PlayMenu.js | 2 +- .../src/components/Game/local/GameLocal.js | 237 ++++++++++++++++++ .../src/components/Game/{ => remote}/Game.js | 10 +- .../components/Game/{ => remote}/GameScene.js | 4 +- 6 files changed, 249 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/Game/local/GameLocal.js rename frontend/src/components/Game/{ => remote}/Game.js (93%) rename frontend/src/components/Game/{ => remote}/GameScene.js (97%) diff --git a/frontend/src/api/token.js b/frontend/src/api/token.js index 8b4950e4..65637c6a 100644 --- a/frontend/src/api/token.js +++ b/frontend/src/api/token.js @@ -14,7 +14,7 @@ const refreshToken = async () => { throw new Error("Refresh token expired"); } - const response = await axios.post(process.env.REACT_APP_ENV == 'production' ? '/api/v1/auth/token/refresh' : 'http://localhost:8000/api/v1/auth/token/refresh', { refresh }); + const response = await axios.post(process.env.REACT_APP_ENV === 'production' ? '/api/v1/auth/token/refresh' : 'http://localhost:8000/api/v1/auth/token/refresh', { refresh }); const newToken = response.data.access; localStorage.setItem("token", newToken); diff --git a/frontend/src/app/Router.js b/frontend/src/app/Router.js index d188c7f3..f5100c85 100644 --- a/frontend/src/app/Router.js +++ b/frontend/src/app/Router.js @@ -10,7 +10,7 @@ import { isValidToken } from '../api/api'; import Root from '../components/Root'; import SignIn from '../components/Auth/SignIn'; import SignUp from '../components/Auth/SignUp'; -import Game from '../components/Game/Game'; +import Game from '../components/Game/remote/Game'; import Home from '../components/Home/Home'; import Profile from '../components/Profile/Profile'; import Verify from '../components/Auth/Verify'; @@ -24,6 +24,7 @@ import Friends from '../components/Friends/Friends'; import PageNotFound from '../components/PageNotFound/PageNotFound'; import Settings from '../components/Settings/Settings'; import { useAuth } from '../context/AuthContext'; +import GameLocal from '../components/Game/local/GameLocal'; const PrivateRoutes = () => { const { isLoggedIn, setIsLoggedIn } = useAuth(); @@ -73,6 +74,7 @@ const Router = createBrowserRouter(createRoutesFromElements( }> }/> }/> + }/> }/> diff --git a/frontend/src/components/Game/PlayMenu.js b/frontend/src/components/Game/PlayMenu.js index 6f9b39c9..263bd408 100644 --- a/frontend/src/components/Game/PlayMenu.js +++ b/frontend/src/components/Game/PlayMenu.js @@ -18,7 +18,7 @@ const PlayMenu = () => { navigate('/game', { state: { mode: '1v1' } })}>

1 v 1

- + navigate('/game-local')}>

Local

diff --git a/frontend/src/components/Game/local/GameLocal.js b/frontend/src/components/Game/local/GameLocal.js new file mode 100644 index 00000000..cc32ae3c --- /dev/null +++ b/frontend/src/components/Game/local/GameLocal.js @@ -0,0 +1,237 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { GameSceneContainer, PageContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "../styles/Game.styled"; +import GameCanvas from "../../../scripts/game"; +import { useNavigate } from "react-router-dom"; + +const GameLocal = () => { + const navigate = useNavigate(); + const [keyPressedA, setKeyPressedA] = useState(null); + const [keyPressedB, setKeyPressedB] = useState(null); + const [isHit, setIsHit] = useState(false); + const [timer, setTimer] = useState(5); + + const [scoreA, setScoreA] = useState(0); + const [scoreB, setScoreB] = useState(0); + const [isGameStarted, setIsGameStarted] = useState(false); + const [activateTimer, setActivateTimer] = useState(false); + + const canvas = useRef(null); + const paddle1 = useRef(null); + const paddle2 = useRef(null); + const ball = useRef(null); + const hit = useRef(null); + const intervalRef = useRef(null); + + + const terrain = useMemo(() => ({ + WIDTH: 1200, + HEIGHT: 750, + SCENEWIDTH: 22, + SCENEHEIGHT: 15, + SCALEX: 22 / 1200, + SCALEY: 15 / 750, + }), []); + + // Ball speed and direction + const ballVelocity = useRef({ x: 0.1, y: 0.1 }); + + // Paddle movement speed + const paddleSpeed = 0.1; + + const movePaddle = useCallback((direction, paddle) => { + if (!paddle.current) return; + if (direction === 'up') { + paddle.current.position.y = Math.min(terrain.SCENEHEIGHT / 2 - 1.37, paddle.current.position.y + paddleSpeed); // Move up, ensure it doesn't go beyond top bound + } + if (direction === 'down') { + paddle.current.position.y = Math.max(-terrain.SCENEHEIGHT / 2 + 1.45, paddle.current.position.y - paddleSpeed); // Move down, ensure it doesn't go beyond bottom bound + } + }, [terrain.SCENEHEIGHT, paddleSpeed]); + + useEffect(() => { + const handleKeyDown = event => { + if (!isGameStarted && !activateTimer) { + setActivateTimer(true); + return; + } + if (event.key === 'ArrowUp') setKeyPressedB('up'); + else if (event.key === 'ArrowDown') setKeyPressedB('down'); + else if (event.key === 'w') setKeyPressedA('up'); + else if (event.key === 's') setKeyPressedA('down'); + else if (event.key === 'q') navigate('/'); + } + + const handleKeyUp = event => { + if (event.key === 'ArrowUp' || event.key === 'ArrowDown') setKeyPressedB(null); + if (event.key === 'w' || event.key === 's') setKeyPressedA(null); + } + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + } + }, [navigate, isGameStarted, activateTimer]); + + // Reset ball to the center after a goal + const resetBall = useCallback(() => { + if (ball.current) { + ball.current.position.set(0, 0, 0); + + // Randomize the ball's direction: 50% chance to go left (-1) or right (1) + const directionX = Math.random() < 0.5 ? -1 : 1; + const directionY = Math.random() < 0.5 ? -1 : 1; + + // Set the ball's velocity with random direction on both axes + ballVelocity.current = { + x: directionX * 0.1, // Random left (-1) or right (1) + y: directionY * 0.1 // Random up (-1) or down (1) + }; + } + }, []); + + useEffect(() => { + if (!isHit) return; + const timeoutID = setTimeout(() => setIsHit(false), 500); + + return () => clearTimeout(timeoutID); + }, [isHit]); + + // Ball movement logic and collision detection + const updateBallPosition = useCallback(() => { + if (!ball.current || !paddle1.current || !paddle2.current) return; + + ball.current.position.x += ballVelocity.current.x; + ball.current.position.y += ballVelocity.current.y; + + if (ball.current.position.y > terrain.SCENEHEIGHT / 2 - 0.3 || ball.current.position.y < -terrain.SCENEHEIGHT / 2 + 0.4) { + ballVelocity.current.y *= -1; + } + + const paddleHeight = 120 * terrain.SCALEY; + const paddleWidth = 20 * terrain.SCALEX; + const ballRadius = 25 * terrain.SCALEX / 2; + + const paddle1Top = paddle1.current.position.y + paddleHeight / 2; + const paddle1Bottom = paddle1.current.position.y - paddleHeight / 2; + + const paddle2Top = paddle2.current.position.y + paddleHeight / 2; + const paddle2Bottom = paddle2.current.position.y - paddleHeight / 2; + + if (ball.current.position.x - ballRadius <= paddle1.current.position.x + paddleWidth / 2 && + ball.current.position.x - ballRadius >= paddle1.current.position.x - paddleWidth / 2 && + ball.current.position.y - ballRadius <= paddle1Top && + ball.current.position.y + ballRadius >= paddle1Bottom) { + ballVelocity.current.x *= -1; + ballVelocity.current.x *= 1.1; + ballVelocity.current.y *= 1.1; + hit.current = { x: ball.current.position.x, y: ball.current.position.y }; + setIsHit(true); + } + + if (ball.current.position.x + ballRadius >= paddle2.current.position.x - paddleWidth / 2 && + ball.current.position.x + ballRadius <= paddle2.current.position.x + paddleWidth / 2 && + ball.current.position.y - ballRadius <= paddle2Top && + ball.current.position.y + ballRadius >= paddle2Bottom) { + ballVelocity.current.x *= -1; + ballVelocity.current.x *= 1.1; + ballVelocity.current.y *= 1.1; + hit.current = { x: ball.current.position.x, y: ball.current.position.y }; + setIsHit(true); + } + + if (ball.current.position.x < -terrain.SCENEWIDTH / 2) { + setScoreB((prev) => prev + 1); + resetBall(); + } + + if (ball.current.position.x > terrain.SCENEWIDTH / 2) { + setScoreA((prev) => prev + 1); + resetBall(); + } + + if (scoreA >= 10 || scoreB >= 10) { + navigate('/'); + } + }, [terrain, resetBall, scoreA, scoreB, navigate]); + + // Timer logic + useEffect(() => { + if (activateTimer && timer > 0) { + intervalRef.current = setInterval(() => { + setTimer(prev => prev - 1); + }, 1000); + } + + if (timer === 0) { + setActivateTimer(false); + clearInterval(intervalRef.current); + setIsGameStarted(true); + } + return () => clearInterval(intervalRef.current); + }, [activateTimer, timer]); + + // Initialize game canvas + useEffect(() => { + if (!canvas.current) return; + + const { renderer, camera, dispose } = GameCanvas(canvas.current, paddle1, paddle2, ball, terrain, hit); + + const handleResize = () => { + renderer.setSize(terrain.WIDTH, terrain.HEIGHT); + camera.aspect = terrain.WIDTH / terrain.HEIGHT; + camera.updateProjectionMatrix(); + } + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + dispose(); + } + }, [terrain]); + + // Animate ball and game logic + useEffect(() => { + let animationFrameId; + + const gameLoop = () => { + if (isGameStarted) { + if (keyPressedA) movePaddle(keyPressedA, paddle1); + if (keyPressedB) movePaddle(keyPressedB, paddle2); + updateBallPosition(); + } + + animationFrameId = requestAnimationFrame(gameLoop); + }; + gameLoop(); + + return () => cancelAnimationFrame(animationFrameId); + }, [updateBallPosition, movePaddle, keyPressedA, keyPressedB, isGameStarted]); + + return ( + + + + + {scoreA} + {scoreB} + + {!isGameStarted && !activateTimer && ( + + Press any key to start the game + + )} + {activateTimer && ( + + {timer} + + )} + + + ); +}; + +export default GameLocal; diff --git a/frontend/src/components/Game/Game.js b/frontend/src/components/Game/remote/Game.js similarity index 93% rename from frontend/src/components/Game/Game.js rename to frontend/src/components/Game/remote/Game.js index 16d89423..cfa072aa 100644 --- a/frontend/src/components/Game/Game.js +++ b/frontend/src/components/Game/remote/Game.js @@ -1,11 +1,11 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import useWebSocket, { ReadyState } from "react-use-websocket"; -import GameProfiles from "./GameProfiles"; +import GameProfiles from "../GameProfiles"; import GameScene from "./GameScene"; -import { formatUserData } from "../../api/user"; -import logger from "../../api/logger"; -import { PageContainer } from "./styles/Game.styled"; +import { formatUserData } from "../../../api/user"; +import logger from "../../../api/logger"; +import { PageContainer } from "../styles/Game.styled"; const Game = () => { const navigate = useNavigate(); @@ -30,7 +30,7 @@ const Game = () => { const maxReconnectAttempts = 5; - const { sendMessage, lastMessage, readyState } = useWebSocket(process.env.REACT_APP_ENV == 'production' ? '/ws/match' : 'ws://localhost:8000/ws/match', { + const { sendMessage, lastMessage, readyState } = useWebSocket(process.env.REACT_APP_ENV === 'production' ? '/ws/match' : 'ws://localhost:8000/ws/match', { onClose: event => { if (event.code === 1006) handleReconnect() }, shouldReconnect: () => true, }); diff --git a/frontend/src/components/Game/GameScene.js b/frontend/src/components/Game/remote/GameScene.js similarity index 97% rename from frontend/src/components/Game/GameScene.js rename to frontend/src/components/Game/remote/GameScene.js index 509e1376..cc0d99c4 100644 --- a/frontend/src/components/Game/GameScene.js +++ b/frontend/src/components/Game/remote/GameScene.js @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { GameSceneContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "./styles/Game.styled"; -import GameCanvas from "../../scripts/game"; +import { GameSceneContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "../styles/Game.styled"; +import GameCanvas from "../../../scripts/game"; import { useNavigate } from "react-router-dom"; const GameScene = ({ matchState, hitPos, sendMessage, activateTimer, setActivateTimer }) => { From 34054d89b0ec47fe3c33b359ec8d2441f4c8eee4 Mon Sep 17 00:00:00 2001 From: hanmpark Date: Fri, 11 Oct 2024 16:53:42 +0200 Subject: [PATCH 13/14] Game: Added quit indication in local game --- frontend/src/components/Game/local/GameLocal.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Game/local/GameLocal.js b/frontend/src/components/Game/local/GameLocal.js index cc32ae3c..888b31bc 100644 --- a/frontend/src/components/Game/local/GameLocal.js +++ b/frontend/src/components/Game/local/GameLocal.js @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { GameSceneContainer, PageContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "../styles/Game.styled"; +import { GameSceneContainer, PageContainer, ProfilesContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "../styles/Game.styled"; import GameCanvas from "../../../scripts/game"; import { useNavigate } from "react-router-dom"; @@ -213,6 +213,9 @@ const GameLocal = () => { return ( + +

Press Q to quit game

+
From 3d76555784e7977b44f501c1e108f3ed107267bc Mon Sep 17 00:00:00 2001 From: Brandon <103316367+okbrandon@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:29:44 +0200 Subject: [PATCH 14/14] game: New event BALL_SCORED --- backend/api/consumers.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/api/consumers.py b/backend/api/consumers.py index 10f443be..4a1026cb 100644 --- a/backend/api/consumers.py +++ b/backend/api/consumers.py @@ -641,11 +641,11 @@ async def run_match_loop(self, match_id): if match_state['ball']['x'] <= 0: match_state['scores'][match_state['playerB']['id']] += 1 self.reset_ball(match_state) - logger.info(f"[{self.__class__.__name__}] Player B scored in match: {match_id}") + await self.send_ball_scored(match_state['playerB']) elif match_state['ball']['x'] + BALL_RADIUS >= TERRAIN_WIDTH: match_state['scores'][match_state['playerA']['id']] += 1 self.reset_ball(match_state) - logger.info(f"[{self.__class__.__name__}] Player A scored in match: {match_id}") + await self.send_ball_scored(match_state['playerA']) # Check if game has ended if match_state['scores'][match_state['playerA']['id']] >= MAX_SCORE or match_state['scores'][match_state['playerB']['id']] >= MAX_SCORE: @@ -674,6 +674,22 @@ async def paddle_hit(self, event): }) logger.info(f"[{self.__class__.__name__}] Paddle hit event sent for player: {event['player']['id']}") + async def send_ball_scored(self, player): + await self.channel_layer.group_send( + f"match_{self.match.matchID}", + { + "type": "ball.scored", + "player": player + } + ) + + async def ball_scored(self, event): + await self.send_json({ + "e": "BALL_SCORED", + "d": {"player": event["player"]} + }) + logger.info(f"[{self.__class__}] Ball scored event processed for player: {event['player']['id']}") + def reset_ball(self, match_state): dx = random.choice([-8, 8]) # Increased ball speed slightly dy = random.choice([-8, 8])