🎮ArcadeLab

Ocean Surfers 🌊

by DriftBuilder15
556 lines14.3 KB
▶ Play
<!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.