-
Notifications
You must be signed in to change notification settings - Fork 648
Description
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const coinsEl = document.getElementById('coins');
const levelEl = document.getElementById('level');
const leftBtn = document.getElementById('left');
const rightBtn = document.getElementById('right');
const jumpBtn = document.getElementById('jump');
const shopBtn = document.getElementById('shopBtn');
const restartBtn = document.getElementById('restartBtn');
const shopModal = document.getElementById('shopModal');
const closeShop = document.getElementById('closeShop');
const walletEl = document.getElementById('wallet');
const items = document.querySelectorAll('.item');
const ownedList = document.getElementById('ownedList');
// Responsive
function fit() {
const w = Math.min(window.innerWidth - 20, 420);
const h = Math.min(window.innerHeight - 40, 720);
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
const ratio = window.devicePixelRatio || 1;
canvas.width = Math.floor(w * ratio);
canvas.height = Math.floor(h * ratio);
ctx.setTransform(ratio,0,0,ratio,0,0);
}
fit(); window.addEventListener('resize', fit);
// Persistent data
const store = {
coins: parseInt(localStorage.getItem('cts_coins') || '0',10),
lives: parseInt(localStorage.getItem('cts_lives') || '1',10),
upgrades: JSON.parse(localStorage.getItem('cts_upgrades') || '{}'),
bestLevel: parseInt(localStorage.getItem('cts_bestLevel') || '1',10)
};
function save() {
localStorage.setItem('cts_coins', store.coins);
localStorage.setItem('cts_lives', store.lives);
localStorage.setItem('cts_upgrades', JSON.stringify(store.upgrades));
localStorage.setItem('cts_bestLevel', store.bestLevel);
}
// Game state
let levelIndex = 0;
const levels = [
{id:1, height:800, difficulty:0.9, spikes:0.02, bonus:5},
{id:2, height:900, difficulty:1.0, spikes:0.035, bonus:8},
{id:3, height:1000, difficulty:1.12, spikes:0.05, bonus:12},
{id:4, height:1100, difficulty:1.25, spikes:0.07, bonus:16},
{id:5, height:1250, difficulty:1.4, spikes:0.09, bonus:22},
{id:6, height:1400, difficulty:1.6, spikes:0.11, bonus:30},
{id:7, height:1600, difficulty:1.8, spikes:0.14, bonus:40},
{id:8, height:1800, difficulty:2.0, spikes:0.17, bonus:55},
];
const maxLevel = levels.length;
let player = {
x: 200, y: 600, w:34, h:40, vx:0, vy:0, onWall:false, canDoubleJump:false
};
let cameraY = 0;
let platforms = [];
let hazards = [];
let bonusItems = [];
let score = 0;
let running = false;
let lastTime = performance.now();
// Input
let left=false, right=false, wantJump=false;
leftBtn.addEventListener('touchstart',e=>{e.preventDefault();left=true}, {passive:false});
leftBtn.addEventListener('touchend',e=>{e.preventDefault();left=false}, {passive:false});
rightBtn.addEventListener('touchstart',e=>{e.preventDefault();right=true}, {passive:false});
rightBtn.addEventListener('touchend',e=>{e.preventDefault();right=false}, {passive:false});
jumpBtn.addEventListener('click', ()=> wantJump=true);
window.addEventListener('keydown', (e)=>{
if (e.code==='ArrowLeft') left=true;
if (e.code==='ArrowRight') right=true;
if (e.code==='Space') wantJump=true;
if (e.code==='KeyS') shopToggle();
});
window.addEventListener('keyup', (e)=>{
if (e.code==='ArrowLeft') left=false;
if (e.code==='ArrowRight') right=false;
if (e.code==='Space') wantJump=false;
});
// UI updates
function uiUpdate() {
coinsEl.textContent = Puan: ${store.coins};
levelEl.textContent = Bölüm: ${levels[levelIndex].id};
walletEl.textContent = store.coins;
// owned
ownedList.innerHTML = '';
for (const [k,v] of Object.entries(store.upgrades)) {
const li = document.createElement('li');
li.textContent = ${k} : ${v};
ownedList.appendChild(li);
}
}
uiUpdate();
// Shop
function shopToggle(){ shopModal.classList.toggle('hidden'); uiUpdate(); }
shopBtn.addEventListener('click', shopToggle);
closeShop.addEventListener('click', shopToggle);
items.forEach(btn=>{
btn.addEventListener('click', ()=>{
const id = btn.dataset.id;
const costMap = {extraLife:50, grip:120, rope:200, doubleJump:150};
const cost = costMap[id] || 9999;
if (store.coins < cost) { alert('Puanın yetmiyor'); return; }
store.coins -= cost;
if (id==='extraLife') {
store.lives = (store.lives || 0) + 1;
} else {
store.upgrades[id] = (store.upgrades[id] || 0) + 1;
}
save();
uiUpdate();
alert('Satın alındı!');
});
});
restartBtn.addEventListener('click', ()=> location.reload());
// Level generator
function genLevel(cfg) {
platforms = [];
hazards = [];
bonusItems = [];
const w = canvas.width / (window.devicePixelRatio || 1);
const h = cfg.height;
// Start platform
platforms.push({x: w/2 - 60, y: h - 40, w:120, h:18});
// generate steps upward
let y = h - 120;
while (y > 60) {
const pw = 70 + Math.random()90;
const px = Math.random()(w - pw);
platforms.push({x: px, y: y, w: pw, h: 14});
// scatter hazards with probability
if (Math.random() < cfg.spikes) {
hazards.push({x: px + Math.random()(pw-20), y: y-10, w:18, h:18});
}
// some bonus items
if (Math.random() < 0.08) {
bonusItems.push({x: px + 10 + Math.random()(pw-30), y: y-26, w:12, h:12, val:5 + Math.floor(Math.random()*cfg.bonus)});
}
y -= 60 + Math.random()*80;
}
// top flag
platforms.push({x: w/2 - 40, y: 30, w:80, h:18, goal:true});
}
function worldToScreen(y) {
// cameraY keeps bottom of view at cameraY (distance climbed)
const h = canvas.height / (window.devicePixelRatio || 1);
return h - (y - cameraY);
}
function rectsIntersect(a,b) {
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
function resetPlayerForLevel() {
const w = canvas.width / (window.devicePixelRatio || 1);
const cfg = levels[levelIndex];
player.x = w/2 - 10;
player.y = cfg.height - 160;
player.vx = 0; player.vy = 0;
cameraY = cfg.height - (canvas.height/(window.devicePixelRatio||1)) + 50;
score = 0;
running = true;
player.canDoubleJump = !!store.upgrades.doubleJump;
}
function startLevel(i) {
levelIndex = Math.max(0, Math.min(i, maxLevel-1));
genLevel(levels[levelIndex]);
resetPlayerForLevel();
uiUpdate();
}
// initialize
startLevel(store.bestLevel - 1 || 0);
// Game loop
function update(dt) {
if (!running) return;
const cfg = levels[levelIndex];
// input
const speed = 120 * cfg.difficulty * (store.upgrades.grip?0.95:1);
if (left) player.vx = -speed;
else if (right) player.vx = speed;
else player.vx = 0;
// physics
const gravity = 900 * cfg.difficulty;
player.vy += gravity * dt/1000;
player.x += player.vx * dt/1000;
player.y += player.vy * dt/1000;
// horizontal bounds
const w = canvas.width / (window.devicePixelRatio || 1);
if (player.x < 4) player.x = 4;
if (player.x + player.w > w-4) player.x = w - 4 - player.w;
// platform collisions (simple from above)
let onPlatform = false;
for (const p of platforms) {
const pr = {x:p.x, y:p.y, w:p.w, h:p.h};
// convert platform y coordinates relative to world (platform.y)
if (player.vy >= 0 && player.x + player.w > pr.x && player.x < pr.x + pr.w) {
// landing check: if player's feet pass through platform
if (player.y + player.h > pr.y - 6 && player.y + player.h < pr.y + 20) {
player.y = pr.y - player.h;
player.vy = 0;
onPlatform = true;
player.canDoubleJump = !!store.upgrades.doubleJump;
if (p.goal) {
// level complete
running = false;
const reward = 30 + Math.floor(Math.random()*levels[levelIndex].bonus);
store.coins += reward;
store.bestLevel = Math.max(store.bestLevel, levels[levelIndex].id+1);
save();
alert(`Bölüm tamam! +${reward} puan kazandın.`);
// advance to next level or loop
const next = Math.min(levelIndex+1, maxLevel-1);
startLevel(next);
return;
}
}
}
}
// hazards collision
for (let i=hazards.length-1;i>=0;i--) {
const h = hazards[i];
if (rectsIntersect(player, h)) {
// if player has rope upgrade, consume and prevent death
if (store.upgrades.rope) {
store.upgrades.rope -= 1;
save();
hazards.splice(i,1);
alert('İpin sayesinde düştün ama kurtuldun!');
uiUpdate();
continue;
}
// otherwise lose life
store.lives = Math.max(0, (store.lives||1) - 1);
save();
if ((store.lives || 0) > 0) {
// respawn on last platform
resetPlayerForLevel();
alert('Hasar aldın! Bir can eksildi.');
} else {
// game over -> lose some coins
const lost = Math.min(store.coins, 20);
store.coins -= lost;
save();
running = false;
alert(`Öldün. ${lost} puan kaybettin.`);
// restart level
startLevel(Math.max(0, levelIndex));
}
uiUpdate();
return;
}
}
// bonus items collection
for (let i=bonusItems.length-1;i>=0;i--) {
const b = bonusItems[i];
if (rectsIntersect(player, b)) {
store.coins += b.val;
save();
score += b.val;
bonusItems.splice(i,1);
uiUpdate();
}
}
// jump handling (press to jump; if on platform -> jump; else if doubleJump -> use)
if (wantJump) {
if (onPlatform) {
player.vy = -520;
onPlatform = false;
} else if (player.canDoubleJump) {
player.vy = -420;
player.canDoubleJump = false;
player.canDoubleJump = !!store.upgrades.doubleJump; // reset if still have doublejump upgrade
}
wantJump = false;
}
// update camera to follow player upward
const viewH = canvas.height / (window.devicePixelRatio || 1);
const topScreenY = cameraY + viewH;
// if player moves above middle of screen, move camera up
const playerWorldY = player.y + player.h;
if (playerWorldY < cameraY + viewH*0.5) {
cameraY -= (cameraY + viewH*0.5 - playerWorldY) * 0.1;
}
// if player falls below bottom -> lose life
if (player.y > levels[levelIndex].height + 80) {
// fell off the mountain
store.lives = Math.max(0, (store.lives||1) - 1);
save();
if ((store.lives||0) > 0) {
resetPlayerForLevel();
alert('Düştün! Can kaybettin.');
} else {
const lost = Math.min(store.coins, 20);
store.coins -= lost;
save();
running = false;
alert(`Düştün ve oyun bitti. ${lost} puan kaybettin.`);
startLevel(Math.max(0, levelIndex));
}
uiUpdate();
}
}
function draw() {
const w = canvas.width / (window.devicePixelRatio || 1);
const h = canvas.height / (window.devicePixelRatio || 1);
// background
const g = ctx.createLinearGradient(0,0,0,h);
g.addColorStop(0,'#1b3b57'); g.addColorStop(1,'#071422');
ctx.fillStyle = g;
ctx.fillRect(0,0,w,h);
// parallax distant mountains
for (let i=0;i<3;i++) {
const mw = w*(0.7 - i*0.15);
const mh = 120 + i*40;
const offset = ((Date.now()/1000) * (0.5+i*0.2))%mw;
drawMountain(-offset + i*80, h - (levels[levelIndex].height - cameraY) - 40, mw, mh);
}
// draw platforms
ctx.fillStyle = '#2e5d7a';
for (const p of platforms) {
const sy = worldToScreen(p.y);
ctx.fillRect(p.x, sy, p.w, p.h);
if (p.goal) {
// flag
ctx.fillStyle = '#ffd166';
ctx.fillRect(p.x + p.w - 12, sy - 20, 6, 20);
ctx.fillStyle = '#ef476f';
ctx.fillRect(p.x + p.w - 6, sy - 14, 18, 10);
ctx.fillStyle = '#2e5d7a';
}
}
// hazards
ctx.fillStyle = '#c94b4b';
for (const s of hazards) {
const sy = worldToScreen(s.y);
ctx.beginPath();
ctx.moveTo(s.x, sy);
ctx.lineTo(s.x + s.w/2, sy - s.h);
ctx.lineTo(s.x + s.w, sy);
ctx.closePath();
ctx.fill();
}
// bonus
ctx.fillStyle = '#ffd166';
for (const b of bonusItems) {
const sy = worldToScreen(b.y);
ctx.beginPath();
ctx.arc(b.x + b.w/2, sy + b.h/2, b.w/2, 0, Math.PI*2);
ctx.fill();
}
// player
ctx.fillStyle = '#e6f0ff';
const py = worldToScreen(player.y);
ctx.fillRect(player.x, py, player.w, player.h);
// backpack
ctx.fillStyle = '#9bb8cc';
ctx.fillRect(player.x-6, py+8, 8, 22);
// HUD
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.fillRect(8,8,140,40);
ctx.fillStyle = '#e6f0ff';
ctx.font = '16px system-ui, -apple-system';
ctx.fillText('Puan: ' + store.coins, 16, 32);
ctx.fillText('Can: ' + (store.lives||0), 110, 32);
}
function drawMountain(x,y,w,h) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w0.5, y - h);
ctx.lineTo(x + w, y);
ctx.closePath();
ctx.fillStyle = '#163f57';
ctx.fill();
// snow
ctx.beginPath();
ctx.moveTo(x + w0.5, y - h);
ctx.lineTo(x + w0.6, y - h0.6);
ctx.lineTo(x + w0.4, y - h0.6);
ctx.closePath();
ctx.fillStyle = '#e6f0ff';
ctx.fill();
}
// main loop
function loop(now) {
const dt = Math.min(40, now - lastTime);
lastTime = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
})();