WHEN USING AI STUDIO, U HAVE TO GENERATE PROMPT FIRST
Create a complete 3d car racing game using html, css, and javascript in a single code block. The game
should include the following features
Absolutely! Here's a complete and detailed prompt you can use to create a 3D racing game—
whether you’re using a game engine like Unity, Unreal Engine, or working with an AI code
generator.
🎮 Prompt to Create a 3D Racing Game
Create a complete 3D racing game with the following features:
A main menu with options: Start Game, Settings, and Exit
A garage screen where players can choose between 3 different cars with unique
stats (speed, handling, acceleration)
One fully designed 3D racetrack with terrain, road, obstacles, and props (trees,
billboards, etc.)
Realistic physics-based vehicle controls (acceleration, braking, drifting,
collisions)
A third-person camera that follows the car with smooth transitions and adjustable
angles
A lap timer and lap counter (3 laps total)
AI opponents (at least 3) that race against the player with basic pathfinding and
varied difficulty
A simple HUD showing speedometer, current lap, position, and race time
A start countdown ("3, 2, 1, GO!") and a finish screen showing final results
Background music and engine sound effects
Optimized for PC (Windows) with keyboard controls (WASD or arrow keys)
Optional:
Add nitro boost feature (press a key like Shift for temporary speed boost with a
cooldown)
Add checkpoints and respawn logic if the car goes off-track
Include lighting effects for day/night race options
Platform FOR GAME
App builder
Ai studio
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple JS Racer</title>
<style>
body {
margin: 0;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #333;
font-family: 'Arial', sans-serif;
color: white;
}
#game-container {
position: relative;
width: 640px; /* Standard retro resolution */
height: 480px;
border: 2px solid white;
overflow: hidden; /* Ensure UI stays within bounds */
background-color: black; /* Fallback background */
}
canvas {
display: block;
background-color: #70c5ce; /* Sky color */
}
.ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
z-index: 10;
}
.ui-overlay h1 {
margin-bottom: 20px;
font-size: 3em;
color: #ffcc00;
}
.ui-overlay button {
padding: 15px 30px;
font-size: 1.5em;
margin: 10px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
.ui-overlay button:hover {
background-color: #45a049;
}
.hidden {
display: none;
}
#hud {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
display: flex;
justify-content: space-between;
font-size: 1.2em;
color: white;
text-shadow: 1px 1px 2px black;
z-index: 5;
}
#countdown {
font-size: 5em;
color: yellow;
text-shadow: 3px 3px 5px black;
}
#garage-screen .car-option {
border: 2px solid white;
padding: 15px;
margin: 10px;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.1);
}
#garage-screen .car-option:hover {
background-color: rgba(255, 255, 255, 0.3);
}
#garage-screen .car-option.selected {
border-color: yellow;
background-color: rgba(255, 255, 0, 0.3);
}
#garage-screen p {
font-size: 0.9em;
margin-top: 5px;
}
#finish-screen p {
font-size: 1.5em;
margin: 5px 0;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas" width="640" height="480"></canvas>
<!-- Main Menu -->
<div id="main-menu" class="ui-overlay">
<h1>JS Racer</h1>
<button id="start-button">Start Game</button>
<button id="settings-button">Settings</button>
<button id="exit-button">Exit</button>
</div>
<!-- Garage Screen -->
<div id="garage-screen" class="ui-overlay hidden">
<h1>Choose Your Car</h1>
<div id="car-options">
<div class="car-option selected" data-car="1">
<h2>Sporty</h2>
<p>Speed: High, Handling: Medium, Accel: High</p>
</div>
<div class="car-option" data-car="2">
<h2>Balanced</h2>
<p>Speed: Medium, Handling: High, Accel: Medium</p>
</div>
<div class="car-option" data-car="3">
<h2>Tank</h2>
<p>Speed: Low, Handling: Low, Accel: Low (More durable?)</p>
</div>
</div>
<button id="race-button">Race!</button>
<button id="back-to-menu-button">Back</button>
</div>
<!-- Settings Screen (Placeholder) -->
<div id="settings-screen" class="ui-overlay hidden">
<h1>Settings</h1>
<p>(Settings UI would go here)</p>
<button id="settings-back-button">Back</button>
</div>
<!-- HUD -->
<div id="hud" class="hidden">
<div>Speed: <span id="speed">0</span> km/h</div>
<div>Time: <span id="time">0.00</span></div>
<div>Lap: <span id="lap">1</span> / 3</div>
<div>Position: <span id="position">4</span> / 4</div>
</div>
<!-- Countdown Overlay -->
<div id="countdown-overlay" class="ui-overlay hidden">
<span id="countdown">3</span>
</div>
<!-- Finish Screen -->
<div id="finish-screen" class="ui-overlay hidden">
<h1>Race Finished!</h1>
<p>Your Time: <span id="final-time">0.00</span></p>
<p>Your Position: <span id="final-position">?</span></p>
<button id="play-again-button">Play Again</button>
<button id="finish-menu-button">Main Menu</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gameContainer = document.getElementById('game-container');
// --- UI Elements ---
const mainMenu = document.getElementById('main-menu');
const garageScreen = document.getElementById('garage-screen');
const settingsScreen = document.getElementById('settings-screen');
const hud = document.getElementById('hud');
const countdownOverlay = document.getElementById('countdown-overlay');
const finishScreen = document.getElementById('finish-screen');
const speedDisplay = document.getElementById('speed');
const timeDisplay = document.getElementById('time');
const lapDisplay = document.getElementById('lap');
const positionDisplay = document.getElementById('position');
const countdownDisplay = document.getElementById('countdown');
const finalTimeDisplay = document.getElementById('final-time');
const finalPositionDisplay = document.getElementById('final-position');
// --- Buttons ---
const startButton = document.getElementById('start-button');
const settingsButton = document.getElementById('settings-button');
const exitButton = document.getElementById('exit-button'); // Note: Can't actually close
browser tab
const raceButton = document.getElementById('race-button');
const backToMenuButton = document.getElementById('back-to-menu-button');
const settingsBackButton = document.getElementById('settings-back-button');
const playAgainButton = document.getElementById('play-again-button');
const finishMenuButton = document.getElementById('finish-menu-button');
// --- Game States ---
const GAME_STATE = {
MENU: 'MENU',
GARAGE: 'GARAGE',
SETTINGS: 'SETTINGS',
COUNTDOWN: 'COUNTDOWN',
RACING: 'RACING',
FINISHED: 'FINISHED'
};
let currentGameState = GAME_STATE.MENU;
// --- Game Variables ---
let playerX = 0; // Player horizontal position relative to center (-1 to 1)
let playerZ = 0; // Player distance down the track
let speed = 0; // Current speed
let keys = {}; // Input tracking
const roadWidth = 2000;
const segmentLength = 200; // Length of one road segment
const rumbleLength = 3; // Number of segments per rumble strip
const lanes = 3;
const FOV = 100; // Field of view
const cameraHeight = 1000;
const cameraDepth = 1 / Math.tan((FOV / 2) * Math.PI / 180); // Camera distance from
screen
const drawDistance = 300; // Number of segments to draw
const maxSpeed = 600; // Max speed (units per frame) -> Adjust for km/h display
const accel = 1.5; // Acceleration rate
const braking = -4; // Braking rate
const decel = -0.7; // Natural deceleration (friction)
const offRoadDecel = -2.5; // Deceleration when off-road
const centrifugal = 0.3; // How much curve pulls car
const maxSteer = 0.8; // Max steering input effect
let trackLength = 0;
let road = []; // Array of road segments
let lap = 1;
const totalLaps = 3;
let raceTime = 0;
let countdownValue = 3;
let raceStartTime = 0;
let opponents = []; // Array for AI opponents
const totalCars = 4; // Player + 3 AI
let selectedCar = 1; // Default selected car
// --- Colors ---
const COLORS = {
SKY: '#70c5ce',
GRASS_LIGHT: '#6ab06a',
GRASS_DARK: '#5a9a5a',
ROAD_LIGHT: '#6b6b6b',
ROAD_DARK: '#6a6a6a',
RUMBLE_LIGHT: '#bbbbbb',
RUMBLE_DARK: '#acacac',
LANE: '#cccccc'
};
// --- Utility Functions ---
function project(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
p.camera.x = (p.world.x || 0) - cameraX;
p.camera.y = (p.world.y || 0) - cameraY;
p.camera.z = (p.world.z || 0) - cameraZ;
p.screen.scale = cameraDepth / p.camera.z;
p.screen.x = Math.round((width / 2) + (p.screen.scale * p.camera.x * width / 2));
p.screen.y = Math.round((height / 2) - (p.screen.scale * p.camera.y * height / 2));
p.screen.w = Math.round((p.screen.scale * roadWidth * width / 2));
}
function overlap(x1, w1, x2, w2, percent) {
let half = (percent || 1) / 2;
let min1 = x1 - (w1 * half);
let max1 = x1 + (w1 * half);
let min2 = x2 - (w2 * half);
let max2 = x2 + (w2 * half);
return !((max1 < min2) || (min1 > max2));
}
function drawQuad(ctx, layer, color, x1, y1, w1, x2, y2, w2) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x1 - w1, y1);
ctx.lineTo(x1 + w1, y1);
ctx.lineTo(x2 + w2, y2);
ctx.lineTo(x2 - w2, y2);
ctx.closePath();
ctx.fill();
}
function getSegment(z) {
if (trackLength === 0) return null;
return road[Math.floor(z / segmentLength) % road.length];
}
// --- Road Generation ---
function buildRoad() {
road = [];
// Simple curve and hills example
addStraight(100);
addCurve(50, 2);
addHill(50, 300);
addCurve(50, -4);
addStraight(150);
addHill(30, -400);
addCurve(100, 3);
addStraight(50);
// Ensure loop
addCurve(100, -3) // Curve back towards start X
addHill(50, calculateRequiredHillToEndLevel(road)); // Adjust height to end flat
addStraight(50); // Final straight to start line
trackLength = road.length * segmentLength;
// Add simple scenery (placeholder - needs sprite drawing)
for(let n = 0 ; n < road.length ; n += 10) {
if (road[n]) {
road[n].sprites = [{ source: 'tree', x: (Math.random() > 0.5 ? 1.5 : -1.5) }]; //
Example: Tree sprite
}
}
// Add opponents
opponents = [];
for (let i = 0; i < totalCars - 1; i++) {
opponents.push({
z: trackLength - (i + 1) * 30 * segmentLength, // Start further back
x: (Math.random() - 0.5) * 1.5, // Random initial X offset
speed: maxSpeed * (0.6 + Math.random() * 0.2), // Vary speeds slightly
color: `hsl(${Math.random() * 360}, 80%, 60%)` // Random color
});
}
}
function addSegment(curve, y) {
const index = road.length;
const previousY = road.length > 0 ? road[road.length - 1].p2.world.y : 0;
road.push({
index: index,
p1: { world: { y: previousY, z: index * segmentLength }, camera: {}, screen: {} },
p2: { world: { y: y + previousY, z: (index + 1) * segmentLength }, camera: {}, screen:
{} },
curve: curve,
color: Math.floor(index / rumbleLength) % 2 ? COLORS.ROAD_LIGHT :
COLORS.ROAD_DARK,
sprites: [], // Add sprites array
cars: [] // Add cars array for this segment
});
}
function addRoad(enter, hold, leave, curve, y) {
const startY = road.length > 0 ? road[road.length - 1].p2.world.y : 0;
const endY = startY + (y * segmentLength);
const total = enter + hold + leave;
for (let n = 0; n < enter; n++) addSegment(easeIn(0, curve, n / enter), easeInOut(startY,
endY, n / total));
for (let n = 0; n < hold; n++) addSegment(curve, easeInOut(startY, endY, (enter + n) /
total));
for (let n = 0; n < leave; n++) addSegment(easeInOut(curve, 0, n / leave),
easeInOut(startY, endY, (enter + hold + n) / total));
}
function addStraight(num) { addRoad(num, num, num, 0, 0); }
function addCurve(num, curve) { addRoad(num, num, num, curve, 0); }
function addHill(num, height) { addRoad(num, num, num, 0, height); }
// --- Easing Functions ---
function easeIn(a, b, percent) { return a + (b - a) * Math.pow(percent, 2); }
function easeOut(a, b, percent) { return a + (b - a) * (1 - Math.pow(1 - percent, 2)); }
function easeInOut(a, b, percent) { return a + (b - a) * ((-Math.cos(percent * Math.PI) / 2) +
0.5); }
function calculateRequiredHillToEndLevel(currentRoad) {
if (currentRoad.length === 0) return 0;
const currentY = currentRoad[currentRoad.length - 1].p2.world.y;
// Calculate the Y change needed over the *next* hill segment group
// Assume addHill adds 3 * num segments
const segmentsInNextHill = 50 * 3; // Based on the planned addHill(50, ...)
return -currentY / segmentsInNextHill; // Needs this much change per segment
}
// --- Input Handling ---
document.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; });
document.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; });
// --- Update Function ---
function update(dt) {
if (currentGameState !== GAME_STATE.RACING) return;
const playerSegment = getSegment(playerZ + cameraHeight); // Segment camera is over
const playerPercent = (playerZ % segmentLength) / segmentLength;
const speedPercent = speed / maxSpeed;
const dx = dt * 2 * speedPercent; // Multiplier for steering based on speed
// --- Player Movement ---
// Acceleration/Deceleration
if (keys['w'] || keys['arrowup']) {
speed = Math.min(maxSpeed, speed + accel * dt);
} else if (keys['s'] || keys['arrowdown']) {
speed = Math.max(0, speed + braking * dt); // Braking is negative
} else {
speed = Math.max(0, speed + decel * dt); // Natural deceleration
}
// Steering
if (keys['a'] || keys['arrowleft']) {
playerX = playerX - dx * maxSteer;
// Simple car sprite lean - needs drawing logic
} else if (keys['d'] || keys['arrowright']) {
playerX = playerX + dx * maxSteer;
// Simple car sprite lean - needs drawing logic
} else {
// Reset lean
}
// Centrifugal force on curves
if (playerSegment) {
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
}
// Keep player on track (simple boundary)
if (Math.abs(playerX) > 1) {
speed = Math.max(0, speed + offRoadDecel * dt); // Slow down significantly off-road
// Basic collision check with scenery (placeholder)
if (playerSegment && playerSegment.sprites.length > 0) {
for(let sprite of playerSegment.sprites) {
const spriteX = sprite.x; // Sprite position relative to center
// Simple check: if player X is beyond road AND near sprite X
if((playerX > 1 && spriteX > 0) || (playerX < -1 && spriteX < 0)) {
if(Math.abs(playerX - spriteX) < 0.5) { // Arbitrary collision distance
speed = speed * 0.2; // Drastic slowdown on hitting scenery
playerZ = Math.max(0, playerZ - segmentLength * 0.5); // Push back
slightly
}
}
}
}
}
// Clamp playerX (prevent going too far off)
playerX = Math.max(-2, Math.min(2, playerX)); // Allow going slightly off road
// Update player position (Z)
playerZ = (playerZ + speed * dt); // No dt scaling on speed yet
// --- Lap Handling ---
if (playerZ >= trackLength) {
playerZ -= trackLength;
lap++;
if (lap > totalLaps) {
finishRace();
}
}
// --- Update AI Opponents ---
updateAI(dt);
// --- Update Race Time ---
raceTime = (performance.now() - raceStartTime) / 1000;
}
// --- AI Update Function ---
function updateAI(dt) {
for (let i = 0; i < opponents.length; i++) {
const car = opponents[i];
const oldSegment = getSegment(car.z);
// Basic AI: follow the curve, try to stay centered, maintain speed
const targetX = 0; // Aim for center for now
const segment = getSegment(car.z + cameraHeight); // Segment ahead of AI car
if (segment) {
// Steer based on curve and target
const steer = segment.curve * centrifugal * (car.speed / maxSpeed); // Simplified
centrifugal for AI
car.x += (targetX - car.x) * 0.1 * dt * (car.speed / maxSpeed); // Gradually move
towards target X
car.x -= steer * dt * 2 * (car.speed / maxSpeed); // Counter-steer curve
}
// Very simple collision avoidance (if close behind player or another AI)
// Check player
if (Math.abs(car.z - playerZ) < segmentLength * 3 && Math.abs(car.x - playerX) <
0.5) {
car.speed *= 0.95; // Slow down if close behind player
}
// Check other AI cars
for (let j = 0; j < opponents.length; j++) {
if (i === j) continue;
const otherCar = opponents[j];
if (car.z > otherCar.z && (car.z - otherCar.z) < segmentLength * 3 &&
Math.abs(car.x - otherCar.x) < 0.5) {
car.speed *= 0.98; // Slow down slightly if close behind another AI
car.x += (car.x > otherCar.x ? 0.01 : -0.01) * dt; // Try to slightly move aside
break; // Only react to one car at a time
}
}
// Clamp AI X position (keep them mostly on road)
car.x = Math.max(-1.2, Math.min(1.2, car.x));
car.z = (car.z + car.speed * dt);
if (car.z >= trackLength) {
car.z -= trackLength; // Wrap around
}
// Add car to the segment it's currently in (for rendering)
const newSegment = getSegment(car.z);
if (oldSegment) oldSegment.cars = oldSegment.cars.filter(c => c !== car); // Remove
from old segment
if (newSegment) {
if (!newSegment.cars) newSegment.cars = [];
newSegment.cars.push(car); // Add to new segment
}
}
}
// --- Render Function ---
function render() {
// Ensure canvas size matches CSS size for context scaling
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const width = canvas.width;
const height = canvas.height;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Draw sky background
ctx.fillStyle = COLORS.SKY;
ctx.fillRect(0, 0, width, height);
// --- Render Road ---
const baseSegment = getSegment(playerZ);
const basePercent = (playerZ % segmentLength) / segmentLength;
let cameraX = playerX * roadWidth * (baseSegment ? baseSegment.curve : 0); // Camera
shifts slightly based on player X and curve
const cameraY = cameraHeight + (baseSegment ? baseSegment.p1.world.y : 0); // Add
base segment height
const cameraZ = playerZ; // Camera Z is player Z
let maxy = height;
let x = 0; // Current X offset due to curves
let dx = 0; // Curve delta per segment
// Reset cars array on segments for this frame
road.forEach(segment => segment.cars = []);
// Re-add opponents to their current segments for rendering order
opponents.forEach(car => {
const seg = getSegment(car.z);
if (seg) {
if (!seg.cars) seg.cars = [];
seg.cars.push(car);
}
});
for (let n = 0; n < drawDistance; n++) {
const segmentIndex = (baseSegment.index + n) % road.length;
const segment = road[segmentIndex];
const looped = segmentIndex < baseSegment.index; // Is this segment wrapping
around?
const segmentLoopedZ = looped ? trackLength : 0;
// Project segment points
project(segment.p1, cameraX - x, cameraY, cameraZ - segmentLoopedZ,
cameraDepth, width, height, roadWidth);
project(segment.p2, cameraX - x - dx, cameraY, cameraZ - segmentLoopedZ,
cameraDepth, width, height, roadWidth);
x += dx;
dx += segment.curve;
// Don't draw if behind camera or too far left/right
if ((segment.p1.camera.z <= cameraDepth) || // Behind camera
(segment.p2.screen.y >= maxy) || // Bottom edge is below previous top edge
(segment.p2.screen.y >= segment.p1.screen.y)) // Vanishing point issue
{ continue; }
// --- Draw Segment ---
// Grass
const grassColor = Math.floor(segment.index / rumbleLength) % 2 ?
COLORS.GRASS_LIGHT : COLORS.GRASS_DARK;
drawQuad(ctx, 0, grassColor, 0, segment.p1.screen.y, width, 0, segment.p2.screen.y,
width);
// Road and Rumble Strips
const rumbleWidth1 = segment.p1.screen.w * 1.15; // Wider rumble strip
const rumbleWidth2 = segment.p2.screen.w * 1.15;
const roadWidth1 = segment.p1.screen.w;
const roadWidth2 = segment.p2.screen.w;
const laneMarkerWidth1 = roadWidth1 / lanes / 8; // Width of lane marker
const laneMarkerWidth2 = roadWidth2 / lanes / 8;
const rumbleColor = Math.floor(segment.index / rumbleLength) % 2 ?
COLORS.RUMBLE_LIGHT : COLORS.RUMBLE_DARK;
drawQuad(ctx, 1, rumbleColor, segment.p1.screen.x, segment.p1.screen.y,
rumbleWidth1, segment.p2.screen.x, segment.p2.screen.y, rumbleWidth2);
drawQuad(ctx, 1, segment.color, segment.p1.screen.x, segment.p1.screen.y,
roadWidth1, segment.p2.screen.x, segment.p2.screen.y, roadWidth2);
// Lane Markers (only draw if segment index has lanes)
if (Math.floor(segment.index / rumbleLength) % 2) { // Only draw on one color road
for dashed effect
const laneColor = COLORS.LANE;
const laneWidthRatio = 1 / lanes; // How much of road width is one lane center-to-
center
for (let lane = 1; lane < lanes; lane++) {
const laneX1 = segment.p1.screen.x - roadWidth1 + (roadWidth1 * 2 * lane *
laneWidthRatio);
const laneX2 = segment.p2.screen.x - roadWidth2 + (roadWidth2 * 2 * lane *
laneWidthRatio);
drawQuad(ctx, 2, laneColor, laneX1, segment.p1.screen.y, laneMarkerWidth1,
laneX2, segment.p2.screen.y, laneMarkerWidth2);
}
}
maxy = segment.p1.screen.y; // Clip next segment's bottom edge to this top edge
// --- Render Sprites (Scenery and Cars) ---
// Scenery
for(let i = 0; i < segment.sprites.length; i++) {
const sprite = segment.sprites[i];
const spriteScale = segment.p1.screen.scale;
const spriteX = segment.p1.screen.x + (spriteScale * sprite.x * roadWidth * width /
2);
const spriteY = segment.p1.screen.y;
// Simple rectangle sprite placeholder
const spriteW = (sprite.source === 'tree' ? 100 : 150) * spriteScale * (width/640); //
Adjust size based on canvas width
const spriteH = (sprite.source === 'tree' ? 200 : 100) * spriteScale * (width/640);
// Draw sprite centered horizontally, bottom aligned with segment start
drawSprite(ctx, 3, sprite.source, spriteX - spriteW / 2, spriteY - spriteH, spriteW,
spriteH);
}
// Opponent Cars on this segment
if (segment.cars) {
segment.cars.sort((a, b) => b.z - a.z); // Draw cars further back first
segment.cars.forEach(car => {
const carPercent = (car.z % segmentLength) / segmentLength;
const carY = interpolate(segment.p1.screen.y, segment.p2.screen.y, carPercent);
const carScale = interpolate(segment.p1.screen.scale, segment.p2.screen.scale,
carPercent);
const carX = segment.p1.screen.x + (carScale * car.x * roadWidth * width / 2) +
interpolate(segment.p1.screen.x - segment.p2.screen.x, 0, carPercent); // Interpolate position
between segment points
const carW = 80 * carScale * (width/640);
const carH = 50 * carScale * (width/640);
drawSprite(ctx, 4, 'car', carX - carW / 2, carY - carH, carW, carH, car.color); //
Use car's color
});
}
}
// --- Render Player Car --- (Simple placeholder rectangle)
const playerCarY = height - 60; // Position near bottom
const playerCarW = 80;
const playerCarH = 50;
// Slight lean based on input - very basic
const leanAmount = (keys['a'] || keys['arrowleft'] ? -15 : (keys['d'] || keys['arrowright'] ?
15 : 0));
ctx.save();
ctx.translate(width / 2, playerCarY);
ctx.rotate(leanAmount * Math.PI / 180); // Apply lean rotation
ctx.fillStyle = 'red'; // Player car color
ctx.fillRect(-playerCarW / 2, -playerCarH / 2, playerCarW, playerCarH); // Draw
centered
// Add simple windscreen
ctx.fillStyle = 'lightblue';
ctx.fillRect(-playerCarW * 0.4, -playerCarH * 0.4, playerCarW * 0.8, playerCarH * 0.3);
ctx.restore();
// --- Update HUD ---
if (currentGameState === GAME_STATE.RACING || currentGameState ===
GAME_STATE.FINISHED) {
const displaySpeed = Math.round(speed * 0.36); // Convert speed units to rough km/h
speedDisplay.textContent = displaySpeed;
timeDisplay.textContent = raceTime.toFixed(2);
lapDisplay.textContent = `${Math.min(lap, totalLaps)} / ${totalLaps}`;
positionDisplay.textContent = calculatePosition();
}
}
function interpolate(a, b, percent) {
return a + (b - a) * percent;
}
function drawSprite(ctx, layer, type, x, y, w, h, color = 'green') {
// Simple placeholder drawing logic
if (type === 'tree') {
// Trunk
ctx.fillStyle = '#8B4513'; // Brown
ctx.fillRect(x + w * 0.4, y + h * 0.6, w * 0.2, h * 0.4);
// Leaves
ctx.fillStyle = '#228B22'; // Forest green
ctx.beginPath();
ctx.moveTo(x + w * 0.5, y); // Top point
ctx.lineTo(x + w, y + h * 0.7);
ctx.lineTo(x, y + h * 0.7);
ctx.closePath();
ctx.fill();
} else if (type === 'car') {
ctx.fillStyle = color; // Use provided car color
ctx.fillRect(x, y, w, h);
// Simple windscreen
ctx.fillStyle = 'rgba(173, 216, 230, 0.8)'; // Light blue semi-transparent
ctx.fillRect(x + w * 0.1, y + h*0.1, w * 0.8, h * 0.3);
} else {
// Default billboard/obstacle placeholder
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
}
function calculatePosition() {
let playerPos = lap + playerZ / trackLength;
let aheadCount = 1; // Start at 1 (player itself)
for (let i = 0; i < opponents.length; i++) {
// Need to track opponent laps too for accurate position over multiple laps
// Simple version: just compare Z position within the current lap context
let oppZ = opponents[i].z;
// Basic check: if opponent is further along track OR on same lap but further Z
// This is very basic and doesn't handle lap differences correctly yet.
if (oppZ > playerZ) { // Need lap comparison!
aheadCount++;
}
}
return aheadCount; // Placeholder - needs proper lap tracking for AI
}
// --- Game Loop ---
let lastTime = performance.now();
function gameLoop(timestamp) {
const dt = Math.min(1, (timestamp - lastTime) / 1000); // Delta time in seconds, capped at
1s
if (currentGameState === GAME_STATE.RACING) {
update(dt * 60); // Multiply dt by 60 for frame-independent physics based on 60fps
logic
render();
} else if (currentGameState === GAME_STATE.COUNTDOWN) {
render(); // Render the track behind countdown
} else {
// For menu states, we don't need constant update/render unless animating menus
}
lastTime = timestamp;
requestAnimationFrame(gameLoop);
}
// --- State Management ---
function showScreen(screenToShow) {
mainMenu.classList.add('hidden');
garageScreen.classList.add('hidden');
settingsScreen.classList.add('hidden');
hud.classList.add('hidden');
countdownOverlay.classList.add('hidden');
finishScreen.classList.add('hidden');
canvas.style.display = 'none'; // Hide canvas by default
if (screenToShow === GAME_STATE.MENU) {
mainMenu.classList.remove('hidden');
} else if (screenToShow === GAME_STATE.GARAGE) {
garageScreen.classList.remove('hidden');
} else if (screenToShow === GAME_STATE.SETTINGS) {
settingsScreen.classList.remove('hidden');
} else if (screenToShow === GAME_STATE.COUNTDOWN) {
canvas.style.display = 'block';
hud.classList.remove('hidden'); // Show HUD during countdown
countdownOverlay.classList.remove('hidden');
} else if (screenToShow === GAME_STATE.RACING) {
canvas.style.display = 'block';
hud.classList.remove('hidden');
} else if (screenToShow === GAME_STATE.FINISHED) {
canvas.style.display = 'block'; // Often show track behind results
hud.classList.remove('hidden');
finishScreen.classList.remove('hidden');
}
}
function changeState(newState) {
currentGameState = newState;
showScreen(newState);
}
// --- Game Flow Functions ---
function startGame() {
// Reset game variables
playerX = 0;
playerZ = 0;
speed = 0;
lap = 1;
raceTime = 0;
countdownValue = 3;
keys = {};
// Build track and place opponents
buildRoad();
changeState(GAME_STATE.COUNTDOWN);
countdownDisplay.textContent = countdownValue;
startCountdown();
}
function startCountdown() {
let interval = setInterval(() => {
countdownValue--;
if (countdownValue > 0) {
countdownDisplay.textContent = countdownValue;
} else if (countdownValue === 0) {
countdownDisplay.textContent = 'GO!';
} else {
clearInterval(interval);
changeState(GAME_STATE.RACING);
raceStartTime = performance.now(); // Start timer precisely now
}
}, 1000);
}
function finishRace() {
changeState(GAME_STATE.FINISHED);
finalTimeDisplay.textContent = raceTime.toFixed(2);
finalPositionDisplay.textContent = calculatePosition(); // Final position calc
// Maybe stop engine sounds here
}
// --- Event Listeners for UI ---
startButton.addEventListener('click', () => changeState(GAME_STATE.GARAGE));
settingsButton.addEventListener('click', () => changeState(GAME_STATE.SETTINGS));
exitButton.addEventListener('click', () => {
// Cannot close tab via JS, maybe just go back to menu or display message
alert("Exiting... (In a real app, this might close or go back)");
changeState(GAME_STATE.MENU);
});
// Garage selection
const carOptions = document.querySelectorAll('#garage-screen .car-option');
carOptions.forEach(option => {
option.addEventListener('click', () => {
carOptions.forEach(opt => opt.classList.remove('selected')); // Deselect others
option.classList.add('selected'); // Select clicked one
selectedCar = parseInt(option.getAttribute('data-car'));
// TODO: Actually apply different car stats (speed, accel, handling) here
console.log("Selected car:", selectedCar);
});
});
raceButton.addEventListener('click', startGame);
backToMenuButton.addEventListener('click', () => changeState(GAME_STATE.MENU));
settingsBackButton.addEventListener('click', () => changeState(GAME_STATE.MENU));
playAgainButton.addEventListener('click', startGame); // Restart the game
finishMenuButton.addEventListener('click', () => changeState(GAME_STATE.MENU));
// --- Initial Setup ---
changeState(GAME_STATE.MENU); // Start at the main menu
requestAnimationFrame(gameLoop); // Start the loop
// Placeholder for audio loading/init (needs implementation)
// let engineSound = new Audio('path/to/engine.wav'); engineSound.loop = true;
// let crashSound = new Audio('path/to/crash.wav');
// let music = new Audio('path/to/music.mp3'); music.loop = true;
// function playSound(sound) { /* sound.play(); */ }
// function stopSound(sound) { /* sound.pause(); sound.currentTime = 0; */ }
// function adjustEngineSound(volume, pitch) { /* engineSound.volume = volume;
engineSound.playbackRate = pitch; */ }
</script>
</body>
</html>
FOR VIDEO THUMBNAIL DOWNLOADER
can you create a complete youtube, facebook, tiktok, instagram thumbnail downloader in
javascript
DEEPSEEK
GROK
TO MONETIZE:
Mobiroller: Android & iOS App Maker | Free App Maker | App Builder
PROMPT: can you create a complete youtube, facebook, tiktok, instagram thumbnail
downloader in javascript
Edit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Social Media Thumbnail Downloader</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 {
text-align: center;
color: #333;
}
.container {
background: #f9f9f9;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
input[type="text"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background: #4285f4;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #3367d6;
}
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
.thumbnail {
max-width: 100%;
margin-top: 10px;
border: 1px solid #eee;
}
.error {
color: #d32f2f;
margin-top: 10px;
}
.platform-selector {
margin-bottom: 15px;
}
.platform-selector label {
margin-right: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>Social Media Thumbnail Downloader</h1>
<div class="platform-selector">
<label>
<input type="radio" name="platform" value="youtube" checked> YouTube
</label>
<label>
<input type="radio" name="platform" value="facebook"> Facebook
</label>
<label>
<input type="radio" name="platform" value="tiktok"> TikTok
</label>
<label>
<input type="radio" name="platform" value="instagram"> Instagram
</label>
</div>
<input type="text" id="urlInput" placeholder="Enter video URL...">
<button id="downloadBtn">Get Thumbnail</button>
<div id="resultContainer" class="result" style="display: none;">
<h3>Thumbnail Preview:</h3>
<img id="thumbnailImg" class="thumbnail" src="" alt="Thumbnail">
<div id="thumbnailOptions"></div>
<a id="downloadLink" href="#" download><button>Download
Thumbnail</button></a>
</div>
<div id="errorContainer" class="error"></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const urlInput = document.getElementById('urlInput');
const downloadBtn = document.getElementById('downloadBtn');
const resultContainer = document.getElementById('resultContainer');
const thumbnailImg = document.getElementById('thumbnailImg');
const thumbnailOptions = document.getElementById('thumbnailOptions');
const downloadLink = document.getElementById('downloadLink');
const errorContainer = document.getElementById('errorContainer');
downloadBtn.addEventListener('click', fetchThumbnail);
function fetchThumbnail() {
const url = urlInput.value.trim();
const platform = document.querySelector('input[name="platform"]:checked').value;
// Clear previous results
resultContainer.style.display = 'none';
errorContainer.textContent = '';
thumbnailOptions.innerHTML = '';
if (!url) {
showError('Please enter a URL');
return;
}
try {
let thumbnailUrl;
switch(platform) {
case 'youtube':
thumbnailUrl = getYouTubeThumbnail(url);
if (thumbnailUrl) {
showThumbnail(thumbnailUrl);
showYouTubeQualityOptions(url);
} else {
showError('Invalid YouTube URL');
}
break;
case 'facebook':
getFacebookThumbnail(url)
.then(thumbnailUrl => {
if (thumbnailUrl) {
showThumbnail(thumbnailUrl);
} else {
showError('Could not fetch Facebook thumbnail');
}
})
.catch(error => {
showError('Error fetching Facebook thumbnail: ' + error.message);
});
break;
case 'tiktok':
getTikTokThumbnail(url)
.then(thumbnailUrl => {
if (thumbnailUrl) {
showThumbnail(thumbnailUrl);
} else {
showError('Could not fetch TikTok thumbnail');
}
})
.catch(error => {
showError('Error fetching TikTok thumbnail: ' + error.message);
});
break;
case 'instagram':
getInstagramThumbnail(url)
.then(thumbnailUrl => {
if (thumbnailUrl) {
showThumbnail(thumbnailUrl);
} else {
showError('Could not fetch Instagram thumbnail');
}
})
.catch(error => {
showError('Error fetching Instagram thumbnail: ' + error.message);
});
break;
default:
showError('Unsupported platform');
}
} catch (error) {
showError('Error: ' + error.message);
}
}
function getYouTubeThumbnail(url) {
// Extract video ID from various YouTube URL formats
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
const match = url.match(regExp);
if (match && match[2].length === 11) {
const videoId = match[2];
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
}
return null;
}
function showYouTubeQualityOptions(url) {
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
const match = url.match(regExp);
if (match && match[2].length === 11) {
const videoId = match[2];
const qualities = [
{ name: 'Maximum Resolution', url:
`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg` },
{ name: 'High Quality', url:
`https://img.youtube.com/vi/${videoId}/hqdefault.jpg` },
{ name: 'Medium Quality', url:
`https://img.youtube.com/vi/${videoId}/mqdefault.jpg` },
{ name: 'Standard Quality', url:
`https://img.youtube.com/vi/${videoId}/sddefault.jpg` },
{ name: 'Default', url: `https://img.youtube.com/vi/${videoId}/default.jpg` }
];
const optionsDiv = document.createElement('div');
optionsDiv.innerHTML = '<h4>Quality Options:</h4>';
qualities.forEach(quality => {
const btn = document.createElement('button');
btn.textContent = quality.name;
btn.style.margin = '5px';
btn.addEventListener('click', () => {
thumbnailImg.src = quality.url;
downloadLink.href = quality.url;
});
optionsDiv.appendChild(btn);
});
thumbnailOptions.appendChild(optionsDiv);
}
}
async function getFacebookThumbnail(url) {
// Note: Facebook requires server-side scraping due to client-side restrictions
// This is a simplified approach that may not work for all videos
// Try to extract video ID
const regExp = /(?:https?:\/\/)?(?:www\.|m\.|mobile\.)?(?:facebook\.com|fb\.watch)\/(?:
[^\/]+\/videos\/|video\.php\?v=|watch\/?\?v=)(\d+)/;
const match = url.match(regExp);
if (match && match[1]) {
const videoId = match[1];
return `https://graph.facebook.com/${videoId}/picture`;
}
// Alternative approach using oEmbed (requires server-side implementation)
try {
const oembedUrl = `https://www.facebook.com/plugins/video/oembed.json/?url=$
{encodeURIComponent(url)}`;
const response = await fetch(oembedUrl);
const data = await response.json();
if (data.thumbnail_url) {
return data.thumbnail_url;
}
} catch (error) {
console.error('Facebook oEmbed error:', error);
}
throw new Error('Could not extract Facebook thumbnail URL');
}
async function getTikTokThumbnail(url) {
// TikTok thumbnails can be obtained by modifying the video URL
// This approach may break if TikTok changes their URL structure
//
For image scanner
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Image Text Editor</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f9f9f9;
}
#output {
margin-top: 20px;
}
textarea {
width: 100%;
height: 200px;
}
canvas {
max-width: 100%;
margin-top: 20px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>Image Text Extractor and Editor</h1>
<input type="file" id="imageInput" accept="image/*" />
<div id="output"></div>
<canvas id="canvas"></canvas>
<script>
const imageInput = document.getElementById('imageInput');
const output = document.getElementById('output');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
imageInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = async () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
output.innerHTML = '<p><strong>Extracting text...</strong></p>';
const result = await Tesseract.recognize(img, 'eng', {
logger: m => console.log(m)
});
output.innerHTML = `
<p><strong>Edit Extracted Text:</strong></p>
<textarea id="editText">${result.data.text}</textarea>
<button onclick="drawText()">Apply Text to Image</button>
`;
};
});
function drawText() {
const text = document.getElementById('editText').value;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const img = new Image();
img.src = URL.createObjectURL(imageInput.files[0]);
img.onload = () => {
ctx.drawImage(img, 0, 0);
ctx.font = "20px Arial";
ctx.fillStyle = "red";
const lines = text.split('\n');
lines.forEach((line, index) => {
ctx.fillText(line, 20, 30 + index * 25);
});
};
}
</script>
</body>
</html>