Meta Description" name="description" />
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Şehir Bilgi Yarışması - 3D</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #000; }
#game-container { position: relative; width: 100vw; height: 100vh; }
/* === INTRO SCREEN === */
#intro-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
display: flex; flex-direction: column; justify-content: center; align-items: center;
z-index: 1000; transition: opacity 1s ease;
}
#intro-screen h1 {
font-size: 4rem; color: #e94560; text-shadow: 0 0 20px rgba(233,69,96,0.5);
margin-bottom: 20px; animation: pulse 2s infinite;
}
@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
#intro-screen .subtitle { font-size: 1.5rem; color: #a0a0a0; margin-bottom: 40px; }
#intro-screen .instructions {
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
border-radius: 15px; padding: 30px; max-width: 600px; color: #ddd;
line-height: 1.8; font-size: 1.1rem; margin-bottom: 40px;
}
#intro-screen .instructions h3 { color: #e94560; margin-bottom: 15px; font-size: 1.3rem; }
#intro-screen .instructions ul { list-style: none; padding: 0; }
#intro-screen .instructions li { padding: 8px 0; padding-left: 30px; position: relative; }
#intro-screen .instructions li::before { content: "🎮"; position: absolute; left: 0; }
#start-btn {
padding: 18px 60px; font-size: 1.5rem; font-weight: bold;
background: linear-gradient(45deg, #e94560, #ff6b6b); color: white;
border: none; border-radius: 50px; cursor: pointer;
box-shadow: 0 10px 30px rgba(233,69,96,0.4); transition: all 0.3s ease;
}
#start-btn:hover { transform: translateY(-3px); box-shadow: 0 15px 40px rgba(233,69,96,0.6); }
/* === CANVAS === */
canvas { display: block; width: 100%; height: 100%; }
/* === HUD === */
#hud {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none; z-index: 10;
}
/* Mini Map */
#minimap {
position: absolute; bottom: 20px; left: 20px;
width: 180px; height: 180px; background: rgba(0,0,0,0.7);
border: 2px solid rgba(255,255,255,0.3); border-radius: 50%;
overflow: hidden; pointer-events: auto;
}
#minimap-canvas { width: 100%; height: 100%; }
/* Speedometer */
#speedometer {
position: absolute; bottom: 20px; right: 20px;
background: rgba(0,0,0,0.7); border: 2px solid rgba(255,255,255,0.3);
border-radius: 15px; padding: 15px 25px; color: white; text-align: center;
}
#speed-value { font-size: 2.5rem; font-weight: bold; color: #4ecdc4; }
#speed-label { font-size: 0.9rem; color: #888; }
/* Timer */
#timer {
position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.7); border: 2px solid rgba(255,255,255,0.3);
border-radius: 15px; padding: 15px 40px; color: white; text-align: center;
}
#timer-value { font-size: 2.5rem; font-weight: bold; }
#timer-value.warning { color: #ff6b6b; animation: blink 0.5s infinite; }
@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
#timer-label { font-size: 0.9rem; color: #888; }
/* Navigation Arrow */
#nav-arrow {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 60px; height: 60px; pointer-events: none; z-index: 20;
filter: drop-shadow(0 0 10px rgba(78,205,196,0.8));
}
/* Distance to target */
#distance-to-target {
position: absolute; top: 100px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.6); color: #4ecdc4; padding: 8px 20px;
border-radius: 20px; font-size: 1rem; font-weight: bold;
}
/* Question Modal */
#question-modal {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85); display: none; flex-direction: column;
justify-content: center; align-items: center; z-index: 500;
pointer-events: auto;
}
#question-modal.active { display: flex; }
#question-box {
background: linear-gradient(135deg, #1a1a2e, #16213e);
border: 2px solid #e94560; border-radius: 20px;
padding: 40px; max-width: 600px; width: 90%; text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
#question-text { font-size: 1.5rem; color: white; margin-bottom: 30px; line-height: 1.5; }
.answer-btn {
display: block; width: 100%; padding: 15px; margin: 10px 0;
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.2);
border-radius: 10px; color: white; font-size: 1.1rem; cursor: pointer;
transition: all 0.3s ease; text-align: left; padding-left: 20px;
}
.answer-btn:hover { background: rgba(233,69,96,0.2); border-color: #e94560; transform: translateX(5px); }
.answer-btn.correct { background: rgba(46,204,113,0.3); border-color: #2ecc71; }
.answer-btn.wrong { background: rgba(231,76,60,0.3); border-color: #e74c3c; }
/* Feedback */
#feedback {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
font-size: 3rem; font-weight: bold; padding: 20px 40px; border-radius: 15px;
display: none; z-index: 600; animation: popIn 0.5s ease;
}
@keyframes popIn { 0% { transform: translate(-50%, -50%) scale(0); } 80% { transform: translate(-50%, -50%) scale(1.1); } 100% { transform: translate(-50%, -50%) scale(1); } }
#feedback.correct { background: rgba(46,204,113,0.9); color: white; }
#feedback.wrong { background: rgba(231,76,60,0.9); color: white; }
/* Game Over */
#game-over {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.9); display: none; flex-direction: column;
justify-content: center; align-items: center; z-index: 1000;
pointer-events: auto;
}
#game-over.active { display: flex; }
#game-over h2 { font-size: 3rem; color: #e94560; margin-bottom: 20px; }
#game-over .stats { color: #ddd; font-size: 1.3rem; margin-bottom: 30px; line-height: 2; }
#restart-btn {
padding: 15px 50px; font-size: 1.3rem; background: #e94560; color: white;
border: none; border-radius: 30px; cursor: pointer; transition: all 0.3s;
}
#restart-btn:hover { background: #ff6b6b; transform: scale(1.05); }
/* Progress */
#progress {
position: absolute; top: 20px; right: 20px;
background: rgba(0,0,0,0.7); border: 2px solid rgba(255,255,255,0.3);
border-radius: 15px; padding: 15px 20px; color: white;
}
#progress-text { font-size: 1.2rem; }
/* Controls hint */
#controls-hint {
position: absolute; bottom: 220px; left: 20px;
background: rgba(0,0,0,0.6); color: #aaa; padding: 10px 15px;
border-radius: 10px; font-size: 0.85rem; line-height: 1.6;
}
</style>
</head>
<body>
<div id="game-container">
<!-- Intro Screen -->
<div id="intro-screen">
<h1>🏙️ ŞEHİR BİLGİ YARIŞMASI</h1>
<p class="subtitle">3D Açık Dünya Bilgi Yarışması</p>
<div class="instructions">
<h3>🎮 Nasıl Oynanır?</h3>
<ul>
<li><strong>WASD</strong> veya <strong>Ok Tuşları</strong> ile aracı süz</li>
<li>Haritadaki <strong>5 kırmızı noktaya</strong> git ve soruları cevapla</li>
<li>Doğru cevap: <strong>+15 saniye</strong> kazan</li>
<li>Yanlış cevap: <strong>-10 saniye</strong> kaybet</li>
<li>Tüm soruları cevapla veya süre bitmeden bitir!</li>
<li>Navigasyon oku sana hedefi gösterir</li>
</ul>
</div>
<button id="start-btn" onclick="startGame()">🚗 OYUNA BAŞLA</button>
</div>
<!-- Canvas -->
<canvas id="gameCanvas"></canvas>
<!-- HUD -->
<div id="hud" style="display:none;">
<div id="minimap">
<canvas id="minimap-canvas" width="180" height="180"></canvas>
</div>
<div id="controls-hint">
<strong>Kontroller:</strong><br>
W / ↑ : İleri<br>
S / ↓ : Geri<br>
A / ← : Sola<br>
D / → : Sağa<br>
SPACE : Fren
</div>
<div id="speedometer">
<div id="speed-value">0</div>
<div id="speed-label">km/h</div>
</div>
<div id="timer">
<div id="timer-value">120</div>
<div id="timer-label">SANİYE</div>
</div>
<div id="distance-to-target">Hedefe Mesafe: -- m</div>
<div id="progress">
<div id="progress-text">Soru: 0/5</div>
</div>
<svg id="nav-arrow" viewBox="0 0 100 100">
<polygon points="50,10 20,90 50,70 80,90" fill="#4ecdc4" stroke="white" stroke-width="2"/>
</svg>
</div>
<!-- Question Modal -->
<div id="question-modal">
<div id="question-box">
<div id="question-text">Soru burada görünecek...</div>
<div id="answers-container"></div>
</div>
</div>
<!-- Feedback -->
<div id="feedback"></div>
<!-- Game Over -->
<div id="game-over">
<h2 id="game-over-title">Oyun Bitti!</h2>
<div class="stats" id="game-over-stats"></div>
<button id="restart-btn" onclick="restartGame()">🔄 Yeniden Başla</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ==================== GAME STATE ====================
let gameState = {
started: false,
paused: false,
timeRemaining: 120,
score: 0,
questionsAnswered: 0,
totalQuestions: 5,
currentTarget: 0,
targetsCompleted: [],
gameOver: false
};
// ==================== QUESTIONS ====================
const questions = [
{
question: "Türkiye'nin başkenti hangi şehirdir?",
answers: ["İstanbul", "Ankara", "İzmir", "Bursa"],
correct: 1
},
{
question: "Dünya üzerindeki en yüksek dağ hangisidir?",
answers: ["K2", "Kilimanjaro", "Everest", "Aconcagua"],
correct: 2
},
{
question: "Hangi gezegen Güneş Sistemi'nde en büyüktür?",
answers: ["Satürn", "Mars", "Jüpiter", "Neptün"],
correct: 2
},
{
question: "İnsan vücudunda kaç kemik vardır (yetişkin)?",
answers: ["206", "208", "210", "212"],
correct: 0
},
{
question: "Hangi yıl Ay'a ilk kez ayak basıldı?",
answers: ["1967", "1969", "1971", "1973"],
correct: 1
}
];
// ==================== THREE.JS SETUP ====================
let scene, camera, renderer;
let car, carBody, carWheels = [];
let buildings = [], roads = [], trees = [], streetLights = [];
let targets = [];
let minimapCanvas, minimapCtx;
// Car physics
let carSpeed = 0;
let carAngle = 0;
const maxSpeed = 0.8;
const acceleration = 0.02;
const friction = 0.95;
const turnSpeed = 0.04;
// Input
const keys = { w: false, a: false, s: false, d: false, space: false };
// City size
const citySize = 400;
const blockSize = 80;
const roadWidth = 20;
function init() {
const canvas = document.getElementById('gameCanvas');
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 50, 300);
// Camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 30, 50);
// Renderer
renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
sunLight.position.set(100, 200, 100);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.camera.near = 0.5;
sunLight.shadow.camera.far = 500;
sunLight.shadow.camera.left = -200;
sunLight.shadow.camera.right = 200;
sunLight.shadow.camera.top = 200;
sunLight.shadow.camera.bottom = -200;
scene.add(sunLight);
// Ground
const groundGeo = new THREE.PlaneGeometry(citySize * 2, citySize * 2);
const groundMat = new THREE.MeshLambertMaterial({ color: 0x2d5a27 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Generate City
generateCity();
// Create Car
createCar();
// Create Targets
createTargets();
// Minimap setup
minimapCanvas = document.getElementById('minimap-canvas');
minimapCtx = minimapCanvas.getContext('2d');
// Event listeners
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('resize', onWindowResize);
// Start loop
animate();
}
function generateCity() {
// Grid of roads
const gridCount = Math.floor(citySize / blockSize);
// Road material
const roadMat = new THREE.MeshLambertMaterial({ color: 0x333333 });
const lineMat = new THREE.MeshBasicMaterial({ color: 0xFFD700 });
// Horizontal roads
for (let i = -gridCount; i <= gridCount; i++) {
const z = i * blockSize;
const roadGeo = new THREE.PlaneGeometry(citySize * 2, roadWidth);
const road = new THREE.Mesh(roadGeo, roadMat);
road.rotation.x = -Math.PI / 2;
road.position.set(0, 0.1, z);
road.receiveShadow = true;
scene.add(road);
roads.push(road);
// Center line
const lineGeo = new THREE.PlaneGeometry(citySize * 2, 0.5);
const line = new THREE.Mesh(lineGeo, lineMat);
line.rotation.x = -Math.PI / 2;
line.position.set(0, 0.2, z);
scene.add(line);
// Dashed lines
for (let x = -citySize; x < citySize; x += 15) {
const dashGeo = new THREE.PlaneGeometry(8, 0.8);
const dash = new THREE.Mesh(dashGeo, lineMat);
dash.rotation.x = -Math.PI / 2;
dash.position.set(x, 0.21, z);
scene.add(dash);
}
}
// Vertical roads
for (let i = -gridCount; i <= gridCount; i++) {
const x = i * blockSize;
const roadGeo = new THREE.PlaneGeometry(roadWidth, citySize * 2);
const road = new THREE.Mesh(roadGeo, roadMat);
road.rotation.x = -Math.PI / 2;
road.position.set(x, 0.1, 0);
road.receiveShadow = true;
scene.add(road);
roads.push(road);
// Center line
const lineGeo = new THREE.PlaneGeometry(0.5, citySize * 2);
const line = new THREE.Mesh(lineGeo, lineMat);
line.rotation.x = -Math.PI / 2;
line.position.set(x, 0.2, 0);
scene.add(line);
// Dashed lines
for (let z = -citySize; z < citySize; z += 15) {
const dashGeo = new THREE.PlaneGeometry(0.8, 8);
const dash = new THREE.Mesh(dashGeo, lineMat);
dash.rotation.x = -Math.PI / 2;
dash.position.set(x, 0.21, z);
scene.add(dash);
}
}
// Crosswalks at intersections
for (let i = -gridCount; i <= gridCount; i++) {
for (let j = -gridCount; j <= gridCount; j++) {
const x = i * blockSize;
const z = j * blockSize;
// Crosswalk lines
const cwMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
for (let k = 0; k < 5; k++) {
const cwGeo = new THREE.PlaneGeometry(2, roadWidth - 2);
const cw = new THREE.Mesh(cwGeo, cwMat);
cw.rotation.x = -Math.PI / 2;
cw.position.set(x - 6 + k * 3, 0.22, z);
scene.add(cw);
}
for (let k = 0; k < 5; k++) {
const cwGeo = new THREE.PlaneGeometry(roadWidth - 2, 2);
const cw = new THREE.Mesh(cwGeo, cwMat);
cw.rotation.x = -Math.PI / 2;
cw.position.set(x, 0.22, z - 6 + k * 3);
scene.add(cw);
}
}
}
// Buildings
const buildingColors = [0x1a1a2e, 0x16213e, 0x0f3460, 0x2c3e50, 0x34495e];
for (let i = -gridCount; i <= gridCount; i++) {
for (let j = -gridCount; j <= gridCount; j++) {
const x = i * blockSize;
const z = j * blockSize;
// Skip intersections
if (Math.abs(x) < roadWidth && Math.abs(z) < roadWidth) continue;
// Random building in each block
if (Math.random() > 0.3) {
const bx = x + (Math.random() - 0.5) * (blockSize - roadWidth - 10);
const bz = z + (Math.random() - 0.5) * (blockSize - roadWidth - 10);
const height = 20 + Math.random() * 60;
const width = 15 + Math.random() * 20;
const depth = 15 + Math.random() * 20;
const geo = new THREE.BoxGeometry(width, height, depth);
const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
const mat = new THREE.MeshLambertMaterial({ color: color });
const building = new THREE.Mesh(geo, mat);
building.position.set(bx, height / 2, bz);
building.castShadow = true;
building.receiveShadow = true;
scene.add(building);
buildings.push(building);
// Windows
const windowMat = new THREE.MeshBasicMaterial({ color: 0x88ccff, transparent: true, opacity: 0.6 });
for (let wy = 5; wy < height - 5; wy += 8) {
for (let wx = -width/2 + 3; wx < width/2 - 3; wx += 6) {
const winGeo = new THREE.PlaneGeometry(3, 4);
const win = new THREE.Mesh(winGeo, windowMat);
win.position.set(bx + wx, wy, bz + depth/2 + 0.1);
scene.add(win);
const win2 = win.clone();
win2.position.set(bx + wx, wy, bz - depth/2 - 0.1);
win2.rotation.y = Math.PI;
scene.add(win2);
}
}
}
}
}
// Trees
for (let i = 0; i < 80; i++) {
const tx = (Math.random() - 0.5) * citySize * 1.5;
const tz = (Math.random() - 0.5) * citySize * 1.5;
// Don't place on roads
const onRoad = Math.abs(tx % blockSize) < roadWidth/2 + 5 ||
Math.abs(tz % blockSize) < roadWidth/2 + 5;
if (!onRoad) {
createTree(tx, tz);
}
}
// Street lights
for (let i = -gridCount; i <= gridCount; i++) {
for (let j = -gridCount; j <= gridCount; j++) {
if (Math.random() > 0.5) {
const lx = i * blockSize + roadWidth/2 + 3;
const lz = j * blockSize;
createStreetLight(lx, lz);
}
}
}
}
function createTree(x, z) {
const trunkGeo = new THREE.CylinderGeometry(0.5, 0.8, 4, 6);
const trunkMat = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.set(x, 2, z);
trunk.castShadow = true;
scene.add(trunk);
const leavesGeo = new THREE.ConeGeometry(3, 8, 8);
const leavesMat = new THREE.MeshLambertMaterial({ color: 0x228B22 });
const leaves = new THREE.Mesh(leavesGeo, leavesMat);
leaves.position.set(x, 7, z);
leaves.castShadow = true;
scene.add(leaves);
trees.push({ trunk, leaves });
}
function createStreetLight(x, z) {
const poleGeo = new THREE.CylinderGeometry(0.2, 0.3, 12, 8);
const poleMat = new THREE.MeshLambertMaterial({ color: 0x555555 });
const pole = new THREE.Mesh(poleGeo, poleMat);
pole.position.set(x, 6, z);
scene.add(pole);
const lampGeo = new THREE.SphereGeometry(1, 8, 8);
const lampMat = new THREE.MeshBasicMaterial({ color: 0xffffcc });
const lamp = new THREE.Mesh(lampGeo, lampMat);
lamp.position.set(x, 12, z);
scene.add(lamp);
const light = new THREE.PointLight(0xffffcc, 0.5, 30);
light.position.set(x, 11, z);
scene.add(light);
streetLights.push({ pole, lamp, light });
}
function createCar() {
car = new THREE.Group();
// Car body
const bodyGeo = new THREE.BoxGeometry(4, 1.5, 8);
const bodyMat = new THREE.MeshLambertMaterial({ color: 0xe74c3c });
carBody = new THREE.Mesh(bodyGeo, bodyMat);
carBody.position.y = 1.2;
carBody.castShadow = true;
car.add(carBody);
// Car top
const topGeo = new THREE.BoxGeometry(3, 1, 4);
const topMat = new THREE.MeshLambertMaterial({ color: 0xc0392b });
const top = new THREE.Mesh(topGeo, topMat);
top.position.set(0, 2.5, -0.5);
top.castShadow = true;
car.add(top);
// Windshield
const windGeo = new THREE.PlaneGeometry(2.8, 1.5);
const windMat = new THREE.MeshBasicMaterial({ color: 0x88ccff, transparent: true, opacity: 0.5 });
const wind = new THREE.Mesh(windGeo, windMat);
wind.position.set(0, 2.5, 1.51);
car.add(wind);
// Wheels
const wheelGeo = new THREE.CylinderGeometry(0.6, 0.6, 0.5, 12);
const wheelMat = new THREE.MeshLambertMaterial({ color: 0x222222 });
const positions = [
[-2.2, 0.6, 2.5], [2.2, 0.6, 2.5],
[-2.2, 0.6, -2.5], [2.2, 0.6, -2.5]
];
positions.forEach(pos => {
const wheel = new THREE.Mesh(wheelGeo, wheelMat);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(...pos);
wheel.castShadow = true;
car.add(wheel);
carWheels.push(wheel);
});
// Headlights
const lightGeo = new THREE.SphereGeometry(0.3, 8, 8);
const lightMat = new THREE.MeshBasicMaterial({ color: 0xffffcc });
const hl1 = new THREE.Mesh(lightGeo, lightMat);
hl1.position.set(-1.2, 1.2, 4.1);
car.add(hl1);
const hl2 = new THREE.Mesh(lightGeo, lightMat);
hl2.position.set(1.2, 1.2, 4.1);
car.add(hl2);
// Headlight beams
const beam1 = new THREE.SpotLight(0xffffcc, 0.5, 50, Math.PI/6, 0.5);
beam1.position.set(-1.2, 1.2, 4);
beam1.target.position.set(-1.2, 0, 10);
car.add(beam1);
car.add(beam1.target);
const beam2 = new THREE.SpotLight(0xffffcc, 0.5, 50, Math.PI/6, 0.5);
beam2.position.set(1.2, 1.2, 4);
beam2.target.position.set(1.2, 0, 10);
car.add(beam2);
car.add(beam2.target);
// Taillights
const tailMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const tl1 = new THREE.Mesh(lightGeo, tailMat);
tl1.position.set(-1.2, 1.2, -4.1);
car.add(tl1);
const tl2 = new THREE.Mesh(lightGeo, tailMat);
tl2.position.set(1.2, 1.2, -4.1);
car.add(tl2);
car.position.set(0, 0, 0);
scene.add(car);
}
function createTargets() {
const targetPositions = [
{ x: 120, z: 120 },
{ x: -120, z: 120 },
{ x: -120, z: -120 },
{ x: 120, z: -120 },
{ x: 0, z: 200 }
];
targetPositions.forEach((pos, index) => {
// Platform
const platformGeo = new THREE.CylinderGeometry(8, 8, 0.5, 16);
const platformMat = new THREE.MeshLambertMaterial({ color: 0xe94560 });
const platform = new THREE.Mesh(platformGeo, platformMat);
platform.position.set(pos.x, 0.3, pos.z);
scene.add(platform);
// Glowing pillar
const pillarGeo = new THREE.CylinderGeometry(1, 1, 15, 8);
const pillarMat = new THREE.MeshBasicMaterial({ color: 0xe94560, transparent: true, opacity: 0.7 });
const pillar = new THREE.Mesh(pillarGeo, pillarMat);
pillar.position.set(pos.x, 7.5, pos.z);
scene.add(pillar);
// Light
const light = new THREE.PointLight(0xe94560, 1, 30);
light.position.set(pos.x, 10, pos.z);
scene.add(light);
// Floating ring
const ringGeo = new THREE.TorusGeometry(3, 0.3, 8, 16);
const ringMat = new THREE.MeshBasicMaterial({ color: 0xffd700 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.position.set(pos.x, 8, pos.z);
ring.rotation.x = Math.PI / 2;
scene.add(ring);
targets.push({
position: new THREE.Vector3(pos.x, 0, pos.z),
platform, pillar, light, ring,
index: index,
completed: false
});
});
}
// ==================== INPUT HANDLING ====================
function onKeyDown(e) {
const key = e.key.toLowerCase();
if (key === 'w' || key === 'arrowup') keys.w = true;
if (key === 'a' || key === 'arrowleft') keys.a = true;
if (key === 's' || key === 'arrowdown') keys.s = true;
if (key === 'd' || key === 'arrowright') keys.d = true;
if (key === ' ') keys.space = true;
}
function onKeyUp(e) {
const key = e.key.toLowerCase();
if (key === 'w' || key === 'arrowup') keys.w = false;
if (key === 'a' || key === 'arrowleft') keys.a = false;
if (key === 's' || key === 'arrowdown') keys.s = false;
if (key === 'd' || key === 'arrowright') keys.d = false;
if (key === ' ') keys.space = false;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// ==================== GAME LOGIC ====================
function updateCar() {
if (!gameState.started || gameState.paused || gameState.gameOver) return;
// Acceleration
if (keys.w) carSpeed += acceleration;
if (keys.s) carSpeed -= acceleration;
// Braking
if (keys.space) carSpeed *= 0.9;
// Friction
carSpeed *= friction;
// Max speed
if (carSpeed > maxSpeed) carSpeed = maxSpeed;
if (carSpeed < -maxSpeed / 2) carSpeed = -maxSpeed / 2;
// Stop if very slow
if (Math.abs(carSpeed) < 0.001) carSpeed = 0;
// Turning
if (Math.abs(carSpeed) > 0.01) {
if (keys.a) carAngle += turnSpeed * Math.sign(carSpeed);
if (keys.d) carAngle -= turnSpeed * Math.sign(carSpeed);
}
// Update position
car.position.x += Math.sin(carAngle) * carSpeed;
car.position.z += Math.cos(carAngle) * carSpeed;
// Rotation
car.rotation.y = carAngle;
// Wheel animation
carWheels.forEach(wheel => {
wheel.rotation.x += carSpeed * 0.5;
});
// Boundary check
const bound = citySize - 10;
if (car.position.x > bound) car.position.x = bound;
if (car.position.x < -bound) car.position.x = -bound;
if (car.position.z > bound) car.position.z = bound;
if (car.position.z < -bound) car.position.z = -bound;
}
function updateCamera() {
if (!car) return;
// Chase camera
const distance = 25;
const height = 15;
const offsetX = car.position.x - Math.sin(carAngle) * distance;
const offsetZ = car.position.z - Math.cos(carAngle) * distance;
camera.position.x += (offsetX - camera.position.x) * 0.1;
camera.position.z += (offsetZ - camera.position.z) * 0.1;
camera.position.y += (height - camera.position.y) * 0.1;
camera.lookAt(car.position.x, 2, car.position.z);
}
function updateTargets() {
if (!gameState.started || gameState.paused || gameState.gameOver) return;
const currentTarget = getCurrentTarget();
if (!currentTarget) return;
const dist = car.position.distanceTo(currentTarget.position);
// Animate ring
currentTarget.ring.rotation.z += 0.02;
currentTarget.ring.position.y = 8 + Math.sin(Date.now() * 0.002) * 1;
// Check proximity
if (dist < 10 && !gameState.paused) {
showQuestion(currentTarget.index);
}
// Update distance display
document.getElementById('distance-to-target').textContent =
`Hedefe Mesafe: ${Math.floor(dist)} m`;
}
function getCurrentTarget() {
for (let target of targets) {
if (!target.completed) return target;
}
return null;
}
function updateNavigationArrow() {
const currentTarget = getCurrentTarget();
const arrow = document.getElementById('nav-arrow');
if (!currentTarget || !gameState.started) {
arrow.style.display = 'none';
return;
}
arrow.style.display = 'block';
// Calculate angle to target
const dx = currentTarget.position.x - car.position.x;
const dz = currentTarget.position.z - car.position.z;
const targetAngle = Math.atan2(dx, dz);
// Relative angle
let relativeAngle = targetAngle - carAngle;
// Normalize
while (relativeAngle > Math.PI) relativeAngle -= Math.PI * 2;
while (relativeAngle < -Math.PI) relativeAngle += Math.PI * 2;
// Convert to degrees and rotate arrow
const degrees = relativeAngle * (180 / Math.PI);
arrow.style.transform = `translate(-50%, -50%) rotate(${degrees}deg)`;
}
function updateMinimap() {
if (!minimapCtx || !car) return;
const ctx = minimapCtx;
const w = minimapCanvas.width;
const h = minimapCanvas.height;
const scale = w / (citySize * 2.5);
const cx = w / 2;
const cy = h / 2;
// Clear
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, w, h);
// Draw roads
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
const gridCount = Math.floor(citySize / blockSize);
for (let i = -gridCount; i <= gridCount; i++) {
const pos = i * blockSize * scale;
ctx.beginPath();
ctx.moveTo(cx + pos, 0);
ctx.lineTo(cx + pos, h);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, cy + pos);
ctx.lineTo(w, cy + pos);
ctx.stroke();
}
// Draw targets
targets.forEach(target => {
const tx = cx + target.position.x * scale;
const tz = cy + target.position.z * scale;
ctx.fillStyle = target.completed ? '#2ecc71' : '#e94560';
ctx.beginPath();
ctx.arc(tx, tz, 6, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.stroke();
});
// Draw car
const carX = cx + car.position.x * scale;
const carZ = cy + car.position.z * scale;
ctx.fillStyle = '#4ecdc4';
ctx.beginPath();
ctx.arc(carX, carZ, 5, 0, Math.PI * 2);
ctx.fill();
// Car direction
ctx.strokeStyle = '#4ecdc4';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(carX, carZ);
ctx.lineTo(
carX + Math.sin(carAngle) * 12,
carZ + Math.cos(carAngle) * 12
);
ctx.stroke();
// Draw circle border
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(w/2, h/2, w/2 - 1, 0, Math.PI * 2);
ctx.stroke();
}
// ==================== QUESTION SYSTEM ====================
function showQuestion(targetIndex) {
gameState.paused = true;
carSpeed = 0;
const modal = document.getElementById('question-modal');
const questionText = document.getElementById('question-text');
const answersContainer = document.getElementById('answers-container');
const q = questions[targetIndex];
questionText.textContent = q.question;
answersContainer.innerHTML = '';
q.answers.forEach((answer, i) => {
const btn = document.createElement('button');
btn.className = 'answer-btn';
btn.textContent = `${String.fromCharCode(65 + i)}) ${answer}`;
btn.onclick = () => answerQuestion(targetIndex, i, btn);
answersContainer.appendChild(btn);
});
modal.classList.add('active');
}
function answerQuestion(targetIndex, answerIndex, btn) {
const q = questions[targetIndex];
const isCorrect = answerIndex === q.correct;
// Visual feedback on buttons
const buttons = document.querySelectorAll('.answer-btn');
buttons.forEach((b, i) => {
if (i === q.correct) b.classList.add('correct');
else if (i === answerIndex && !isCorrect) b.classList.add('wrong');
});
// Show feedback
showFeedback(isCorrect);
// Update game state
if (isCorrect) {
gameState.timeRemaining += 15;
gameState.score += 100;
} else {
gameState.timeRemaining -= 10;
}
// Mark target as completed
targets[targetIndex].completed = true;
gameState.questionsAnswered++;
// Update progress
document.getElementById('progress-text').textContent =
`Soru: ${gameState.questionsAnswered}/${gameState.totalQuestions}`;
// Close modal after delay
setTimeout(() => {
document.getElementById('question-modal').classList.remove('active');
gameState.paused = false;
// Check win condition
if (gameState.questionsAnswered >= gameState.totalQuestions) {
endGame(true);
}
}, 2000);
}
function showFeedback(isCorrect) {
const feedback = document.getElementById('feedback');
feedback.textContent = isCorrect ? '✅ DOĞRU! +15 sn' : '❌ YANLIŞ! -10 sn';
feedback.className = isCorrect ? 'correct' : 'wrong';
feedback.style.display = 'block';
setTimeout(() => {
feedback.style.display = 'none';
}, 1500);
}
// ==================== TIMER ====================
let lastTime = Date.now();
function updateTimer() {
if (!gameState.started || gameState.paused || gameState.gameOver) return;
const now = Date.now();
const delta = (now - lastTime) / 1000;
lastTime = now;
gameState.timeRemaining -= delta;
if (gameState.timeRemaining <= 0) {
gameState.timeRemaining = 0;
endGame(false);
}
const timerEl = document.getElementById('timer-value');
timerEl.textContent = Math.ceil(gameState.timeRemaining);
if (gameState.timeRemaining <= 20) {
timerEl.classList.add('warning');
} else {
timerEl.classList.remove('warning');
}
}
// ==================== GAME FLOW ====================
function startGame() {
document.getElementById('intro-screen').style.opacity = '0';
setTimeout(() => {
document.getElementById('intro-screen').style.display = 'none';
}, 1000);
document.getElementById('hud').style.display = 'block';
gameState.started = true;
gameState.timeRemaining = 120;
gameState.score = 0;
gameState.questionsAnswered = 0;
gameState.gameOver = false;
gameState.paused = false;
// Reset targets
targets.forEach(t => t.completed = false);
// Reset car
car.position.set(0, 0, 0);
carAngle = 0;
carSpeed = 0;
lastTime = Date.now();
document.getElementById('progress-text').textContent = 'Soru: 0/5';
}
function endGame(won) {
gameState.gameOver = true;
gameState.started = false;
const gameOverScreen = document.getElementById('game-over');
const title = document.getElementById('game-over-title');
const stats = document.getElementById('game-over-stats');
if (won) {
title.textContent = '🎉 TEBRİKLER!';
title.style.color = '#2ecc71';
} else {
title.textContent = '⏰ SÜRE DOLDU!';
title.style.color = '#e94560';
}
stats.innerHTML = `
Cevaplanan Soru: ${gameState.questionsAnswered}/${gameState.totalQuestions}<br>
Puan: ${gameState.score}<br>
Kalan Süre: ${Math.ceil(gameState.timeRemaining)} sn
`;
gameOverScreen.classList.add('active');
}
function restartGame() {
document.getElementById('game-over').classList.remove('active');
startGame();
}
// ==================== ANIMATION LOOP ====================
function animate() {
requestAnimationFrame(animate);
updateCar();
updateCamera();
updateTargets();
updateNavigationArrow();
updateMinimap();
updateTimer();
// Update speedometer
const speedKmh = Math.abs(Math.round(carSpeed * 100));
document.getElementById('speed-value').textContent = speedKmh;
renderer.render(scene, camera);
}
// Initialize
window.onload = init;
</script>
</body>
</html>2
2
162KB
633KB
304.0ms
296.0ms
305.0ms