CodeWeek 2025 - Yeşil Vatan (Yılan Oyunu)

 CodeWeek 2025 kapsamında Yeşil Vatan temasıyla Kahramanmaraş TOBB Fen Lisesi kodlama etkinliği başlatıyor. Bu kapsamda HTML tabanlı YEŞİL VATAN temalı bir yılan oyunu geliştiriyoruz. Oyunun bitmiş haline ve kaynak kodlarına aşağıdan erişebilirsiniz. 

Oyunu Denemek İçin Tıklayın



  1. Aşağıdaki kodları kopyalayın. 
  2. Visual Studio Code'da File menüsünden New File alt menüsüne tıklayın. 
  3. index.html adında yeni bir dosya oluşturun ve aşağıdaki kodları sayfaınıza yapıştırıp kaydedin. 
  4. Ardından kodlarınınızı F5 tuşuyla çalıştırın. 
  5. Kodlarda değişiklik yaparak kendi özelleştirilmiş versiyonunuzu geliştirebilirsiniz.

<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Yeşil Vatan - Yılan Oyunu</title>
<style>
  :root{
    --bg:#e9f7ee;
    --panel:#dff1df;
    --accent:#2b7a3b;
    --dark:#15421f;
    --muted:#6b8a6a;
  }
  body{
    margin:0;
    font-family:Inter, Arial, sans-serif;
    background: linear-gradient(180deg, #f0fff4 0%, var(--bg) 100%);
    display:flex;
    align-items:center;
    justify-content:center;
    min-height:100vh;
    gap:20px;
    padding:20px;
    color:var(--dark);
  }
  .container{
    display:flex;
    gap:20px;
    align-items:flex-start;
    flex-wrap:wrap;
  }
  .game-card{
    background:var(--panel);
    border-radius:12px;
    padding:16px;
    box-shadow:0 8px 20px rgba(0,0,0,0.08);
  }
  canvas{
    background:linear-gradient(90deg,#dff7e6,#e8f9ee);
    border-radius:8px;
    display:block;
  }
  .hud{
    display:flex;
    gap:10px;
    align-items:center;
    margin-bottom:10px;
    flex-wrap:wrap;
  }
  .stat{
    background:#fff;
    padding:8px 12px;
    border-radius:8px;
    box-shadow:0 4px 10px rgba(0,0,0,0.05);
    min-width:88px;
    text-align:center;
  }
  .stat small{display:block;color:var(--muted);font-size:12px}
  .controls{
    display:flex;
    gap:8px;
    margin-top:8px;
  }
  button{
    background:var(--accent);
    color:white;
    border:0;
    padding:8px 12px;
    border-radius:8px;
    cursor:pointer;
    font-weight:600;
  }
  button.ghost{
    background:transparent;
    color:var(--dark);
    border:1px solid rgba(21,66,31,0.12);
  }
  .mobile-ctrl{
    display:none;
    margin-top:10px;
    user-select:none;
    -webkit-user-select:none;
  }
  .dpad{
    width:160px;
    height:160px;
    position:relative;
    display:grid;
    grid-template-columns:1fr 1fr 1fr;
    grid-template-rows:1fr 1fr 1fr;
    gap:8px;
  }
  .dpad button{
    width:48px;height:48px;border-radius:8px;padding:0;
  }
  .dpad .center{grid-column:2;grid-row:2;background:transparent;box-shadow:none}
  @media (max-width:800px){
    .container{flex-direction:column; align-items:center;}
    .mobile-ctrl{display:block;}
  }
  .foot{
    margin-top:8px;
    font-size:13px;color:var(--muted)
  }
</style>
</head>
<body>
  <div class="container">
    <div class="game-card">
      <div class="hud">
        <div class="stat">
          <div id="score">0</div>
          <small>Skor</small>
        </div>
        <div class="stat">
          <div id="highscore">0</div>
          <small>Yüksek Skor</small>
        </div>
        <div class="stat">
          <div id="level">1</div>
          <small>Seviye</small>
        </div>
        <div style="margin-left:auto">
          <button id="pauseBtn" class="ghost">Duraklat</button>
          <button id="restartBtn">Yeniden Başlat</button>
        </div>
      </div>

      <canvas id="game" width="600" height="600"></canvas>

      <div class="foot">Tema: <strong>Yeşil Vatan</strong> — Fidan topla, ağaç dik, toprağı yeşillendir 🌱</div>

      <div class="mobile-ctrl">
        <div style="display:flex;gap:8px;justify-content:center;margin-top:10px">
          <div class="dpad" id="dpad">
            <div></div>
            <button data-dir="up"></button>
            <div></div>
            <button data-dir="left"></button>
            <div class="center"></div>
            <button data-dir="right"></button>
            <div></div>
            <button data-dir="down"></button>
            <div></div>
          </div>
        </div>
        <div style="text-align:center;margin-top:8px;color:var(--muted)">Mobil için dokunmatik yön tuşları</div>
      </div>

    </div>

    <div class="game-card" style="min-width:240px;max-width:360px;">
      <h3 style="margin:0 0 8px 0;color:var(--dark)">Oyun Bilgileri</h3>
      <p style="margin:0 0 8px 0;color:var(--muted)">
        Yeşil Vatan Yılan Oyunu'nda fidanları (yeşil yem) toplayarak skor kazanırsın. Her 3 fidan topladığında toprağa yeni bir ağaç engeli eklenir. Engellere veya kendi bedenine çarparsan oyun biter.
      </p>
      <ul style="margin:8px 0 0 16px;color:var(--muted)">
        <li>Kontroller: Ok tuşları / WASD / Mobil dokunmatik</li>
        <li>Duraklat: "Duraklat" butonu</li>
        <li>Yüksek skor tarayıcıda saklanır</li>
      </ul>
      <hr style="margin:12px 0">
      <h4 style="margin:6px 0 4px 0">Seviye ve Hız</h4>
      <p style="margin:0;color:var(--muted)">Seviye arttıkça yılanın hızı artar. Her 5 yemde seviye artar.</p>
      <div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;">
        <button id="muteBtn" class="ghost">Ses Kapat</button>
        <button id="saveScoreBtn" class="ghost">Skoru Manuel Kaydet</button>
      </div>
    </div>
  </div>

<script>
/*
  Yeşil Vatan - Snake
  Türkçe yorumlar ile tek dosya
*/

// --- Ayarlar ---
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const gridSize = 20; // grid kutu boyutu (px)
const cols = canvas.width / gridSize;
const rows = canvas.height / gridSize;

let snake = [{x: Math.floor(cols/2), y: Math.floor(rows/2)}];
let dir = {x:1,y:0}; // başlangıç yönü sağ
let nextDir = null;
let food = null;
let obstacles = [];
let score = 0;
let highScore = parseInt(localStorage.getItem('yesilVatanHighScore') || '0',10);
let level = 1;
let speed = 8; // hareket/saniye
let running = true;
let eatenSinceLastObstacle = 0;
let foodCountTotal = 0;
let muted = false;

// HUD elemanları
const scoreEl = document.getElementById('score');
const highEl = document.getElementById('highscore');
const levelEl = document.getElementById('level');
const pauseBtn = document.getElementById('pauseBtn');
const restartBtn = document.getElementById('restartBtn');
const muteBtn = document.getElementById('muteBtn');
const saveScoreBtn = document.getElementById('saveScoreBtn');

highEl.textContent = highScore;

// Ses: basit WebAudio efektleri
const AudioCtx = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
function ensureAudio(){
  if(!audioCtx) audioCtx = new AudioCtx();
}
function beep(freq=440, duration=0.08, gain=0.08){
  if(muted) return;
  ensureAudio();
  const o = audioCtx.createOscillator();
  const g = audioCtx.createGain();
  o.type='sine';
  o.frequency.value = freq;
  g.gain.value = gain;
  o.connect(g); g.connect(audioCtx.destination);
  o.start();
  o.stop(audioCtx.currentTime + duration);
}

// Yardımcı: rastgele grid pozisyonu çakışma kontrolü
function randomGridPosition(){
  return {
    x: Math.floor(Math.random()*cols),
    y: Math.floor(Math.random()*rows)
  };
}
function positionCollides(pos, list){
  return list.some(p => p.x===pos.x && p.y===pos.y);
}

// Yeni fidan oluştur
function spawnFood(){
  let tries = 0;
  while(tries < 200){
    const p = randomGridPosition();
    if(positionCollides(p, snake)) { tries++; continue; }
    if(positionCollides(p, obstacles)) { tries++; continue; }
    food = p;
    return;
  }
  // Eğer yer kalmadıysa oyunu bitir
  food = null;
}

// Yeni engel (ağaç) ekle
function addObstacle(){
  let tries=0;
  while(tries<300){
    const p = randomGridPosition();
    if(positionCollides(p, snake) || (food && p.x===food.x && p.y===food.y) || positionCollides(p, obstacles)){
      tries++; continue;
    }
    obstacles.push(p);
    return;
  }
}

// Oyun sıfırlama
function resetGame(){
  snake = [{x: Math.floor(cols/2), y: Math.floor(rows/2)}];
  dir = {x:1,y:0};
  nextDir = null;
  obstacles = [];
  score = 0;
  level = 1;
  speed = 8;
  eatenSinceLastObstacle = 0;
  foodCountTotal = 0;
  running = true;
  spawnFood();
  updateHUD();
}

// HUD güncelle
function updateHUD(){
  scoreEl.textContent = score;
  highEl.textContent = highScore;
  levelEl.textContent = level;
}

// Çarpışma kontrolü
function checkGameOver(head){
  // kendi vücuduna çarpma
  for(let i=1;i<snake.length;i++){
    if(snake[i].x===head.x && snake[i].y===head.y) return true;
  }
  // engel çarpması
  if(positionCollides(head, obstacles)) return true;
  // duvar yerine sarmalı davranış: dışarı çıkarsa karşı taraftan gir
  return false;
}

// Hızı güncelle (seviye ile)
function updateSpeed(){
  // temel hız + level faktörü
  speed = 8 + Math.floor((level-1)*1.5);
}

// Oyun döngüsü (zaman tabanlı)
let lastTime = 0;
let moveInterval = 1000 / speed;
function loop(timestamp){
  if(!running){
    lastTime = timestamp;
    requestAnimationFrame(loop);
    return;
  }
  if(!lastTime) lastTime = timestamp;
  const elapsed = timestamp - lastTime;
  moveInterval = 1000 / speed;
  if(elapsed > moveInterval){
    update(); // oyun mantığı
    lastTime = timestamp;
  }
  draw();
  requestAnimationFrame(loop);
}

// Mantık güncelle
function update(){
  if(nextDir){
    // dikey/horizontal tersine gitmeyi engelle
    if(!(nextDir.x === -dir.x && nextDir.y === -dir.y)){
      dir = nextDir;
    }
    nextDir = null;
  }
  const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y};

  // sınırları sarmal: ekranın diğer tarafından geri gelsin
  if(head.x < 0) head.x = cols-1;
  if(head.x >= cols) head.x = 0;
  if(head.y < 0) head.y = rows-1;
  if(head.y >= rows) head.y = 0;

  if(checkGameOver(head)){
    // oyun biter
    beep(120,0.25,0.15);
    running = false;
    pauseBtn.textContent = "Devam Et";
    // yüksek skor kaydet
    if(score > highScore){
      highScore = score;
      localStorage.setItem('yesilVatanHighScore', String(highScore));
    }
    updateHUD();
    return;
  }

  snake.unshift(head);

  // yemi yediyse
  if(food && head.x===food.x && head.y===food.y){
    score += 10;
    foodCountTotal++;
    eatenSinceLastObstacle++;
    beep(880,0.06,0.08); // yeme sesi
    // her 3 yemde yeni engel
    if(eatenSinceLastObstacle >= 3){
      addObstacle();
      eatenSinceLastObstacle = 0;
      beep(220,0.1,0.08);
    }
    // seviye artışı her 5 yem
    if(foodCountTotal % 5 === 0){
      level++;
      updateSpeed();
    }
    spawnFood();
  } else {
    // normal hareket: kuyruğu kes
    snake.pop();
  }

  // skor HUD
  if(score > highScore){
    highScore = score;
    localStorage.setItem('yesilVatanHighScore', String(highScore));
  }
  updateHUD();
}

// Çizim
function draw(){
  // temizle
  ctx.clearRect(0,0,canvas.width,canvas.height);

  // hafif toprak dokusu
  for(let x=0;x<cols;x++){
    for(let y=0;y<rows;y++){
      if((x+y)%2===0){
        ctx.fillStyle = '#eaf9ec';
      } else {
        ctx.fillStyle = 'rgba(238,253,240,0.7)';
      }
      ctx.fillRect(x*gridSize,y*gridSize,gridSize,gridSize);
    }
  }

  // engeller (ağaçlar)
  obstacles.forEach(o => {
    const cx = o.x*gridSize + gridSize/2;
    const cy = o.y*gridSize + gridSize/2;
    // gövde
    ctx.fillStyle = '#8b5a2b';
    ctx.fillRect(o.x*gridSize + gridSize*0.35, o.y*gridSize + gridSize*0.5, gridSize*0.3, gridSize*0.45);
    // yaprak katmanları
    ctx.fillStyle = '#2f8a3a';
    ctx.beginPath();
    ctx.ellipse(cx, cy - 4, gridSize*0.45, gridSize*0.35, 0, 0, Math.PI*2);
    ctx.fill();
    ctx.beginPath();
    ctx.ellipse(cx, cy - gridSize*0.08, gridSize*0.35, gridSize*0.25, 0, 0, Math.PI*2);
    ctx.fill();
  });

  // fidan (food)
  if(food){
    const fx = food.x*gridSize;
    const fy = food.y*gridSize;
    // toprak
    ctx.fillStyle = '#b3e6b3';
    ctx.fillRect(fx+4, fy+4, gridSize-8, gridSize-8);
    // küçük fidan
    ctx.fillStyle = '#2e8b33';
    ctx.fillRect(fx + gridSize*0.45, fy + gridSize*0.25, gridSize*0.1, gridSize*0.45);
    ctx.fillStyle = '#4ccc64';
    ctx.beginPath();
    ctx.moveTo(fx + gridSize*0.5, fy + gridSize*0.25);
    ctx.lineTo(fx + gridSize*0.33, fy + gridSize*0.45);
    ctx.lineTo(fx + gridSize*0.67, fy + gridSize*0.45);
    ctx.fill();
  }

  // yılan (baş farklı renkte)
  for(let i=0;i<snake.length;i++){
    const s = snake[i];
    const x = s.x * gridSize;
    const y = s.y * gridSize;
    if(i===0){
      // baş
      ctx.fillStyle = '#15421f';
      roundRect(ctx, x+1, y+1, gridSize-2, gridSize-2, 6, true);
      // göz
      ctx.fillStyle = '#dff5e9';
      ctx.fillRect(x + gridSize*0.55, y + gridSize*0.28, gridSize*0.08, gridSize*0.08);
    } else {
      // gövde
      // degrade için index kullan
      const t = 0.3 + 0.7*(i/snake.length);
      ctx.fillStyle = `rgba(${Math.floor(20*t+30)}, ${Math.floor(90*t+120)}, ${Math.floor(30*t+40)}, 1)`;
      roundRect(ctx, x+1, y+1, gridSize-2, gridSize-2, 5, true);
    }
  }

  // eğer duraklatıldıysa ekranda mesaj
  if(!running){
    ctx.fillStyle = 'rgba(0,0,0,0.35)';
    ctx.fillRect(0, canvas.height/2 - 40, canvas.width, 80);
    ctx.fillStyle = '#fff';
    ctx.font = '28px Arial';
    ctx.textAlign = 'center';
    ctx.fillText('Oyun Bitti — Yeniden Başlatın', canvas.width/2, canvas.height/2 + 8);
  }
}

// yardım: yuvarlak dikdörtgen
function roundRect(ctx, x, y, w, h, r, fill){
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
  if(fill) ctx.fill();
}

// Klavye ve buton kontrolleri
window.addEventListener('keydown', e => {
  if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','w','a','s','d','W','A','S','D'].includes(e.key)){
    e.preventDefault();
    handleKey(e.key);
  } else if(e.key === ' '){ // space pause
    togglePause();
  }
});
function handleKey(k){
  if(k === 'ArrowUp' || k === 'w' || k === 'W') nextDir = {x:0,y:-1};
  if(k === 'ArrowDown' || k === 's' || k === 'S') nextDir = {x:0,y:1};
  if(k === 'ArrowLeft' || k === 'a' || k === 'A') nextDir = {x:-1,y:0};
  if(k === 'ArrowRight' || k === 'd' || k === 'D') nextDir = {x:1,y:0};
}

