import React, { useState, useEffect, useRef } from 'react';
import { Camera, X, Heart } from 'lucide-react';
// NOTE: Replace these image paths with your actual image assets when you have them
const IMAGES = {
logo: "/your-logo.png", // Replace with your actual logo path
skull: "/skull-image.png", // Replace with your actual skull image
building: {
floor1: "/building-floor1.png",
floor2: "/building-floor2.png",
floor3: "/building-floor3.png",
floor4: "/building-floor4.png",
floor5: "/building-floor5.png",
}
};
// Main Game Component
export default function TimeToStrikeGame() {
// Game states
const [gameState, setGameState] = useState('initial'); // initial, countdown, playing, gameover, win
const [currentFloor, setCurrentFloor] = useState(1);
const [timeElapsed, setTimeElapsed] = useState(0);
const [lives, setLives] = useState(3);
const [countdown, setCountdown] = useState(3);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [leaderboard, setLeaderboard] = useState([
{ name: "Alex", email: "alex@example.com", time: 10.23 },
{ name: "Sam", email: "sam@example.com", time: 11.45 },
{ name: "Jamie", email: "jamie@example.com", time: 12.67 }
]);
// Game mechanics
const [mpaValue, setMpaValue] = useState(0);
const [curveProgress, setCurveProgress] = useState(0);
// Handle image loading
const [imagesLoaded, setImagesLoaded] = useState(false);
// Initialize with placeholder images until real ones are provided
useEffect(() => {
// This would be where you'd verify all images are loaded
// For now we'll assume they're available
setImagesLoaded(true);
}, []);
// Refs for animation and timing
const gameTimerRef = useRef(null);
const curveTimerRef = useRef(null);
const startTimeRef = useRef(null);
// Start countdown
const startCountdown = () => {
setGameState('countdown');
setCountdown(3);
const countdownInterval = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(countdownInterval);
startGame();
return 0;
}
return prev - 1;
});
}, 1000);
};
// Start the game
const startGame = () => {
setGameState('playing');
setCurrentFloor(1);
setLives(3);
setTimeElapsed(0);
setMpaValue(0);
setCurveProgress(0);
startTimeRef.current = Date.now();
// Start the game timer
gameTimerRef.current = setInterval(() => {
setTimeElapsed((Date.now() - startTimeRef.current) / 1000);
}, 100);
// Start the concrete maturity curve
startCurveTicker();
};
// Handle concrete curve animation
const startCurveTicker = () => {
if (curveTimerRef.current) {
clearInterval(curveTimerRef.current);
}
setMpaValue(0);
setCurveProgress(0);
// Speed up curve progression for higher floors (increasing difficulty)
const baseSpeed = 30;
const speed = baseSpeed - (currentFloor * 3);
curveTimerRef.current = setInterval(() => {
setCurveProgress(prev => {
// S-curve function to simulate concrete strength gain
const newProgress = prev + 0.01;
// Sigmoid function to calculate MPa based on progress
const mpa = 30 / (1 + Math.exp(-10 * (newProgress - 0.5)));
setMpaValue(mpa);
return newProgress >= 1 ? 1 : newProgress;
});
}, speed);
};
// Handle player action (spacebar)
const handleStrike = () => {
if (gameState !== 'playing') return;
clearInterval(curveTimerRef.current);
// Check if player hit at the right time (after 20 MPa)
if (mpaValue >= 20) {
// Success! Move to next floor
if (currentFloor >= 5) {
// Game won!
clearInterval(gameTimerRef.current);
setGameState('win');
} else {
// Next floor
setCurrentFloor(prev => prev + 1);
startCurveTicker();
}
} else {
// Too early, lose a life
setLives(prev => {
const newLives = prev - 1;
if (newLives <= 0) {
// Game over
clearInterval(gameTimerRef.current);
clearInterval(curveTimerRef.current);
setGameState('gameover');
return 0;
}
startCurveTicker();
return newLives;
});
}
};
// Submit score to leaderboard
const submitScore = () => {
if (!name || !email) return;
const newScore = { name, email, time: parseFloat(timeElapsed.toFixed(2)) };
const newLeaderboard = [...leaderboard, newScore].sort((a, b) => a.time - b.time).slice(0, 10);
setLeaderboard(newLeaderboard);
setGameState('leaderboard');
};
// Handle spacebar press
useEffect(() => {
const handleKeyDown = (e) => {
if (e.code === 'Space') {
e.preventDefault();
handleStrike();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [gameState, mpaValue]);
// Clean up timers
useEffect(() => {
return () => {
if (gameTimerRef.current) clearInterval(gameTimerRef.current);
if (curveTimerRef.current) clearInterval(curveTimerRef.current);
};
}, []);
// Show loading screen until images are ready
if (!imagesLoaded) {
return (
<div className="w-full h-full flex items-center justify-center bg-white">
<div className="text-center">
<h2 className="text-2xl mb-4" style={{ fontFamily: 'Press Start 2P, cursive' }}>LOADING...</h2>
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto"></div>
</div>
</div>
);
}
return (
<div className="w-full h-full flex flex-col bg-white px-4 py-6 font-mono">
{/* Logo and Title */}
<div className="flex justify-between items-center mb-6">
<div className="flex items-center">
{/* Replace SVG with actual logo image */}
<img src={IMAGES.logo} alt="Converge Logo" className="h-16 mr-4" />
</div>
<h1 className="text-3xl font-bold text-center flex-grow" style={{ fontFamily: 'Press Start 2P, cursive' }}>TIME TO STRIKE</h1>
<div className="w-32"></div> {/* Spacer for alignment */}
</div>
{/* Game Container */}
<div className="flex flex-grow gap-4">
{/* Leaderboard */}
<div className="w-1/4 p-4 border-2 border-gray-300 rounded-lg">
<h2 className="text-xl font-bold mb-4" style={{ fontFamily: 'Press Start 2P, cursive' }}>LEADERBOARD</h2>
<ol className="list-decimal pl-5">
{leaderboard.map((player, index) => (
<li key={index} className="mb-2">
{player.name} - {player.time.toFixed(2)}s
</li>
))}
</ol>
</div>
{/* Game Area */}
<div className="flex-grow flex gap-4">
{/* Building Visualization */}
<div className="w-1/2 flex flex-col items-center justify-center">
{gameState === 'countdown' ? (
<div className="text-8xl font-bold text-center animate-pulse" style={{ fontFamily: 'Press Start 2P, cursive' }}>
{countdown}
</div>
) : gameState === 'gameover' ? (
<div className="flex flex-col items-center">
<div className="pixel-art">
{/* Skull image using your supplied PNG */}
<div className="w-64 h-64 flex items-center justify-center mb-4">
<div className="text-center">
<div className="text-2xl mb-2" style={{ fontFamily: 'Press Start 2P, cursive' }}>I'LL BE BACK...</div>
<div className="text-lg mb-8" style={{ fontFamily: 'Press Start 2P, cursive' }}>WHEN IT'S TIME TO STRIKE</div>
<img src={IMAGES.skull} alt="Game Over Skull" className="w-40 h-40 object-contain" />
</div>
</div>
</div>
<button
onClick={startCountdown}
className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"
style={{ fontFamily: 'Press Start 2P, cursive' }}
>
TRY AGAIN
</button>
</div>
) : gameState === 'win' ? (
<div className="flex flex-col items-center text-center">
<h2 className="text-2xl font-bold mb-2" style={{ fontFamily: 'Press Start 2P, cursive' }}>BUILDING COMPLETE!</h2>
<p className="text-4xl font-bold mb-6" style={{ fontFamily: 'Press Start 2P, cursive' }}>{timeElapsed.toFixed(2)}s</p>
<div className="w-full max-w-md">
<div className="mb-4">
<label className="block mb-2">Name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded"
/>
</div>
<div className="mb-4">
<label className="block mb-2">Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded"
/>
</div>
<button
onClick={submitScore}
className="w-full px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"
style={{ fontFamily: 'Press Start 2P, cursive' }}
>
SUBMIT SCORE
</button>
</div>
</div>
) : gameState === 'leaderboard' ? (
<div className="flex flex-col items-center">
<h2 className="text-2xl font-bold mb-6" style={{ fontFamily: 'Press Start 2P, cursive' }}>CONGRATULATIONS!</h2>
<div className="w-64 h-64 mb-6">
{/* Complete building using all floor images */}
<div className="pixel-art-building relative">
{[5, 4, 3, 2, 1].map(floor => {
const zIndex = 5 - floor;
const top = (5 - floor) * 20; // Adjust spacing between floors
return (
<div key={floor} className="absolute" style={{ top: `${top}px`, zIndex }}>
<img
src={IMAGES.building[`floor${floor}`]}
alt={`Building Floor ${floor}`}
className="w-64 h-48 object-contain"
/>
</div>
);
})}
</div>
<button
onClick={startCountdown}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition mt-40"
style={{ fontFamily: 'Press Start 2P, cursive' }}
>
PLAY AGAIN
</button>
</div>
</div>
) : (
<div className="pixel-art-building">
{/* Replace SVG with actual building images */}
<div className="relative w-64 h-64">
{Array.from({ length: currentFloor }).map((_, index) => {
const floor = currentFloor - index;
// Stack floor images from bottom to top
const zIndex = 5 - floor;
const top = (5 - floor) * 20; // Adjust spacing between floors
return (
<div key={floor} className="absolute" style={{ top: `${top}px`, zIndex }}>
<img
src={IMAGES.building[`floor${floor}`]}
alt={`Building Floor ${floor}`}
className="w-64 h-48 object-contain"
/>
</div>
);
})}
</div>
</div>
)}
{gameState === 'playing' && (
<div className="mt-6 text-xl" style={{ fontFamily: 'Press Start 2P, cursive' }}>
Building Floor: {currentFloor}/5
</div>
)}
{/* Lives Display */}
{gameState === 'playing' && (
<div className="flex mt-4 gap-2">
{Array.from({ length: 3 }).map((_, i) => (
<Heart key={i} fill={i < lives ? "#ff0000" : "none"} stroke="#ff0000" size={32} />
))}
</div>
)}
{gameState === 'initial' && (
<div className="text-center">
<h2 className="text-2xl font-bold mb-4" style={{ fontFamily: 'Press Start 2P, cursive' }}>HOW TO PLAY</h2>
<p className="mb-4">Hit SPACE when the concrete strength reaches 20 MPa!</p>
<p className="mb-4">Build all 5 floors as fast as you can.</p>
<button
onClick={startCountdown}
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition text-xl"
style={{ fontFamily: 'Press Start 2P, cursive' }}
>
START GAME
</button>
</div>
)}
</div>
{/* MPa Graph */}
{gameState === 'playing' && (
<div className="w-1/2 flex flex-col items-center justify-center">
<div className="text-right w-full mb-2">
<div className="text-2xl font-bold" style={{ fontFamily: 'Press Start 2P, cursive' }}>
{mpaValue.toFixed(1)} MPa
</div>
</div>
<div className="w-full h-64 border-2 border-gray-300 relative">
{/* Graph Container */}
<div className="absolute left-0 top-0 w-full h-full">
{/* Y-axis line */}
<div className="absolute left-0 top-0 w-1 h-full bg-black"></div>
{/* X-axis line */}
<div className="absolute left-0 bottom-0 w-full h-1 bg-black"></div>
{/* Target line (20 MPa) */}
<div className="absolute left-0 top-1/3 w-full h-1 bg-red-500 border-dashed"></div>
<div className="absolute left-1 top-1/3 transform -translate-y-full">
<span className="text-lg font-bold text-red-500" style={{ fontFamily: 'Press Start 2P, cursive' }}>20 MPa</span>
</div>
{/* Curve */}
<svg className="absolute inset-0 w-full h-full">
<path
d={`M 0 ${240 - (240 * 0)}
Q ${curveProgress * 400 / 2} ${240 - (240 * 0.1)},
${curveProgress * 400} ${240 - (240 * (mpaValue / 30))}`}
stroke="#026DF5"
strokeWidth="3"
fill="none"
/>
</svg>
</div>
</div>
<div className="mt-6">
<button
onClick={handleStrike}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-xl"
style={{ fontFamily: 'Press Start 2P, cursive' }}
>
STRIKE FORMWORK!
</button>
<p className="text-center mt-2">or press SPACE</p>
</div>
{gameState === 'playing' && (
<div className="mt-4 text-xl" style={{ fontFamily: 'Press Start 2P, cursive' }}>
Time: {timeElapsed.toFixed(2)}s
</div>
)}
</div>
)}
</div>
</div>
{/* Custom CSS for pixel font */}
<style jsx>{`
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
.pixel-art {
image-rendering: pixelated;
}
`}</style>
</div>
);
}