Ocean Surfers 🌊
by DriftBuilder15556 lines14.3 KB
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Ocean Surfers 🌊</title>
<style>
body {
margin: 0;
background: #001a2c;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
overflow: hidden;
}
canvas {
border: 3px solid #00d4ff;
border-radius: 10px;
box-shadow: 0 0 30px #00d4ff88;
}
</style>
</head>
<body>
<canvas id="game" width="480" height="720"></canvas>
<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const W = canvas.width;
const H = canvas.height;
// ---------- OYUN DURUMU ----------
let state = "menu"; // menu, playing, gameover
let distance = 0;
let speed = 4;
let coins = 0;
let score = 0;
let hearts = 3;
let level = 1;
let frameCount = 0;
// ---------- ŞERİTLER ----------
const lanes = [W * 0.25, W * 0.5, W * 0.75];
let currentLane = 1;
let playerX = lanes[currentLane];
let targetX = playerX;
// ---------- OYUNCU ----------
const player = {
y: H - 150,
width: 50,
height: 60,
jumping: false,
jumpVelocity: 0,
gravity: 0.9,
diving: false,
diveTimer: 0,
};
// ---------- LİSTELER ----------
let obstacles = [];
let collectibles = [];
let particles = [];
let waves = [];
// ---------- SEF SEVİYE SİSTEMİ ----------
function getLevelConfig() {
if (distance < 500) return { name: "Kıyı Suları", color: "#4ecdc4", speed: 4, spawn: 90 };
if (distance < 1500) return { name: "Açık Deniz", color: "#1e90ff", speed: 5.5, spawn: 75 };
if (distance < 3000) return { name: "Fırtına", color: "#34495e", speed: 7, spawn: 60 };
if (distance < 5000) return { name: "Mercan Resifi",color: "#ff6b6b", speed: 8.5, spawn: 50 };
if (distance < 8000) return { name: "Derin Okyanus",color: "#6a0dad", speed: 10, spawn: 42 };
return { name: "Atlantis", color: "#000000", speed: 12, spawn: 35 };
}
// ---------- GİRİŞ ----------
document.addEventListener("keydown", (e) => {
if (state === "menu" && (e.key === " " || e.key === "Enter")) {
startGame();
return;
}
if (state === "gameover" && (e.key === " " || e.key === "Enter")) {
resetGame();
startGame();
return;
}
if (state !== "playing") return;
if (e.key === "ArrowLeft" || e.key === "a") moveLane(-1);
if (e.key === "ArrowRight" || e.key === "d") moveLane(1);
if (e.key === "ArrowUp" || e.key === "w") jump();
if (e.key === "ArrowDown" || e.key === "s") dive();
});
// Dokunmatik kontrol
let touchStartX = 0, touchStartY = 0;
canvas.addEventListener("touchstart", (e) => {
const t = e.touches[0];
touchStartX = t.clientX;
touchStartY = t.clientY;
if (state === "menu") startGame();
else if (state === "gameover") { resetGame(); startGame(); }
});
canvas.addEventListener("touchend", (e) => {
if (state !== "playing") return;
const t = e.changedTouches[0];
const dx = t.clientX - touchStartX;
const dy = t.clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 30) moveLane(1);
else if (dx < -30) moveLane(-1);
} else {
if (dy < -30) jump();
else if (dy > 30) dive();
}
});
// ---------- HAREKETLER ----------
function moveLane(dir) {
const newLane = currentLane + dir;
if (newLane >= 0 && newLane <= 2) {
currentLane = newLane;
targetX = lanes[currentLane];
}
}
function jump() {
if (!player.jumping && !player.diving) {
player.jumping = true;
player.jumpVelocity = -16;
}
}
function dive() {
if (!player.jumping && !player.diving) {
player.diving = true;
player.diveTimer = 25;
}
}
// ---------- SPAWN ----------
function spawnObstacle() {
const lane = Math.floor(Math.random() * 3);
const types = ["rock", "boat", "net", "shark", "wave", "coral"];
let type = types[Math.floor(Math.random() * Math.min(types.length, 2 + level))];
// Zorunlu dalış/zıplama engelleri
if (type === "wave") {
obstacles.push({ x: lanes[lane], y: -80, w: 60, h: 40, type, required: "jump" });
return;
}
if (type === "net") {
obstacles.push({ x: lanes[lane], y: -80, w: 80, h: 30, type, required: "dive" });
return;
}
obstacles.push({ x: lanes[lane], y: -80, w: 50, h: 50, type });
}
function spawnCoin() {
const lane = Math.floor(Math.random() * 3);
collectibles.push({
x: lanes[lane],
y: -30,
r: 12,
type: Math.random() < 0.1 ? "diamond" : "coin",
});
}
// ---------- ÇARPIŞMA ----------
function rectCircleCollide(rx, ry, rw, rh, cx, cy, cr) {
const nx = Math.max(rx, Math.min(cx, rx + rw));
const ny = Math.max(ry, Math.min(cy, ry + rh));
const dx = cx - nx;
const dy = cy - ny;
return dx * dx + dy * dy < cr * cr;
}
// ---------- GÜNCELLEME ----------
function update() {
const cfg = getLevelConfig();
level = Math.round(cfg.speed);
speed = cfg.speed;
distance += speed * 0.1;
score = Math.floor(distance) + coins * 10;
// Oyuncu yumuşak hareket
playerX += (targetX - playerX) * 0.2;
// Zıplama
if (player.jumping) {
player.jumpVelocity += player.gravity;
player.y += player.jumpVelocity;
if (player.y >= H - 150) {
player.y = H - 150;
player.jumping = false;
player.jumpVelocity = 0;
}
}
// Dalış
if (player.diving) {
player.diveTimer--;
if (player.diveTimer <= 0) player.diving = false;
}
// Spawn
frameCount++;
if (frameCount % Math.max(20, cfg.spawn) === 0) spawnObstacle();
if (frameCount % Math.max(15, cfg.spawn - 20) === 0) spawnCoin();
// Dalga animasyonu (görsel)
if (frameCount % 8 === 0) {
waves.push({ x: Math.random() * W, y: H, r: 5, alpha: 1 });
}
// Engeller
for (let i = obstacles.length - 1; i >= 0; i--) {
const o = obstacles[i];
o.y += speed;
if (o.y > H + 100) {
obstacles.splice(i, 1);
continue;
}
// Çarpışma
const py = player.diving ? player.y + 25 : player.y;
const ph = player.diving ? 35 : player.height;
if (rectCircleCollide(playerX - 25, py, 50, ph, o.x, o.y + 25, 25)) {
// Dalga üstü zıplama kontrolü
if (o.required === "jump" && player.jumping) continue;
if (o.required === "dive" && player.diving) continue;
hearts--;
createParticles(o.x, o.y, "#ff4444", 15);
obstacles.splice(i, 1);
if (hearts <= 0) state = "gameover";
}
}
// Toplanabilir
for (let i = collectibles.length - 1; i >= 0; i--) {
const c = collectibles[i];
c.y += speed;
if (c.y > H + 50) { collectibles.splice(i, 1); continue; }
const py = player.diving ? player.y + 25 : player.y;
const ph = player.diving ? 35 : player.height;
if (rectCircleCollide(playerX - 25, py, 50, ph, c.x, c.y, c.r + 5)) {
if (c.type === "coin") { coins += 1; createParticles(c.x, c.y, "#ffd700", 8); }
else { coins += 10; createParticles(c.x, c.y, "#00ffff", 12); }
collectibles.splice(i, 1);
}
}
// Partiküller
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.3;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
// Dalgalar
for (let i = waves.length - 1; i >= 0; i--) {
const w = waves[i];
w.r += 3;
w.alpha -= 0.03;
if (w.alpha <= 0) waves.splice(i, 1);
}
}
function createParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
particles.push({
x, y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6 - 2,
life: 30,
color,
});
}
}
// ---------- ÇİZİM ----------
function draw() {
const cfg = getLevelConfig();
// Gökyüzü gradyanı
const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
skyGrad.addColorStop(0, cfg.color);
skyGrad.addColorStop(0.5, "#0a3a5a");
skyGrad.addColorStop(1, "#001a2c");
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, W, H);
// Su dalgası efekti
for (const w of waves) {
ctx.strokeStyle = `rgba(0, 212, 255, ${w.alpha})`;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(w.x, w.y, w.r, 0, Math.PI * 2);
ctx.stroke();
}
// Şerit çizgileri
ctx.strokeStyle = "rgba(255,255,255,0.15)";
ctx.lineWidth = 2;
ctx.setLineDash([15, 20]);
for (let i = 1; i < 3; i++) {
const x = (W / 3) * i;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, H);
ctx.stroke();
}
ctx.setLineDash([]);
// Toplanabilir
for (const c of collectibles) {
if (c.type === "coin") {
ctx.fillStyle = "#ffd700";
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#b8860b";
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = "#b8860b";
ctx.font = "bold 14px Arial";
ctx.textAlign = "center";
ctx.fillText("$", c.x, c.y + 5);
} else {
ctx.fillStyle = "#00ffff";
ctx.beginPath();
ctx.moveTo(c.x, c.y - c.r);
ctx.lineTo(c.x + c.r, c.y);
ctx.lineTo(c.x, c.y + c.r);
ctx.lineTo(c.x - c.r, c.y);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 1;
ctx.stroke();
}
}
// Engeller
for (const o of obstacles) {
if (o.type === "rock") {
ctx.fillStyle = "#555";
ctx.beginPath();
ctx.arc(o.x, o.y + 25, 28, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#333";
ctx.beginPath();
ctx.arc(o.x - 8, o.y + 18, 10, 0, Math.PI * 2);
ctx.fill();
} else if (o.type === "boat") {
ctx.fillStyle = "#8b4513";
ctx.fillRect(o.x - 30, o.y, 60, 25);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.moveTo(o.x, o.y);
ctx.lineTo(o.x + 20, o.y - 25);
ctx.lineTo(o.x, o.y - 25);
ctx.closePath();
ctx.fill();
} else if (o.type === "shark") {
ctx.fillStyle = "#607d8b";
ctx.beginPath();
ctx.moveTo(o.x, o.y + 40);
ctx.lineTo(o.x + 15, o.y + 10);
ctx.lineTo(o.x - 15, o.y + 10);
ctx.closePath();
ctx.fill();
} else if (o.type === "wave") {
ctx.fillStyle = "rgba(0, 150, 255, 0.8)";
ctx.beginPath();
ctx.arc(o.x, o.y + 40, 40, Math.PI, 0);
ctx.fill();
} else if (o.type === "net") {
ctx.strokeStyle = "#ddd";
ctx.lineWidth = 2;
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.moveTo(o.x - 30 + i * 15, o.y);
ctx.lineTo(o.x - 30 + i * 15, o.y + 30);
ctx.stroke();
}
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(o.x - 30, o.y + i * 10);
ctx.lineTo(o.x + 30, o.y + i * 10);
ctx.stroke();
}
} else if (o.type === "coral") {
ctx.fillStyle = "#ff6b6b";
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.arc(o.x - 15 + i * 8, o.y + 25 + Math.sin(i) * 5, 6, 0, Math.PI * 2);
ctx.fill();
}
}
}
// Oyuncu (sörfçü)
const py = player.diving ? player.y + 25 : player.y;
const ph = player.diving ? 35 : player.height;
// Sörf tahtası
ctx.fillStyle = "#ffeb3b";
ctx.beginPath();
ctx.ellipse(playerX, py + ph - 5, 35, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#f57c00";
ctx.lineWidth = 2;
ctx.stroke();
// Vücut
ctx.fillStyle = "#2196f3";
ctx.fillRect(playerX - 12, py + 10, 24, ph - 20);
// Kafa
ctx.fillStyle = "#ffcc80";
ctx.beginPath();
ctx.arc(playerX, py + 5, 12, 0, Math.PI * 2);
ctx.fill();
// Su sıçraması (sörf efekti)
if (!player.diving) {
ctx.fillStyle = "rgba(0, 200, 255, 0.5)";
ctx.beginPath();
ctx.ellipse(playerX, py + ph, 40, 8, 0, 0, Math.PI * 2);
ctx.fill();
}
// Partiküller
for (const p of particles) {
ctx.globalAlpha = p.life / 30;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// ---------- HUD ----------
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(0, 0, W, 60);
ctx.fillStyle = "#fff";
ctx.font = "bold 18px Arial";
ctx.textAlign = "left";
ctx.fillText(`🪙 ${coins}`, 15, 25);
ctx.fillText(`📏 ${Math.floor(distance)}m`, 15, 50);
ctx.textAlign = "right";
ctx.fillText(`Skor: ${score}`, W - 15, 25);
// Kalpler
for (let i = 0; i < 3; i++) {
ctx.fillStyle = i < hearts ? "#ff3b3b" : "#444";
ctx.font = "18px Arial";
ctx.fillText("❤", W - 100 + i * 25, 50);
}
// Seviye ismi
ctx.textAlign = "center";
ctx.fillStyle = "rgba(255,255,255,0.7)";
ctx.font = "italic 14px Arial";
ctx.fillText(`~ ${cfg.name} ~`, W / 2, 85);
// ---------- MENÜ / GAME OVER ----------
if (state === "menu") {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = "#00d4ff";
ctx.font = "bold 42px Arial";
ctx.textAlign = "center";
ctx.fillText("🌊 OCEAN", W / 2, H / 2 - 80);
ctx.fillText("SURFERS 🏄", W / 2, H / 2 - 30);
ctx.fillStyle = "#fff";
ctx.font = "18px Arial";
ctx.fillText("Başlamak için BOŞLUK / TIKLA", W / 2, H / 2 + 40);
ctx.font = "14px Arial";
ctx.fillStyle = "#aaa";
ctx.fillText("← → : Şerit değiştir", W / 2, H / 2 + 90);
ctx.fillText("↑ : Zıpla | ↓ : Dal", W / 2, H / 2 + 115);
ctx.fillText("Engellerden kaç, altın topla!", W / 2, H / 2 + 140);
}
if (state === "gameover") {
ctx.fillStyle = "rgba(0,0,0,0.8)";
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = "#ff3b3b";
ctx.font = "bold 48px Arial";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", W / 2, H / 2 - 60);
ctx.fillStyle = "#fff";
ctx.font = "bold 24px Arial";
ctx.fillText(`Skor: ${score}`, W / 2, H / 2);
ctx.font = "18px Arial";
ctx.fillText(`Mesafe: ${Math.floor(distance)}m`, W / 2, H / 2 + 35);
ctx.fillText(`Altın: ${coins}`, W / 2, H / 2 + 65);
ctx.fillStyle = "#00d4ff";
ctx.font = "18px Arial";
ctx.fillText("Tekrar için BOŞLUK / TIKLA", W / 2, H / 2 + 130);
}
}
// ---------- OYUN DÖNGÜSÜ ----------
function gameLoop() {
if (state === "playing") update();
draw();
requestAnimationFrame(gameLoop);
}
// ---------- BAŞLAT / SIFIRLA ----------
function startGame() {
state = "playing";
distance = 0;
speed = 4;
coins = 0;
score = 0;
hearts = 3;
frameCount = 0;
obstacles = [];
collectibles = [];
particles = [];
waves = [];
currentLane = 1;
playerX = lanes[1];
targetX = playerX;
player.y = H - 150;
player.jumping = false;
player.diving = false;
}
function resetGame() {
startGame();
}
gameLoop();
</script>
</body>
</html>Game Source: Ocean Surfers 🌊
Creator: DriftBuilder15
Libraries: none
Complexity: complex (556 lines, 14.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: ocean-surfers-driftbuilder15" to link back to the original. Then publish at arcadelab.ai/publish.