Skip to content

Commit

Permalink
Merge: frontend/Hanmin
Browse files Browse the repository at this point in the history
  • Loading branch information
hanmpark committed Oct 14, 2024
2 parents b705ec3 + c3ef8f0 commit 15e6fce
Show file tree
Hide file tree
Showing 40 changed files with 1,066 additions and 683 deletions.
49 changes: 49 additions & 0 deletions backend/api/views/leaderboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response

from ..models import User
from ..serializers import UserSerializer
from ..util import get_safe_profile
from ..validators import *

from .users import Stats


class Leaderboard(APIView):

@staticmethod
def get_leaderboard(period, order_by):
users = User.objects.all()
global_stats = {}

for user in users:
global_stats[user.userID] = Stats.get_user_stats(user, period)

leaderboard_data = [
{
'user': get_safe_profile(UserSerializer(user).data, me=False),
'stats': {
'gamesPlayed': global_stats[user.userID]['stats']['gamesPlayed'],
'gamesWon': global_stats[user.userID]['stats']['gamesWon'],
'gamesLost': global_stats[user.userID]['stats']['gamesLost']
}
}
for user in users
]

leaderboard_data.sort(key=lambda x: x['stats'][order_by], reverse=True)

return leaderboard_data

def get(self, request, *args, **kwargs):
period = request.query_params.get('period', 'lifetime')
requested_stats = request.query_params.get('stats', None)

if not requested_stats:
return Response({'error': 'No stats requested'}, status=status.HTTP_400_BAD_REQUEST)
if requested_stats not in ['gamesPlayed', 'gamesWon', 'gamesLost']:
return Response({'error': 'Invalid stats requested'}, status=status.HTTP_400_BAD_REQUEST)

leaderboard = self.get_leaderboard(period, requested_stats)
return Response(leaderboard, status=status.HTTP_200_OK)
1 change: 1 addition & 0 deletions frontend/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RouterProvider } from 'react-router-dom';
import NotificationProvider from '../context/NotificationContext';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
import 'react-circular-progressbar/dist/styles.css';
import { Suspense } from 'react';

