Skip to content

Commit

Permalink
Frontend: Display Player names along with cards
Browse files Browse the repository at this point in the history
1. The Player component was refactored to separate JSX for rendering player cards.

2. Highlighting functionality was added to indicate the current player's turn.

3. Player names were styled to appear in a white box with black text.

4. Brightness adjustment was applied to highlight the active player.

Fixes #158
  • Loading branch information
kuv2707 authored and asmit27rai committed Jun 28, 2024
1 parent e87b337 commit 4243c7a
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 143 deletions.
1 change: 1 addition & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type APIPlayer = {
id: string;
// will store more info like profile pic etc of the player
name: string;
cards: string[]; // only contains the card ids
cards: UNOCard[]; // todo: change to card ids
};

export type RunningEvents = {
Expand Down
41 changes: 0 additions & 41 deletions backend/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Response } from 'express';
import { AuthRequest } from './middlewares/authMiddleware';
import { CardColor, UNOCard } from './types';

export type ControllerFunction = (
req: AuthRequest,
Expand All @@ -16,43 +15,3 @@ export function catchError(fn: ControllerFunction): ControllerFunction {
}
};
}

export function getCardImageName(card: UNOCard): string {
function getColorAbbreviation(color: CardColor): string {
switch (color) {
case 'red':
return 'r';
case 'blue':
return 'b';
case 'green':
return 'g';
case 'yellow':
return 'o';
default:
return '';
}
}
if (card.type === 'wild') {
if (card.value === 'colchange') {
return 'CC';
} else {
return 'P4';
}
} else if (card.type === 'special') {
let value;
switch (card.value) {
case 'skip':
value = 'r';
break;
case 'reverse':
value = 'x';
break;
case 'draw2':
value = 'p2';
break;
}
return `${getColorAbbreviation(card.color)}${value}`;
} else {
return `${getColorAbbreviation(card.color)}${card.value}`;
}
}
22 changes: 10 additions & 12 deletions frontend/src/clientDispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ const handleThrowCard = (gameState: GameState, event: GameEvent): GameState => {
...gameState,
players: gameState.players.map((player) =>
player.id === playerId
? { ...player, cards: player.cards.filter((c) => c !== cardId) }
? {
...player,
cards: player.cards.filter((c) => c.id !== cardId),
}
: player
),
lastThrownCard:
Expand Down Expand Up @@ -130,7 +133,10 @@ const handleStateSync = (gameState: GameState, event: GameEvent): GameState => {
lastThrownCard: data.lastThrownCard,
direction: data.direction,
status: data.status,
runningEvents: data.runningEvents,
runningEvents: {
vulnerableToUNO: data.runningEvents.vulnerableToUNO?.id || null,
hasAnnouncedUNO: data.runningEvents.hasAnnouncedUNO?.id || null,
},
};
};

