🎮ArcadeLab

El mundo de mi princesita 🌻

by LunarFlare61
1725 lines28.4 KB
▶ Play


<!DOCTYPE html>

<html lang="es">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>El mundo de mi princesita 🌻</title>

<style>

@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Nunito:wght@600;800;900&display=swap');

:root {

  --pink:#ff76b7;

  --dark:#251b49;

  --cream:#fff1cb;

}

* { box-sizing:border-box; }

body {

  margin:0;

  min-height:100vh;

  color:white;

  background:#211942;

  font-family:'Nunito',sans-serif;

  text-align:center;

}

header { padding:15px 8px 5px; }

h1 {

  color:#ffb7df;

  text-shadow:3px 3px #693d9a,5px 5px #17152e;

  font-size:clamp(20px,4vw,36px);

  margin:8px 0;

}

.subtitle { color:#fff1ba; font-weight:900; }

.game-wrap {

  width:min(100% - 16px,960px);

  margin:12px auto;

  padding:8px;

  border:4px solid #a78bfa;

  border-radius:14px;

  background:#302452;

  box-shadow:0 0 30px #b56cff40;

}

canvas {

  width:100%;

  display:block;

  image-rendering:pixelated;

  border-radius:6px;

  background:#70d5ff;

  touch-action:none;

}

.hud {

  display:flex;

  justify-content:space-around;

  flex-wrap:wrap;

  gap:9px;

  padding:12px 2px 5px;

  font-family:'Press Start 2P',monospace;

  font-size:10px;

  line-height:2;

}

.hud span { color:#ffe47b; }

.controls {

  display:flex;

  justify-content:center;

  flex-wrap:wrap;

  gap:10px;

  padding:10px 0 5px;

}

button {

  border:3px solid #8d3679;

  background:var(--pink);

  color:#35163b;

  border-radius:12px;

  padding:12px 15px;

  font-weight:900;

  font-size:14px;

  cursor:pointer;

  box-shadow:0 4px #6e2a71;

  touch-action:manipulation;

}

button:active { transform:translateY(3px);box-shadow:0 1px #6e2a71; }

button:focus-visible { outline:3px solid white; }

.secondary { background:#ffe89a; }

.instructions {

  max-width:850px;

  margin:12px auto 28px;

  padding:14px;

  color:#eee7ff;

  line-height:1.8;

}

.instructions b { color:#ffb9de; }

.small { font-size:12px; opacity:.85; }

</style>

</head>

<body>

<header>

  <h1>🌻 El mundo de mi princesita 🌻</h1>

  <div class="subtitle">El Reino de los Girasoles Perdidos</div>

  <p class="small">Una aventura creada con muchísimo cariño ♡</p>

</header>

<div class="game-wrap">

  <canvas id="game" width="960" height="540"

    aria-label="Videojuego de aventuras de la princesa"></canvas>

  <div class="hud">

    <div>NIVEL <span id="level">1/10</span></div>

    <div>🪙 <span id="coins">0</span></div>

    <div>🌻 <span id="flowers">0</span></div>

    <div>🎁 <span id="chests">0</span></div>

    <div>❤️ <span id="lives">3</span></div>

  </div>

  <div class="controls">

    <button id="left" aria-label="Mover a la izquierda">◀</button>

    <button id="right" aria-label="Mover a la derecha">▶</button>

    <button id="jump">⬆ SALTAR</button>

    <button id="attack">⚔ ATACAR</button>

    <button id="interact" class="secondary">♡ HABLAR</button>

    <button id="pause" class="secondary">Ⅱ PAUSA</button>

  </div>

</div>

<div class="instructions">

  <b>Cómo jugar</b><br>

  Computadora: A/D o flechas para moverte, espacio para saltar,

  X para atacar y E para interactuar.<br>

  Teléfono: usa los botones de abajo del juego.<br>

  Recoge girasoles, monedas y cofres. ¡Encuentra la salida y

  derrota al jefe para desbloquear el siguiente mundo!

  <p class="small">Pulsa los botones del menú para comenzar, leer la carta

  o visitar tu rincón tranquilo.</p>

</div>

<script>

(() => {

'use strict';

const canvas = document.getElementById('game');

const ctx = canvas.getContext('2d');

ctx.imageSmoothingEnabled = false;

const W = canvas.width, H = canvas.height;

const $ = id => document.getElementById(id);

const levels = [

  {name:'Pradera de los Primeros Girasoles',sky:'#71d9ff',ground:'#55bc54',boss:'Abejorro Gruñón',hp:3,flower:'🌻'},

  {name:'Las Islas de las Nubes',sky:'#bceaff',ground:'#86cfff',boss:'Rey Nube',hp:4,flower:'🌷'},

  {name:'Desierto de los Sueños',sky:'#ffca83',ground:'#c88b51',boss:'Escorpión de Arena',hp:5,flower:'🌻'},

  {name:'Bosque de los Conejitos',sky:'#a5efbd',ground:'#4baf69',boss:'Lobo de las Sombras',hp:5,flower:'🌼'},

  {name:'Cueva de los Cristales',sky:'#8d8be7',ground:'#6659a6',boss:'Dragón Cristalino',hp:6,flower:'💎'},

  {name:'Océano de las Estrellas',sky:'#66bfe8',ground:'#3979bd',boss:'Pulpo Real',hp:6,flower:'⭐'},

  {name:'Montañas de Algodón',sky:'#f5b8df',ground:'#bd89d8',boss:'Gigante de Caramelo',hp:7,flower:'🌸'},

  {name:'Jardín de la Luna',sky:'#313d79',ground:'#47639a',boss:'Guardián Nocturno',hp:7,flower:'🌙'},

  {name:'Castillo de los Recuerdos',sky:'#f6a8b9',ground:'#aa668f',boss:'Bruja del Olvido',hp:8,flower:'🌹'},

  {name:'Reino de los Girasoles',sky:'#ffcb76',ground:'#55b96b',boss:'Dragón de la Tristeza',hp:10,flower:'🌻'}

];

const phrases = [

 'Eres mucho más importante de lo que imaginas. ♡',

 'No tienes que tener un día perfecto para merecer cariño.',

 'Incluso las flores necesitan descansar antes de florecer.',

 'Tu sonrisa es bonita, pero no tienes que sonreír todo el tiempo.',

 'Un día difícil no define toda tu historia.',

 'No tienes que poder con todo tú solita.',

 'Está bien descansar. No tienes que ganarte el descanso.',

 'Ojalá pudieras verte con la ternura con la que te veo.',

 'Tu existencia hace que este pequeño mundo sea más bonito.',

 'No eres una carga por necesitar compañía.',

 'Mereces paciencia, respeto y un cariño sincero.',

 'Paso a paso también se llega a lugares preciosos.',

 'No tienes que compararte con ninguna flor. Todas florecen distinto.',

 'Puedes empezar de nuevo tantas veces como lo necesites.',

 'Tu valor no depende de cuánto hagas en un día.',

 'Hay días para correr y días para abrazar una almohada.',

 'Gracias por existir en este pequeño universo.',

 'No tienes que esconder siempre lo que sientes.',

 'Descansar también es una manera de cuidarte.',

 'Ojalá hoy encuentres una razón pequeñita para sonreír.',

 'Eres suficiente incluso cuando dudas de ti misma.',

 'Las cosas bonitas también pueden llegar despacito.',

 'No estás obligada a resolverlo todo hoy.',

 'Este jardín tiene un girasol guardado especialmente para ti.',

 'Que nunca te falten motivos para volver a intentarlo.',

 'Tu historia todavía tiene muchísimas páginas bonitas.',

 'Puedes sentirte triste y seguir siendo una persona maravillosa.',

 'No necesitas ser fuerte en cada momento.',

 'Que este rinconcito te recuerde que mereces tranquilidad.',

 'Un abrazo imaginario viaja hasta donde estés. ♡'

];

const jokes = [

 '¿Qué le dijo un girasol al sol? ¡Contigo sí que florezco!',

 '¿Qué hace una abeja en el gimnasio? ¡Zum-ba!',

 '¿Qué hace un conejo cuando está feliz? ¡Da saltitos de alegría!',

 '¿Cuál es el colmo de una flor? ¡Que su pareja la deje plantada!',

 '¿Por qué el libro estaba feliz? ¡Porque tenía un final bonito!',

 '¿Qué le dijo una nube a otra? ¡Nos vemos en el cielo!',

 '¿Qué hace una moneda en el jardín? ¡Busca cambio de ambiente!',

 '¿Qué dice un girasol al despertar? ¡Hoy voy a brillar!',

 '¿Qué animal siempre llega con regalos? ¡El oso, porque es un osito regalón!',

 '¿Qué le dijo el jardín a la princesa? ¡Aquí tienes raíces para descansar!',

 '¿Por qué el conejo llevó una maleta? ¡Porque iba de salto en salto!',

 '¿Qué hace una estrella en una fiesta? ¡Brilla en la pista!',

 '¿Cuál es la flor más divertida? ¡La que siempre está de buen humor!',

 '¿Qué le dijo el corazón al calendario? ¡Cada día cuenta contigo!',

 '¿Por qué la nube fue a la escuela? ¡Para mejorar su pronóstico!',

 '¿Qué hace un gato en el jardín? ¡Miau-ravillas!',

 '¿Qué le dijo una galleta a otra? ¡Nos vemos en las miguitas!',

 '¿Por qué el girasol nunca se pierde? ¡Porque sigue la luz!',

 '¿Qué hace un fantasma amable? ¡Da sustos de cariño!',

 '¿Qué dijo el sol cuando llegó tarde? ¡Perdón, tuve un eclipse de horario!',

 '¿Por qué el dragón no usa ventilador? ¡Porque él mismo echa aire caliente!',

 '¿Qué le dijo la luna a la princesa? ¡Te guardé un pedacito de noche bonita!',

 '¿Cómo se despide un pez? ¡Nada de tristezas!',

 '¿Qué hace una flor con sueño? ¡Se echa una siestita de pétalos!',

 '¿Cuál es el superpoder de una princesa? ¡Convertir un día común en aventura!'

];

const bossLines = [

 '¡Ni un paso más! ¡Este jardín es mío!',

 '¡A ver si puedes superar mi desafío!',

 '¡Mis nubes mágicas no te dejarán pasar!',

 '¡Mi arena cubrirá todos tus caminos!',

 '¡Este bosque necesita una nueva guardiana!',

 '¡Mis cristales son invencibles!',

 '¡Prepárate para las olas mágicas!',

 '¡Nadie supera al gigante de caramelo!',

 '¡La noche pondrá a prueba tu valentía!',

 '¡No permitiré que llegues al castillo!',

 '¡El último desafío comienza ahora!'

];

let state = 'menu';

let level = 0, coins = 0, flowers = 0, chests = 0, lives = 3;

let keys = {};

let lastTime = 0, elapsed = 0;

let dialog = null, dialogTimer = 0;

let collected = new Set();

let boss = null;

let particles = [];

let toast = '';

let toastTimer = 0;

let calmIndex = 0;

let jokeIndex = 0;

let victoryMessage = '';

let gameOver = false;

const player = {

 x:70,y:350,w:28,h:42,vx:0,vy:0,

 speed:240,jump:-470,onGround:false,

 invulnerable:0,face:1,attack:0

};

const platforms = [];

const items = [];

const enemies = [];

let exit = {x:890,y:385,w:42,h:95};

let cameraX = 0;

const worldWidth = 2100;

const gravity = 1150;

function platform(x,y,w,h=22) {

 platforms.push({x,y,w,h});

}

function item(type,x,y) {

 items.push({type,x,y,w:25,h:25,taken:false});

}

function buildLevel() {

 platforms.length=0; items.length=0; enemies.length=0;

 particles.length=0; collected.clear();

 cameraX=0;

 player.x=65;player.y=350;player.vx=0;player.vy=0;

 player.invulnerable=0;player.attack=0;

 boss=null;gameOver=false;

 // Suelo continuo con plataformas elevadas.

 platform(0,465,worldWidth,100);

 const spots = [

  [150,390,150],[355,325,125],[535,380,120],

  [710,300,155],[920,360,140],[1120,280,140],

  [1310,355,150],[1515,290,135],[1710,365,130]

 ];

 spots.forEach(([x,y,w],i)=>platform(x,y,w,22));

 for(let i=0;i<12;i++) {

   const x=130+i*150;

   item('coin',x,430-(i%3)*55);

 }

 for(let i=0;i<8;i++) {

   const x=240+i*225;

   item('flower',x,350-(i%2)*45);

 }

 item('chest',600,342);

 item('chest',1400,317);

 item('heart',1020,420);

 exit={x:1990,y:370,w:45,h:95};

}

function resetGame() {

 level=0;coins=0;flowers=0;chests=0;lives=3;

 state='play';dialog=null;toast='';

 buildLevel();updateHUD();

 showToast('¡Tu aventura comienza! 🌻');

}

function updateHUD() {

 $('level').textContent=(level+1)+'/10';

 $('coins').textContent=coins;

 $('flowers').textContent=flowers;

 $('chests').textContent=chests;

 $('lives').textContent=lives;

}

function showToast(text) {

 toast=text;toastTimer=3;

}

function say(text, duration=4) {

 dialog=text;dialogTimer=duration;

}

function rect(x,y,w,h,color) {

 ctx.fillStyle=color;ctx.fillRect(Math.round(x),Math.round(y),w,h);

}

function text(str,x,y,size=16,color='#fff',align='left') {

 ctx.font=`${size}px "Nunito", sans-serif`;

 ctx.textAlign=align;ctx.fillStyle=color;

 ctx.fillText(str,x,y);

}

function pixelText(str,x,y,size=12,color='#fff',align='left') {

 ctx.font=`${size}px "Press Start 2P", monospace`;

 ctx.textAlign=align;ctx.fillStyle=color;

 ctx.fillText(str,x,y);

}

function drawCloud(x,y,s=1) {

 ctx.fillStyle='#ffffffd9';

 ctx.beginPath();

 ctx.arc(x,y,19*s,0,Math.PI*2);

 ctx.arc(x+20*s,y-10*s,25*s,0,Math.PI*2);

 ctx.arc(x+45*s,y,19*s,0,Math.PI*2);

 ctx.fill();

}

function drawSun() {

 ctx.fillStyle='#ffd84b';

 ctx.beginPath();ctx.arc(845,72,31,0,Math.PI*2);ctx.fill();

 ctx.strokeStyle='#ffe66e';ctx.lineWidth=5;

 for(let i=0;i<8;i++){

  const a=i*Math.PI/4;

  ctx.beginPath();

  ctx.moveTo(845+38*Math.cos(a),72+38*Math.sin(a));

  ctx.lineTo(845+48*Math.cos(a),72+48*Math.sin(a));

  ctx.stroke();

 }

}

function drawBackground() {

 const lev=levels[level]||levels[0];

 ctx.fillStyle=lev.sky;ctx.fillRect(0,0,W,H);

 drawSun();

 for(let i=0;i<7;i++) {

  drawCloud(((i*183-cameraX*.15)%(W+180)+W+180)%(W+180)-50,

            75+(i%3)*60,.8+(i%2)*.2);

 }

 // Montañas pixeladas.

 for(let i=0;i<9;i++){

  let x=((i*180-cameraX*.28)%(W+200)+W+200)%(W+200)-80;

  ctx.fillStyle=level===7?'#3e477f':'#78c8a1';

  ctx.beginPath();ctx.moveTo(x,360);ctx.lineTo(x+85,175+(i%3)*30);

  ctx.lineTo(x+180,360);ctx.fill();

 }

 // Flores decorativas.

 for(let i=0;i<25;i++){

  let x=((i*53-cameraX*.6)%(W+50)+W+50)%(W+50);

  const y=451+(i%2)*5;

  ctx.fillStyle='#39904c';ctx.fillRect(x,y-13,3,13);

  ctx.fillStyle=i%2?'#ff8dcc':'#ffdf54';

  for(let a=0;a<5;a++){

   ctx.beginPath();

   ctx.arc(x+2+Math.cos(a*1.256)*5,y-17+Math.sin(a*1.256)*5,3,0,Math.PI*2);

   ctx.fill();

  }

 }

}

function drawPlatforms() {

 platforms.forEach(p=>{

  const x=p.x-cameraX;

  if(x+p.w<0||x>W)return;

  rect(x,p.y,p.w,p.h,'#9b583f');

  rect(x,p.y,p.w,8,levels[level].ground);

  rect(x,p.y+8,p.w,4,'#4c9e49');

  for(let xx=x+12;xx<x+p.w;xx+=29){

   rect(xx,p.y+16,5,5,'#c97c50');

   rect(xx+12,p.y+30,4,4,'#784b55');

  }

 });

}

function drawSunflower(x,y) {

 ctx.fillStyle='#3c9d48';ctx.fillRect(x-2,y+5,4,19);

 ctx.fillStyle='#ffcf35';

 for(let i=0;i<8;i++){

  let a=i*Math.PI/4;

  ctx.beginPath();

  ctx.arc(x+Math.cos(a)*9,y+Math.sin(a)*9,5,0,Math.PI*2);

  ctx.fill();

 }

 ctx.fillStyle='#754728';ctx.beginPath();

 ctx.arc(x,y,6,0,Math.PI*2);ctx.fill();

}

function drawItem(it) {

 if(it.taken)return;

 const x=it.x-cameraX,y=it.y;

 if(x<-40||x>W+40)return;

 if(it.type==='coin'){

  ctx.fillStyle='#ffdb43';ctx.beginPath();

  ctx.ellipse(x+12,y+12,9,12,0,0,Math.PI*2);ctx.fill();

  ctx.strokeStyle='#b98125';ctx.lineWidth=2;ctx.stroke();

  rect(x+10,y+5,4,14,'#fff0a1');

 } else if(it.type==='flower'){

  drawSunflower(x+12,y+10);

 } else if(it.type==='chest'){

  rect(x,y+5,25,19,'#9a542d');

  rect(x-2,y+1,29,8,'#e9a345');

  rect(x+10,y+8,5,9,'#ffe68a');

 } else if(it.type==='heart'){

  text('❤️',x,y+22,23);

 }

}

function drawPrincess(x,y) {

 // Personaje pixel-art original, dibujado con formas.

 ctx.save();ctx.translate(Math.round(x),Math.round(y));

 if(player.face<0){ctx.translate(player.w,0);ctx.scale(-1,1);}

 // Pelo largo.

 rect(3,2,22,19,'#60324e');

 rect(1,11,8,22,'#60324e');

 // Cara.

 rect(7,9,15,17,'#f4c5a6');

 rect(6,5,17,7,'#60324e');

 // Ojos.

 rect(10,16,3,3,'#392541');rect(19,16,3,3,'#392541');

 // Sonrisa.

 rect(14,22,6,2,'#b64f73');

 // Corona pequeña.

 rect(8,1,13,3,'#ffda4d');

 rect(9,-3,3,5,'#ffda4d');rect(15,-5,3,7,'#ffda4d');

 // Vestido.

 rect(5,27,20,10,'#f06da9');

 rect(2,35,27,6,'#d94e94');

 // Brazos.

 rect(0,28,5,9,'#f4c5a6');rect(25,28,5,9,'#f4c5a6');

 // Botas.

 rect(5,40,8,4,'#66394c');rect(20,40,8,4,'#66394c');

 // Brillo.

 rect(8,30,3,3,'#fff0ac');

 ctx.restore();

}

function drawBunny(x,y) {

 text('🐰',x,y,30);

}

function drawExit() {

 const x=exit.x-cameraX;

 rect(x,exit.y,exit.w,exit.h,'#c05c9c');

 rect(x-7,exit.y-8,exit.w+14,15,'#ff91cb');

 rect(x+7,exit.y+16,exit.w-14,exit.h-16,'#55326d');

 ctx.beginPath();

 ctx.arc(x+exit.w/2,exit.y+27,11,Math.PI,0);

 ctx.fillStyle='#ad80dc';ctx.fill();

 rect(x+exit.w/2,exit.y+48,3,4,'#ffe57c');

 pixelText('SALIDA',x+exit.w/2,exit.y-13,9,'#fff','center');

}

function drawBoss() {

 if(!boss||boss.defeated)return;

 const x=boss.x-cameraX,y=boss.y;

 if(x<-100||x>W+100)return;

 // Silueta de jefe con aspecto de monstruo de videojuego.

 const colors=['#754a9e','#5484bf','#bd703d','#4a865a','#7e65b8',

 '#3c6da4','#bb6d9d','#5e6bb3','#9a4c72','#db7554'];

 const c=colors[level];

 rect(x+12,y+18,65,48,c);

 rect(x+3,y+35,82,31,c);

 rect(x+20,y+5,16,18,c);

 rect(x+54,y+5,16,18,c);

 rect(x+24,y+28,9,10,'#fff');

 rect(x+54,y+28,9,10,'#fff');

 rect(x+28,y+31,5,6,'#28203d');

 rect(x+58,y+31,5,6,'#28203d');

 rect(x+32,y+49,27,5,'#39213f');

 rect(x+18,y+65,17,12,c);rect(x+58,y+65,17,12,c);

 text(level===9?'🔥':'✦',x+43,y+18,19,'#fff1a6','center');

 // Barra de vida.

 const bw=115;

 rect(x-10,y-17,bw,9,'#3b2448');

 rect(x-8,y-15,(bw-4)*Math.max(0,boss.hp/boss.maxHp),5,'#ff5e81');

}

function drawParticles(dt) {

 particles.forEach(p=>{

  p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=200*dt;p.life-=dt;

  ctx.globalAlpha=Math.max(0,p.life);

  rect(p.x-cameraX,p.y,5,5,p.color);

 });

 ctx.globalAlpha=1;

 particles=particles.filter(p=>p.life>0);

}

function burst(x,y,color='#ffdb57') {

 for(let i=0;i<12;i++){

  particles.push({x,y,vx:(Math.random()-.5)*230,

  vy:-Math.random()*180,life:.6+Math.random()*.5,color});

 }

}

function drawWorld() {

 drawBackground();

 drawPlatforms();

 items.forEach(drawItem);

 drawExit();

 // Conejitos amistosos.

 drawBunny(330-cameraX,310);

 drawBunny(1030-cameraX,263);

 drawBunny(1650-cameraX,348);

 enemies.forEach(e=>{

  if(!e.dead){

   const x=e.x-cameraX;

   rect(x,e.y,30,25,'#d75f80');

   rect(x+5,e.y-7,6,10,'#d75f80');

   rect(x+19,e.y-7,6,10,'#d75f80');

   rect(x+6,e.y+8,4,4,'white');

   rect(x+20,e.y+8,4,4,'white');

  }

 });

 drawBoss();

 drawPrincess(player.x-cameraX,player.y);

 drawParticles(0);

}

function drawOverlay(title,subtitle,buttons=[]) {

 ctx.fillStyle='#211b46c9';ctx.fillRect(0,0,W,H);

 ctx.textAlign='center';

 ctx.font='bold 34px "Nunito",sans-serif';

 ctx.fillStyle='#fff0b6';ctx.fillText(title,W/2,145);

 ctx.font='bold 19px "Nunito",sans-serif';

 ctx.fillStyle='white';

 const lines=Array.isArray(subtitle)?subtitle:[subtitle];

 lines.forEach((line,i)=>ctx.fillText(line,W/2,190+i*31));

 buttons.forEach((b,i)=>{

  const y=300+i*61;

  rect(W/2-180,y,360,45,'#d94e9b');

  rect(W/2-180,y,360,4,'#ffb5e0');

  ctx.fillStyle='#fff';ctx.font='bold 17px "Nunito",sans-serif';

  ctx.fillText(b.label,W/2,y+28);

 });

}

function drawDialog() {

 if(!dialog)return;

 rect(25,407,910,105,'#231b3e');

 rect(28,410,904,99,'#fff0d9');

 rect(28,410,904,5,'#ff8ecb');

 text('🐰 Turrón:',47,441,20,'#8d438b');

 wrapText(dialog,47,466,855,19,'#392547');

}

function wrapText(str,x,y,maxWidth,lineHeight,color) {

 ctx.font='bold 16px Nunito,sans-serif';ctx.fillStyle=color;

 const words=str.split(' ');let line='';

 for(const word of words){

  const test=line+word+' ';

  if(ctx.measureText(test).width>maxWidth&&line){

   ctx.fillText(line,x,y);line=word+' ';y+=lineHeight;

  } else line=test;

 }

 ctx.fillText(line,x,y);

}

function draw() {

 drawWorld();

 if(state==='menu') {

  drawOverlay('EL MUNDO DE MI PRINCESITA',

   ['🌻 Una aventura con Turrón 🌻','Un pequeño universo hecho para ti.'],

   [{label:'1. EMPEZAR AVENTURA'},

    {label:'2. LEER LA CARTA'},

    {label:'3. ZONA TRANQUILA'}]);

 } else if(state==='letter') {

  drawOverlay('PARA MI PRINCESITA',[

   'No tienes que estar feliz todos los días.',

   'Puedes descansar, respirar y empezar despacito.',

   'Este mundo guarda girasoles para tus días grises.',

   'Turrón te espera al otro lado del jardín. ♡',

   'Pulsa ESC para volver.'

  ]);

 } else if(state==='calm') {

  drawOverlay('TU RINCÓN TRANQUILO',[

   'Inhala despacito... y suelta el aire.',

   'No tienes que resolverlo todo ahora.',

   'Un momento a la vez. ♡',

   phrases[calmIndex%phrases.length],

   'Pulsa ESPACIO para otro mensaje.',

   'Pulsa ESC para volver.'

  ]);

 } else if(state==='paused') {

  drawOverlay('AVENTURA EN PAUSA',

   ['Puedes descansar todo el tiempo que necesites.',

    'Tu aventura estará aquí cuando quieras volver.'],

   [{label:'CONTINUAR'}]);

 } else if(state==='joke') {

  drawOverlay('¡HORA DE REÍR! 🌻',

   [jokes[jokeIndex%jokes.length],

    'Pulsa ESPACIO para otro chiste.',

    'Pulsa ESC para volver.']);

 } else if(state==='bossIntro') {

  drawOverlay('¡JEFE FINAL!',[

   levels[level].boss,

   bossLines[level+1],

   'Pulsa ESPACIO o ATACAR para comenzar.',

   'Usa X para atacar y SALTAR para esquivar.'

  ]);

 } else if(state==='bossWin') {

  drawOverlay('¡JEFE DERROTADO!',[

   '¡Lo hiciste, campeona! 🌻',

   phrases[(level*3+5)%phrases.length],

   level===9?'¡Has salvado el reino entero!':'El siguiente mundo te espera.',

   'Pulsa ESPACIO para continuar.'

  ]);

 } else if(state==='gameover') {

  drawOverlay('UN DESCANSITO',[

   'Hasta las heroínas necesitan una pausa.',

   'Puedes volver a intentarlo cuando quieras. ♡'

  ],[{label:'VOLVER A INTENTAR'}]);

 } else if(state==='ending') {

  drawOverlay('¡AVENTURA COMPLETADA!',[

   'Has recorrido los diez mundos.',

   `Monedas: ${coins} | Girasoles: ${flowers}`,

   `Cofres encontrados: ${chests}`,

   'Gracias por jugar, princesita. ♡',

   'Ojalá este pequeño mundo te haya regalado una sonrisa.',

   'Pulsa ESPACIO para volver al menú.'

  ]);

 }

 if(dialog&&state==='play')drawDialog();

 if(toastTimer>0&&state==='play'){

  ctx.fillStyle='#281c45df';ctx.fillRect(240,15,480,42);

  text(toast,W/2,42,16,'#ffeab0','center');

 }

}

function collides(a,b) {

 return a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;

}

function movePlayer(dt) {

 const left=keys.ArrowLeft||keys.a||keys.A||keys.left;

 const right=keys.ArrowRight||keys.d||keys.D||keys.right;

 player.vx=0;

 if(left){player.vx=-player.speed;player.face=-1;}

 if(right){player.vx=player.speed;player.face=1;}

 player.x+=player.vx*dt;

 player.x=Math.max(0,Math.min(worldWidth-player.w,player.x));

 player.vy+=gravity*dt;

 const oldY=player.y;

 player.y+=player.vy*dt;

 player.onGround=false;

 for(const p of platforms){

  if(player.x+player.w>p.x&&player.x<p.x+p.w&&

     oldY+player.h<=p.y+8&&player.y+player.h>=p.y&&player.vy>=0){

    player.y=p.y-player.h;player.vy=0;player.onGround=true;

  }

 }

 if(player.y>H+150){

  loseLife();

 }

 player.invulnerable=Math.max(0,player.invulnerable-dt);

 player.attack=Math.max(0,player.attack-dt);

 cameraX=Math.max(0,Math.min(worldWidth-W,player.x-W*.37));

}

function jump() {

 if(state==='play'&&player.onGround) {

  player.vy=player.jump;player.onGround=false;

 }

}

function attack() {

 if(state==='bossIntro'){state='play';say('¡A por él! ¡Tú puedes! ♡');return;}

 if(state!=='play')return;

 player.attack=.25;

 if(boss&&!boss.defeated){

  const dx=boss.x-player.x;

  if(Math.abs(dx)<115&&Math.abs(boss.y-player.y)<90){

   boss.hp--;

   burst(boss.x+40,boss.y+30,'#ffdc66');

   showToast('¡Golpe acertado! ✨');

   if(boss.hp<=0){

    boss.defeated=true;

    state='bossWin';

    flowers+=2;coins+=5;

    updateHUD();

   }

  } else showToast('¡Acércate un poquito al jefe!');

 }

}

function interact() {

 if(state==='menu'){return;}

 if(state==='play'){

  if(player.x>exit.x-90){

   if(boss&&!boss.defeated){

    say('¡Derrota al jefe para abrir la salida!');

   }else completeLevel();

   return;

  }

  const nearby=items.find(it=>!it.taken&&Math.abs(it.x-player.x)<70&&Math.abs(it.y-player.y)<80);

  if(nearby){collect(nearby);return;}

  say(phrases[Math.floor(Math.random()*phrases.length)]);

 }

}

function collect(it) {

 it.taken=true;

 if(it.type==='coin'){coins++;showToast('¡Moneda recogida! 🪙');}

 if(it.type==='flower'){

  flowers++;showToast('Un girasol para ti. 🌻');

  say(phrases[(flowers*3)%phrases.length],3);

 }

 if(it.type==='chest'){

  chests++;coins+=3;flowers++;

  showToast('¡Cofre mágico! +3 monedas y un girasol');

  say('Dentro del cofre hay un recordatorio: mereces cosas bonitas. ♡',4);

 }

 if(it.type==='heart'){

  lives=Math.min(5,lives+1);showToast('¡Una vida extra! ❤️');

 }

 updateHUD();

 burst(it.x,it.y);

}

function loseLife() {

 lives--;updateHUD();

 player.x=Math.max(20,player.x-90);

 player.y=350;player.vy=0;player.invulnerable=1.5;

 if(lives<=0){state='gameover';}

 else showToast('¡No pasa nada! Puedes volver a intentarlo. ♡');

}

function completeLevel() {

 if(level>=9){state='ending';return;}

 level++;

 buildLevel();

 state='play';

 updateHUD();

 say('¡Bienvenida a '+levels[level].name+'! '+phrases[(level*2)%phrases.length],5);

 showToast('¡Nuevo mundo desbloqueado! 🌻');

}

function startBoss() {

 const p=platforms.find(p=>p.x>1650);

 boss={

  x:1750,y:388,w:85,h:77,

  hp:levels[level].hp,maxHp:levels[level].hp,

  defeated:false,dir:-1,speed:80+level*7,

  attackTimer:2

 };

 state='bossIntro';

 say('');

}

function updateBoss(dt) {

 if(!boss||boss.defeated)return;

 boss.x+=boss.dir*boss.speed*dt;

 if(boss.x<1500){boss.x=1500;boss.dir=1;}

 if(boss.x>1900){boss.x=1900;boss.dir=-1;}

 boss.attackTimer-=dt;

 if(boss.attackTimer<=0){

  boss.attackTimer=Math.max(.8,2-level*.08);

  const dx=Math.abs((player.x+player.w/2)-(boss.x+40));

  if(dx<95&&player.invulnerable<=0){

   lives--;player.invulnerable=1.5;updateHUD();

   showToast('¡Esquiva al jefe! ❤️');

   if(lives<=0)state='gameover';

  } else if(dx<300) {

   // Ataque a distancia que se puede esquivar saltando.

   particles.push({x:boss.x+25,y:boss.y+35,vx:(player.x>boss.x?1:-1)*230,

    vy:-10,life:1.2,color:'#ff82bd',projectile:true});

  }

 }

 particles.forEach(p=>{

  if(p.projectile){

   p.x+=p.vx*dt;

   if(player.invulnerable<=0&&collides(

     {x:player.x,y:player.y,w:player.w,h:player.h},

     {x:p.x,y:p.y,w:12,h:12})){

      p.life=0;lives--;player.invulnerable=1.5;updateHUD();

      if(lives<=0)state='gameover';

   }

  }

 });

}

function update(dt) {

 elapsed+=dt;

 if(toastTimer>0)toastTimer-=dt;

 if(dialogTimer>0){dialogTimer-=dt;if(dialogTimer<=0)dialog=null;}

 if(state!=='play')return;

 movePlayer(dt);

 for(const it of items){

  if(!it.taken&&collides(player,it))collect(it);

 }

 if(!boss&&player.x>1600)startBoss();

 if(boss&&!boss.defeated)updateBoss(dt);

 if(player.x>exit.x&&(!boss||boss.defeated))completeLevel();

}

function loop(timestamp) {

 const dt=Math.min((timestamp-lastTime)/1000||0,0.035);

 lastTime=timestamp;

 if(state==='play')update(dt);

 draw();

 requestAnimationFrame(loop);

}

function menuChoice(index) {

 if(state==='menu'){

  if(index===0)resetGame();

  if(index===1)state='letter';

  if(index===2)state='calm';

 }

}

canvas.addEventListener('click',e=>{

 const r=canvas.getBoundingClientRect();

 const x=(e.clientX-r.left)*W/r.width;

 const y=(e.clientY-r.top)*H/r.height;

 if(state==='menu'&&y>270&&y<455){

  menuChoice(Math.max(0,Math.min(2,Math.floor((y-270)/61))));

 } else if(state==='paused'){

  state='play';

 } else if(state==='bossIntro'){

  attack();

 } else if(state==='bossWin'){

  completeLevel();

 } else if(state==='gameover'){

  resetGame();

 } else if(state==='ending'){

  state='menu';

 }

});

document.addEventListener('keydown',e=>{

 const k=e.key;

 if(['ArrowLeft','ArrowRight','ArrowUp',' ','ArrowDown'].includes(k))e.preventDefault();

 keys[k]=true;

 if(k==='Escape'){

  if(['letter','calm','joke'].includes(state))state='menu';

  else if(state==='paused')state='play';

  else if(state==='play'){state='paused';}

  else if(state==='menu')return;

 }

 if(k===' '||k==='ArrowUp'||k==='w'||k==='W'){

  if(state==='menu')resetGame();

  else if(state==='play')jump();

  else if(state==='calm')calmIndex++;

  else if(state==='joke')jokeIndex++;

  else if(state==='bossIntro')attack();

  else if(state==='bossWin')completeLevel();

  else if(state==='ending')state='menu';

  else if(state==='gameover')resetGame();

 }

 if(k==='x'||k==='X')attack();

 if(k==='e'||k==='E'||k==='Enter'){

  if(state==='menu')menuChoice(0);

  else if(state==='play')interact();

 }

 if(k==='j'||k==='J'){

  if(state==='play')state='joke';

  else if(state==='joke')jokeIndex++;

 }

});

document.addEventListener('keyup',e=>{keys[e.key]=false;});

function holdButton(id,key) {

 const b=$(id);

 const down=e=>{e.preventDefault();keys[key]=true;};

 const up=e=>{e.preventDefault();keys[key]=false;};

 b.addEventListener('pointerdown',down);

 b.addEventListener('pointerup',up);

 b.addEventListener('pointerleave',up);

 b.addEventListener('pointercancel',up);

}

holdButton('left','left');

holdButton('right','right');

$('jump').addEventListener('pointerdown',e=>{e.preventDefault();jump();});

$('attack').addEventListener('pointerdown',e=>{e.preventDefault();attack();});

$('interact').addEventListener('pointerdown',e=>{

 e.preventDefault();

 if(state==='menu')menuChoice(1);

 else if(state==='calm'||state==='letter'||state==='joke')state='menu';

 else if(state==='bossWin')completeLevel();

 else interact();

});

$('pause').addEventListener('click',()=>{

 if(state==='play')state='paused';

 else if(state==='paused')state='play';

 else if(state==='menu')state='calm';

});

buildLevel();

updateHUD();

requestAnimationFrame(loop);

})();

</script>

</body>

</html>

Game Source: El mundo de mi princesita 🌻

Creator: LunarFlare61

Libraries: none

Complexity: complex (1725 lines, 28.4 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: el-mundo-de-mi-princesita-lunarflare61-mv5qz5bn" to link back to the original. Then publish at arcadelab.ai/publish.