🎮ArcadeLab

Bounce 100

by NovaWizard65
1235 lines43.6 KB
▶ Play
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Bounce 100</title>
    <!-- GamePix SDK -->
    <script src="https://integration.gamepix.com/sdk/v3/gamepix.sdk.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }
        html, body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: #0f172a;
            font-family: 'Segoe UI', Arial, sans-serif;
        }
        #gameWrapper {
            position: relative;
            width: 100%;
            height: 100%;
            max-width: 900px;
            max-height: 550px;
            margin: 0 auto;
            background: #000;
            border: 3px solid #ffd700;
            border-radius: 16px;
            overflow: hidden;
            box-shadow: 0 20px 40px rgba(0,0,0,0.8);
            aspect-ratio: 900 / 550;
        }
        #gameWrapper canvas {
            display: block;
            width: 100%;
            height: 100%;
            image-rendering: pixelated;
        }
        .overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: none;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            background: rgba(0,0,0,0.88);
            backdrop-filter: blur(4px);
            z-index: 20;
            padding: 20px;
            text-align: center;
            border-radius: 16px;
        }
        .overlay.active {
            display: flex;
        }
        .overlay h1 {
            font-size: clamp(2rem, 6vw, 3.2rem);
            font-weight: 900;
            color: #ffd700;
            text-shadow: 0 0 30px #ffaa00, 0 0 60px #ff6600;
            letter-spacing: 4px;
            margin-bottom: 8px;
        }
        .overlay p {
            color: #ccc;
            max-width: 400px;
            font-size: clamp(0.85rem, 2vw, 1rem);
            margin: 6px 0;
        }
        .overlay .keys {
            color: #aaa;
            font-size: 0.8rem;
            background: rgba(255,255,255,0.06);
            padding: 6px 16px;
            border-radius: 40px;
            border: 1px solid rgba(255,255,255,0.08);
            margin: 6px 0;
        }
        .overlay .keys kbd {
            background: #1a1a2e;
            color: #ffd700;
            padding: 2px 10px;
            border-radius: 4px;
            border: 1px solid #444;
            font-family: monospace;
            font-size: 0.8rem;
        }
        .btn {
            background: linear-gradient(135deg, #ff416c, #ff4b2b);
            border: none;
            padding: 12px 32px;
            border-radius: 40px;
            font-weight: bold;
            font-size: clamp(0.9rem, 2vw, 1.1rem);
            color: #fff;
            cursor: pointer;
            margin: 6px;
            transition: transform 0.15s;
            box-shadow: 0 4px 20px rgba(255,65,108,0.4);
            touch-action: manipulation;
            min-height: 48px;
            min-width: 120px;
            pointer-events: auto;
        }
        .btn:hover {
            transform: scale(1.04);
        }
        .btn:active {
            transform: scale(0.96);
        }
        .btn.secondary {
            background: rgba(255,255,255,0.12);
            border: 1px solid rgba(255,255,255,0.2);
            box-shadow: none;
        }
        .btn.gold {
            background: linear-gradient(135deg, #f7971e, #ffd200);
            color: #1a1a2e;
            box-shadow: 0 4px 20px rgba(255,215,0,0.4);
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(10, 1fr);
            gap: 4px;
            max-width: 380px;
            max-height: 140px;
            overflow-y: auto;
            margin: 6px 0;
            width: 100%;
            padding: 4px;
        }
        .grid button {
            background: rgba(255,215,0,0.12);
            border: 1px solid rgba(255,215,0,0.2);
            color: #ffd700;
            padding: 4px 0;
            border-radius: 6px;
            font-size: 0.65rem;
            cursor: pointer;
            transition: 0.1s;
            touch-action: manipulation;
            pointer-events: auto;
        }
        .grid button:hover:not(.locked) {
            background: rgba(255,215,0,0.3);
        }
        .grid button.locked {
            opacity: 0.35;
            cursor: not-allowed;
            border-color: #444;
            color: #666;
        }
        .grid button.completed {
            border-color: #4caf50;
            color: #4caf50;
        }
        .grid button.current {
            border-color: #ff9800;
            color: #ff9800;
            background: rgba(255,152,0,0.2);
        }
        .header {
            position: absolute;
            top: 8px;
            left: 8px;
            right: 8px;
            display: flex;
            justify-content: space-between;
            z-index: 10;
            pointer-events: none;
            gap: 6px;
            flex-wrap: wrap;
        }
        .header .stats {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
            pointer-events: none;
        }
        .header .stats span {
            background: rgba(0,0,0,0.75);
            padding: 3px 10px;
            border-radius: 20px;
            font-size: clamp(0.6rem, 1.2vw, 0.75rem);
            font-weight: bold;
            color: #ffd700;
            pointer-events: none;
            white-space: nowrap;
        }
        .header .menu-btn {
            background: rgba(0,0,0,0.75);
            border: 1px solid #ffd700;
            color: #ffd700;
            padding: 3px 12px;
            border-radius: 20px;
            font-size: clamp(0.6rem, 1.2vw, 0.75rem);
            cursor: pointer;
            pointer-events: auto;
            transition: 0.15s;
            touch-action: manipulation;
            min-height: 28px;
        }
        .header .menu-btn:hover {
            background: #ffd700;
            color: #000;
        }
        .header .menu-btn:active {
            transform: scale(0.95);
        }
        .title-in-game {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255,215,0,0.10);
            font-size: clamp(1rem, 3vw, 1.8rem);
            font-weight: 900;
            letter-spacing: 6px;
            pointer-events: none;
            z-index: 5;
            text-transform: uppercase;
            white-space: nowrap;
        }
        .grid::-webkit-scrollbar {
            width: 4px;
        }
        .grid::-webkit-scrollbar-track {
            background: rgba(255,255,255,0.05);
            border-radius: 10px;
        }
        .grid::-webkit-scrollbar-thumb {
            background: #ffd700;
            border-radius: 10px;
        }
        .hidden {
            display: none !important;
        }
        @media (max-width: 600px) {
            #gameWrapper {
                border-radius: 0;
                border-width: 2px;
                max-width: 100%;
                max-height: 100%;
                aspect-ratio: 900/550;
            }
            .header .stats span {
                font-size: 0.55rem;
                padding: 2px 6px;
            }
            .header .menu-btn {
                font-size: 0.55rem;
                padding: 2px 8px;
                min-height: 24px;
            }
            .btn {
                padding: 10px 20px;
                font-size: 0.85rem;
                min-height: 40px;
                min-width: 80px;
            }
            .grid {
                max-width: 100%;
                gap: 3px;
            }
            .grid button {
                font-size: 0.55rem;
                padding: 3px 0;
            }
            .overlay h1 {
                font-size: 1.8rem;
            }
        }
        @media (max-width: 400px) {
            .header .stats span {
                font-size: 0.45rem;
                padding: 1px 4px;
            }
            .header .menu-btn {
                font-size: 0.45rem;
                padding: 1px 6px;
                min-height: 20px;
            }
            .btn {
                padding: 8px 14px;
                font-size: 0.75rem;
                min-height: 34px;
                min-width: 60px;
            }
            .overlay h1 {
                font-size: 1.4rem;
            }
            .grid button {
                font-size: 0.45rem;
                padding: 2px 0;
            }
        }
    </style>