Expand All @@ -147,19 +153,11 @@ const handleAnnounceUno = (
const player: APIPlayer = gameState.players.find(
(p) => p.id == playerId
) as APIPlayer;
const gamePlayer = {
id: player.id,
cards: player.cards.map((cardId) => {
const card = gameState.cardDeck.find((c) => c.id === cardId);
if (!card)
throw new Error(`Card with id ${cardId} not found in deck`);
return card;
}),
};

return {
...gameState,
runningEvents: {
hasAnnouncedUNO: gamePlayer,
hasAnnouncedUNO: player.id,
vulnerableToUNO: null,
},
};
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/contexts/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { clientDispatch } from '../clientDispatch';
import {
APIPlayer,
GameStatus,
RunningEvents,
UNOCard,
AppEvent,
GameEvent,
Expand All @@ -25,7 +24,10 @@ export interface GameState {
lastThrownCard: UNOCard | null;
direction: number;
status: GameStatus | '';
runningEvents: RunningEvents;
runningEvents: {
vulnerableToUNO: string | null;
hasAnnouncedUNO: string | null;
};
}

interface GameContextProps {
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/library/Player.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';

interface Player {
name: string;
cards: { length: number };
}

interface PlayerProps {
player: Player;
highlighted: boolean;
positionStyle: React.CSSProperties;
}

const Player: React.FC<PlayerProps> = ({
player,
highlighted,
positionStyle,
}) => {
return (
<div
className="absolute flex flex-col items-center justify-center bg-player-icon-bg"
style={{
...positionStyle,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
width: '70px',
height: '80px',
zIndex: 2,
filter: highlighted ? 'brightness(1.5)' : 'none',
}}
>
<div
className="player-cards font-[Kavoon] text-gray-900 mt-[96px] text-[12px]"
style={{ marginLeft: '-8px' }}
>
{player.cards.length}
</div>
<div
className="player-name font-[Kavoon] mb-2 text-[14px] text-black bg-gray-400 rounded-lg shadow-md"
style={{ marginLeft: '-8px' }}
>
{player.name}
</div>
</div>
);
};

export default Player;
144 changes: 57 additions & 87 deletions frontend/src/pages/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,59 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { useGameContext } from '../contexts/GameContext';
import Button from '../library/button';
import { useModal } from '../library/modal/ModalContext';
import CopyButton from '../library/copyButton';
import Chatbox from '../library/chatbox/Chatbox';
import { GameEventTypes } from '../../../backend/src/types';
import { CardColor, GameEventTypes, UNOCard } from '../../../backend/src/types';
import * as channel from '../channel';
import { IoSettings } from 'react-icons/io5';
import { useNavigate } from 'react-router-dom';
import { triggerEvent } from '../channel';
import { useAuth } from '../contexts/AuthContext';
import { useToast } from '../library/toast/toast-context';
import Player from '../library/Player';

function getCardImageName(card: UNOCard): string {
function getColorAbbreviation(color: CardColor): string {
switch (color) {
case 'red':
return 'r';
case 'blue':
return 'b';
case 'green':
return 'g';
case 'yellow':
return 'o';
default:
return '';
}
}
if (card.type === 'wild') {
if (card.value === 'colchange') {
return 'CC';
} else {
return 'P4';
}
} else if (card.type === 'special') {
let value: string;
switch (card.value) {
case 'skip':
value = 'r';
break;
case 'reverse':
value = 'x';
break;
case 'draw2':
value = 'p2';
break;
default:
value = '';
}
return `${getColorAbbreviation(card.color)}${value}`;
} else {
return `${getColorAbbreviation(card.color)}${card.value}`;
}
}

function Game() {
const { gameState } = useGameContext();
Expand All @@ -19,67 +62,6 @@ function Game() {
const navigate = useNavigate();
const [FirstUser, setFirstUser] = useState(true);
const modal = useModal();
const userCards = useMemo(() => {
const cards = [
'r0',
'r1',
'r2',
'r3',
'r4',
'r5',
'r6',
'r7',
'r8',
'r9',
'rp2',
'rx',
'rr',
'g0',
'g1',
'g2',
'g3',
'g4',
'g5',
'g6',
'g7',
'g8',
'g9',
'gp2',
'gx',
'gr',
'b0',
'b1',
'b2',
'b3',
'b4',
'b5',
'b6',
'b7',
'b8',
'b9',
'bp2',
'bx',
'br',
'o0',
'o1',
'o2',
'o3',
'o4',
'o5',
'o6',
'o7',
'o8',
'o9',
'op2',
'ox',
'or',
// 'zzzz',
'P4',
'CC',
];
const shuffledCards = [...cards].sort(() => Math.random() - 0.5);
return shuffledCards.slice(0, 7);
}, []);
useEffect(() => {
modal.show(<GamePropertiesModal />, 'large', [], false);
// eslint-disable-next-line
Expand Down Expand Up @@ -192,17 +174,19 @@ function Game() {
{ top: '75%', left: '85%', transform: 'translate(-50%, -50%)' },
];

const cardStyles = {
filter: 'brightness(1)',
};

if (!gameState) {
return (
<div className="bg-gray-800 h-screen text-white text-5xl font-kavoon text-center">
Loading...
</div>
);
}

const myCards = (
gameState.players.find((player) => player.id === getUser()!.id)
?.cards ?? []
).map(getCardImageName);

return (
<div className="flex justify-center items-center min-h-screen bg-table-bg bg-cover">
<div className="relative w-full max-w-6xl h-[75vh]">
Expand All @@ -212,26 +196,12 @@ function Game() {
</div>
{/* Players */}
{gameState.players.slice(0, 6).map((player, index) => (
<div
<Player
key={index}
className={`absolute flex flex-col items-center justify-center bg-player-icon-bg`}
style={{
...playerPositions[index],
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
width: '70px',
height: '80px',
zIndex: 2,
...(index === gameState.currentPlayerIndex && {
...cardStyles,
filter: 'brightness(1.5)',
}),
}}
>
<div className="player-cards text-black mt-[61px]">
{player.cards.length}
</div>
</div>
player={player}
highlighted={index === gameState.currentPlayerIndex}
positionStyle={playerPositions[index]}
/>
))}

{/* Center Deck and UNO Button */}
Expand Down Expand Up @@ -275,7 +245,7 @@ function Game() {
{/* Player Mat */}
{/* <div className="absolute bottom-20 left-1/2 transform -translate-x-1/2 flex flex-col items-center z-20"> */}
<div className="absolute top-[80%] flex z-30 flex-row w-full justify-center h-96">
{userCards.map((card, index) => (
{myCards.map((card, index) => (
<img
key={index}
src={`/card_faces/${card}.svg`}
Expand Down

0 comments on commit 4243c7a

Please sign in to comment.