Bounce 100
by NovaWizard651174 lines41.8 KB
<!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>
<!-- ========== POKI SDK ========== -->
<script src="//game-cdn.poki.com/scripts/v2/poki-sdk.js"></script>
<script>
// ========== INITIALISATION POKI SDK ==========
let pokiReady = false;
function initPokiSDK() {
if (typeof PokiSDK !== 'undefined') {
PokiSDK.init().then(() => {
console.log("✅ Poki SDK initialized successfully");
pokiReady = true;
// Démarrage du jeu après initialisation
startGame();
}).catch(() => {
console.log("⚠️ Poki SDK init failed (adblock?)");
pokiReady = false;
// Lancement du jeu même sans SDK
startGame();
});
} else {
console.log("⚠️ Poki SDK not loaded");
// Lancement du jeu sans SDK (pour test local)
startGame();
}
}
// Attendre le chargement de la page
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPokiSDK);
} else {
initPokiSDK();
}
</script>
<style>
/* ========== STYLES ========== */
* {
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;
}
/* Overlays */
.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;
}
.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);
}
/* Grille des niveaux */
.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;
}
.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 en jeu */
.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);
}
/* Titre en jeu */
.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;
}
/* Scrollbar personnalisée pour la grille */
.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;
}
/* Responsive */
@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>
<button class="menu-btn" id="inGameMenuBtn">🏠 Menu</button>
</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> · <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";
// ========== POKI SDK FONCTIONS ==========
function pokiGameplayStart() {
try {
if (window.PokiSDK && typeof window.PokiSDK.gameplayStart === 'function') {
window.PokiSDK.gameplayStart();
console.log("✅ Poki: gameplayStart");
}
} catch(e) {}
}
function pokiGameplayStop() {
try {
if (window.PokiSDK && typeof window.PokiSDK.gameplayStop === 'function') {
window.PokiSDK.gameplayStop();
console.log("✅ Poki: gameplayStop");
}
} catch(e) {}
}
function pokiCommercialBreak(callback) {
try {
if (window.PokiSDK && typeof window.PokiSDK.commercialBreak === 'function') {
window.PokiSDK.commercialBreak().then(() => {
if (callback) callback();
}).catch(() => {
if (callback) callback();
});
} else {
if (callback) callback();
}
} catch(e) {
if (callback) callback();
}
}
function pokiRewardedBreak(callback) {
try {
if (window.PokiSDK && typeof window.PokiSDK.rewardedBreak === 'function') {
window.PokiSDK.rewardedBreak().then((success) => {
if (callback) callback(success);
}).catch(() => {
if (callback) callback(false);
});
} else {
if (callback) callback(false);
}
} catch(e) {
if (callback) callback(false);
}
}
// ========== É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');
// ========== 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(el => el.classList.remove('active'));
levelGridContainer.classList.add('hidden');
}
function show(id) {
hideAll();
document.getElementById(id).classList.add('active');
}
// ========== SAUVEGARDE (avec try/catch pour incognito) ==========
function save() {
try {
let data = { maxUnlocked, highScore: Math.max(highScore, score), completed, score };
localStorage.setItem('bounce100', JSON.stringify(data));
} catch(e) {
console.log("⚠️ Sauvegarde impossible (incognito)");
}
}
function load() {
try {
let raw = localStorage.getItem('bounce100');
if(raw) {
let data = JSON.parse(raw);
maxUnlocked = data.maxUnlocked || 1;
highScore = data.highScore || 0;
completed = data.completed || [];
score = data.score || 0;
return;
}
} catch(e) {
console.log("⚠️ Chargement impossible (incognito)");
}
maxUnlocked = 1;
highScore = 0;
completed = [];
score = 0;
}
// ========== GÉNÉRATION NIVEAU ==========
function genLevel(n) {
let plat=[], starList=[], enemyList=[], movingList=[], teleList=[], windList=[];
plat.push({ x:0, y:GROUND, w:W, h:25, type:'normal' });
let diff = (n-1)/99;
let pCount = 5 + Math.floor(n/8);
let eCount = Math.min(6, 1 + Math.floor(n/12));
let sCount = Math.min(8, 3 + Math.floor(n/10));
let theme = Math.floor((n%5));
for(let i=0; i<pCount; i++) {
let x = 80 + (i*(W-160)/pCount) + (Math.sin(n*i)*35);
x = Math.min(Math.max(x,60), W-110);
let y = GROUND - 55 - (i*10) - (Math.sin(n+i)*18);
y = Math.min(Math.max(y, GROUND-190), GROUND-45);
let w = 55 + (Math.sin(n+i)*15);
w = Math.min(Math.max(w,45), 90);
let 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, y, w, h:18, type:'normal', range:60, speed:1, dir:1, startX:x });
continue;
}
plat.push({ x, y, w, h:18, type });
}
for(let i=0; i<sCount; i++) {
let idx = 1 + (i%(plat.length-1));
let pl = plat[idx];
if(pl && pl.type!=='spike') {
starList.push({ x: pl.x + pl.w/2 - 7, y: pl.y - 18, collected: false });
}
}
for(let i=0; i<eCount; i++) {
let idx = 1 + (i%(plat.length-2));
let pl = plat[idx];
if(pl && pl.type!=='spike' && pl.type!=='goal') {
enemyList.push({
x: pl.x+15, y: pl.y-25, w:22, h:22,
dir: i%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 };
}
let 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; }
let 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();
pokiGameplayStart();
}
// ========== UI ==========
function updateUI() {
livesSpan.textContent = lives;
levelSpan.textContent = level;
scoreSpan.textContent = score;
let total = stars.length || 0;
starsSpan.textContent = starsGot + '/' + total;
}
function buildGrid() {
levelGridContainer.innerHTML = '';
let div = document.createElement('div');
div.className = 'grid';
for(let i=1; i<=100; i++) {
let btn = document.createElement('button');
btn.textContent = i;
if(i <= maxUnlocked) {
if(completed.includes(i)) 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() {
pokiGameplayStop();
hideAll();
mainMenu.classList.add('active');
running = false;
buildGrid();
isPaused = true;
}
function showGameOver() {
pokiGameplayStop();
hideAll();
finalScoreSpan.textContent = score;
gameOverOverlay.classList.add('active');
running = false;
isPaused = true;
}
function showLevelComplete(msg) {
pokiGameplayStop();
hideAll();
completeMsgSpan.innerHTML = msg;
levelCompleteOverlay.classList.add('active');
running = false;
isPaused = true;
// Publicité Poki après un niveau terminé
pokiCommercialBreak(function() {});
}
function showFinalWin() {
pokiGameplayStop();
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();
}
// ========== BOUTON MENU EN JEU ==========
inGameMenuBtn.addEventListener('click', function() {
if(confirm('Revenir au menu principal ? La progression actuelle sera perdue.')) {
goMenu();
}
});
// ========== PHYSIQUE ==========
function applyPhysics() {
for(let w of wind) {
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(let m of moving) {
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(let plat of platforms) {
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(let t of tele) {
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(let s of stars) {
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');
}
}
}
function updateEnemies() {
for(let e of enemies) {
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(enemies.indexOf(e), 1);
p.vy = JUMP * 0.6;
score += 100;
updateUI();
addParticles(e.x+e.w/2, e.y+e.h/2, '#ff6666', 15);
playSound('collect');
} 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;
let bonus = starsGot * 50 + 200;
score += bonus;
if(!completed.includes(level)) completed.push(level);
if(level === maxUnlocked && level < 100) maxUnlocked = level + 1;
if(score > highScore) highScore = score;
save();
playSound('levelComplete');
let msg = '+' + bonus + ' pts ⭐ ' + starsGot + '/' + stars.length;
showLevelComplete(msg);
}
// ========== SON ==========
let actx = null;
function playSound(type) {
try {
if(!actx) actx = new (window.AudioContext || window.webkitAudioContext)();
let osc = actx.createOscillator();
let gain = actx.createGain();
osc.connect(gain);
gain.connect(actx.destination);
let 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(let i=0; i<count; i++) {
particles.push({
x, y,
vx: (Math.random()-0.5)*4,
vy: (Math.random()-0.5)*3-1,
life: 0.7,
color,
size: Math.random()*3+1.5
});
}
}
function updateParticles() {
for(let i=0; i<particles.length; i++) {
let 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() {
let 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(let 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(let plat of platforms) {
ctx.shadowBlur = 3;
if(plat.type === 'spike') {
ctx.fillStyle = '#aa4444';
ctx.fillRect(plat.x, plat.y, plat.w, plat.h);
for(let 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(let i=0;i<4;i++) ctx.fillRect(plat.x+8+i*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 {
let 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(let m of moving) {
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(let t of tele) {
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(let s of stars) {
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(let e of enemies) {
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';
let 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(let pt of particles) {
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) {
let 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) {
let 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 initPokiAndStart() {
// Cette fonction est appelée après l'init du SDK
load();
goMenu();
}
// Redéfinition de startGame pour l'appel externe
window.startGame = function() {
initPokiAndStart();
};
// Si le SDK n'est pas chargé, on lance quand même
if (typeof PokiSDK === 'undefined') {
console.log("⚠️ Poki SDK non chargé, lancement direct");
initPokiAndStart();
}
// Événements
playBtn.addEventListener('click', startGame);
retryBtn.addEventListener('click', retry);
menuBtnRetry.addEventListener('click', goMenu);
nextLevelBtn.addEventListener('click', nextLevel);
menuBtnComplete.addEventListener('click', goMenu);
winMenuBtn.addEventListener('click', goMenu);
levelsBtn.addEventListener('click', function() {
if(levelGridContainer.classList.contains('hidden')) {
buildGrid();
} else {
levelGridContainer.classList.add('hidden');
}
});
window.addEventListener('keydown', keyDown);
window.addEventListener('keyup', keyUp);
// Lancer la boucle
loop();
// ========== GESTION DE LA VISIBILITÉ (POKI) ==========
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// Le joueur a quitté l'onglet
if (running && !isPaused) {
pokiGameplayStop();
isPaused = true;
}
} else {
// Le joueur est revenu
if (running && isPaused && !document.querySelector('.overlay.active')) {
pokiGameplayStart();
isPaused = false;
}
}
});
})();
</script>
</body>
</html>Game Source: Bounce 100
Creator: NovaWizard65
Libraries: none
Complexity: complex (1174 lines, 41.8 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" to link back to the original. Then publish at arcadelab.ai/publish.