</head>
<body>
<div id="gameWrapper">
    <canvas id="gameCanvas" width="900" height="550"></canvas>

    <!-- HEADER -->
    <div class="header">
        <div class="stats">
            <span>❤️ <span id="livesVal">3</span></span>
            <span>⭐ <span id="levelVal">1</span>/100</span>
            <span>🏆 <span id="scoreVal">0</span></span>
            <span>💎 <span id="starsVal">0</span></span>
        </div>
        <div style="display:flex; gap:6px; pointer-events:auto;">
            <button class="menu-btn" id="rewardBtn">📺 +1 vie</button>
            <button class="menu-btn" id="inGameMenuBtn">🏠 Menu</button>
        </div>
    </div>
    <div class="title-in-game">BOUNCE 100</div>

    <!-- MENU PRINCIPAL -->
    <div id="mainMenu" class="overlay active">
        <h1>⚡ BOUNCE 100 ⚡</h1>
        <p>100 niveaux de plateformes addictifs.</p>
        <p style="font-size:0.9rem;color:#aaa;">Collecte les étoiles, évite les ennemis, atteins le drapeau.</p>
        <div class="keys">
            <kbd>←</kbd> <kbd>→</kbd> ou <kbd>Q</kbd> <kbd>D</kbd> &nbsp;·&nbsp; <kbd>↑</kbd> <kbd>Espace</kbd> <kbd>W</kbd> sauter
        </div>
        <button class="btn" id="playBtn">🎮 Jouer</button>
        <button class="btn secondary" id="levelsBtn">📋 Niveaux</button>
        <div id="levelGridContainer" class="hidden" style="width:100%;"></div>
    </div>

    <!-- GAME OVER -->
    <div id="gameOverOverlay" class="overlay">
        <h1 style="color:#ff6b6b;">💀 GAME OVER</h1>
        <p>Score : <span id="finalScoreSpan">0</span></p>
        <button class="btn" id="retryBtn">🔄 Réessayer</button>
        <button class="btn secondary" id="menuBtnRetry">🏠 Menu</button>
    </div>

    <!-- NIVEAU TERMINÉ -->
    <div id="levelCompleteOverlay" class="overlay">
        <h1>🎉 Niveau terminé !</h1>
        <p id="completeMsg"></p>
        <button class="btn" id="nextLevelBtn">⏩ Suivant</button>
        <button class="btn secondary" id="menuBtnComplete">🏠 Menu</button>
    </div>

    <!-- VICTOIRE FINALE -->
    <div id="finalWinOverlay" class="overlay">
        <h1>🏆 LÉGENDE !</h1>
        <p>Vous avez terminé les 100 niveaux !</p>
        <p>Score final : <span id="finalWinScore">0</span></p>
        <button class="btn" id="winMenuBtn">🏠 Menu</button>
    </div>
