<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cartoon Racer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Simple Cartoon Race!</h1>
<div id="game-container">
<div id="track">
<div id="start-line"></div>
<div id="finish-line"></div>
<div id="player-car" class="car">
<div class="windshield"></div>
<div class="roof">P1</div>
</div>
<div id="ai-car" class="car">
<div class="windshield"></div>
<div class="roof">AI</div>
</div>
</div>
<div id="controls-info">Press and Hold [Right Arrow] Key to Accelerate!</div>
<div id="message-area">
<p id="message">Click to Start Race!</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
display: flex;
flex-direction: column; /* Stack title above game */
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #87CEEB; /* Sky blue background */
font-family: 'Comic Sans MS', cursive, sans-serif; /* Cartoonish font */
margin: 0;
overflow: hidden;
}
h1 {
color: #FFD700; /* Gold */
text-shadow: 2px 2px 4px #555;
margin-bottom: 20px;
}
#game-container {
background-color: #90EE90; /* Light green grass */
padding: 20px;
border-radius: 15px;
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
position: relative; /* For message positioning */
text-align: center;
}
#track {
width: 800px;
height: 200px;
background-color: #A9A9A9; /* Grey track */
border: 5px dashed #FFFF00; /* Yellow dashed border */
position: relative; /* For positioning cars and lines */
margin: 0 auto; /* Center track if container is wider */
overflow: hidden; /* Keep cars visually inside */
}
#start-line, #finish-line {
position: absolute;
top: 0;
height: 100%;
width: 10px;
background: repeating-linear-gradient(
white,
white 10px,
black 10px,
black 20px
);
}
#start-line {
left: 40px; /* Position start line slightly in */
}
#finish-line {
right: 40px; /* Position finish line slightly before edge */
}
.car {
position: absolute;
width: 60px;
height: 35px;
border-radius: 15px 15px 5px 5px; /* Rounded top, flatter bottom */
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
left: 10px; /* Starting position */
transition: left 0.1s linear; /* Smooth movement */
border: 2px solid black;
}
#player-car {
background-color: #FF4500; /* OrangeRed */
top: 40px; /* Position player car */
}
#ai-car {
background-color: #1E90FF; /* DodgerBlue */
top: 120px; /* Position AI car */
}
.car .windshield {
position: absolute;
top: 5px;
left: 10px;
width: 25px;
height: 20px;
background-color: #ADD8E6; /* Light blue */
border: 1px solid black;
border-radius: 5px 5px 0 0;
clip-path: polygon(0 0, 100% 0, 80% 100%, 20% 100%); /* Tapered shape */
}
.car .roof {
position: absolute;
top: 0px;
left: 32px;
right: 3px;
height: 27px;
background: inherit; /* Match car color */
border-left: 2px solid black;
border-radius: 0 10px 0 0;
font-size: 10px;
font-weight: bold;
color: white;
text-shadow: 1px 1px 1px black;
line-height: 27px; /* Center text vertically */
text-align: center;
}
#controls-info {
margin-top: 15px;
font-size: 0.9em;
color: #333;
}
#message-area {
margin-top: 10px;
font-size: 1.5em;
font-weight: bold;
color: #DC143C; /* Crimson */
cursor: pointer;
}
#message.winner {
color: #32CD32; /* LimeGreen */
}
#message.loser {
color: #8B0000; /* DarkRed */
}
document.addEventListener('DOMContentLoaded', () => {
const track = document.getElementById('track');
const playerCar = document.getElementById('player-car');
const aiCar = document.getElementById('ai-car');
const finishLine = document.getElementById('finish-line');
const messageArea = document.getElementById('message-area');
const message = document.getElementById('message');
const TRACK_WIDTH = track.clientWidth;
const CAR_WIDTH = playerCar.offsetWidth;
// Calculate finish based on finish line's left position relative to track
const FINISH_POSITION = finishLine.offsetLeft - CAR_WIDTH;
const START_POSITION = 10; // Match initial CSS 'left'
// --- Game Parameters ---
const PLAYER_ACCELERATION = 0.15;
const MAX_PLAYER_SPEED = 6;
const FRICTION = 0.05; // Slowdown when not accelerating
const AI_BASE_SPEED = 2.5;
const AI_SPEED_VARIATION = 1.5; // How much AI speed can fluctuate
let gameActive = false;
let playerPosition = START_POSITION;
let aiPosition = START_POSITION;
let playerSpeed = 0;
let aiSpeed = 0;
let isAccelerating = false;
let animationFrameId;
// --- Initialization ---
function resetGame() {
cancelAnimationFrame(animationFrameId); // Stop previous loop if any
gameActive = false;
playerPosition = START_POSITION;
aiPosition = START_POSITION;
playerSpeed = 0;
aiSpeed = 0;
isAccelerating = false;
playerCar.style.left = `${playerPosition}px`;
aiCar.style.left = `${aiPosition}px`;
message.textContent = "Click to Start Race!";
message.className = ''; // Reset win/loss styling
messageArea.style.cursor = 'pointer';
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
}
// --- Game Start ---
messageArea.addEventListener('click', startGame);
function startGame() {
if (gameActive) return;
resetGame(); // Ensure clean state
gameActive = true;
message.textContent = "GO GO GO!";
messageArea.style.cursor = 'default';
// Start listening for controls
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
// Start the game loop
animationFrameId = requestAnimationFrame(gameLoop);
}
// --- Player Controls ---
function handleKeyDown(e) {
if (e.key === 'ArrowRight') {
isAccelerating = true;
}
}
function handleKeyUp(e) {
if (e.key === 'ArrowRight') {
isAccelerating = false;
}
}
// --- Game Loop ---
function gameLoop() {
if (!gameActive) return;
// 1. Update Player Speed & Position
if (isAccelerating) {
playerSpeed += PLAYER_ACCELERATION;
} else {
playerSpeed -= FRICTION;
}
// Clamp speed (prevent negative speed from friction and limit max speed)
playerSpeed = Math.max(0, Math.min(playerSpeed, MAX_PLAYER_SPEED));
playerPosition += playerSpeed;
// 2. Update AI Speed & Position
// Simple AI: Varies speed slightly around a base value
let aiTargetSpeed = AI_BASE_SPEED + (Math.random() - 0.5) * AI_SPEED_VARIATION * 2;
aiSpeed = aiTargetSpeed; // AI instantly adjusts for simplicity
aiSpeed = Math.max(0, aiSpeed); // Ensure non-negative speed
aiPosition += aiSpeed;
// Clamp positions to track boundaries (just before finish mostly)
playerPosition = Math.min(playerPosition, TRACK_WIDTH - CAR_WIDTH);
aiPosition = Math.min(aiPosition, TRACK_WIDTH - CAR_WIDTH);
// 3. Render Car Positions
playerCar.style.left = `${playerPosition}px`;
aiCar.style.left = `${aiPosition}px`;
// 4. Check Win Condition
let winner = null;
if (playerPosition >= FINISH_POSITION) {
winner = 'Player 1';
} else if (aiPosition >= FINISH_POSITION) {
winner = 'AI';
}
if (winner) {
endGame(winner);
} else {
// Request next frame
animationFrameId = requestAnimationFrame(gameLoop);
}
}
// --- End Game ---
function endGame(winner) {
gameActive = false;
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
if (winner === 'Player 1') {
message.textContent = "YOU WIN! Click to Race Again!";
message.className = 'winner';
} else {
message.textContent = "AI Wins! Click to Race Again!";
message.className = 'loser';
}
messageArea.style.cursor = 'pointer';
}
// --- Initial Setup ---
resetGame(); // Set initial state when the page loads
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.