// Ekran butonları
document.querySelectorAll('[data-dir]').forEach(b => {
  b.addEventListener('touchstart', (e)=>{
    e.preventDefault();
    const d = b.dataset.dir;
    if(d==='up') nextDir={x:0,y:-1};
    if(d==='down') nextDir={x:0,y:1};
    if(d==='left') nextDir={x:-1,y:0};
    if(d==='right') nextDir={x:1,y:0};
  });
  b.addEventListener('mousedown', (e)=>{
    const d = b.dataset.dir;
    if(d==='up') nextDir={x:0,y:-1};
    if(d==='down') nextDir={x:0,y:1};
    if(d==='left') nextDir={x:-1,y:0};
    if(d==='right') nextDir={x:1,y:0};
  });
});

// Dokunmatik swipe kontrolleri
let touchStart = null;
canvas.addEventListener('touchstart', e => {
  const t = e.changedTouches[0];
  touchStart = {x:t.clientX, y:t.clientY, time:Date.now()};
});
canvas.addEventListener('touchend', e => {
  if(!touchStart) return;
  const t = e.changedTouches[0];
  const dx = t.clientX - touchStart.x;
  const dy = t.clientY - touchStart.y;
  const absx = Math.abs(dx);
  const absy = Math.abs(dy);
  if(Math.max(absx,absy) > 30){
    if(absx > absy){
      if(dx > 0) nextDir = {x:1,y:0}; else nextDir = {x:-1,y:0};
    } else {
      if(dy > 0) nextDir = {x:0,y:1}; else nextDir = {x:0,y:-1};
    }
  }
  touchStart = null;
});

