chore: gitignore erweitert, versehentliche .bak_-Datei entfernt
This commit is contained in:
parent
be7998e864
commit
28b8c3bc5d
2 changed files with 1 additions and 180 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,20 +1,15 @@
|
||||||
# Backups
|
|
||||||
backups/
|
backups/
|
||||||
backup_*/
|
backup_*/
|
||||||
_backup_*/
|
_backup_*/
|
||||||
*.bak
|
*.bak
|
||||||
*.bak.*
|
*.bak.*
|
||||||
*.bak?
|
*.bak_*
|
||||||
editor_backup_*
|
editor_backup_*
|
||||||
game.html.bak2
|
game.html.bak2
|
||||||
|
|
||||||
# Build & Deps
|
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
/**
|
|
||||||
* minigames/flappy.js
|
|
||||||
* 🐦 Flappy Bird — Klicke oder Leertaste, um durch die Röhren zu fliegen!
|
|
||||||
*/
|
|
||||||
window.MG_flappy = (function() {
|
|
||||||
|
|
||||||
const ID = 'flappy';
|
|
||||||
const EMOJI = '🐦';
|
|
||||||
const NAME = 'Flappy Bird';
|
|
||||||
const DESC = '3 Leben, 15 Hindernisse. Klick oder Leertaste zum Fliegen!';
|
|
||||||
const CONTROLS = 'Klick / Leertaste';
|
|
||||||
const MULTI = 1;
|
|
||||||
|
|
||||||
function run(wrap, W, H, cfg, onDone) {
|
|
||||||
const { canvas, ctx } = MGAPI.makeCanvas(wrap, W, H);
|
|
||||||
const theme = cfg.theme || { primary: '#06b6d4' };
|
|
||||||
const WIN_PIPES = cfg.winPipes || 15;
|
|
||||||
const MAX_LIVES = cfg.lives || 3;
|
|
||||||
|
|
||||||
const GRAV = 0.35;
|
|
||||||
const JUMP = -7;
|
|
||||||
const PW = 38;
|
|
||||||
const BASE_SPEED = 4.5; // spürbar schneller von Anfang
|
|
||||||
const SPEED_INC = 0.16;
|
|
||||||
const BASE_GAP = 175; // großzügige Lücke zu Beginn
|
|
||||||
const MIN_GAP = 135; // bleibt auch am Ende gut spielbar
|
|
||||||
const GAP_DEC = 2.7; // sanfte Verkleinerung pro Hindernis
|
|
||||||
const minTop = 35;
|
|
||||||
|
|
||||||
let bird = { y: H / 2, vy: 0 };
|
|
||||||
let pipes = [{ x: W, gap: rndGap(0) }];
|
|
||||||
let score = 0;
|
|
||||||
let speed = BASE_SPEED;
|
|
||||||
let lives = MAX_LIVES;
|
|
||||||
let dead = false;
|
|
||||||
let dying = false;
|
|
||||||
let started = false;
|
|
||||||
let stopped = false;
|
|
||||||
let raf, endTimer, respawnTimer;
|
|
||||||
|
|
||||||
function curGAP(sc) {
|
|
||||||
return Math.max(MIN_GAP, BASE_GAP - sc * GAP_DEC);
|
|
||||||
}
|
|
||||||
function rndGap(sc) {
|
|
||||||
const gap = curGAP(sc);
|
|
||||||
const maxTop = H - gap - 35;
|
|
||||||
return minTop + Math.random() * Math.max(0, maxTop - minTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
const jump = () => { if (!dead) { bird.vy = JUMP; started = true; } };
|
|
||||||
const onKey = e => { if (e.code === 'Space') { e.preventDefault(); jump(); } };
|
|
||||||
document.addEventListener('keydown', onKey);
|
|
||||||
canvas.addEventListener('click', jump);
|
|
||||||
canvas.addEventListener('touchstart', e => { e.preventDefault(); jump(); }, { passive: false });
|
|
||||||
|
|
||||||
function die() {
|
|
||||||
if (dying) return;
|
|
||||||
dying = true;
|
|
||||||
dead = true;
|
|
||||||
lives--;
|
|
||||||
if (lives <= 0) {
|
|
||||||
endTimer = setTimeout(() => onDone(false), 1100);
|
|
||||||
} else {
|
|
||||||
respawnTimer = setTimeout(() => {
|
|
||||||
bird = { y: H / 2, vy: 0 };
|
|
||||||
pipes = pipes.filter(p => p.x > 120);
|
|
||||||
if (!pipes.length) pipes = [{ x: W, gap: rndGap(score) }];
|
|
||||||
dead = false;
|
|
||||||
dying = false;
|
|
||||||
}, 900);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loop() {
|
|
||||||
if (stopped) return;
|
|
||||||
raf = requestAnimationFrame(loop);
|
|
||||||
|
|
||||||
const grad = ctx.createLinearGradient(0, 0, 0, H);
|
|
||||||
grad.addColorStop(0, '#0a0a1a');
|
|
||||||
grad.addColorStop(1, '#050508');
|
|
||||||
ctx.fillStyle = grad;
|
|
||||||
ctx.fillRect(0, 0, W, H);
|
|
||||||
|
|
||||||
if (started && !dead) {
|
|
||||||
bird.vy += GRAV;
|
|
||||||
bird.y += bird.vy;
|
|
||||||
|
|
||||||
pipes.forEach(p => p.x -= speed);
|
|
||||||
if (pipes[pipes.length - 1].x < W - 210)
|
|
||||||
pipes.push({ x: W, gap: rndGap(score) });
|
|
||||||
pipes = pipes.filter(p => p.x > -PW);
|
|
||||||
|
|
||||||
pipes.forEach(p => {
|
|
||||||
if (p.x + PW < 50 && !p.passed) {
|
|
||||||
p.passed = true;
|
|
||||||
score++;
|
|
||||||
speed = BASE_SPEED + score * SPEED_INC;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (bird.y < 0 || bird.y + 20 > H) die();
|
|
||||||
pipes.forEach(p => {
|
|
||||||
const g = curGAP(score);
|
|
||||||
if (50 < p.x + PW && 70 > p.x &&
|
|
||||||
(bird.y < p.gap || bird.y + 20 > p.gap + g)) die();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (score >= WIN_PIPES && !endTimer)
|
|
||||||
endTimer = setTimeout(() => onDone(true), 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Röhren
|
|
||||||
pipes.forEach(p => {
|
|
||||||
const g = curGAP(score);
|
|
||||||
MGAPI.roundRect(ctx, p.x, 0, PW, p.gap - 8, 4, `${theme.primary}cc`, null);
|
|
||||||
MGAPI.roundRect(ctx, p.x - 3, p.gap - 12, PW + 6, 12, 4, `${theme.primary}ee`, null);
|
|
||||||
MGAPI.roundRect(ctx, p.x, p.gap + g + 8, PW, H - p.gap - g - 8, 4, `${theme.primary}cc`, null);
|
|
||||||
MGAPI.roundRect(ctx, p.x - 3, p.gap + g, PW + 6, 12, 4, `${theme.primary}ee`, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Vogel
|
|
||||||
ctx.shadowColor = dead ? '#ef4444' : theme.primary;
|
|
||||||
ctx.shadowBlur = 12;
|
|
||||||
ctx.fillStyle = dead ? '#ef4444' : theme.primary;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(50 + 14, bird.y + 10, 14, 10, bird.vy * 0.04, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.shadowBlur = 0;
|
|
||||||
ctx.fillStyle = '#fff';
|
|
||||||
ctx.beginPath(); ctx.arc(50 + 20, bird.y + 7, 4, 0, Math.PI * 2); ctx.fill();
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.beginPath(); ctx.arc(50 + 21, bird.y + 7, 2, 0, Math.PI * 2); ctx.fill();
|
|
||||||
|
|
||||||
// HUD
|
|
||||||
MGAPI.text(ctx, `🐦 ${score} / ${WIN_PIPES}`, 8, 14,
|
|
||||||
{ align: 'left', size: 13, color: theme.primary });
|
|
||||||
const hearts = '❤️'.repeat(lives) + '🖤'.repeat(Math.max(0, MAX_LIVES - lives));
|
|
||||||
MGAPI.text(ctx, hearts, W / 2, 14, { align: 'center', size: 12 });
|
|
||||||
MGAPI.text(ctx, `⚡ ${speed.toFixed(1)}`, W - 8, 14,
|
|
||||||
{ align: 'right', size: 11, color: 'rgba(255,255,255,0.4)' });
|
|
||||||
|
|
||||||
if (!started)
|
|
||||||
MGAPI.text(ctx, 'Klicken oder Leertaste', W / 2, H / 2 + 40,
|
|
||||||
{ size: 13, color: 'rgba(255,255,255,0.7)' });
|
|
||||||
|
|
||||||
if (dead && lives <= 0)
|
|
||||||
MGAPI.resultScreen(ctx, W, H, false, `Nur ${score} Hindernisse — nächstes Mal!`);
|
|
||||||
else if (dead && dying)
|
|
||||||
MGAPI.text(ctx, `${lives} ${lives === 1 ? 'Leben' : 'Leben'} übrig`, W / 2, H / 2,
|
|
||||||
{ size: 16, color: '#ef4444' });
|
|
||||||
|
|
||||||
if (score >= WIN_PIPES && !dead)
|
|
||||||
MGAPI.resultScreen(ctx, W, H, true, `Alle ${WIN_PIPES} Hindernisse geschafft!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
raf = requestAnimationFrame(loop);
|
|
||||||
return {
|
|
||||||
stop() {
|
|
||||||
stopped = true;
|
|
||||||
cancelAnimationFrame(raf);
|
|
||||||
clearTimeout(endTimer);
|
|
||||||
clearTimeout(respawnTimer);
|
|
||||||
document.removeEventListener('keydown', onKey);
|
|
||||||
canvas.removeEventListener('click', jump);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function launch(wrap, W, H, cfg) { return run(wrap, W, H, cfg, won => MGAPI.onResult(won)); }
|
|
||||||
function preview(wrap, W, H, cfg) { return run(wrap, W, H, { ...cfg, winPipes: 15, lives: 3 }, won => MGAPI.onResult(won)); }
|
|
||||||
|
|
||||||
return { id: ID, emoji: EMOJI, name: NAME, desc: DESC, controls: CONTROLS, multi: MULTI, launch, preview };
|
|
||||||
|
|
||||||
})();
|
|
||||||
Loading…
Reference in a new issue