dodge-the-cars / index.html
enzostvs's picture
enzostvs HF staff
Update index.html
bf565d5 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Highway Dodge</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: "Arial", sans-serif;
background-color: #87ceeb;
}
#startScreen,
#gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
}
#gameContainer {
position: relative;
width: 100%;
height: 100vh;
}
.game-button {
padding: 15px 30px;
font-size: 24px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 10px;
}
.game-button:hover {
background-color: #45a049;
}
#hud {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: Arial, sans-serif;
font-size: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
z-index: 100;
}
#score,
#timer {
margin-bottom: 10px;
}
#gameTitle {
font-size: 48px;
color: white;
margin-bottom: 30px;
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5);
}
#finalScore {
font-size: 36px;
color: white;
margin-bottom: 20px;
text-align: center;
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="startScreen">
<h1 id="gameTitle">Highway Dodge</h1>
<button id="startButton" class="game-button">Start Game</button>
</div>
<div id="gameOverScreen" style="display: none">
<div id="finalScore">Score: 0</div>
<button id="restartButton" class="game-button">Try Again</button>
</div>
<div id="hud" style="display: none">
<div id="score">Score: 0</div>
<div id="timer">Time: 00:00</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Game configuration
const config = {
roadLength: 10000,
roadWidth: 15, // Wider road for 3 lanes
playerSpeed: 0.3, // Decreased player speed
trafficSpeed: 0.5, // Increased base traffic speed
colors: [
{ color: 0xff0000, name: "Red" },
{ color: 0x0000ff, name: "Blue" },
{ color: 0x00ff00, name: "Green" },
{ color: 0x000000, name: "Black" },
{ color: 0x800080, name: "Purple" },
{ color: 0xa52a2a, name: "Brown" },
{ color: 0xffa500, name: "Orange" },
{ color: 0xffff00, name: "Yellow" },
],
maxTrafficVehicles: 170, // Increased for more traffic
clusterChance: 0.9, // Chance of creating a vehicle cluster
clusterSize: { min: 2, max: 5 }, // Range of vehicles in a cluster
clusterSpacing: { min: 3, max: 5 }, // Spacing between vehicles in a cluster
lanePositions: [-5, 0, 5], // Three lanes
vehicleTypes: ["car", "bus", "truck"],
vehicleProperties: {
car: { length: 2, width: 1, height: 0.8, speed: 1 }, // Increased speed
bus: { length: 3.5, width: 1.2, height: 1.2, speed: 0.8 }, // Increased speed
truck: { length: 4, width: 1.3, height: 1.5, speed: 0.6 }, // Increased speed
},
};
// Game state
const state = {
score: 0,
gameOver: false,
difficulty: 1,
startTime: 0,
elapsedTime: 0,
difficultyMultiplier: 1,
};
// Scene, camera, and renderer setup
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x87ceeb, 10, 70);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("gameContainer").appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(0, 10, 0);
scene.add(directionalLight);
// Create vehicle models
function createVehicleModel(type, color, isPlayer = false) {
const props = config.vehicleProperties[type];
const group = new THREE.Group();
const baseHeight = 0.6;
// Base of the vehicle
const baseGeometry = new THREE.BoxGeometry(
props.width,
props.height * 0.5,
props.length
);
const baseMaterial = new THREE.MeshPhongMaterial({ color: color });
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = baseHeight;
group.add(base);
// Roof - different for each type
if (type === "car") {
const roofGeometry = new THREE.BoxGeometry(
props.width * 0.8,
props.height * 0.5,
props.length * 0.75
);
const roofMaterial = new THREE.MeshPhongMaterial({
color: isPlayer ? 0xff4500 : color,
opacity: 1,
transparent: false,
});
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.y = baseHeight + props.height * 0.5;
group.add(roof);
} else if (type === "bus") {
const roofGeometry = new THREE.BoxGeometry(
props.width,
props.height * 0.5,
props.length * 0.95
);
const roofMaterial = new THREE.MeshPhongMaterial({
color: isPlayer ? 0xff4500 : color,
});
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.y = baseHeight + props.height * 0.5;
group.add(roof);
} else if (type === "truck") {
// Cab for truck
const cabGeometry = new THREE.BoxGeometry(
props.width,
props.height * 0.7,
props.length * 0.3
);
const cabMaterial = new THREE.MeshPhongMaterial({
color: isPlayer ? 0xff4500 : color,
});
const cab = new THREE.Mesh(cabGeometry, cabMaterial);
cab.position.y = baseHeight + props.height * 0.35;
cab.position.z = props.length * 0.35;
group.add(cab);
// Cargo container
const cargoGeometry = new THREE.BoxGeometry(
props.width * 1.1,
props.height * 1,
props.length * 0.65
);
const cargoMaterial = new THREE.MeshPhongMaterial({
color: 0x888888,
});
const cargo = new THREE.Mesh(cargoGeometry, cargoMaterial);
cargo.position.y = baseHeight + props.height * 0.5;
cargo.position.z = -props.length * 0.17;
group.add(cargo);
}
// Windows (for cars and buses)
if (type !== "truck") {
const windowMaterial = new THREE.MeshPhongMaterial({
color: 0x87ceeb,
opacity: 0.7,
transparent: true,
});
// Side windows
const windowSize = type === "car" ? 0.4 : 0.6;
const windowFront = new THREE.Mesh(
new THREE.PlaneGeometry(props.width * 0.9, windowSize),
windowMaterial
);
windowFront.rotation.y = Math.PI / 2;
windowFront.position.set(
props.width / 2 + 0.01,
baseHeight + props.height * 0.5,
0
);
group.add(windowFront);
const windowBack = windowFront.clone();
windowBack.position.set(
-props.width / 2 - 0.01,
baseHeight + props.height * 0.5,
0
);
windowBack.rotation.y = -Math.PI / 2;
group.add(windowBack);
}
// Wheels
const wheelRadius = type === "car" ? 0.2 : 0.3;
const wheelGeometry = new THREE.CylinderGeometry(
wheelRadius,
wheelRadius,
0.2,
16
);
const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x000000 });
// Position wheels based on vehicle type
const wheelOffsetX = (props.width / 2) * 0.8;
const wheelOffsetY = baseHeight - props.height * 0.3;
let wheelZPositions;
if (type === "car") {
wheelZPositions = [props.length / 2 - 0.3, -props.length / 2 + 0.3];
} else if (type === "bus") {
wheelZPositions = [
props.length / 2 - 0.5,
0,
-props.length / 2 + 0.5,
];
} else {
// truck
wheelZPositions = [
props.length / 2 - 0.5,
0,
-props.length / 2 + 0.5,
];
}
// Add wheels
wheelZPositions.forEach((zPos) => {
const wheelLeft = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheelLeft.rotation.z = Math.PI / 2;
wheelLeft.position.set(-wheelOffsetX, wheelOffsetY, zPos);
group.add(wheelLeft);
const wheelRight = wheelLeft.clone();
wheelRight.position.set(wheelOffsetX, wheelOffsetY, zPos);
group.add(wheelRight);
});
// Add collision box
group.userData = {
type: type,
width: props.width,
length: props.length,
speed: props.speed,
boundingBox: new THREE.Box3().setFromObject(group),
};
return group;
}
// Background
const backgroundGeometry = new THREE.SphereGeometry(500, 60, 40);
const backgroundMaterial = new THREE.MeshBasicMaterial({
color: 0x87ceeb,
side: THREE.BackSide,
});
const background = new THREE.Mesh(backgroundGeometry, backgroundMaterial);
scene.add(background);
// Road
const roadGeometry = new THREE.PlaneGeometry(
config.roadWidth,
config.roadLength
);
const roadMaterial = new THREE.MeshPhongMaterial({ color: 0x505050 });
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
scene.add(road);
// Lane markings
function createLaneMarkings() {
// Lane dividers
for (let i = -1; i <= 1; i += 2) {
const lineGeometry = new THREE.PlaneGeometry(0.15, config.roadLength);
const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const line = new THREE.Mesh(lineGeometry, lineMaterial);
line.rotation.x = -Math.PI / 2;
line.position.x = i * 2.5; // Center line between lanes
line.position.y = 0.01; // Slightly above road
scene.add(line);
}
// Dashed center lines
for (
let z = -config.roadLength / 2;
z < config.roadLength / 2;
z += 5
) {
const dashGeometry = new THREE.PlaneGeometry(0.15, 3);
const dashMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
// Left dashed line
const leftDash = new THREE.Mesh(dashGeometry, dashMaterial);
leftDash.rotation.x = -Math.PI / 2;
leftDash.position.set(-2.5, 0.01, z);
scene.add(leftDash);
// Right dashed line
const rightDash = new THREE.Mesh(dashGeometry, dashMaterial);
rightDash.rotation.x = -Math.PI / 2;
rightDash.position.set(2.5, 0.01, z);
scene.add(rightDash);
}
}
// Player Car
const playerColor = 0xff0000;
const playerCar = createVehicleModel("car", playerColor, true);
playerCar.position.y = 0;
scene.add(playerCar);
// Traffic Vehicles
const trafficVehicles = [];
function createTrafficVehicle() {
// Randomly select vehicle type
const vehicleType =
config.vehicleTypes[
Math.floor(Math.random() * config.vehicleTypes.length)
];
const colorObj =
config.colors[Math.floor(Math.random() * config.colors.length)];
const vehicle = createVehicleModel(vehicleType, colorObj.color);
// Place in a random lane
const laneIndex = Math.floor(
Math.random() * config.lanePositions.length
);
vehicle.position.x = config.lanePositions[laneIndex];
// Stagger vehicle positions - longer range for traffic jam effect
vehicle.position.z = -Math.random() * config.roadLength * 0.75;
// Add to scene and array
scene.add(vehicle);
trafficVehicles.push(vehicle);
// Update the bounding box after positioning
vehicle.userData.boundingBox.setFromObject(vehicle);
}
// Spawn initial traffic
function initTraffic() {
createLaneMarkings();
for (let i = 0; i < config.maxTrafficVehicles; i++) {
createTrafficVehicle();
}
}
// Camera positioning
camera.position.y = 3;
camera.position.z = 7;
camera.lookAt(playerCar.position);
// Improved collision detection using bounding boxes
function checkCollisions() {
if (state.gameOver) return;
// Update player bounding box
playerCar.userData.boundingBox.setFromObject(playerCar);
for (let i = 0; i < trafficVehicles.length; i++) {
// Update vehicle bounding box
trafficVehicles[i].userData.boundingBox.setFromObject(
trafficVehicles[i]
);
// Check for intersection
if (
playerCar.userData.boundingBox.intersectsBox(
trafficVehicles[i].userData.boundingBox
)
) {
endGame();
break;
}
}
}
// Format time to MM:SS
function formatTime(milliseconds) {
const totalSeconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes.toString().padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}`;
}
// Movement
const keys = {};
window.addEventListener("keydown", (e) => {
keys[e.key] = true;
});
window.addEventListener("keyup", (e) => {
keys[e.key] = false;
});
function updatePlayerMovement() {
if (state.gameOver) return;
if (
keys["ArrowLeft"] &&
playerCar.position.x > -(config.roadWidth / 2 - 1)
) {
playerCar.position.x -= config.playerSpeed;
}
if (
keys["ArrowRight"] &&
playerCar.position.x < config.roadWidth / 2 - 1
) {
playerCar.position.x += config.playerSpeed;
}
if (keys["ArrowUp"]) {
playerCar.position.z -= config.playerSpeed * 2;
state.score++;
document.getElementById(
"score"
).textContent = `Score: ${state.score}`;
}
if (keys["ArrowDown"]) {
playerCar.position.z += config.playerSpeed;
}
}
// Update traffic vehicles
function updateTrafficVehicles() {
// Increase difficulty based on score and time
state.difficultyMultiplier =
1 + state.score / 2000 + state.elapsedTime / 80000;
trafficVehicles.forEach((vehicle, index) => {
// Move vehicles with their specific speeds
const vehicleSpeed =
config.trafficSpeed *
vehicle.userData.speed *
state.difficultyMultiplier;
vehicle.position.z += vehicleSpeed;
// Update bounding box with new position
vehicle.userData.boundingBox.setFromObject(vehicle);
// Award points when successfully dodging a vehicle
if (
!vehicle.userData.passed &&
vehicle.position.z > playerCar.position.z
) {
vehicle.userData.passed = true;
state.score++;
document.getElementById(
"score"
).textContent = `Score: ${state.score}`;
}
// Remove vehicles that are out of view and create new ones
if (vehicle.position.z > 15) {
scene.remove(vehicle);
trafficVehicles.splice(index, 1);
createTrafficVehicle();
}
});
}
// Update timer
function updateTimer() {
state.elapsedTime = Date.now() - state.startTime;
document.getElementById("timer").textContent = `Time: ${formatTime(
state.elapsedTime
)}`;
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
updatePlayerMovement();
updateTrafficVehicles();
checkCollisions();
updateTimer();
// Update camera to follow player
camera.position.x = playerCar.position.x;
camera.position.z = playerCar.position.z + 7; // Positioned further back
camera.lookAt(
new THREE.Vector3(
playerCar.position.x,
playerCar.position.y,
playerCar.position.z - 3
)
);
renderer.render(scene, camera);
}
// Start Game
function startGame() {
// Reset game state
state.score = 0;
state.gameOver = false;
state.difficulty = 1;
state.difficultyMultiplier = 1;
// Hide start screen
document.getElementById("startScreen").style.display = "none";
document.getElementById("gameOverScreen").style.display = "none";
document.getElementById("hud").style.display = "block";
// Reset player position
playerCar.position.x = 0;
playerCar.position.z = 0;
// Clear existing traffic
trafficVehicles.forEach((vehicle) => scene.remove(vehicle));
trafficVehicles.length = 0;
// Start timer and animation but delay traffic spawn
document.getElementById("score").textContent = `Score: ${state.score}`;
document.getElementById("timer").textContent = `Time: 00:00`;
// Show countdown or ready message
const readyMessage = document.createElement("div");
readyMessage.style.position = "absolute";
readyMessage.style.top = "50%";
readyMessage.style.left = "50%";
readyMessage.style.transform = "translate(-50%, -50%)";
readyMessage.style.fontSize = "48px";
readyMessage.style.color = "white";
readyMessage.style.textShadow = "2px 2px 4px black";
readyMessage.style.zIndex = "1000";
readyMessage.textContent = "Ready...";
document.getElementById("gameContainer").appendChild(readyMessage);
// Start animation immediately
animate();
// Wait 3 seconds before spawning traffic
setTimeout(() => {
// Remove ready message
document.getElementById("gameContainer").removeChild(readyMessage);
// Start the timer now
state.startTime = Date.now();
// Spawn traffic
initTraffic();
}, 3000);
}
// End Game
function endGame() {
state.gameOver = true;
// Show game over screen
document.getElementById("hud").style.display = "none";
document.getElementById("gameOverScreen").style.display = "flex";
document.getElementById("finalScore").textContent = `Score: ${
state.score
}
Time: ${formatTime(state.elapsedTime)}`;
}
// Event Listeners
document
.getElementById("startButton")
.addEventListener("click", startGame);
document
.getElementById("restartButton")
.addEventListener("click", startGame);
// Handle window resizing
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>