#app
View Compiled
* {
box-sizing: border-box;
}
:root {
--control: #e69119;
--color: #fff;
}
button {
background: var(--control);
color: var(--color);
padding: 1rem 2rem;
border-radius: 1rem;
border: 4px solid var(--color);
font-family: 'Fredoka One', cursive;
font-size: 1.2rem;
}
body {
background: linear-gradient(#d1e9fa 0 40%, #86c270 40%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Fredoka One', cursive;
}
.end-game {
position: fixed;
top: 1rem;
right: 1rem;
}
.mole {
display: none;
}
.mole-hole {
overflow: hidden;
}
.moles {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(3, auto);
grid-template-rows: repeat(2, auto);
align-items: center;
}
.moles > *:nth-of-type(4),
.moles > *:nth-of-type(5) {
transform: translate(50%, 0)
}
import React, { Fragment, useEffect, useRef, useState } from 'https://cdn.skypack.dev/react'
import { render } from 'https://cdn.skypack.dev/react-dom'
import gsap from 'https://cdn.skypack.dev/gsap'
const TIME_LIMIT = 30000
const MOLE_SCORE = 100
const NUMBER_OF_MOLES = 5
const Moles = ({ children }) => <div className="moles">{children}</div>
const Mole = ({ onWhack }) => {
const buttonRef = useRef(null)
useEffect(() => {
gsap.set(buttonRef.current, {
yPercent: 100,
display: 'block'
})
gsap.to(buttonRef.current, {
yPercent: 0,
yoyo: true,
repeat: -1,
})
}, [])
return (
<div className="mole-hole">
<button className="mole" ref={buttonRef} onClick={() => onWhack(MOLE_SCORE)}>Mole</button>
</div>
)
}
const Score = ({ value }) => <div>{`Score: ${value}`}</div>
const Timer = ({ time, interval = 1000, onEnd }) => {
const [internalTime, setInternalTime] = useState(time)
const timerRef = useRef(time)
const timeRef = useRef(time)
useEffect(() => {
if (internalTime === 0 && onEnd) {
onEnd()
}
}, [internalTime, onEnd])
useEffect(() => {
timerRef.current = setInterval(
() => setInternalTime((timeRef.current -= interval)),
interval
)
return () => {
clearInterval(timerRef.current)
}
}, [interval])
return <div>{`Time: ${internalTime / 1000}s`}</div>
}
const Game = () => {
const [playing, setPlaying] = useState(false)
const [finished, setFinished] = useState(false)
const [score, setScore] = useState(0)
const onWhack = points => setScore(score + points)
const endGame = () => {
setPlaying(false)
setFinished(true)
}
const startGame = () => {
setScore(0)
setPlaying(true)
setFinished(false)
}
return (
<Fragment>
{!playing && !finished &&
<Fragment>
<h1>Whac a Mole</h1>
<button onClick={startGame}>Start Game</button>
</Fragment>
}
{playing &&
<Fragment>
<button
className="end-game"
onClick={endGame}
>
End Game
</button>
<Score value={score} />
<Timer
time={TIME_LIMIT}
onEnd={endGame}
/>
<Moles>
{new Array(NUMBER_OF_MOLES).fill().map((_, index) => (
<Mole key={index} onWhack={onWhack} />
))}
</Moles>
</Fragment>
}
{finished &&
<Fragment>
<Score value={score} />
<button onClick={startGame}>Play Again</button>
</Fragment>
}
</Fragment>
)
}
render(<Game/>, document.getElementById('app'))
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.