1 <!doctype html>2 <html lang="es" class="h-full">3 <head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>Bouncy Ball Platformer</title>7 <script src="https://cdn.tailwindcss.com"></script>8 <script src="/_sdk/element_sdk.js"></script>9 <style>10 body {11 box-sizing: border-box;12 }13 14 @import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&display=swap');15 16 * {17 font-family: 'Fredoka', sans-serif;18 }19 20 .game-canvas {21 image-rendering: pixelated;22 }23 24 @keyframes float {25 0%, 100% { transform: translateY(0); }26 50% { transform: translateY(-10px); }27 }28 29 .floating {30 animation: float 2s ease-in-out infinite;31 }32 33 @keyframes pulse {34 0%, 100% { transform: scale(1); }35 50% { transform: scale(1.1); }36 }37 38 .pulse {39 animation: pulse 1s ease-in-out infinite;40 }41 42 @keyframes shake {43 0%, 100% { transform: translateX(0); }44 25% { transform: translateX(-5px); }45 75% { transform: translateX(5px); }46 }47 48 .shake {49 animation: shake 0.3s ease-in-out;50 }51 </style>52 <style>@view-transition { navigation: auto; }</style>53 <script src="/_sdk/data_sdk.js" type="text/javascript"></script>54 </head>55 <body class="h-full overflow-hidden bg-gradient-to-b from-indigo-900 via-purple-900 to-slate-900">56 <div id="app" class="h-full w-full flex flex-col"><!-- Header -->57 <div class="flex justify-between items-center p-4 bg-black/30 backdrop-blur-sm">58 <h1 id="game-title" class="text-2xl font-bold text-white drop-shadow-lg">🎮 Bouncy Ball Platformer</h1>59 <div class="flex items-center gap-6">60 <div class="flex items-center gap-2"><span class="text-yellow-400 text-xl">⭐</span> <span id="score-display" class="text-white font-bold text-xl">0</span>61 </div>62 <div id="lives-display" class="flex gap-1"><span class="text-2xl">❤️</span> <span class="text-2xl">❤️</span> <span class="text-2xl">❤️</span>63 </div>64 </div>65 </div><!-- Game Container -->66 <div id="game-container" class="flex-1 relative overflow-hidden">67 <canvas id="game-canvas" class="game-canvas w-full h-full"></canvas><!-- Start Screen -->68 <div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-black/60 backdrop-blur-sm">69 <div class="text-center">70 <div class="text-8xl mb-4 floating">71 🔴72 </div>73 <h2 class="text-4xl font-bold text-white mb-4 drop-shadow-lg">¡Bouncy Ball!</h2>74 <p class="text-xl text-purple-200 mb-8">Salta entre plataformas y recoge estrellas</p>75 <div class="bg-white/10 rounded-xl p-6 mb-8 text-left">76 <p class="text-white mb-2">🎮 <span class="text-purple-200">Controles:</span></p>77 <p class="text-purple-300 text-sm mb-1">← → o A/D: Mover</p>78 <p class="text-purple-300 text-sm mb-1">↑ o W o Espacio: Saltar</p>79 <p class="text-purple-300 text-sm">⚠️ ¡Cuidado con los agujeros!</p>80 </div><button id="start-btn" class="px-8 py-4 bg-gradient-to-r from-pink-500 to-purple-600 text-white font-bold text-xl rounded-full hover:from-pink-600 hover:to-purple-700 transition-all transform hover:scale-105 shadow-lg"> ¡Jugar! </button>81 </div>82 </div><!-- Game Over Screen -->83 <div id="gameover-screen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black/70 backdrop-blur-sm">84 <div class="text-center">85 <div class="text-6xl mb-4">86 💀87 </div>88 <h2 class="text-4xl font-bold text-red-400 mb-4">¡Game Over!</h2>89 <p class="text-xl text-white mb-2">Puntuación final:</p>90 <p id="final-score" class="text-5xl font-bold text-yellow-400 mb-8">0</p><button id="restart-btn" class="px-8 py-4 bg-gradient-to-r from-green-500 to-emerald-600 text-white font-bold text-xl rounded-full hover:from-green-600 hover:to-emerald-700 transition-all transform hover:scale-105 shadow-lg"> Reintentar </button>91 </div>92 </div><!-- Win Screen -->93 <div id="win-screen" class="absolute inset-0 hidden flex-col items-center justify-center bg-black/60 backdrop-blur-sm">94 <div class="text-center">95 <div class="text-6xl mb-4 pulse">96 🏆97 </div>98 <h2 class="text-4xl font-bold text-yellow-400 mb-4">¡Nivel Completado!</h2>99 <p class="text-xl text-white mb-2">Puntuación:</p>100 <p id="win-score" class="text-5xl font-bold text-yellow-400 mb-8">0</p><button id="next-btn" class="px-8 py-4 bg-gradient-to-r from-yellow-500 to-orange-600 text-white font-bold text-xl rounded-full hover:from-yellow-600 hover:to-orange-700 transition-all transform hover:scale-105 shadow-lg"> Siguiente Nivel </button>101 </div>102 </div>103 </div><!-- Mobile Controls -->104 <div id="mobile-controls" class="md:hidden flex justify-between items-center p-4 bg-black/30">105 <div class="flex gap-2"><button id="left-btn" class="w-16 h-16 bg-white/20 rounded-full text-3xl active:bg-white/40 transition-colors">←</button> <button id="right-btn" class="w-16 h-16 bg-white/20 rounded-full text-3xl active:bg-white/40 transition-colors">→</button>106 </div><button id="jump-btn" class="w-20 h-20 bg-gradient-to-r from-pink-500 to-purple-600 rounded-full text-3xl active:from-pink-600 active:to-purple-700 transition-colors shadow-lg">↑</button>107 </div>108 </div>109 <script>110 const defaultConfig = {111 game_title: "🎮 Bouncy Ball Platformer"112 };113 114 let config = { ...defaultConfig };115 116 // Game State117 const game = {118 canvas: null,119 ctx: null,120 width: 0,121 height: 0,122 running: false,123 score: 0,124 lives: 3,125 level: 1,126 cameraX: 0,127 levelWidth: 3000128 };129 130 // Player (Elastic Ball)131 const player = {132 x: 100,133 y: 0,134 width: 40,135 height: 40,136 baseWidth: 40,137 baseHeight: 40,138 vx: 0,139 vy: 0,140 speed: 5,141 jumpForce: -15,142 gravity: 0.6,143 grounded: false,144 scaleX: 1,145 scaleY: 1,146 targetScaleX: 1,147 targetScaleY: 1,148 color: '#ff4757',149 eyeOffset: 0150 };151 152 // Input153 const keys = {154 left: false,155 right: false,156 jump: false157 };158 159 // Level elements160 let platforms = [];161 let movingPlatforms = [];162 let stars = [];163 let particles = [];164 let goal = null;165 166 // Initialize game167 function init() {168 game.canvas = document.getElementById('game-canvas');169 game.ctx = game.canvas.getContext('2d');170 resizeCanvas();171 window.addEventListener('resize', resizeCanvas);172 setupControls();173 generateLevel();174 }175 176 function resizeCanvas() {177 const container = document.getElementById('game-container');178 game.canvas.width = container.clientWidth;179 game.canvas.height = container.clientHeight;180 game.width = game.canvas.width;181 game.height = game.canvas.height;182 }183 184 function setupControls() {185 // Keyboard186 document.addEventListener('keydown', (e) => {187 if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;188 if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;189 if ((e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W' || e.key === ' ') && player.grounded) {190 keys.jump = true;191 }192 });193 194 document.addEventListener('keyup', (e) => {195 if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;196 if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;197 if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W' || e.key === ' ') keys.jump = false;198 });199 200 // Mobile201 const leftBtn = document.getElementById('left-btn');202 const rightBtn = document.getElementById('right-btn');203 const jumpBtn = document.getElementById('jump-btn');204 205 leftBtn.addEventListener('touchstart', (e) => { e.preventDefault(); keys.left = true; });206 leftBtn.addEventListener('touchend', () => keys.left = false);207 rightBtn.addEventListener('touchstart', (e) => { e.preventDefault(); keys.right = true; });208 rightBtn.addEventListener('touchend', () => keys.right = false);209 jumpBtn.addEventListener('touchstart', (e) => { e.preventDefault(); if (player.grounded) keys.jump = true; });210 jumpBtn.addEventListener('touchend', () => keys.jump = false);211 212 // Buttons213 document.getElementById('start-btn').addEventListener('click', startGame);214 document.getElementById('restart-btn').addEventListener('click', restartGame);215 document.getElementById('next-btn').addEventListener('click', nextLevel);216 }217 218 function generateLevel() {219 platforms = [];220 movingPlatforms = [];221 stars = [];222 223 const groundY = game.height - 60;224 game.levelWidth = 2500 + game.level * 500;225 226 // Generate ground with gaps227 let x = 0;228 while (x < game.levelWidth) {229 const isGap = x > 400 && Math.random() < 0.15 + game.level * 0.02;230 231 if (isGap) {232 const gapWidth = 100 + Math.random() * 80;233 x += gapWidth;234 } else {235 const platformWidth = 150 + Math.random() * 200;236 platforms.push({237 x: x,238 y: groundY,239 width: platformWidth,240 height: 40,241 type: 'ground',242 color: '#4a5568'243 });244 x += platformWidth;245 }246 }247 248 // Floating platforms249 const numPlatforms = 15 + game.level * 5;250 for (let i = 0; i < numPlatforms; i++) {251 const px = 300 + Math.random() * (game.levelWidth - 500);252 const py = groundY - 100 - Math.random() * 250;253 254 // Some platforms are moving255 if (Math.random() < 0.2 + game.level * 0.05) {256 movingPlatforms.push({257 x: px,258 y: py,259 width: 100 + Math.random() * 60,260 height: 25,261 startX: px,262 startY: py,263 moveX: Math.random() > 0.5 ? 100 + Math.random() * 100 : 0,264 moveY: Math.random() > 0.5 ? 50 + Math.random() * 50 : 0,265 speed: 0.5 + Math.random() * 1,266 phase: Math.random() * Math.PI * 2,267 color: '#f59e0b'268 });269 } else {270 platforms.push({271 x: px,272 y: py,273 width: 80 + Math.random() * 80,274 height: 25,275 type: 'floating',276 color: '#6366f1'277 });278 }279 }280 281 // Stars282 const numStars = 20 + game.level * 5;283 for (let i = 0; i < numStars; i++) {284 stars.push({285 x: 200 + Math.random() * (game.levelWidth - 400),286 y: 100 + Math.random() * (groundY - 200),287 collected: false,288 angle: 0289 });290 }291 292 // Goal293 goal = {294 x: game.levelWidth - 150,295 y: groundY - 100,296 width: 60,297 height: 80298 };299 300 // End platform for goal301 platforms.push({302 x: game.levelWidth - 200,303 y: groundY,304 width: 200,305 height: 40,306 type: 'ground',307 color: '#10b981'308 });309 }310 311 function startGame() {312 document.getElementById('start-screen').classList.add('hidden');313 game.running = true;314 game.score = 0;315 game.lives = 3;316 game.level = 1;317 resetPlayer();318 generateLevel();319 updateUI();320 requestAnimationFrame(gameLoop);321 }322 323 function restartGame() {324 document.getElementById('gameover-screen').classList.add('hidden');325 document.getElementById('gameover-screen').style.display = 'none';326 game.running = true;327 game.score = 0;328 game.lives = 3;329 game.level = 1;330 resetPlayer();331 generateLevel();332 updateUI();333 requestAnimationFrame(gameLoop);334 }335 336 function nextLevel() {337 document.getElementById('win-screen').classList.add('hidden');338 document.getElementById('win-screen').style.display = 'none';339 game.level++;340 game.running = true;341 resetPlayer();342 generateLevel();343 requestAnimationFrame(gameLoop);344 }345 346 function resetPlayer() {347 player.x = 100;348 player.y = game.height - 200;349 player.vx = 0;350 player.vy = 0;351 player.grounded = false;352 player.scaleX = 1;353 player.scaleY = 1;354 game.cameraX = 0;355 }356 357 function updateUI() {358 document.getElementById('score-display').textContent = game.score;359 360 const livesDisplay = document.getElementById('lives-display');361 livesDisplay.innerHTML = '';362 for (let i = 0; i < 3; i++) {363 const heart = document.createElement('span');364 heart.className = 'text-2xl';365 heart.textContent = i < game.lives ? '❤️' : '🖤';366 livesDisplay.appendChild(heart);367 }368 }369 370 function loseLife() {371 game.lives--;372 updateUI();373 374 if (game.lives <= 0) {375 gameOver();376 } else {377 // Respawn with brief invincibility visual378 resetPlayer();379 createParticles(player.x, player.y, '#ff4757', 10);380 }381 }382 383 function gameOver() {384 game.running = false;385 document.getElementById('final-score').textContent = game.score;386 document.getElementById('gameover-screen').style.display = 'flex';387 document.getElementById('gameover-screen').classList.remove('hidden');388 }389 390 function winLevel() {391 game.running = false;392 document.getElementById('win-score').textContent = game.score;393 document.getElementById('win-screen').style.display = 'flex';394 document.getElementById('win-screen').classList.remove('hidden');395 }396 397 function createParticles(x, y, color, count) {398 for (let i = 0; i < count; i++) {399 particles.push({400 x: x,401 y: y,402 vx: (Math.random() - 0.5) * 8,403 vy: (Math.random() - 0.5) * 8 - 3,404 life: 1,405 color: color,406 size: 3 + Math.random() * 5407 });408 }409 }410 411 function update() {412 // Input413 if (keys.left) {414 player.vx = -player.speed;415 player.eyeOffset = -3;416 } else if (keys.right) {417 player.vx = player.speed;418 player.eyeOffset = 3;419 } else {420 player.vx *= 0.8;421 player.eyeOffset *= 0.9;422 }423 424 if (keys.jump && player.grounded) {425 player.vy = player.jumpForce;426 player.grounded = false;427 keys.jump = false;428 createParticles(player.x + player.width/2, player.y + player.height, '#ffffff', 5);429 }430 431 // Gravity432 player.vy += player.gravity;433 434 // Elastic deformation based on velocity435 if (player.vy < -5) {436 // Jumping up - stretch vertically437 player.targetScaleX = 0.85;438 player.targetScaleY = 1.2;439 } else if (player.vy > 5) {440 // Falling - stretch vertically slightly441 player.targetScaleX = 0.9;442 player.targetScaleY = 1.15;443 } else if (Math.abs(player.vx) > 3) {444 // Moving horizontally - squish slightly445 player.targetScaleX = 1.1;446 player.targetScaleY = 0.9;447 } else {448 // Idle - return to normal449 player.targetScaleX = 1;450 player.targetScaleY = 1;451 }452 453 // Smooth scale transition454 player.scaleX += (player.targetScaleX - player.scaleX) * 0.2;455 player.scaleY += (player.targetScaleY - player.scaleY) * 0.2;456 457 // Update moving platforms458 movingPlatforms.forEach(p => {459 p.phase += p.speed * 0.02;460 if (p.moveX > 0) {461 p.x = p.startX + Math.sin(p.phase) * p.moveX;462 }463 if (p.moveY > 0) {464 p.y = p.startY + Math.sin(p.phase) * p.moveY;465 }466 });467 468 // Move player469 player.x += player.vx;470 player.y += player.vy;471 472 // Keep player in bounds (left side)473 if (player.x < 0) player.x = 0;474 if (player.x > game.levelWidth - player.width) player.x = game.levelWidth - player.width;475 476 // Platform collision477 player.grounded = false;478 const allPlatforms = [...platforms, ...movingPlatforms];479 480 for (const plat of allPlatforms) {481 if (rectCollision(482 player.x, player.y, player.width, player.height,483 plat.x, plat.y, plat.width, plat.height484 )) {485 // Landing on top486 if (player.vy > 0 && player.y + player.height - player.vy <= plat.y + 10) {487 player.y = plat.y - player.height;488 player.vy = 0;489 player.grounded = true;490 491 // Landing squish effect492 if (player.targetScaleY > 1) {493 player.scaleX = 1.3;494 player.scaleY = 0.7;495 createParticles(player.x + player.width/2, player.y + player.height, '#ffffff', 3);496 }497 }498 // Side collision499 else if (player.vx > 0 && player.x + player.width - player.vx <= plat.x) {500 player.x = plat.x - player.width;501 player.vx = 0;502 player.scaleX = 0.8;503 player.scaleY = 1.1;504 }505 else if (player.vx < 0 && player.x - player.vx >= plat.x + plat.width) {506 player.x = plat.x + plat.width;507 player.vx = 0;508 player.scaleX = 0.8;509 player.scaleY = 1.1;510 }511 // Bottom collision512 else if (player.vy < 0 && player.y - player.vy >= plat.y + plat.height) {513 player.y = plat.y + plat.height;514 player.vy = 0;515 }516 }517 }518 519 // Fall into pit520 if (player.y > game.height + 100) {521 loseLife();522 }523 524 // Collect stars525 stars.forEach(star => {526 if (!star.collected) {527 star.angle += 0.05;528 const dist = Math.hypot(529 (player.x + player.width/2) - star.x,530 (player.y + player.height/2) - star.y531 );532 if (dist < 35) {533 star.collected = true;534 game.score += 100;535 updateUI();536 createParticles(star.x, star.y, '#ffd700', 8);537 }538 }539 });540 541 // Check goal542 if (goal && rectCollision(543 player.x, player.y, player.width, player.height,544 goal.x, goal.y, goal.width, goal.height545 )) {546 winLevel();547 }548 549 // Update particles550 particles = particles.filter(p => {551 p.x += p.vx;552 p.y += p.vy;553 p.vy += 0.2;554 p.life -= 0.02;555 return p.life > 0;556 });557 558 // Camera follow559 const targetCameraX = player.x - game.width / 3;560 game.cameraX += (targetCameraX - game.cameraX) * 0.1;561 game.cameraX = Math.max(0, Math.min(game.cameraX, game.levelWidth - game.width));562 }563 564 function rectCollision(x1, y1, w1, h1, x2, y2, w2, h2) {565 return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2;566 }567 568 function draw() {569 const ctx = game.ctx;570 ctx.clearRect(0, 0, game.width, game.height);571 572 ctx.save();573 ctx.translate(-game.cameraX, 0);574 575 // Background elements (parallax)576 drawBackground();577 578 // Draw platforms579 platforms.forEach(plat => {580 // Platform shadow581 ctx.fillStyle = 'rgba(0,0,0,0.3)';582 roundRect(ctx, plat.x + 5, plat.y + 5, plat.width, plat.height, 8);583 ctx.fill();584 585 // Platform body586 const gradient = ctx.createLinearGradient(plat.x, plat.y, plat.x, plat.y + plat.height);587 if (plat.type === 'ground') {588 gradient.addColorStop(0, '#64748b');589 gradient.addColorStop(1, '#334155');590 } else {591 gradient.addColorStop(0, '#818cf8');592 gradient.addColorStop(1, '#4f46e5');593 }594 ctx.fillStyle = gradient;595 roundRect(ctx, plat.x, plat.y, plat.width, plat.height, 8);596 ctx.fill();597 598 // Platform highlight599 ctx.fillStyle = 'rgba(255,255,255,0.2)';600 roundRect(ctx, plat.x + 4, plat.y + 4, plat.width - 8, plat.height / 3, 4);601 ctx.fill();602 });603 604 // Draw moving platforms605 movingPlatforms.forEach(plat => {606 ctx.fillStyle = 'rgba(0,0,0,0.3)';607 roundRect(ctx, plat.x + 5, plat.y + 5, plat.width, plat.height, 8);608 ctx.fill();609 610 const gradient = ctx.createLinearGradient(plat.x, plat.y, plat.x, plat.y + plat.height);611 gradient.addColorStop(0, '#fbbf24');612 gradient.addColorStop(1, '#d97706');613 ctx.fillStyle = gradient;614 roundRect(ctx, plat.x, plat.y, plat.width, plat.height, 8);615 ctx.fill();616 617 // Arrow indicators618 ctx.fillStyle = 'rgba(255,255,255,0.5)';619 ctx.font = '16px Arial';620 ctx.textAlign = 'center';621 if (plat.moveX > 0) ctx.fillText('↔', plat.x + plat.width/2, plat.y + plat.height/2 + 5);622 if (plat.moveY > 0) ctx.fillText('↕', plat.x + plat.width/2, plat.y + plat.height/2 + 5);623 });624 625 // Draw stars626 stars.forEach(star => {627 if (!star.collected) {628 ctx.save();629 ctx.translate(star.x, star.y);630 ctx.rotate(star.angle);631 632 // Glow633 ctx.shadowColor = '#ffd700';634 ctx.shadowBlur = 15;635 636 ctx.font = '28px Arial';637 ctx.textAlign = 'center';638 ctx.textBaseline = 'middle';639 ctx.fillText('⭐', 0, 0);640 ctx.restore();641 }642 });643 644 // Draw goal645 if (goal) {646 ctx.save();647 ctx.translate(goal.x + goal.width/2, goal.y + goal.height/2);648 649 // Glow effect650 ctx.shadowColor = '#10b981';651 ctx.shadowBlur = 20;652 653 ctx.font = '50px Arial';654 ctx.textAlign = 'center';655 ctx.textBaseline = 'middle';656 ctx.fillText('🚩', 0, 0);657 ctx.restore();658 }659 660 // Draw player (elastic ball)661 drawPlayer();662 663 // Draw particles664 particles.forEach(p => {665 ctx.globalAlpha = p.life;666 ctx.fillStyle = p.color;667 ctx.beginPath();668 ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);669 ctx.fill();670 });671 ctx.globalAlpha = 1;672 673 ctx.restore();674 675 // UI overlay676 drawUI();677 }678 679 function drawBackground() {680 const ctx = game.ctx;681 682 // Distant mountains (parallax)683 for (let i = 0; i < game.levelWidth / 200; i++) {684 const x = i * 200 + game.cameraX * 0.3;685 ctx.fillStyle = 'rgba(99, 102, 241, 0.2)';686 ctx.beginPath();687 ctx.moveTo(x, game.height);688 ctx.lineTo(x + 100, game.height - 150 - Math.sin(i) * 50);689 ctx.lineTo(x + 200, game.height);690 ctx.closePath();691 ctx.fill();692 }693 694 // Stars in sky695 ctx.fillStyle = 'rgba(255,255,255,0.3)';696 for (let i = 0; i < 50; i++) {697 const x = (i * 137) % game.levelWidth;698 const y = (i * 89) % (game.height * 0.5);699 ctx.beginPath();700 ctx.arc(x, y, 1 + (i % 2), 0, Math.PI * 2);701 ctx.fill();702 }703 }704 705 function drawPlayer() {706 const ctx = game.ctx;707 const centerX = player.x + player.width / 2;708 const centerY = player.y + player.height / 2;709 710 ctx.save();711 ctx.translate(centerX, centerY);712 ctx.scale(player.scaleX, player.scaleY);713 714 // Shadow715 ctx.fillStyle = 'rgba(0,0,0,0.3)';716 ctx.beginPath();717 ctx.ellipse(3, player.baseHeight/2 - 5, player.baseWidth/2, 8, 0, 0, Math.PI * 2);718 ctx.fill();719 720 // Main body gradient721 const gradient = ctx.createRadialGradient(-5, -5, 0, 0, 0, player.baseWidth/2);722 gradient.addColorStop(0, '#ff6b7a');723 gradient.addColorStop(0.7, '#ff4757');724 gradient.addColorStop(1, '#c0392b');725 726 ctx.fillStyle = gradient;727 ctx.beginPath();728 ctx.ellipse(0, 0, player.baseWidth/2, player.baseHeight/2, 0, 0, Math.PI * 2);729 ctx.fill();730 731 // Highlight732 ctx.fillStyle = 'rgba(255,255,255,0.4)';733 ctx.beginPath();734 ctx.ellipse(-8, -8, 8, 6, -0.5, 0, Math.PI * 2);735 ctx.fill();736 737 // Eyes738 const eyeY = -3;739 const eyeSpacing = 8;740 741 // Eye whites742 ctx.fillStyle = 'white';743 ctx.beginPath();744 ctx.ellipse(-eyeSpacing + player.eyeOffset, eyeY, 7, 8, 0, 0, Math.PI * 2);745 ctx.fill();746 ctx.beginPath();747 ctx.ellipse(eyeSpacing + player.eyeOffset, eyeY, 7, 8, 0, 0, Math.PI * 2);748 ctx.fill();749 750 // Pupils751 ctx.fillStyle = '#1a1a2e';752 const pupilOffset = player.vx * 0.3;753 ctx.beginPath();754 ctx.arc(-eyeSpacing + player.eyeOffset + pupilOffset, eyeY + 1, 4, 0, Math.PI * 2);755 ctx.fill();756 ctx.beginPath();757 ctx.arc(eyeSpacing + player.eyeOffset + pupilOffset, eyeY + 1, 4, 0, Math.PI * 2);758 ctx.fill();759 760 // Pupil highlights761 ctx.fillStyle = 'white';762 ctx.beginPath();763 ctx.arc(-eyeSpacing + player.eyeOffset + pupilOffset - 1, eyeY - 1, 1.5, 0, Math.PI * 2);764 ctx.fill();765 ctx.beginPath();766 ctx.arc(eyeSpacing + player.eyeOffset + pupilOffset - 1, eyeY - 1, 1.5, 0, Math.PI * 2);767 ctx.fill();768 769 // Smile770 ctx.strokeStyle = '#8b0000';771 ctx.lineWidth = 2;772 ctx.lineCap = 'round';773 ctx.beginPath();774 ctx.arc(0, 5, 8, 0.2, Math.PI - 0.2);775 ctx.stroke();776 777 ctx.restore();778 }779 780 function drawUI() {781 const ctx = game.ctx;782 783 // Level indicator784 ctx.fillStyle = 'rgba(255,255,255,0.8)';785 ctx.font = 'bold 16px Fredoka';786 ctx.textAlign = 'right';787 ctx.fillText(`Nivel ${game.level}`, game.width - 20, 30);788 789 // Progress bar790 const progress = Math.min(1, (player.x) / (game.levelWidth - 200));791 ctx.fillStyle = 'rgba(0,0,0,0.3)';792 roundRect(ctx, game.width/2 - 100, 10, 200, 10, 5);793 ctx.fill();794 795 ctx.fillStyle = '#10b981';796 roundRect(ctx, game.width/2 - 100, 10, 200 * progress, 10, 5);797 ctx.fill();798 799 // Player indicator on progress bar800 ctx.fillStyle = '#ff4757';801 ctx.beginPath();802 ctx.arc(game.width/2 - 100 + 200 * progress, 15, 6, 0, Math.PI * 2);803 ctx.fill();804 }805 806 function roundRect(ctx, x, y, width, height, radius) {807 ctx.beginPath();808 ctx.moveTo(x + radius, y);809 ctx.lineTo(x + width - radius, y);810 ctx.quadraticCurveTo(x + width, y, x + width, y + radius);811 ctx.lineTo(x + width, y + height - radius);812 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);813 ctx.lineTo(x + radius, y + height);814 ctx.quadraticCurveTo(x, y + height, x, y + height - radius);815 ctx.lineTo(x, y + radius);816 ctx.quadraticCurveTo(x, y, x + radius, y);817 ctx.closePath();818 }819 820 function gameLoop() {821 if (!game.running) return;822 823 update();824 draw();825 requestAnimationFrame(gameLoop);826 }827 828 // Element SDK Integration829 async function onConfigChange(newConfig) {830 config = { ...defaultConfig, ...newConfig };831 document.getElementById('game-title').textContent = config.game_title || defaultConfig.game_title;832 }833 834 function mapToCapabilities(config) {835 return {836 recolorables: [],837 borderables: [],838 fontEditable: undefined,839 fontSizeable: undefined840 };841 }842 843 function mapToEditPanelValues(config) {844 return new Map([845 ["game_title", config.game_title || defaultConfig.game_title]846 ]);847 }848 849 // Initialize850 window.elementSdk.init({851 defaultConfig,852 onConfigChange,853 mapToCapabilities,854 mapToEditPanelValues855 });856 857 init();858 </script>859 <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9c687284f3bda431',t:'MTc2OTg1NDUyMS4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>860 </html>Enlace
El enlace para compartir es:

