Skip to content

Commit

Permalink
xmas: folder case fix
Browse files Browse the repository at this point in the history
  • Loading branch information
TomNuttall committed Dec 8, 2024
1 parent 436ab4c commit b0cbb3a
Show file tree
Hide file tree
Showing 20 changed files with 710 additions and 0 deletions.
37 changes: 37 additions & 0 deletions frontend/xmas/src/components/CountdownPanel/CountdownPanel.tsx
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
2 changes: 2 additions & 0 deletions frontend/xmas/src/components/CountdownPanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import CountdownPanel from './CountdownPanel'
export default CountdownPanel
17 changes: 17 additions & 0 deletions frontend/xmas/src/components/Hud/Hud.scss
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;
}
}
65 changes: 65 additions & 0 deletions frontend/xmas/src/components/Hud/Hud.tsx
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
2 changes: 2 additions & 0 deletions frontend/xmas/src/components/Hud/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Hud from './Hud'
export default Hud
66 changes: 66 additions & 0 deletions frontend/xmas/src/components/PanelText/PanelText.tsx
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
2 changes: 2 additions & 0 deletions frontend/xmas/src/components/PanelText/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import PanelText from './PanelText'
export default PanelText
194 changes: 194 additions & 0 deletions frontend/xmas/src/components/Player/Player.tsx
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
2 changes: 2 additions & 0 deletions frontend/xmas/src/components/Player/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Player from './Player'
export default Player
Loading

0 comments on commit b0cbb3a

Please sign in to comment.