Skip to content

Commit

Permalink
Merge pull request #107 from the-ai-team/feature/105-race-page-updates
Browse files Browse the repository at this point in the history
[Related #105] Race page refinements
  • Loading branch information
supunTE authored Mar 18, 2024
2 parents d810eea + 339a723 commit cd912c2
Show file tree
Hide file tree
Showing 28 changed files with 440 additions and 86 deletions.
2 changes: 2 additions & 0 deletions apps/client/src/assets/sounds/stop/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Sound Effect by <a href="https://pixabay.com/users/irinairinafomicheva-25140203/?utm_source=link-attribution&utm_medium=referral&utm_campaign=music&utm_content=13692">irinairinafomicheva</a> from <a href="https://pixabay.com/sound-effects//?utm_source=link-attribution&utm_medium=referral&utm_campaign=music&utm_content=13692">Pixabay</a>
License - https://pixabay.com/service/license-summary/
Binary file not shown.
50 changes: 39 additions & 11 deletions apps/client/src/components/atoms/cursor/Cursor.component.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
import { ReactElement } from 'react';
import cs from 'classnames';
import { ReactComponent as LockIcon } from 'pixelarticons/svg/lock.svg';

export interface CursorProps {
isDebug?: boolean;
isAtSpace?: boolean;
isInvalidCursor?: boolean;
isLocked?: boolean;
}

export function Cursor({
isDebug = false,
isAtSpace = false,
isInvalidCursor = false,
isLocked = false,
}: CursorProps): ReactElement {
return (
<span
className={cs(
'absolute h-full w-[20px] top-[0.5px]',
{
'left-[-0.5px]': !isAtSpace,
'left-[0.5px]': isAtSpace,
'bg-primary-30 bg-opacity-60': isDebug,
},
'animate-cursor-blink',
)}>
<div className={cs('bg-neutral-90', 'h-full w-[2.5px] rounded-full')} />
<span className='z-10 relative'>
<span
className={cs(
'absolute h-full w-[20px] top-[0.5px]',
{
'left-[-0.5px]': !isAtSpace,
'left-[0.5px]': isAtSpace,
'bg-primary-30 bg-opacity-60': isDebug,
'animate-cursor-blink': !isLocked,
},
'py-[0.075em]',
)}>
{isLocked && (
<div
className={cs(
'absolute -left-[.35em] top-0 w-full h-full flex items-center justify-center',
'scale-150',
)}>
<LockIcon
className={cs(
'w-5 h-5 p-[0.085em]',
'bg-surface rounded-full',
'border border-neutral-90',
)}
/>
</div>
)}
<div
className={cs('h-full w-[2.5px] rounded-full', {
'bg-error-50': isInvalidCursor,
'bg-neutral-90': !isInvalidCursor,
})}
/>
</span>
</span>
);
}
2 changes: 1 addition & 1 deletion apps/client/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * as Connection from './connection';
export * as Race from './race';
export * as RaceConstants from './race';
export * as TextStyles from './text-styles';
export * as Time from './time';
1 change: 1 addition & 0 deletions apps/client/src/constants/race.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const MAX_INVALID_CHARS_ALLOWED = 5;
export const MIN_RACE_TRACKS = 5;
19 changes: 2 additions & 17 deletions apps/client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { store } from '@razor/store';

import './services/socket-communication';
import './i18n';
import './controllers';

import { NotFound } from './pages/NotFound';
import { Home, Layout, Leaderboard, Race, Room } from './pages';
import { ToastContextProvider } from './providers';
import { Router } from './router';

import './styles.css';

Expand All @@ -22,20 +20,7 @@ root.render(
<StrictMode>
<Provider store={store}>
<ToastContextProvider>
<BrowserRouter>
<Routes>
<Route path='/' element={<Layout />}>
<Route index element={<Home />} />
<Route path=':roomId'>
<Route index element={<Home />} />
<Route path='room' element={<Room />} />
<Route path='race' element={<Race />} />
<Route path='leaderboard' element={<Leaderboard />} />
</Route>
</Route>
<Route path='*' element={<NotFound />} />
</Routes>
</BrowserRouter>
<Router />
</ToastContextProvider>
</Provider>
</StrictMode>,
Expand Down
29 changes: 18 additions & 11 deletions apps/client/src/pages/race/Race.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function Race(): ReactElement {
const [raceReadyTime, setRaceReadyTime] = useState<number>(5);
const [raceTime, setRaceTime] = useState<number>(0);
const selfPlayerId = useRef<PlayerId>(getSavedPlayerId());
const [isTypeLocked, setIsTypeLocked] = useState<boolean>(true);

useEffect(() => {
const tournamentId: AppTournamentId = `T:${roomId}`;
Expand Down Expand Up @@ -102,6 +103,7 @@ export function Race(): ReactElement {
const raceTime = game.racesModel[raceId]?.timeoutDuration;
setRaceTime(raceTime);
sendInitialTypeLog(raceId);
setIsTypeLocked(false);
}
}, [raceReadyTime, raceId]);

Expand All @@ -113,7 +115,7 @@ export function Race(): ReactElement {
)}>
<Logo className='absolute top-0 left-10 w-[150px] h-[150px]' />
{raceId ? (
<div className='flex flex-col items-center justify-center relative'>
<>
{raceReadyTime > 0 ? (
<div
className={cs(
Expand All @@ -126,35 +128,40 @@ export function Race(): ReactElement {
</div>
) : null}
<div
className={cs('relative', {
'opacity-20': raceReadyTime > 0,
})}>
<div className='scale-75'>
<RaceTrack raceId={raceId} />
className={cs(
'flex flex-col items-center justify-center gap-4',
'scale-50 lg:scale-75 xl:scale-90 2xl:scale-95 ',
{
'opacity-20': raceReadyTime > 0,
},
)}>
<div className={cs('flex items-center justify-center')}>
<RaceTrack raceId={raceId} className='scale-95' />
</div>
<div className='grid grid-cols-4'>
<div
className={cs(
'scale-50',
'm-auto',
'fixed right-0 -top-12 2xl:static 2xl:scale-75',
'm-auto 2xl:static',
'scale-50 2xl:scale-90 origin-top-right 2xl:origin-center',
'fixed -top-40 right-8 z-10',
)}>
<Timer
time={raceTime}
onTimeEnd={(): void => console.log('time end')}
onTimeEnd={(): void => setIsTypeLocked(true)}
/>
</div>
<div className='col-span-4 2xl:col-span-3 max-w-6xl m-auto'>
<RaceText
raceId={raceId}
isLocked={isTypeLocked}
onValidType={(charIndex: number): void =>
sendTypeLog(charIndex + 1, raceId)
}
/>
</div>
</div>
</div>
</div>
</>
) : (
<h4 className='text-2xl text-neutral-90'>Race not found</h4>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default {
component: RaceText,
args: {
raceId: testRaceId,
isLocked: false,
debug: {
enableLetterCount: false,
enableSpaceCount: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ import { Cursor, ToastType, UnderlineCursor } from 'apps/client/src/components';
import { AvatarArray } from 'apps/client/src/components/molecules/avatar-array/AvatarArray.component';
import { MAX_INVALID_CHARS_ALLOWED } from 'apps/client/src/constants/race';
import { useToastContext } from 'apps/client/src/hooks/useToastContext';
import { AudioManager } from 'apps/client/src/services';
import { getSavedPlayerId } from 'apps/client/src/utils/save-player-id';
import cs from 'classnames';
import { ReactComponent as GamePad } from 'pixelarticons/svg/gamepad.svg';

import stopSound from '../../../../assets/sounds/stop/stop-sound.mp3';

import {
computeCursorsPerLines,
getCursorPositionsWithPlayerAvatars,
Expand All @@ -35,6 +38,7 @@ import {

export interface RaceTextProps {
raceId: AppRaceId;
isLocked?: boolean;
onValidType: (charIndex: number) => void;
debug?: {
enableLetterCount?: boolean;
Expand All @@ -46,6 +50,7 @@ export interface RaceTextProps {

export function RaceText({
raceId,
isLocked = false,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValidType = (): void => {},
debug = {},
Expand Down Expand Up @@ -127,6 +132,12 @@ export function RaceText({
const [otherPlayerCursorsPerLines, updateOtherPlayerCursorsPerLines] =
useState<AppRacePlayerCursor[][]>([]);

useEffect((): void => {
if (isLocked) {
updateNoOfInvalidChars(0);
}
}, [isLocked]);

useEffect((): void => {
if (!raceData || !selfPlayerId.current || !playerIds) {
return addToast({
Expand Down Expand Up @@ -189,7 +200,14 @@ export function RaceText({
t,
]);

const stopAudioManager = useMemo(() => new AudioManager(stopSound), []);

const handleKeyPressFunction = (char: string): void => {
// When is locked or player finished the race stop accepting input.
if (isLocked || raceText.length === playerCursorAt) {
return;
}

const inputStatus = inputHandler(char, raceText[playerCursorAt]);
if (!selfPlayerId.current) {
return;
Expand All @@ -199,10 +217,11 @@ export function RaceText({
if (noOfInvalidChars > 0) {
return;
}

updatePlayerCursorAt(playerCursorAt + 1);
onValidType(playerCursorAt);
} else if (inputStatus === InputStatus.INCORRECT) {
stopAudioManager.playAudio();

updateNoOfInvalidChars(prev => {
if (prev === MAX_INVALID_CHARS_ALLOWED) {
return MAX_INVALID_CHARS_ALLOWED;
Expand All @@ -212,6 +231,7 @@ export function RaceText({
} else if (inputStatus === InputStatus.BACKSPACE) {
updateNoOfInvalidChars(prev => {
if (prev === 0) {
stopAudioManager.playAudio();
return 0;
}
return prev - 1;
Expand Down Expand Up @@ -280,6 +300,10 @@ export function RaceText({
charIndex,
{ cursorAt: playerCursorAt },
);
const isCursorAtInvalidCursor = indexConverter.isCursorAtChar(
charIndex,
{ cursorAt: invalidCursorAt },
);
const isLetterBehindCursor =
indexConverter.isCharBehindCursor(
charIndex,
Expand All @@ -295,16 +319,32 @@ export function RaceText({
indexConverter.isCursorAtChar(charIndex, {
cursorsAt: otherPlayerCursors,
});
/* Show normal cursor if no invalid chars */
const isVisibleRegularCursor =
(noOfInvalidChars === 0 || isLocked) && isCursorAtLetter;
/* Show invalid cursor if invalid chars */
const isVisibleInvalidCursor =
noOfInvalidChars > 0 &&
!isLocked &&
isCursorAtInvalidCursor;

return (
<span
key={charIndex}
className={cs('relative pl-[0.5px]', {
'text-neutral-30': isLetterBehindCursor,
'text-error-50 bg-error-50 bg-opacity-20':
'text-error-60 bg-error-50 bg-opacity-20':
isLetterBetweenCursors,
})}>
{isCursorAtLetter ? <Cursor isAtSpace={isSpace} /> : null}
{isVisibleRegularCursor ? (
<Cursor isAtSpace={isSpace} isLocked={isLocked} />
) : null}
{isVisibleInvalidCursor ? (
<Cursor
isAtSpace={isSpace}
isInvalidCursor={isCursorAtInvalidCursor}
/>
) : null}
{isOtherPlayerCursorsOnLetter ? (
<UnderlineCursor />
) : null}
Expand Down
Loading

0 comments on commit cd912c2

Please sign in to comment.