-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
436ab4c
commit b0cbb3a
Showing
20 changed files
with
710 additions
and
0 deletions.
There are no files selected for viewing
37 changes: 37 additions & 0 deletions
37
frontend/xmas/src/components/CountdownPanel/CountdownPanel.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useState, useEffect } from 'react' | ||
import PanelText from '../PanelText' | ||
|
||
interface CountdownPanelProps { | ||
duration: number | ||
finishMsg: string | ||
onFinish?: () => void | ||
} | ||
|
||
const CountdownPanel: React.FC<CountdownPanelProps> = ({ | ||
duration, | ||
finishMsg, | ||
onFinish, | ||
}) => { | ||
const [count, setCount] = useState<number>(duration) | ||
|
||
useEffect(() => { | ||
if (count < 0) return | ||
|
||
setTimeout(() => { | ||
setCount(count - 1) | ||
}, 1000) | ||
}, [count]) | ||
|
||
let msg = '' | ||
if (count > 0) { | ||
msg = count.toString() | ||
} else if (count === 0) { | ||
msg = finishMsg | ||
if (onFinish) { | ||
onFinish() | ||
} | ||
} | ||
return <PanelText msg={msg} showOnFinish={count >= 0} scale={2} /> | ||
} | ||
|
||
export default CountdownPanel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import CountdownPanel from './CountdownPanel' | ||
export default CountdownPanel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.hud { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
min-height: 75px; | ||
|
||
&__info { | ||
display: flex; | ||
flex-direction: row; | ||
align-items: center; | ||
gap: 2rem; | ||
} | ||
|
||
a { | ||
padding: 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { GameState } from '../../hooks/useGameState' | ||
import './Hud.scss' | ||
|
||
interface HudProps { | ||
numConnections: number | ||
numPlayers: number | ||
gameState: GameState | ||
onConnect: () => void | ||
onStart: () => void | ||
} | ||
|
||
const Hud: React.FC<HudProps> = ({ | ||
numConnections, | ||
numPlayers, | ||
gameState, | ||
onConnect, | ||
onStart, | ||
}) => { | ||
const onClick = () => { | ||
switch (gameState) { | ||
case GameState.CharacterSelect: | ||
onConnect() | ||
break | ||
case GameState.WaitPlayers: | ||
onStart() | ||
break | ||
} | ||
} | ||
|
||
let buttonText = '' | ||
switch (gameState) { | ||
case GameState.CharacterSelect: | ||
buttonText = 'Connect' | ||
break | ||
case GameState.WaitPlayers: | ||
buttonText = 'Start Game' | ||
break | ||
} | ||
|
||
return ( | ||
<div className="hud"> | ||
<div className="hud__info"> | ||
<div>{`Connections: ${numConnections}`}</div> | ||
{numPlayers > 0 && ( | ||
<> | ||
<div>{`Players: ${numPlayers}`}</div> | ||
{gameState === GameState.WaitPlayers && ( | ||
<div>{`Ready: ${numConnections - 1 === numPlayers}`}</div> | ||
)} | ||
</> | ||
)} | ||
</div> | ||
<div className="hud__info"> | ||
<a href="https://tomnuttall.dev/projects/game"> | ||
https://tomnuttall.dev/projects/game | ||
</a> | ||
{buttonText.length > 0 && ( | ||
<button onClick={onClick}>{buttonText}</button> | ||
)} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Hud |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import Hud from './Hud' | ||
export default Hud |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { useState } from 'react' | ||
import { TextStyle } from '@pixi/text' | ||
import { Container, useTick } from '@pixi/react' | ||
import { useSpring, config } from '@react-spring/web' | ||
import { Text } from '@pixi/react-animated' | ||
|
||
interface PanelTextProps { | ||
msg: string | ||
scale: number | ||
showOnFinish: boolean | ||
} | ||
|
||
const PanelText: React.FC<PanelTextProps> = ({ msg, showOnFinish, scale }) => { | ||
const [fontSize, setFontSize] = useState<number>(36) | ||
const [visible, setVisible] = useState<boolean>(true) | ||
|
||
const [springs] = useSpring(() => { | ||
return { | ||
from: { scale: 1 }, | ||
to: { scale }, | ||
config: config.gentle, | ||
reset: true, | ||
onResolve: () => { | ||
setTimeout(() => { | ||
setVisible(showOnFinish) | ||
}, 2000) | ||
}, | ||
} | ||
}, [msg]) | ||
|
||
useTick(() => { | ||
if (springs.scale.animation.values[0].done) return | ||
|
||
const scale = springs.scale.animation.values[0].getValue() | ||
setFontSize(36 * scale) | ||
}) | ||
|
||
return ( | ||
<Container | ||
name={'annoucements'} | ||
x={640} | ||
y={300} | ||
width={150} | ||
height={150} | ||
visible={visible} | ||
> | ||
<Text | ||
name={'text'} | ||
anchor={0.5} | ||
text={msg} | ||
style={ | ||
new TextStyle({ | ||
align: 'center', | ||
fontFamily: 'Ubuntu Sans', | ||
fill: '#ffffff', | ||
stroke: '#000000', | ||
strokeThickness: 3, | ||
fontSize, | ||
}) | ||
} | ||
/> | ||
</Container> | ||
) | ||
} | ||
|
||
export default PanelText |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import PanelText from './PanelText' | ||
export default PanelText |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { useEffect, useContext, useCallback, useState } from 'react' | ||
import { TextStyle } from '@pixi/text' | ||
import { Container, Graphics, Text, useTick } from '@pixi/react' | ||
import { useSpring } from '@react-spring/web' | ||
import { Sprite } from '@pixi/react-animated' | ||
import { AssetContext } from '../../context/AssetContext' | ||
import { formatOrdinals } from '../../utils/helper' | ||
import { GameState } from '../../hooks/useGameState' | ||
import type { PlayerData } from '../../types' | ||
|
||
const TEXTURE_LOOKUP: Record<string, string> = { | ||
'character-1': 'penguin', | ||
'character-2': 'snowman', | ||
'character-3': 'dalek', | ||
'character-4': 'hippo', | ||
'character-5': 'reindeer', | ||
'character-6': 'grinch', | ||
} | ||
|
||
interface PlayerProps { | ||
gameState: GameState | ||
player: PlayerData | ||
position: { x: number; y: number } | ||
raceDuration: number | undefined | ||
numPlayers: number | ||
} | ||
|
||
const Player: React.FC<PlayerProps> = ({ | ||
gameState, | ||
player, | ||
position, | ||
raceDuration, | ||
numPlayers, | ||
}) => { | ||
const [offset, setOffset] = useState<number>(0) | ||
const [size, setSize] = useState<number>(1) | ||
const [timer, setTimer] = useState<number>(0) | ||
const [raceTimer, setRaceTimer] = useState<number | undefined>(undefined) | ||
|
||
useEffect(() => { | ||
setRaceTimer(raceDuration) | ||
}, [raceDuration]) | ||
|
||
useTick((delta) => { | ||
if (raceTimer === undefined || raceTimer < 0) { | ||
if (gameState === GameState.Results) { | ||
setOffset(0) | ||
} | ||
return | ||
} | ||
|
||
setRaceTimer((prevRaceTimer) => { | ||
if (prevRaceTimer === undefined) return 0 | ||
return prevRaceTimer - delta | ||
}) | ||
|
||
// Near end of race force positions to winning ones | ||
if (raceTimer < Math.floor(Math.random() * 50) + 75) { | ||
const position = player?.position ?? 0 | ||
let distance = 0 | ||
if (position < 3) { | ||
distance = 1085 - position * 100 | ||
} else if (position < 10) { | ||
distance = 800 - position * 75 | ||
} else { | ||
distance = 500 - position * 50 | ||
} | ||
setOffset(distance) | ||
return | ||
} | ||
|
||
setTimer((timer) => timer - delta) | ||
if (timer < 0) { | ||
setTimer(Math.floor(Math.random() * 60) + 40) | ||
const chance = Math.random() * 10 | ||
const scalePlayer = numPlayers > 10 ? Math.floor(Math.random() * 3) : 1 | ||
let maxDistance = 300 | ||
if (chance > 9 && scalePlayer === 1) { | ||
maxDistance = 1075 | ||
} else if (chance > 8 && scalePlayer === 1) { | ||
maxDistance = 875 | ||
} else if (chance > 5 && scalePlayer === 1) { | ||
maxDistance = 600 | ||
} else if (chance > 4 && scalePlayer === 1) { | ||
maxDistance = 450 | ||
} else if (numPlayers > 10) { | ||
maxDistance = -500 | ||
} | ||
setOffset(Math.floor(Math.random() * maxDistance)) | ||
} | ||
}) | ||
|
||
const { textures, assetsLoaded } = useContext(AssetContext) | ||
|
||
const textureName = TEXTURE_LOOKUP[`character-${player?.character ?? '0'}`] | ||
|
||
// Add wobble run animation | ||
const [springs] = useSpring( | ||
() => ({ | ||
from: { x: 0, y: 0 }, | ||
to: { x: 5, y: 3 }, | ||
config: { tension: 30, friction: 10 }, | ||
loop: raceDuration && raceDuration > 0, | ||
reset: true, | ||
}), | ||
[raceDuration], | ||
) | ||
|
||
useEffect(() => { | ||
setSize(gameState === GameState.WaitGame ? 2 : 1) | ||
}, [gameState]) | ||
|
||
const drawRectangle = useCallback( | ||
(g: any) => { | ||
g.clear() | ||
g.lineStyle(1, 0x000000) | ||
g.beginFill(0xffffff) | ||
g.drawRoundedRect(-40 * size, -80 * size, 80 * size, 18 * size, 5 * size) | ||
g.endFill() | ||
}, | ||
[size], | ||
) | ||
|
||
const drawCircle = useCallback( | ||
(g: any) => { | ||
g.clear() | ||
g.lineStyle(1, 0x000000) | ||
g.beginFill(0xffcccb) | ||
g.drawCircle(0, 0, 20 * size) | ||
g.endFill() | ||
}, | ||
[size], | ||
) | ||
|
||
return ( | ||
<Container | ||
name={player.name} | ||
x={position.x + offset} | ||
y={position.y} | ||
width={100 * size} | ||
height={100 * size} | ||
> | ||
{assetsLoaded && ( | ||
<Sprite | ||
name={'character'} | ||
width={100 * size} | ||
height={100 * size} | ||
anchor={0.5} | ||
texture={textures[textureName]} | ||
{...springs} | ||
tint={player.tint} | ||
/> | ||
)} | ||
<Graphics draw={drawRectangle} name={'textBkgd'} /> | ||
<Text | ||
name={'text'} | ||
anchor={0.5} | ||
x={0} | ||
y={-70 * size} | ||
text={player.name} | ||
style={ | ||
new TextStyle({ | ||
fontFamily: 'Ubuntu Sans', | ||
align: 'center', | ||
fill: '0x000000', | ||
fontSize: 12 * size, | ||
}) | ||
} | ||
/> | ||
{gameState === GameState.Results && ( | ||
<> | ||
<Graphics draw={drawCircle} name={'textBkgd'} /> | ||
<Text | ||
name={'text'} | ||
anchor={0.5} | ||
x={0} | ||
y={0} | ||
text={`${formatOrdinals((player?.position ?? 0) + 1)}`} | ||
style={ | ||
new TextStyle({ | ||
fontFamily: 'Ubuntu Sans', | ||
align: 'center', | ||
fill: '0x000000', | ||
fontSize: 18, | ||
}) | ||
} | ||
/> | ||
</> | ||
)} | ||
</Container> | ||
) | ||
} | ||
|
||
export default Player |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import Player from './Player' | ||
export default Player |
Oops, something went wrong.