function App() {
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/Auth/SignUp.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ const SignUp = () => {
/>
<span>{t('auth.signUp.passwordTitle')}</span>
{showPassword ? (
<i className="bi bi-eye-fill" onClick={() => setShowPassword(!showPassword)}/>
<i className="bi bi-eye-fill" onClick={() => setShowPassword(false)}/>
) : (
<i className="bi bi-eye" onClick={() => setShowPassword(!showPassword)}/>
<i className="bi bi-eye" onClick={() => setShowPassword(true)}/>
)}
</FormContainer.Group>
<FormContainer.Group className="mb-3">
Expand All @@ -143,9 +143,9 @@ const SignUp = () => {
/>
<span>{t('auth.signUp.confirmPasswordTitle')}</span>
{showCfPassword ? (
<i className="bi bi-eye-fill" onClick={() => setShowCfPassword(!showCfPassword)}/>
<i className="bi bi-eye-fill" onClick={() => setShowCfPassword(false)}/>
) : (
<i className="bi bi-eye" onClick={() => setShowCfPassword(!showCfPassword)}/>
<i className="bi bi-eye" onClick={() => setShowCfPassword(true)}/>
)}
</FormContainer.Group>
<p>{t('auth.signUp.alreadyRegistered')}<Link to="/signin">{t('auth.signUp.loginButton')}</Link></p>
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/Friends/FriendsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ const FriendsList = ({ friends, setIsRefetch }) => {
const { t } = useTranslation();

const setActivityDescription = activity => {
if (activity === "QUEUEING") {
return "In queue";
} else if (activity === "PLAYING_VS_AI") {
if (activity === "PLAYING_VS_AI") {
return "Playing vs AI";
} else if (activity === "PLAYING_MULTIPLAYER") {
return "Playing multiplayer";
} else if (activity === "PLAYING_LOCAL") {
return "Playing local";
} else if (activity === "HOME") {
return "In lobby";
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/Game/GameProfiles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { ProfilesContainer } from "./styles/Game.styled";
import { PressQContainer, ProfilesContainer } from "./styles/Game.styled";
import GameProfile from "./GameProfile";

const GameProfiles = ({ player, opponent, playerSide }) => {
Expand All @@ -11,7 +11,9 @@ const GameProfiles = ({ player, opponent, playerSide }) => {
player={player}
opponent={opponent}
/>
<p>Press <b>Q</b> to quit game</p>
<PressQContainer>
<p>Press <b>Q</b> to quit game</p>
</PressQContainer>
<GameProfile
side="right"
playerSide={playerSide}
Expand Down
75 changes: 55 additions & 20 deletions frontend/src/components/Game/local/GameLocal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { GameSceneContainer, PageContainer, ProfilesContainer, Score, ScoresContainer, StyledCanvas, Timer, TimerContainer } from "../styles/Game.styled";
import GameCanvas from "../../../scripts/game";
import { useNavigate } from "react-router-dom";
import GameCanvas from "../../../scripts/game";
import {
GameSceneContainer,
PageContainer,
Profile,
ProfileImage,
ProfileName,
ProfilesContainer,
Score,
ScoresContainer,
StyledCanvas,
Timer,
OverlayContainer
} from "../styles/Game.styled";
import PongButton from "../../../styles/shared/PongButton.styled";

const GameLocal = () => {
const navigate = useNavigate();
Expand All @@ -14,6 +27,7 @@ const GameLocal = () => {
const [scoreB, setScoreB] = useState(0);
const [isGameStarted, setIsGameStarted] = useState(false);
const [activateTimer, setActivateTimer] = useState(false);
const [gameOver, setGameOver] = useState(false);

const canvas = useRef(null);
const paddle1 = useRef(null);
Expand All @@ -22,7 +36,6 @@ const GameLocal = () => {
const hit = useRef(null);
const intervalRef = useRef(null);


const terrain = useMemo(() => ({
WIDTH: 1200,
HEIGHT: 750,
Expand All @@ -39,16 +52,18 @@ const GameLocal = () => {
// Paddle movement speed
const paddleSpeed = 0.1;

// Setting up paddle movement
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
paddle.current.position.y = Math.min(terrain.SCENEHEIGHT / 2 - 1.37, paddle.current.position.y + paddleSpeed);
}
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
paddle.current.position.y = Math.max(-terrain.SCENEHEIGHT / 2 + 1.45, paddle.current.position.y - paddleSpeed);
}
}, [terrain.SCENEHEIGHT, paddleSpeed]);

// Keyboard event listeners
useEffect(() => {
const handleKeyDown = event => {
if (!isGameStarted && !activateTimer) {
Expand Down Expand Up @@ -93,6 +108,7 @@ const GameLocal = () => {
}
}, []);

// Ball hit effect
useEffect(() => {
if (!isHit) return;
const timeoutID = setTimeout(() => setIsHit(false), 500);
Expand Down Expand Up @@ -148,19 +164,19 @@ const GameLocal = () => {
}

if (ball.current.position.x < -terrain.SCENEWIDTH / 2) {
setScoreB((prev) => prev + 1);
setScoreB(prev => prev + 1);
resetBall();
}

if (ball.current.position.x > terrain.SCENEWIDTH / 2) {
setScoreA((prev) => prev + 1);
setScoreA(prev => prev + 1);
resetBall();
}

if (scoreA >= 10 || scoreB >= 10) {
navigate('/');
setGameOver(true);
}
}, [terrain, resetBall, scoreA, scoreB, navigate]);
}, [terrain, resetBall, scoreA, scoreB]);

// Timer logic
useEffect(() => {
Expand Down Expand Up @@ -203,7 +219,7 @@ const GameLocal = () => {
let animationFrameId;

const gameLoop = () => {
if (isGameStarted) {
if (isGameStarted && !gameOver) {
if (keyPressedA) movePaddle(keyPressedA, paddle1);
if (keyPressedB) movePaddle(keyPressedB, paddle2);
updateBallPosition();
Expand All @@ -214,28 +230,47 @@ const GameLocal = () => {
gameLoop();

return () => cancelAnimationFrame(animationFrameId);
}, [updateBallPosition, movePaddle, keyPressedA, keyPressedB, isGameStarted]);
}, [updateBallPosition, movePaddle, keyPressedA, keyPressedB, isGameStarted, gameOver]);

return (
<PageContainer>
<ProfilesContainer>
<Profile>
<ProfileImage src='/images/default-profile.png' alt='Player 1'/>
<ProfileName>Player 1</ProfileName>
</Profile>
<p style={{margin: '0 auto'}}>Press <b>Q</b> to quit game</p>
<Profile>
<ProfileImage src='/images/default-profile.png' alt='Player 2'/>
<ProfileName>Player 2</ProfileName>
</Profile>
</ProfilesContainer>
<GameSceneContainer className={isHit ? "hit" : ""}>
<StyledCanvas ref={canvas}/>
<ScoresContainer>
<Score>{scoreA}</Score>
<Score>{scoreB}</Score>
</ScoresContainer>
{!isGameStarted && !activateTimer && (
<TimerContainer>
Press any key to start the game
</TimerContainer>
)}
{activateTimer && (
<TimerContainer>
<Timer>{timer}</Timer>
</TimerContainer>
{gameOver ? (
<OverlayContainer>
<h1>Game Over!</h1>
<p>{scoreA >= 10 ? 'Player 1 Wins' : 'Player 2 Wins'}</p>
<PongButton onClick={() => navigate('/playmenu')}>Go Back to Main Menu</PongButton>
</OverlayContainer>
) : (
<>
{activateTimer ? (
<OverlayContainer>
<Timer>{timer}</Timer>
</OverlayContainer>
) : (
!isGameStarted && (
<OverlayContainer>
Press any key to start the game
</OverlayContainer>
)
)}
</>
)}
</GameSceneContainer>
</PageContainer>
Expand Down
19 changes: 17 additions & 2 deletions frontend/src/components/Game/remote/Game.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ const Game = () => {
opponent: null,
playerSide: null,
});
const [gameOver, setGameOver] = useState(false);
const [gameStarted, setGameStarted] = useState(false);
const [won, setWon] = useState(false);

const [heartbeatIntervalTime, setHeartbeatIntervalTime] = useState(null);
const [hitPos, setHitPos] = useState(null);
const [borderScore, setBorderScore] = useState(null);

const [activateTimer, setActivateTimer] = useState(false);

Expand Down Expand Up @@ -92,12 +96,18 @@ const Game = () => {
setActivateTimer(true);
setGameState(prevState => ({ ...prevState, matchState: data.d }));
break;
case 'MATCH_BEGIN':
setGameStarted(true);
break;
case 'MATCH_UPDATE':
setGameState(prevState => ({ ...prevState, matchState: data.d }));
break;
case 'BALL_SCORED':
setBorderScore(data.d.player);
break;
case 'MATCH_END':
alert(data.d.won ? 'You won!' : 'You lost!');
navigate('/');
setGameOver(true);
setWon(data.d.won);
break;
case 'HEARTBEAT_ACK':
handleHeartbeatAck();
Expand Down Expand Up @@ -133,10 +143,15 @@ const Game = () => {
/>
<GameScene
matchState={gameState.matchState}
playerSide={gameState.playerSide}
hitPos={hitPos}
borderScore={borderScore}
sendMessage={sendMessage}
activateTimer={activateTimer}
setActivateTimer={setActivateTimer}
gameStarted={gameStarted}
gameOver={gameOver}
won={won}
/>
</PageContainer>
)
Expand Down
Loading

0 comments on commit 15e6fce

Please sign in to comment.