</div>

<script>
    (function() {
        "use strict";

        // ========== GAMEPIX SDK FONCTIONS ==========
        function gamepixSetItem(key, value) {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.localStorage && GamePix.localStorage.setItem) {
                    GamePix.localStorage.setItem(key, String(value));
                } else {
                    localStorage.setItem(key, String(value));
                }
            } catch(e) {}
        }

        function gamepixGetItem(key) {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.localStorage && GamePix.localStorage.getItem) {
                    return GamePix.localStorage.getItem(key);
                } else {
                    return localStorage.getItem(key);
                }
            } catch(e) { return null; }
        }

        function gamepixInterstitialAd(callback) {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.interstitialAd) {
                    isPaused = true;
                    GamePix.interstitialAd().then(function(res) {
                        isPaused = false;
                        if (callback) callback(res);
                    }).catch(function() {
                        isPaused = false;
                        if (callback) callback(null);
                    });
                } else {
                    if (callback) callback(null);
                }
            } catch(e) {
                isPaused = false;
                if (callback) callback(null);
            }
        }

        function gamepixRewardAd(callback) {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.rewardAd) {
                    isPaused = true;
                    GamePix.rewardAd().then(function(res) {
                        isPaused = false;
                        if (callback) callback(res && res.success);
                    }).catch(function() {
                        isPaused = false;
                        if (callback) callback(false);
                    });
                } else {
                    if (callback) callback(false);
                }
            } catch(e) {
                isPaused = false;
                if (callback) callback(false);
            }
        }

        function gamepixUpdateLevel(levelNum) {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.updateLevel) {
                    GamePix.updateLevel(levelNum);
                }
            } catch(e) {}
        }

        function gamepixHappyMoment() {
            try {
                if (typeof GamePix !== 'undefined' && GamePix.happyMoment) {
                    GamePix.happyMoment();
                }
            } catch(e) {}
        }

        // ========== ÉLÉMENTS DOM ==========
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const livesSpan = document.getElementById('livesVal');
        const levelSpan = document.getElementById('levelVal');
        const scoreSpan = document.getElementById('scoreVal');
        const starsSpan = document.getElementById('starsVal');
        const finalScoreSpan = document.getElementById('finalScoreSpan');
        const finalWinScoreSpan = document.getElementById('finalWinScore');
        const completeMsgSpan = document.getElementById('completeMsg');

        const mainMenu = document.getElementById('mainMenu');
        const gameOverOverlay = document.getElementById('gameOverOverlay');
        const levelCompleteOverlay = document.getElementById('levelCompleteOverlay');
        const finalWinOverlay = document.getElementById('finalWinOverlay');
        const levelGridContainer = document.getElementById('levelGridContainer');

        const playBtn = document.getElementById('playBtn');
        const levelsBtn = document.getElementById('levelsBtn');
        const retryBtn = document.getElementById('retryBtn');
        const menuBtnRetry = document.getElementById('menuBtnRetry');
        const nextLevelBtn = document.getElementById('nextLevelBtn');
        const menuBtnComplete = document.getElementById('menuBtnComplete');
        const winMenuBtn = document.getElementById('winMenuBtn');
        const inGameMenuBtn = document.getElementById('inGameMenuBtn');
        const rewardBtn = document.getElementById('rewardBtn');

        // ========== CONSTANTES ==========
        const W = 900,
            H = 550;
        const GRAVITY = 0.45;
        const JUMP = -7.6;
        const GROUND = H - 55;

        // ========== ÉTAT JEU ==========
        let level = 1;
        let maxUnlocked = 1;
        let lives = 3;
        let score = 0;
        let starsGot = 0;
        let running = false;
        let levelDone = false;
        let lock = false;
        let highScore = 0;
        let completed = [];
        let isPaused = false;

        let p = { x: 80, y: GROUND - 22, vx: 0, vy: 0, w: 20, h: 20, onGround: false, facingRight: true };
        let left = false,
            right = false,
            jump = false;

        let platforms = [],
            stars = [],
            enemies = [],
            moving = [],
            tele = [],
            wind = [],
            particles = [];

        // ========== OVERLAYS ==========
        function hideAll() {
            document.querySelectorAll('.overlay').forEach(function(el) {
                el.classList.remove('active');
            });
            levelGridContainer.classList.add('hidden');
        }

        function show(id) {
            hideAll();
            var el = document.getElementById(id);
            if (el) el.classList.add('active');
        }

        // ========== SAUVEGARDE ==========
        function save() {
            try {
                var data = JSON.stringify({ maxUnlocked: maxUnlocked, highScore: Math.max(highScore, score),
                    completed: completed, score: score });
                gamepixSetItem('bounce100', data);
            } catch(e) {}
        }

        function load() {
            try {
                var raw = gamepixGetItem('bounce100');
                if (raw) {
                    var data = JSON.parse(raw);
                    maxUnlocked = data.maxUnlocked || 1;
                    highScore = data.highScore || 0;
                    completed = data.completed || [];
                    score = data.score || 0;
                    return;
                }
            } catch(e) {}
            maxUnlocked = 1;
            highScore = 0;
            completed = [];
            score = 0;
        }

        // ========== GÉNÉRATION NIVEAU ==========
        function genLevel(n) {
            var plat = [],
                starList = [],
                enemyList = [],
                movingList = [],
                teleList = [],
                windList = [];
            plat.push({ x: 0, y: GROUND, w: W, h: 25, type: 'normal' });
            var diff = (n - 1) / 99;
            var pCount = 5 + Math.floor(n / 8);
            var eCount = Math.min(6, 1 + Math.floor(n / 12));
            var sCount = Math.min(8, 3 + Math.floor(n / 10));
            var theme = Math.floor((n % 5));
            for (var i = 0; i < pCount; i++) {
                var x = 80 + (i * (W - 160) / pCount) + (Math.sin(n * i) * 35);
                x = Math.min(Math.max(x, 60), W - 110);
                var y = GROUND - 55 - (i * 10) - (Math.sin(n + i) * 18);
                y = Math.min(Math.max(y, GROUND - 190), GROUND - 45);
                var w = 55 + (Math.sin(n + i) * 15);
                w = Math.min(Math.max(w, 45), 90);
                var type = 'normal';
                if (n >= 3 && i === 2 && Math.random() > 0.6) type = 'spring';
                if (n >= 5 && i === 3 && Math.random() > 0.7) type = 'spike';
                if (i === pCount - 1) type = 'goal';
                if (n >= 10 && i === 1 && Math.random() > 0.7) {
                    movingList.push({ x: x, y: y, w: w, h: 18, type: 'normal', range: 60, speed: 1, dir: 1,
                        startX: x });
                    continue;
                }
                plat.push({ x: x, y: y, w: w, h: 18, type: type });
            }
            for (var j = 0; j < sCount; j++) {
                var idx = 1 + (j % (plat.length - 1));
                var pl = plat[idx];
                if (pl && pl.type !== 'spike') {
                    starList.push({ x: pl.x + pl.w / 2 - 7, y: pl.y - 18, collected: false });
                }
            }
            for (var k = 0; k < eCount; k++) {
                var idx2 = 1 + (k % (plat.length - 2));
                var pl2 = plat[idx2];
                if (pl2 && pl2.type !== 'spike' && pl2.type !== 'goal') {
                    enemyList.push({
                        x: pl2.x + 15,
                        y: pl2.y - 25,
                        w: 22,
                        h: 22,
                        dir: k % 2 === 0 ? 1 : -1,
                        speed: 1.2 + diff * 2.5,
                        type: theme === 1 ? 'fire' : 'slime'
                    });
                }
            }
            if (n >= 15 && Math.random() > 0.8) {
                teleList.push({ x: 200, y: GROUND - 100, w: 30, h: 30, tx: W - 200, ty: GROUND - 100 });
                teleList.push({ x: W - 200, y: GROUND - 100, w: 30, h: 30, tx: 200, ty: GROUND - 100 });
            }
            if (n >= 20 && Math.random() > 0.7) {
                windList.push({ x: 400, y: 0, w: 150, h: H, force: 2 + diff * 3, dir: 1 });
            }
            return { platforms: plat, stars: starList, enemies: enemyList, moving: movingList, tele: teleList,
                wind: windList };
        }

        var cache = {};

        function getLevel(n) {
            if (!cache[n]) cache[n] = genLevel(n);
            return JSON.parse(JSON.stringify(cache[n]));
        }

        function loadLevel(n) {
            if (n > 100) { showFinalWin(); return; }
            var lvl = getLevel(n);
            platforms = lvl.platforms;
            stars = lvl.stars;
            enemies = lvl.enemies;
            moving = lvl.moving || [];
            tele = lvl.tele || [];
            wind = lvl.wind || [];
            p.x = 80;
            p.y = GROUND - p.h;
            p.vx = 0;
            p.vy = 0;
            p.onGround = false;
            starsGot = 0;
            running = true;
            levelDone = false;
            isPaused = false;
            updateUI();
            gamepixUpdateLevel(n);
        }

        // ========== UI ==========
        function updateUI() {
            livesSpan.textContent = lives;
            levelSpan.textContent = level;
            scoreSpan.textContent = score;
            var total = stars.length || 0;
            starsSpan.textContent = starsGot + '/' + total;
        }

        function buildGrid() {
            levelGridContainer.innerHTML = '';
            var div = document.createElement('div');
            div.className = 'grid';
            for (var i = 1; i <= 100; i++) {
                var btn = document.createElement('button');
                btn.textContent = i;
                if (i <= maxUnlocked) {
                    if (completed.indexOf(i) !== -1) btn.classList.add('completed');
                    if (i === level) btn.classList.add('current');
                    btn.onclick = (function(lvl) {
                        return function() {
                            if (confirm('Changer de niveau ? La progression actuelle sera perdue.')) {
                                level = lvl;
                                lives = 3;
                                score = 0;
                                startGame();
                            }
                        };
                    })(i);
                } else {
                    btn.classList.add('locked');
                    btn.disabled = true;
                }
                div.appendChild(btn);
            }
            levelGridContainer.appendChild(div);
            levelGridContainer.classList.remove('hidden');
        }

        // ========== TRANSITIONS ==========
        function startGame() {
            load();
            if (maxUnlocked < 1) maxUnlocked = 1;
            if (level > maxUnlocked) level = maxUnlocked;
            lives = 3;
            score = 0;
            starsGot = 0;
            hideAll();
            loadLevel(level);
            running = true;
            updateUI();
        }

        function goMenu() {
            hideAll();
            mainMenu.classList.add('active');
            running = false;
            isPaused = true;
            buildGrid();
        }

        function showGameOver() {
            hideAll();
            finalScoreSpan.textContent = score;
            gameOverOverlay.classList.add('active');
            running = false;
            isPaused = true;
        }

        function showLevelComplete(msg) {
            hideAll();
            completeMsgSpan.innerHTML = msg;
            levelCompleteOverlay.classList.add('active');
            running = false;
            isPaused = true;
            gamepixInterstitialAd(function() {});
        }

        function showFinalWin() {
            hideAll();
            finalWinScoreSpan.textContent = score;
            finalWinOverlay.classList.add('active');
            running = false;
            isPaused = true;
            save();
        }

        function nextLevel() {
            if (lock) return;
            lock = true;
            levelCompleteOverlay.classList.remove('active');
            if (level < 100) {
                level++;
                lives = Math.min(lives + 1, 5);
                loadLevel(level);
                running = true;
                levelDone = false;
                isPaused = false;
                updateUI();
            } else {
                showFinalWin();
            }
            setTimeout(function() { lock = false; }, 200);
        }

        function retry() {
            gameOverOverlay.classList.remove('active');
            startGame();
        }

        // ========== BOUTONS ==========
        // Bouton Jouer
        playBtn.addEventListener('click', startGame);

        // Bouton Niveaux
        levelsBtn.addEventListener('click', function() {
            if (levelGridContainer.classList.contains('hidden')) {
                buildGrid();
            } else {
                levelGridContainer.classList.add('hidden');
            }
        });

        // Bouton Réessayer
        retryBtn.addEventListener('click', retry);

        // Bouton Menu (Game Over)
        menuBtnRetry.addEventListener('click', goMenu);

        // Bouton Niveau suivant
        nextLevelBtn.addEventListener('click', nextLevel);

        // Bouton Menu (Niveau terminé)
        menuBtnComplete.addEventListener('click', goMenu);

        // Bouton Menu (Victoire finale)
        winMenuBtn.addEventListener('click', goMenu);

        // Bouton Menu en jeu
        inGameMenuBtn.addEventListener('click', function() {
            if (confirm('Revenir au menu principal ? La progression actuelle sera perdue.')) {
                goMenu();
            }
        });

        // Bouton +1 vie (récompensé)
        rewardBtn.addEventListener('click', function() {
            if (lives < 5 && running) {
                gamepixRewardAd(function(success) {
                    if (success) {
                        lives++;
                        updateUI();
                        addParticles(p.x + p.w / 2, p.y + p.h / 2, '#ffd700', 20);
                        playSound('collect');
                        gamepixHappyMoment();
                    } else {
                        alert("Publicité non disponible pour le moment.");
                    }
                });
            } else {
                alert("Vies pleines (max 5) ou jeu terminé.");
            }
        });

        // ========== PHYSIQUE ==========
        function applyPhysics() {
            for (var wi = 0; wi < wind.length; wi++) {
                var w = wind[wi];
                if (p.x + p.w > w.x && p.x < w.x + w.w) {
                    p.vx += w.force * w.dir * 0.1;
                }
            }
            p.vy += GRAVITY;
            if (left) {
                p.vx = Math.max(p.vx - 0.55, -5.8);
                p.facingRight = false;
            } else if (right) {
                p.vx = Math.min(p.vx + 0.55, 5.8);
                p.facingRight = true;
            } else {
                p.vx *= 0.94;
            }
            p.x += p.vx;
            p.y += p.vy;

            for (var mi = 0; mi < moving.length; mi++) {
                var m = moving[mi];
                m.x += m.speed * m.dir;
                if (m.x > m.startX + m.range || m.x < m.startX - m.range) m.dir *= -1;
                if (p.x + p.w > m.x && p.x < m.x + m.w && p.y + p.h > m.y && p.y < m.y + m.h) {
                    if (p.vy >= 0 && p.y + p.h - p.vy <= m.y + 12) {
                        p.y = m.y - p.h;
                        p.vy = 0;
                        p.onGround = true;
                        p.x += m.speed * m.dir;
                    }
                }
            }

            p.onGround = false;
            for (var pi = 0; pi < platforms.length; pi++) {
                var plat = platforms[pi];
                if (p.x + p.w > plat.x && p.x < plat.x + plat.w && p.y + p.h > plat.y && p.y < plat.y + plat.h) {
                    if (p.vy >= 0 && p.y + p.h - p.vy <= plat.y + 12) {
                        p.y = plat.y - p.h;
                        p.vy = 0;
                        p.onGround = true;
                        if (plat.type === 'spring') {
                            p.vy = JUMP * 1.3;
                            addParticles(p.x + p.w / 2, p.y + p.h, '#ffaa44', 12);
                            playSound('jump');
                        }
                        if (plat.type === 'spike') {
                            hitPlayer();
                        }
                        if (plat.type === 'goal' && !levelDone && running) {
                            completeLevel();
                        }
                    } else if (p.x + p.w - p.vx <= plat.x + 8) {
                        p.x = plat.x - p.w;
                    } else if (p.x - p.vx >= plat.x + plat.w - 8) {
                        p.x = plat.x + plat.w;
                    } else if (p.vy < 0) {
                        p.y = plat.y + plat.h;
                        p.vy = 0;
                    }
                }
            }

            for (var ti = 0; ti < tele.length; ti++) {
                var t = tele[ti];
                if (p.x + p.w > t.x && p.x < t.x + t.w && p.y + p.h > t.y && p.y < t.y + t.h) {
                    p.x = t.tx;
                    p.y = t.ty;
                    playSound('collect');
                    addParticles(p.x, p.y, '#00ffff', 15);
                }
            }

            if (jump && p.onGround && running) {
                p.vy = JUMP;
                p.onGround = false;
                jump = false;
                addParticles(p.x + p.w / 2, p.y + p.h, '#aaaaff', 8);
                playSound('jump');
            }

            if (p.y > H && running) hitPlayer();
            if (p.x < 0) p.x = 0;
            if (p.x + p.w > W) p.x = W - p.w;
        }

        function collectStars() {
            for (var si = 0; si < stars.length; si++) {
                var s = stars[si];
                if (!s.collected && p.x + p.w > s.x && p.x < s.x + 14 && p.y + p.h > s.y && p.y < s.y + 14) {
                    s.collected = true;
                    score += 50;
                    starsGot++;
                    updateUI();
                    addParticles(s.x + 7, s.y + 7, '#ffd700', 10);
                    playSound('collect');
                    gamepixHappyMoment();
                }
            }
        }

        function updateEnemies() {
            for (var ei = 0; ei < enemies.length; ei++) {
                var e = enemies[ei];
                e.x += e.speed * e.dir;
                if (e.x < 40 || e.x + e.w > W - 40) e.dir *= -1;
                if (p.x + p.w > e.x && p.x < e.x + e.w && p.y + p.h > e.y && p.y < e.y + e.h && running) {
                    if (p.vy > 0 && p.y + p.h - p.vy <= e.y + 10) {
                        enemies.splice(ei, 1);
                        ei--;
                        p.vy = JUMP * 0.6;
                        score += 100;
                        updateUI();
                        addParticles(e.x + e.w / 2, e.y + e.h / 2, '#ff6666', 15);
                        playSound('collect');
                        gamepixHappyMoment();
                    } else {
                        hitPlayer();
                    }
                }
            }
        }

        function hitPlayer() {
            lives--;
            updateUI();
            addParticles(p.x + p.w / 2, p.y + p.h / 2, '#ff0000', 20);
            playSound('hit');
            if (lives <= 0) {
                running = false;
                isPaused = true;
                save();
                showGameOver();
            } else {
                p.x = 80;
                p.y = GROUND - p.h;
                p.vx = 0;
                p.vy = 0;
            }
        }

        function completeLevel() {
            if (levelDone) return;
            levelDone = true;
            running = false;
            isPaused = true;
            var bonus = starsGot * 50 + 200;
            score += bonus;
            if (completed.indexOf(level) === -1) completed.push(level);
            if (level === maxUnlocked && level < 100) maxUnlocked = level + 1;
            if (score > highScore) highScore = score;
            save();
            playSound('levelComplete');
            gamepixHappyMoment();
            var msg = '+' + bonus + ' pts ⭐ ' + starsGot + '/' + stars.length;
            showLevelComplete(msg);
        }

        // ========== SON ==========
        var actx = null;

        function playSound(type) {
            try {
                if (!actx) actx = new(window.AudioContext || window.webkitAudioContext)();
                var osc = actx.createOscillator();
                var gain = actx.createGain();
                osc.connect(gain);
                gain.connect(actx.destination);
                var f = 440,
                    d = 0.2;
                switch (type) {
                    case 'jump':
                        f = 523.25;
                        d = 0.12;
                        break;
                    case 'collect':
                        f = 659.25;
                        d = 0.1;
                        break;
                    case 'hit':
                        f = 220;
                        d = 0.3;
                        break;
                    case 'levelComplete':
                        f = 783.99;
                        d = 0.4;
                        break;
                    case 'levelStart':
                        f = 392;
                        d = 0.2;
                        break;
                }
                osc.frequency.value = f;
                gain.gain.value = 0.2;
                osc.start();
                gain.gain.exponentialRampToValueAtTime(0.00001, actx.currentTime + d);
                osc.stop(actx.currentTime + d);
            } catch(e) {}
        }

        // ========== PARTICULES ==========
        function addParticles(x, y, color, count) {
            if (!count) count = 8;
            for (var i = 0; i < count; i++) {
                particles.push({
                    x: x,
                    y: y,
                    vx: (Math.random() - 0.5) * 4,
                    vy: (Math.random() - 0.5) * 3 - 1,
                    life: 0.7,
                    color: color,
                    size: Math.random() * 3 + 1.5
                });
            }
        }

        function updateParticles() {
            for (var i = 0; i < particles.length; i++) {
                var pt = particles[i];
                pt.x += pt.vx;
                pt.y += pt.vy;
                pt.life -= 0.02;
                if (pt.life <= 0) {
                    particles.splice(i, 1);
                    i--;
                }
            }
        }

        // ========== DESSIN ==========
        function drawBg() {
            var g = ctx.createLinearGradient(0, 0, 0, H);
            g.addColorStop(0, '#0a0f2a');
            g.addColorStop(1, '#1a1f3a');
            ctx.fillStyle = g;
            ctx.fillRect(0, 0, W, H);
            ctx.fillStyle = 'rgba(255,255,200,0.2)';
            for (var i = 0; i < 80; i++) {
                ctx.beginPath();
                ctx.arc((i * 131) % W, (i * 57) % H, 1.2, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function drawPlatforms() {
            for (var pi = 0; pi < platforms.length; pi++) {
                var plat = platforms[pi];
                ctx.shadowBlur = 3;
                if (plat.type === 'spike') {
                    ctx.fillStyle = '#aa4444';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    for (var i = 0; i < plat.w / 12; i++) {
                        ctx.beginPath();
                        ctx.moveTo(plat.x + i * 12, plat.y);
                        ctx.lineTo(plat.x + i * 12 + 6, plat.y - 10);
                        ctx.lineTo(plat.x + i * 12 - 6, plat.y - 10);
                        ctx.fillStyle = '#ff6666';
                        ctx.fill();
                    }
                } else if (plat.type === 'spring') {
                    ctx.fillStyle = '#f0a030';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#d08020';
                    for (var j = 0; j < 4; j++) ctx.fillRect(plat.x + 8 + j * 14, plat.y - 6, 8, 6);
                } else if (plat.type === 'goal') {
                    ctx.fillStyle = '#44aa77';
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#ffdd88';
                    ctx.font = '22px monospace';
                    ctx.fillText('🏁', plat.x + plat.w / 2 - 8, plat.y + 14);
                } else {
                    var g = ctx.createLinearGradient(plat.x, plat.y, plat.x, plat.y + plat.h);
                    g.addColorStop(0, '#5a7a5a');
                    g.addColorStop(1, '#3a5a3a');
                    ctx.fillStyle = g;
                    ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
                    ctx.fillStyle = '#7a9a7a';
                    ctx.fillRect(plat.x, plat.y, plat.w, 4);
                }
            }
            for (var mi = 0; mi < moving.length; mi++) {
                var m = moving[mi];
                ctx.fillStyle = '#88aaff';
                ctx.fillRect(m.x, m.y, m.w, m.h);
                ctx.fillStyle = '#aaccff';
                ctx.fillRect(m.x, m.y, m.w, 4);
            }
            for (var ti = 0; ti < tele.length; ti++) {
                var t = tele[ti];
                ctx.fillStyle = '#44aaff';
                ctx.globalAlpha = 0.7;
                ctx.fillRect(t.x, t.y, t.w, t.h);
                ctx.fillStyle = '#fff';
                ctx.font = '18px monospace';
                ctx.fillText('🌀', t.x + 8, t.y + 22);
                ctx.globalAlpha = 1;
            }
            ctx.shadowBlur = 0;
        }

        function drawStars() {
            for (var si = 0; si < stars.length; si++) {
                var s = stars[si];
                if (!s.collected) {
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#ffaa00';
                    ctx.fillStyle = '#ffdd77';
                    ctx.beginPath();
                    ctx.arc(s.x + 7, s.y + 7, 7, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.fillStyle = '#ffaa33';
                    ctx.font = '16px monospace';
                    ctx.fillText('★', s.x + 2, s.y + 11);
                }
            }
            ctx.shadowBlur = 0;
        }

        function drawEnemies() {
            for (var ei = 0; ei < enemies.length; ei++) {
                var e = enemies[ei];
                ctx.fillStyle = e.type === 'fire' ? '#ff6633' : '#8844cc';
                ctx.shadowBlur = 4;
                ctx.fillRect(e.x, e.y, e.w, e.h);
                ctx.fillStyle = '#fff';
                ctx.fillRect(e.x + 5, e.y + 5, 4, 4);
                ctx.fillRect(e.x + 13, e.y + 5, 4, 4);
                ctx.fillRect(e.x + 4, e.y + 12, 14, 3);
            }
        }

        function drawPlayer() {
            ctx.shadowBlur = 15;
            ctx.shadowColor = '#ff6666';
            var g = ctx.createRadialGradient(p.x + 7, p.y + 7, 3, p.x + 10, p.y + 10, 14);
            g.addColorStop(0, '#ff7e7e');
            g.addColorStop(1, '#cc2222');
            ctx.fillStyle = g;
            ctx.beginPath();
            ctx.arc(p.x + p.w / 2, p.y + p.h / 2, p.w / 2, 0, Math.PI * 2);
            ctx.fill();
            ctx.shadowBlur = 0;
            ctx.fillStyle = 'rgba(255,255,255,0.5)';
            ctx.beginPath();
            ctx.ellipse(p.x + 6, p.y + 6, 4, 2, -0.3, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.arc(p.x + (p.facingRight ? 13 : 7), p.y + 7, 3.5, 0, Math.PI * 2);
            ctx.fill();
            ctx.fillStyle = '#222';
            ctx.beginPath();
            ctx.arc(p.x + (p.facingRight ? 13 : 7), p.y + 6, 2, 0, Math.PI * 2);
            ctx.fill();
            ctx.shadowBlur = 0;
        }

        function drawParticles() {
            for (var pi = 0; pi < particles.length; pi++) {
                var pt = particles[pi];
                ctx.globalAlpha = pt.life;
                ctx.fillStyle = pt.color;
                ctx.fillRect(pt.x, pt.y, pt.size, pt.size);
            }
            ctx.globalAlpha = 1;
        }

        function draw() {
            drawBg();
            drawPlatforms();
            drawStars();
            drawEnemies();
            drawPlayer();
            drawParticles();
            ctx.font = 'bold 12px monospace';
            ctx.fillStyle = 'rgba(255,255,255,0.3)';
            ctx.fillText('Niveau ' + level + '/100', 12, 28);
        }

        // ========== BOUCLE ==========
        function update() {
            if (!running || isPaused) return;
            applyPhysics();
            collectStars();
            updateEnemies();
            updateParticles();
        }

        function loop() {
            update();
            draw();
            requestAnimationFrame(loop);
        }

        // ========== CONTROLES ==========
        function keyDown(e) {
            var k = e.key;
            if (k === 'ArrowLeft' || k === 'q' || k === 'Q' || k === 'a' || k === 'A') { left = true;
                e.preventDefault(); }
            if (k === 'ArrowRight' || k === 'd' || k === 'D') { right = true;
                e.preventDefault(); }
            if (k === 'ArrowUp' || k === ' ' || k === 'w' || k === 'W' || k === 'z' || k === 'Z') { jump = true;
                e.preventDefault(); }
            if (k === 'r' || k === 'R') { startGame();
                e.preventDefault(); }
        }

        function keyUp(e) {
            var k = e.key;
            if (k === 'ArrowLeft' || k === 'q' || k === 'Q' || k === 'a' || k === 'A') { left = false;
                e.preventDefault(); }
            if (k === 'ArrowRight' || k === 'd' || k === 'D') { right = false;
                e.preventDefault(); }
        }

        // ========== INIT ==========
        function init() {
            load();
            goMenu();

            window.addEventListener('keydown', keyDown);
            window.addEventListener('keyup', keyUp);
            loop();
        }

        init();
    })();
</script>
</body>
</html>

Game Source: Bounce 100

Creator: NovaWizard65

Libraries: none

Complexity: complex (1235 lines, 43.6 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: bounce-100-novawizard65-mqjsti6v" to link back to the original. Then publish at arcadelab.ai/publish.