Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Neon Runner</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="ui">
<h1 id="title">Neon Runner</h1>
<div id="menu">
<button id="startBtn">Başla</button>
</div>
<div id="hud" class="hidden">
<span id="score">0</span>
<span id="highscore"></span>
</div>
<div id="gameover" class="hidden">
<h2>Oyun Bitti</h2>
<p>Skor: <span id="finalScore"></span></p>
<button id="restartBtn">Tekrar Oyna</button>
</div>
</div>
<canvas id="game"></canvas>
<script src="main.js" type="module"></script>
</body>
</html>
188 changes: 188 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Neon Runner Game

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

// Resize canvas to fill window
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener("resize", resize);
resize();

// UI Elements
automaticBindUI();

let gameState = "menu"; // menu | playing | gameover
let obstacles = [];
let score = 0;
let highscore = +localStorage.getItem("highscore") || 0;
let lastSpawn = 0;
let spawnRate = 1500; // ms
const gravity = 0.6;
const groundY = () => canvas.height * 0.8;

class Player {
constructor() {
this.reset();
}
reset() {
this.w = 50;
this.h = 50;
this.x = canvas.width * 0.1;
this.y = groundY() - this.h;
this.vy = 0;
this.color = "#00eaff";
this.jumpForce = -15;
this.isGrounded = true;
}
update() {
this.vy += gravity;
this.y += this.vy;

if (this.y + this.h >= groundY()) {
this.y = groundY() - this.h;
this.vy = 0;
this.isGrounded = true;
}
}
jump() {
if (this.isGrounded) {
this.vy = this.jumpForce;
this.isGrounded = false;
}
}
render() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}

class Obstacle {
constructor() {
this.h = 50 + Math.random() * 40;
this.w = 20 + Math.random() * 30;
this.x = canvas.width + this.w;
this.y = groundY() - this.h;
this.speed = 8;
this.color = "#ff007a";
}
update() {
this.x -= this.speed;
}
isOffscreen() {
return this.x + this.w < 0;
}
render() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}

const player = new Player();

function automaticBindUI() {
const $ = (id) => document.getElementById(id);
const startBtn = $("startBtn");
const restartBtn = $("restartBtn");
const menu = $("menu");
const hud = $("hud");
const gameover = $("gameover");
const scoreLbl = $("score");
const highLbl = $("highscore");
const finalScore = $("finalScore");

highLbl.textContent = `Rekor: ${highscore}`;

startBtn.addEventListener("click", () => beginGame());
restartBtn.addEventListener("click", () => beginGame());
window.addEventListener("keydown", (e) => {
if (e.code === "Space" || e.code === "ArrowUp") {
if (gameState === "playing") player.jump();
else if (gameState === "gameover") beginGame();
}
});
canvas.addEventListener("pointerdown", () => {
if (gameState === "playing") player.jump();
else if (gameState === "gameover") beginGame();
});

function beginGame() {
gameState = "playing";
menu.classList.add("hidden");
gameover.classList.add("hidden");
hud.classList.remove("hidden");

obstacles = [];
score = 0;
lastSpawn = performance.now();
player.reset();
}

function endGame() {
gameState = "gameover";
finalScore.textContent = score;
hud.classList.add("hidden");
gameover.classList.remove("hidden");
if (score > highscore) {
highscore = score;
localStorage.setItem("highscore", highscore);
}
highLbl.textContent = `Rekor: ${highscore}`;
}

function gameLoop(timestamp) {
ctx.clearRect(0, 0, canvas.width, canvas.height);

// Draw ground line
ctx.strokeStyle = "rgba(255,255,255,0.1)";
ctx.beginPath();
ctx.moveTo(0, groundY());
ctx.lineTo(canvas.width, groundY());
ctx.stroke();

if (gameState === "playing") {
// Update player
player.update();
player.render();

// Spawn obstacles
if (timestamp - lastSpawn > spawnRate) {
obstacles.push(new Obstacle());
lastSpawn = timestamp;
// Dynamic difficulty
if (spawnRate > 700) spawnRate -= 20;
}

// Update obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.update();
obs.render();
if (obs.isOffscreen()) obstacles.splice(i, 1);

// Collision detection
if (
player.x < obs.x + obs.w &&
player.x + player.w > obs.x &&
player.y < obs.y + obs.h &&
player.y + player.h > obs.y
) {
endGame();
}
}

// Update score
score += 1;
scoreLbl.textContent = score;
} else if (gameState === "gameover") {
player.render();
obstacles.forEach((o) => o.render());
}

requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);
}
86 changes: 86 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* Neon Runner Styles */
:root {
--bg: #0f0f0f;
--primary: #00eaff;
--secondary: #ff007a;
--white: #ffffff;
--font: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: var(--font);
}

body {
background: var(--bg);
color: var(--white);
overflow: hidden;
}

#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none; /* Let clicks pass to buttons but not interfere with canvas */
}

#title {
margin-top: 20px;
font-size: 2.5rem;
color: var(--primary);
text-shadow: 0 0 10px var(--primary), 0 0 20px var(--primary);
pointer-events: auto;
}

button {
margin-top: 20px;
padding: 10px 30px;
font-size: 1.2rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: var(--secondary);
color: var(--white);
box-shadow: 0 0 10px var(--secondary), 0 0 20px var(--secondary);
transition: transform 0.2s ease, background 0.2s ease;
pointer-events: auto;
}
button:hover {
transform: scale(1.05);
background: #ff1a8c;
}

.hidden {
display: none !important;
}

#hud {
margin-top: 10px;
font-size: 1.2rem;
display: flex;
gap: 20px;
pointer-events: none;
}

#game {
display: block;
position: absolute;
top: 0;
left: 0;
}

@media (orientation: portrait) {
#title {
font-size: 2rem;
}
button {
font-size: 1rem;
}
}