// Butonlar
pauseBtn.addEventListener('click', togglePause);
restartBtn.addEventListener('click', ()=>{
  resetGame();
  pauseBtn.textContent = 'Duraklat';
  running = true;
});
muteBtn.addEventListener('click', ()=>{
  muted = !muted;
  muteBtn.textContent = muted ? 'Ses Aç' : 'Ses Kapat';
});
saveScoreBtn.addEventListener('click', ()=>{
  localStorage.setItem('yesilVatanHighScore', String(highScore));
  alert('Yüksek skor kaydedildi: ' + highScore);
});

// pause toggle
function togglePause(){
  running = !running;
  pauseBtn.textContent = running ? 'Duraklat' : 'Devam Et';
  if(!muted) beep(440,0.06,0.05);
}

// Başlangıç
spawnFood();
updateHUD();
requestAnimationFrame(loop);

// Ekstra: pencere odağı kaybında duraklat
window.addEventListener('blur', ()=>{ if(running){ running=false; pauseBtn.textContent='Devam Et'; } });

// Touch-friendly: prevent scroll while touching canvas
['touchstart','touchmove'].forEach(ev => {
  canvas.addEventListener(ev, function(e){ e.preventDefault(); }, {passive:false});
});

</script>
</body>
</html>

Yorumlar