🎮ArcadeLab

El mundo de mi princesita 🌻

by LunarFlare61
2583 lines38.8 KB
▶ Play


<!DOCTYPE html>

<html lang="es">

<head>

<meta charset="UTF-8">

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

<meta name="theme-color" content="#65cfff">

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

<style>

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

:root {

  --pink: #ff91cb;

  --pink-dark: #b94e98;

  --purple: #30234f;

  --cream: #fff0c9;

  --blue: #60cfff;

}

* { box-sizing: border-box; }

body {

  margin: 0;

  min-height: 100vh;

  color: white;

  background:

    radial-gradient(ellipse at top, #39366e, #17162e 70%);

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

  text-align: center;

}

button {

  font-family: inherit;

  cursor: pointer;

  color: #43234f;

  background: var(--pink);

  border: 3px solid #fff4fb;

  border-radius: 8px;

  padding: 12px 15px;

  box-shadow: 0 5px 0 #8d397d;

  font-size: 10px;

  line-height: 1.8;

  transition: transform .12s, background .12s;

  touch-action: manipulation;

}

button:hover {

  background: #ffe58b;

  transform: translateY(-2px);

}

button:active {

  transform: translateY(3px);

  box-shadow: 0 2px 0 #8d397d;

}

button:disabled {

  opacity: .45;

  cursor: not-allowed;

}

header {

  padding: 14px 10px 8px;

}

header h1 {

  color: #ffd4ec;

  font-size: clamp(14px, 3vw, 26px);

  line-height: 1.9;

  text-shadow: 3px 3px #8a3d89, -2px -2px white;

  margin: 8px 0;

}

header p {

  font-size: 9px;

  line-height: 2;

  color: #d7e9ff;

}

#app {

  width: min(100%, 1040px);

  margin: auto;

  padding: 8px;

}

#gameCanvas {

  display: block;

  width: 100%;

  height: auto;

  aspect-ratio: 16 / 9;

  background: #66d0ff;

  border: 5px solid white;

  border-radius: 10px;

  image-rendering: pixelated;

  box-shadow: 0 0 24px #8b7fff55;

  touch-action: none;

}

#gameButtons, #menuButtons, #controls, #extraButtons {

  display: flex;

  justify-content: center;

  align-items: center;

  flex-wrap: wrap;

  gap: 10px;

  padding: 12px 4px;

}

#gameButtons { display: none; }

#status {

  background: #30234f;

  border: 2px solid #ffb9e0;

  border-radius: 10px;

  padding: 14px 10px;

  margin: 10px auto;

  max-width: 960px;

  font-size: 10px;

  line-height: 2.1;

  min-height: 48px;

}

#hud {

  font-size: 9px;

  line-height: 2;

  color: #fff0a8;

}

#mission {

  color: #bcecff;

  font-size: 8px;

  line-height: 2.1;

  padding: 5px;

}

#controls button {

  min-width: 68px;

  min-height: 52px;

  font-size: 12px;

  user-select: none;

  -webkit-user-select: none;

}

#extraButtons button { font-size: 9px; }

#footer {

  color: #c9c4eb;

  font-size: 8px;

  line-height: 2.2;

  padding: 10px;

}

@media(max-width: 600px) {

  #app { padding: 5px; }

  #gameCanvas { border-width: 3px; }

  button { font-size: 9px; padding: 10px; }

  #status { font-size: 9px; }

  #controls { gap: 6px; }

  #controls button { min-width: 58px; }

}

</style>

</head>

<body>

<div id="app">

<header>

  <h1>🌻 EL MUNDO DE MI PRINCESITA 🌻</h1>

  <p>UNA AVENTURA DE PLATAFORMAS LLENA DE CARIÑO ♡</p>

</header>

<div id="hud">🌷 Una aventura con Turrón y miles de girasoles 🌷</div>

<canvas id="gameCanvas" width="960" height="540">

  Tu navegador no admite el elemento canvas.

</canvas>

<div id="status" aria-live="polite">

  ¡Bienvenida, princesita! Turrón te está esperando. 🐰

</div>

<div id="menuButtons">

  <button onclick="startGame()">▶ EMPEZAR AVENTURA</button>

  <button onclick="openPage('letter')">💌 LEER LA CARTA</button>

  <button onclick="openPage('calm')">🌙 ZONA TRANQUILA</button>

  <button onclick="openPage('jokes')">😂 CHISTES</button>

  <button onclick="openPage('shop')">🎁 TIENDA</button>

</div>

<div id="gameButtons">

  <button onclick="interact()">E · INTERACTUAR</button>

  <button onclick="showMission()">📜 MISIÓN</button>

  <button onclick="pauseGame()">Ⅱ PAUSA</button>

  <button onclick="openPage('calm')">🌙 DESCANSAR</button>

</div>

<div id="controls">

  <button id="leftButton" aria-label="Mover a la izquierda">⬅️</button>

  <button id="jumpButton" aria-label="Saltar">⬆️ SALTAR</button>

  <button id="rightButton" aria-label="Mover a la derecha">➡️</button>

</div>

<div id="extraButtons">

  <button onclick="openPage('letter')">💌 Carta especial</button>

  <button onclick="openPage('jokes')">😂 Un chiste</button>

  <button onclick="openPage('calm')">🌙 Rincón tranquilo</button>

  <button onclick="openPage('shop')">🎁 Recompensas</button>

</div>

<div id="mission"></div>

<div id="footer">

  FLECHAS / A-D: MOVER · ESPACIO: SALTAR · E: INTERACTUAR<br>

  RECOGE MONEDAS, ABRE COFRES Y ENCUENTRA GIRASOLES.<br>

  HECHO CON CARIÑO PARA TI. 🌻🐰

</div>

</div>

<script>

"use strict";

/* =====================================================

   EL MUNDO DE MI PRINCESITA

   Juego de plataformas en HTML Canvas y JavaScript.

   No necesita imágenes ni librerías de videojuegos.

   ===================================================== */

const canvas = document.getElementById("gameCanvas");

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

const W = canvas.width;

const H = canvas.height;

const GROUND_Y = 450;

const ui = {

  status: document.getElementById("status"),

  hud: document.getElementById("hud"),

  mission: document.getElementById("mission"),

  menu: document.getElementById("menuButtons"),

  game: document.getElementById("gameButtons")

};

const levels = [

  {

    name: "EL JARDÍN DE LOS GIRASOLES",

    subtitle: "Un pequeño jardín lleno de alegría",

    sky: ["#4bc7ff", "#c7f4ff"],

    ground: "#b97948",

    grass: "#49df5d",

    theme: "garden"

  },

  {

    name: "EL CIELO DE ALGODÓN",

    subtitle: "Salta entre nubes de colores",

    sky: ["#72caff", "#f3dcff"],

    ground: "#8b9ee5",

    grass: "#d5faff",

    theme: "clouds"

  },

  {

    name: "EL DESIERTO DORADO",

    subtitle: "Una aventura bajo el sol",

    sky: ["#ffb56e", "#ffe6a4"],

    ground: "#b77748",

    grass: "#a7d65c",

    theme: "desert"

  },

  {

    name: "EL CASTILLO DE LOS SUEÑOS",

    subtitle: "La última aventura de Turrón",

    sky: ["#685ba8", "#ffb9df"],

    ground: "#8062a4",

    grass: "#8be0a0",

    theme: "castle"

  }

];

let gameState = "menu";

let currentLevel = 0;

let coins = 0;

let flowers = 0;

let chests = 0;

let jokesSeen = 0;

let treasures = 0;

let totalJumps = 0;

let totalDeaths = 0;

let levelCollected = new Set();

let lastTime = 0;

let animationId = 0;

let elapsed = 0;

const keys = {

  left: false,

  right: false

};

const player = {

  x: 55,

  y: 380,

  w: 30,

  h: 38,

  vx: 0,

  vy: 0,

  speed: 240,

  jumpPower: 490,

  grounded: false,

  facing: 1,

  invulnerable: 0

};

const bunny = {

  x: 175,

  y: GROUND_Y - 43,

  w: 34,

  h: 40

};

let platforms = [];

let items = [];

let bricks = [];

let pipes = [];

let door = { x: 908, y: GROUND_Y - 60, w: 38, h: 60 };

const jokes = [

  "¿Qué hace un conejo feliz? ¡Da saltitos de alegría! 🐰",

  "¿Qué le dijo el girasol al sol? ¡Contigo siempre me giro! 🌻",

  "¿Cuál es el deporte favorito de Turrón? ¡Los saltos de altura!",

  "¿Por qué la moneda fue al colegio? ¡Para tener más cambio!",

  "¿Qué hace una nube cuando se aburre? ¡Se pasa el rato flotando!",

  "¿Qué le dice una flor a otra? ¡Nuestra amistad florece! 🌷",

  "¿Qué hace un conejo en una biblioteca? ¡Busca un cuento de saltos!",

  "¿Qué dijo el cofre al aventurero? ¡Aquí hay una sorpresa para ti!",

  "¿Por qué el girasol nunca se pierde? ¡Siempre busca la luz!",

  "¿Qué lleva Turrón en la cabeza? ¡Un lazo lleno de magia! 🎀"

];

const missions = [

  "Misión: recoge 5 monedas.",

  "Misión: encuentra 4 girasoles.",

  "Misión: abre al menos un cofre.",

  "Misión: visita a Turrón.",

  "Misión: encuentra la puerta del mundo."

];

/* =================== DIBUJO BÁSICO =================== */

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

  ctx.fillStyle = color;

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

}

function outlineRect(x, y, w, h, fill, stroke, width = 3) {

  rect(x, y, w, h, fill);

  ctx.strokeStyle = stroke;

  ctx.lineWidth = width;

  ctx.strokeRect(x, y, w, h);

}

function circle(x, y, radius, color) {

  ctx.fillStyle = color;

  ctx.beginPath();

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

  ctx.fill();

}

function pixelText(text, x, y, size = 12, color = "#ffffff", align = "left") {

  ctx.save();

  ctx.font = `bold ${size}px "Courier New", monospace`;

  ctx.textAlign = align;

  ctx.textBaseline = "middle";

  ctx.lineJoin = "round";

  ctx.strokeStyle = "#34234d";

  ctx.lineWidth = Math.max(2, size / 5);

  ctx.strokeText(text, x, y);

  ctx.fillStyle = color;

  ctx.fillText(text, x, y);

  ctx.restore();

}

function heart(x, y, size = 14) {

  pixelText("♥", x, y, size, "#ff8ebf");

}

function drawSun(x, y) {

  circle(x, y, 27, "#ffdb5e");

  circle(x, y, 20, "#fff18a");

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

    const a = i * Math.PI / 4;

    const x1 = x + Math.cos(a) * 32;

    const y1 = y + Math.sin(a) * 32;

    const x2 = x + Math.cos(a) * 40;

    const y2 = y + Math.sin(a) * 40;

    ctx.strokeStyle = "#ffd64b";

    ctx.lineWidth = 5;

    ctx.beginPath();

    ctx.moveTo(x1, y1);

    ctx.lineTo(x2, y2);

    ctx.stroke();

  }

}

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

  ctx.fillStyle = "#ffffff";

  circle(x, y, 17 * scale, "#ffffff");

  circle(x + 18 * scale, y - 9 * scale, 22 * scale, "#ffffff");

  circle(x + 39 * scale, y, 17 * scale, "#ffffff");

  rect(x - 3 * scale, y, 46 * scale, 13 * scale, "#ffffff");

}

function drawHill(x, y, width, height, color) {

  ctx.fillStyle = color;

  ctx.beginPath();

  ctx.moveTo(x, y);

  ctx.quadraticCurveTo(

    x + width * 0.45, y - height * 1.4,

    x + width, y

  );

  ctx.lineTo(x + width, H);

  ctx.lineTo(x, H);

  ctx.closePath();

  ctx.fill();

}

/* =================== PERSONAJE =================== */

function drawPlayer(x, y) {

  ctx.save();

  // Sombra

  circle(x + 15, y + 38, 13, "#00000022");

  // Piernas y zapatos

  rect(x + 5, y + 27, 8, 8, "#5a3a9c");

  rect(x + 18, y + 27, 8, 8, "#5a3a9c");

  rect(x + 2, y + 33, 12, 5, "#5a302e");

  rect(x + 17, y + 33, 12, 5, "#5a302e");

  // Cuerpo y ropa

  rect(x + 5, y + 14, 21, 15, "#ef5b9d");

  rect(x + 8, y + 16, 15, 10, "#ffd0e5");

  rect(x + 4, y + 13, 23, 5, "#7c45b9");

  // Brazos

  rect(x, y + 17, 6, 11, "#edb68f");

  rect(x + 25, y + 17, 6, 11, "#edb68f");

  // Cabeza

  rect(x + 5, y + 1, 21, 17, "#edb68f");

  rect(x + 3, y + 3, 5, 12, "#edb68f");

  rect(x + 23, y + 4, 5, 10, "#edb68f");

  // Cabello

  rect(x + 3, y, 23, 6, "#633657");

  rect(x + 1, y + 4, 7, 13, "#633657");

  rect(x + 7, y - 2, 8, 5, "#633657");

  rect(x + 18, y - 1, 9, 5, "#633657");

  // Ojos

  rect(x + 10, y + 9, 3, 3, "#39234c");

  rect(x + 20, y + 9, 3, 3, "#39234c");

  // Sonrisa

  rect(x + 14, y + 14, 6, 2, "#b64f76");

  // Detalle rosa

  rect(x + 3, y + 3, 4, 4, "#ffb7dc");

  ctx.restore();

}

/* Turrón: una conejita pixel art con orejas, mejillas y lazo rosa. */

function drawTurron(x, y) {

  ctx.save();

  const hop = Math.sin(elapsed * 5) * 2;

  y += hop;

  // Sombra

  circle(x + 16, y + 39, 13, "#00000020");

  // Orejas

  rect(x + 5, y - 18, 8, 23, "#fff7f5");

  rect(x + 19, y - 21, 8, 26, "#fff7f5");

  rect(x + 7, y - 15, 4, 17, "#ffb9d9");

  rect(x + 21, y - 18, 4, 20, "#ffb9d9");

  // Cabeza

  rect(x + 3, y + 1, 27, 22, "#fffaf3");

  rect(x, y + 6, 5, 11, "#fffaf3");

  rect(x + 28, y + 6, 5, 11, "#fffaf3");

  // Mejillas

  rect(x + 4, y + 13, 6, 4, "#ffb8d7");

  rect(x + 23, y + 13, 6, 4, "#ffb8d7");

  // Ojos y nariz

  rect(x + 9, y + 9, 3, 4, "#382443");

  rect(x + 22, y + 9, 3, 4, "#382443");

  rect(x + 15, y + 14, 4, 3, "#e78db2");

  rect(x + 15, y + 17, 4, 2, "#9b557d");

  // Cuerpo

  rect(x + 8, y + 23, 17, 12, "#fffaf3");

  rect(x + 5, y + 27, 7, 8, "#fffaf3");

  rect(x + 22, y + 27, 7, 8, "#fffaf3");

  // Patitas

  rect(x + 6, y + 34, 10, 5, "#fffaf3");

  rect(x + 19, y + 34, 10, 5, "#fffaf3");

  // Lazo rosa

  rect(x + 14, y + 1, 5, 5, "#ff66b7");

  rect(x + 9, y - 1, 6, 5, "#ff91cc");

  rect(x + 19, y - 1, 6, 5, "#ff91cc");

  rect(x + 12, y - 3, 4, 4, "#ffc6e4");

  rect(x + 21, y - 3, 4, 4, "#ffc6e4");

  // Collar de flores

  rect(x + 12, y + 23, 4, 3, "#ff83c6");

  rect(x + 18, y + 23, 4, 3, "#ff83c6");

  ctx.restore();

}

/* =================== MUNDO Y ESCENARIOS =================== */

function drawBackground() {

  const L = levels[currentLevel];

  const gradient = ctx.createLinearGradient(0, 0, 0, H);

  gradient.addColorStop(0, L.sky[0]);

  gradient.addColorStop(1, L.sky[1]);

  rect(0, 0, W, H, gradient);

  drawSun(850, 65);

  drawCloud(100, 90, 1);

  drawCloud(350, 135, .7);

  drawCloud(635, 85, .85);

  if (L.theme === "desert") {

    drawHill(-30, 370, 260, 150, "#eeb16f");

    drawHill(230, 385, 310, 120, "#e4a56a");

    drawHill(620, 380, 370, 165, "#f4c27d");

    drawPalm(760, 345);

  } else if (L.theme === "castle") {

    drawHill(-30, 390, 260, 100, "#897bc0");

    drawHill(300, 395, 300, 110, "#b49bce");

    drawCastle(720, 205);

  } else if (L.theme === "clouds") {

    drawCloud(140, 330, 1.4);

    drawCloud(650, 300, 1.3);

    drawCloud(450, 220, 1.0);

  } else {

    drawHill(-50, 410, 350, 100, "#58c86c");

    drawHill(250, 420, 350, 95, "#73d77b");

    drawHill(600, 415, 400, 100, "#55c878");

    drawTree(55, 340);

  }

  // Estrellas decorativas

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

    const x = (i * 83 + 30) % W;

    const y = 180 + (i % 4) * 39;

    pixelText("✦", x, y, 12, "#ffffff88");

  }

}

function drawTree(x, y) {

  rect(x + 20, y + 35, 20, 70, "#875132");

  circle(x + 28, y + 10, 33, "#31b65c");

  circle(x + 3, y + 32, 26, "#36c565");

  circle(x + 52, y + 34, 25, "#239d54");

}

function drawPalm(x, y) {

  rect(x, y, 12, 85, "#99613e");

  ctx.strokeStyle = "#329b4e";

  ctx.lineWidth = 9;

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

    const a = Math.PI + i * Math.PI / 4;

    ctx.beginPath();

    ctx.moveTo(x + 6, y + 5);

    ctx.lineTo(x + 6 + Math.cos(a) * 48, y + 5 + Math.sin(a) * 35);

    ctx.stroke();

  }

}

function drawCastle(x, y) {

  rect(x, y + 45, 120, 140, "#9c8bd4");

  rect(x + 12, y + 5, 33, 180, "#ad9ae3");

  rect(x + 75, y - 10, 33, 195, "#b9a5ed");

  for (const tx of [x + 12, x + 75]) {

    ctx.fillStyle = "#e777bd";

    ctx.beginPath();

    ctx.moveTo(tx - 3, y + 6);

    ctx.lineTo(tx + 16, y - 22);

    ctx.lineTo(tx + 35, y + 6);

    ctx.fill();

  }

  rect(x + 49, y + 110, 25, 75, "#5b387d");

  rect(x + 24, y + 62, 13, 18, "#fff2a0");

  rect(x + 84, y + 62, 13, 18, "#fff2a0");

}

function drawGround() {

  const L = levels[currentLevel];

  rect(0, GROUND_Y, W, H - GROUND_Y, L.ground);

  rect(0, GROUND_Y, W, 12, L.grass);

  rect(0, GROUND_Y + 12, W, 6, "#308e4c");

  for (let x = 0; x < W; x += 42) {

    rect(x, GROUND_Y + 28, 30, 4, "#8b4f36");

    rect(x + 15, GROUND_Y + 45, 26, 4, "#c58b56");

  }

  for (let x = 12; x < W; x += 83) {

    drawSunflower(x, GROUND_Y - 6, .6);

  }

}

function drawSunflower(x, y, scale = 1) {

  ctx.save();

  ctx.translate(x, y);

  ctx.scale(scale, scale);

  rect(-2, 0, 4, 20, "#32984a");

  rect(-9, 10, 8, 4, "#42b959");

  rect(2, 14, 8, 4, "#42b959");

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

    const a = i * Math.PI / 4;

    circle(Math.cos(a) * 8, Math.sin(a) * 8 - 10, 5, "#ffca32");

  }

  circle(0, -10, 7, "#75472e");

  circle(-2, -12, 2, "#9e693a");

  ctx.restore();

}

function drawPlatforms() {

  for (const p of platforms) {

    rect(p.x, p.y, p.w, p.h, "#a86642");

    rect(p.x, p.y, p.w, 9, "#4ade61");

    rect(p.x, p.y + 9, p.w, 5, "#298d49");

    for (let x = p.x + 8; x < p.x + p.w - 8; x += 30) {

      rect(x, p.y + 20, 17, 4, "#c4824e");

    }

  }

}

/* Bloques de ladrillo, bloques sorpresa y tuberías */

function drawBricks() {

  for (const b of bricks) {

    if (b.type === "question") {

      outlineRect(b.x, b.y, b.w, b.h, "#ffc53e", "#a85c28", 4);

      pixelText("?", b.x + b.w / 2, b.y + b.h / 2, 23, "#ffffff", "center");

      rect(b.x + 5, b.y + 5, 5, 5, "#fff3a8");

    } else {

      outlineRect(b.x, b.y, b.w, b.h, "#dc9255", "#98502e", 3);

      rect(b.x, b.y + 15, b.w, 3, "#98502e");

      rect(b.x + b.w / 2 - 2, b.y, 4, 15, "#98502e");

    }

  }

  for (const p of pipes) {

    rect(p.x, p.y + 13, p.w, p.h - 13, "#168a37");

    rect(p.x + 7, p.y + 13, 10, p.h - 13, "#36cb50");

    rect(p.x + p.w - 12, p.y + 13, 6, p.h - 13, "#116a30");

    outlineRect(p.x - 7, p.y, p.w + 14, 18, "#32db4b", "#12672a", 4);

  }

}

/* =================== OBJETOS COLECCIONABLES =================== */

function drawCoin(x, y) {

  circle(x, y, 12, "#a96815");

  circle(x, y, 9, "#ffdb44");

  rect(x - 2, y - 6, 4, 12, "#fff4a2");

}

function drawChest(x, y) {

  outlineRect(x, y + 7, 31, 23, "#a85d2d", "#623622", 3);

  rect(x, y + 5, 31, 7, "#eaa34b");

  rect(x + 12, y + 10, 7, 9, "#ffdf62");

  rect(x + 3, y + 24, 5, 6, "#f5c261");

  rect(x + 23, y + 24, 5, 6, "#f5c261");

}

function drawItem(it) {

  if (it.taken) return;

  const bob = Math.sin(elapsed * 4 + it.x) * 3;

  const x = it.x;

  const y = it.y + bob;

  if (it.type === "coin") drawCoin(x, y);

  if (it.type === "flower") drawSunflower(x, y + 9, .8);

  if (it.type === "chest") drawChest(x - 15, y - 12);

  if (it.type === "heart") heart(x, y, 20);

  if (it.type === "star") pixelText("★", x, y, 23, "#ffe95d", "center");

}

/* =================== PUERTA =================== */

function drawDoor() {

  outlineRect(door.x, door.y, door.w, door.h, "#9a51bb", "#582c7e", 4);

  rect(door.x + 6, door.y + 7, door.w - 12, door.h - 7, "#6c3c98");

  rect(door.x + 12, door.y + 13, door.w - 24, door.h - 13, "#b582d4");

  circle(door.x + door.w - 10, door.y + 33, 3, "#ffe77a");

  pixelText("SALIDA", door.x + door.w / 2, door.y - 12, 10, "#ffffff", "center");

}

/* =================== PREPARACIÓN DE NIVELES =================== */

function createLevel() {

  levelCollected = new Set();

  const floor = GROUND_Y;

  platforms = [

    {x: 0, y: floor, w: W, h: 90},

    {x: 120, y: 360, w: 125, h: 22},

    {x: 300, y: 305, w: 125, h: 22},

    {x: 480, y: 350, w: 120, h: 22},

    {x: 645, y: 270, w: 130, h: 22}

  ];

  if (currentLevel === 1) {

    platforms = [

      {x: 0, y: floor, w: W, h: 90},

      {x: 95, y: 375, w: 115, h: 22},

      {x: 250, y: 320, w: 115, h: 22},

      {x: 410, y: 270, w: 115, h: 22},

      {x: 570, y: 330, w: 115, h: 22},

      {x: 735, y: 270, w: 115, h: 22}

    ];

  }

  if (currentLevel === 2) {

    platforms = [

      {x: 0, y: floor, w: W, h: 90},

      {x: 130, y: 365, w: 100, h: 22},

      {x: 280, y: 305, w: 110, h: 22},

      {x: 430, y: 355, w: 110, h: 22},

      {x: 575, y: 285, w: 110, h: 22},

      {x: 730, y: 335, w: 100, h: 22}

    ];

  }

  if (currentLevel === 3) {

    platforms = [

      {x: 0, y: floor, w: W, h: 90},

      {x: 110, y: 370, w: 105, h: 22},

      {x: 260, y: 310, w: 110, h: 22},

      {x: 410, y: 260, w: 105, h: 22},

      {x: 555, y: 320, w: 110, h: 22},

      {x: 700, y: 265, w: 110, h: 22}

    ];

  }

  bricks = [

    {x: 240, y: 210, w: 36, h: 36, type: "question"},

    {x: 276, y: 210, w: 36, h: 36, type: "brick"},

    {x: 312, y: 210, w: 36, h: 36, type: "question"},

    {x: 510, y: 240, w: 36, h: 36, type: "brick"},

    {x: 546, y: 240, w: 36, h: 36, type: "question"}

  ];

  pipes = [

    {x: 385, y: 390, w: 45, h: 60},

    {x: 765, y: 390, w: 45, h: 60}

  ];

  items = [];

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

    items.push({

      id: `coin-${currentLevel}-${i}`,

      type: "coin",

      x: 95 + i * 83,

      y: 330 - (i % 3) * 52,

      taken: false

    });

  }

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

    items.push({

      id: `flower-${currentLevel}-${i}`,

      type: "flower",

      x: 50 + i * 103,

      y: 405 - (i % 3) * 57,

      taken: false

    });

  }

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

    items.push({

      id: `chest-${currentLevel}-${i}`,

      type: "chest",

      x: 225 + i * 250,

      y: 235 + (i % 2) * 60,

      taken: false

    });

  }

  items.push({

    id: `star-${currentLevel}`,

    type: "star",

    x: 485,

    y: 205,

    taken: false

  });

  player.x = 40;

  player.y = 390;

  player.vx = 0;

  player.vy = 0;

  player.grounded = false;

  player.invulnerable = 0;

  bunny.x = 175;

  bunny.y = floor - 43;

  door = {

    x: 900,

    y: floor - 60,

    w: 38,

    h: 60

  };

  updateHUD();

  updateMission();

}

/* =================== INTERFAZ =================== */

function setStatus(message) {

  ui.status.textContent = message;

}

function updateHUD() {

  ui.hud.textContent =

    `MUNDO ${currentLevel + 1}/4 · 🪙 ${coins} · 🌻 ${flowers} · 🎁 ${chests} · ⭐ ${treasures}`;

}

function updateMission() {

  let completed = 0;

  if (coins >= 5) completed++;

  if (flowers >= 4) completed++;

  if (chests >= 1) completed++;

  if (player.x > bunny.x - 60 && player.x < bunny.x + 60) completed++;

  ui.mission.textContent =

    `MISIÓN DEL MUNDO: ${missions[currentLevel % missions.length]} · Progreso: ${completed}/4`;

}

function setInterface() {

  const playing = gameState === "playing";

  ui.menu.style.display = playing ? "none" : "flex";

  ui.game.style.display = playing ? "flex" : "none";

}

function startGame() {

  cancelAnimationFrame(animationId);

  currentLevel = 0;

  coins = 0;

  flowers = 0;

  chests = 0;

  treasures = 0;

  totalJumps = 0;

  totalDeaths = 0;

  gameState = "playing";

  elapsed = 0;

  lastTime = 0;

  createLevel();

  setInterface();

  setStatus(

    "¡La aventura ha comenzado! 🌻 Recoge objetos, explora y encuentra a Turrón."

  );

  animationId = requestAnimationFrame(loop);

}

function pauseGame() {

  if (gameState !== "playing") return;

  gameState = "paused";

  setInterface();

  setStatus("Juego en pausa. Respira, descansa y vuelve cuando quieras. ♡");

  draw();

}

function resumeGame() {

  if (gameState !== "paused") return;

  gameState = "playing";

  setInterface();

  lastTime = 0;

  animationId = requestAnimationFrame(loop);

}

function openPage(page) {

  gameState = page;

  cancelAnimationFrame(animationId);

  setInterface();

  draw();

  const pages = {

    letter: {

      title: "💌 PARA MI PRINCESITA",

      text: "No tienes que estar feliz todos los días. Puedes descansar, respirar y empezar despacito. Este mundo guarda girasoles para tus días grises. Mereces cariño, paciencia y tranquilidad. Ojalá esta pequeña aventura te regale una sonrisa. Turrón te espera al otro lado del jardín. ♡"

    },

    calm: {

      title: "🌙 TU RINCÓN TRANQUILO",

      text: "Inhala despacio… y suelta el aire. Relaja los hombros. No tienes que resolverlo todo ahora. Date permiso para descansar. Puedes ir a tu ritmo, un momento a la vez. Incluso los días nublados forman parte del cielo. ♡"

    },

    jokes: {

      title: "😂 CHISTES DE TURRÓN",

      text: getJoke()

    },

    shop: {

      title: "🎁 TIENDA DE RECOMPENSAS",

      text: "Intercambia tus monedas por sorpresas especiales."

    }

  };

  if (pages[page]) {

    setStatus(pages[page].title + " — " + pages[page].text);

  }

}

function getJoke() {

  jokesSeen++;

  return jokes[(jokesSeen - 1) % jokes.length];

}

function showMission() {

  if (gameState !== "playing") return;

  const mission = missions[currentLevel % missions.length];

  setStatus("📜 " + mission);

}

function interact() {

  if (gameState === "paused") {

    resumeGame();

    return;

  }

  if (gameState !== "playing") return;

  const nearBunny = Math.abs(player.x - bunny.x) < 75;

  const nearDoor = player.x > 840;

  if (nearBunny) {

    setStatus(

      "🐰🎀 Turrón: ¡Holiii, princesita! Soy tu conejita compañera. ¡Vamos a saltar, recoger monedas y encontrar todos los girasoles! ♡"

    );

    treasures++;

    updateHUD();

    return;

  }

  if (nearDoor) {

    completeLevel();

    return;

  }

  setStatus(

    "🌻 Sigue explorando. Salta a las plataformas, toca los bloques sorpresa y busca los cofres."

  );

}

/* =================== TIENDA =================== */

function buyReward(type) {

  if (gameState !== "shop") {

    setStatus("Visita la tienda desde el menú para ver las recompensas.");

    return;

  }

  const rewards = {

    flower: {cost: 3, message: "🌻 ¡Has comprado un girasol especial!"},

    heart: {cost: 5, message: "💗 ¡Has desbloqueado un corazón de amistad!"},

    star: {cost: 8, message: "⭐ ¡Una estrella para tu colección!"}

  };

  const reward = rewards[type];

  if (coins < reward.cost) {

    setStatus(`Necesitas ${reward.cost} monedas para comprar esta recompensa.`);

    return;

  }

  coins -= reward.cost;

  if (type === "flower") flowers++;

  if (type === "heart") treasures++;

  if (type === "star") treasures += 2;

  updateHUD();

  setStatus(reward.message + " Gracias por jugar. ♡");

}

/* =================== FÍSICA Y COLISIONES =================== */

function overlaps(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 jump() {

  if (gameState !== "playing") return;

  if (player.grounded) {

    player.vy = -player.jumpPower;

    player.grounded = false;

    totalJumps++;

  }

}

function update(dt) {

  elapsed += dt;

  player.vx = 0;

  if (keys.left) {

    player.vx = -player.speed;

    player.facing = -1;

  }

  if (keys.right) {

    player.vx = player.speed;

    player.facing = 1;

  }

  const oldX = player.x;

  const oldY = player.y;

  player.x += player.vx * dt;

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

  player.vy += 1300 * dt;

  player.y += player.vy * dt;

  player.grounded = false;

  // Plataformas: aterrizaje desde arriba.

  for (const p of platforms) {

    const oldBottom = oldY + player.h;

    const newBottom = player.y + player.h;

    const horizontal =

      player.x + player.w > p.x &&

      player.x < p.x + p.w;

    if (

      horizontal &&

      player.vy >= 0 &&

      oldBottom <= p.y + 4 &&

      newBottom >= p.y

    ) {

      player.y = p.y - player.h;

      player.vy = 0;

      player.grounded = true;

    }

  }

  // Los bloques y las tuberías también pueden sostener al jugador.

  for (const p of pipes) {

    const top = {x: p.x - 7, y: p.y, w: p.w + 14, h: 18};

    if (

      player.vy >= 0 &&

      player.x + player.w > top.x &&

      player.x < top.x + top.w &&

      oldY + player.h <= top.y + 4 &&

      player.y + player.h >= top.y

    ) {

      player.y = top.y - player.h;

      player.vy = 0;

      player.grounded = true;

    }

  }

  // Si el jugador cae, regresa al suelo.

  if (player.y > H + 80) {

    player.x = 40;

    player.y = GROUND_Y - player.h;

    player.vy = 0;

    totalDeaths++;

    setStatus("¡Vuelve a intentarlo! Turrón te espera en el jardín. 🐰");

  }

  // Recoger objetos.

  const playerBox = {

    x: player.x,

    y: player.y,

    w: player.w,

    h: player.h

  };

  for (const it of items) {

    if (it.taken) continue;

    const itemBox = {

      x: it.x - 12,

      y: it.y - 13,

      w: 25,

      h: 27

    };

    if (!overlaps(playerBox, itemBox)) continue;

    it.taken = true;

    levelCollected.add(it.id);

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

      coins++;

      setStatus("🪙 ¡Moneda conseguida! Una más para tu aventura.");

    }

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

      flowers++;

      setStatus("🌻 ¡Un girasol para tus días grises!");

    }

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

      chests++;

      coins += 5;

      setStatus("🎁 ¡Cofre encontrado! Ganaste cinco monedas.");

    }

    if (it.type === "star") {

      treasures++;

      coins += 3;

      setStatus("⭐ ¡Estrella especial! Has ganado tres monedas extra.");

    }

    updateHUD();

    updateMission();

  }

  // Abrir bloques sorpresa golpeándolos desde abajo.

  if (player.vy < 0) {

    for (const b of bricks) {

      if (b.type !== "question" || b.used) continue;

      const horizontal =

        player.x + player.w > b.x &&

        player.x < b.x + b.w;

      const oldTop = oldY;

      const newTop = player.y;

      if (

        horizontal &&

        oldTop >= b.y + b.h - 3 &&

        newTop <= b.y + b.h

      ) {

        b.used = true;

        coins += 2;

        updateHUD();

        setStatus("❔ ¡Bloque sorpresa! Encontraste dos monedas.");

        player.vy = 80;

      }

    }

  }

  // Salida del nivel.

  if (player.x > 845 && player.y + player.h > door.y - 20) {

    setStatus("🚪 ¡Has encontrado la salida! Pulsa E para continuar.");

  }

}

/* =================== PROGRESO =================== */

function completeLevel() {

  if (currentLevel < levels.length - 1) {

    const previous = levels[currentLevel].name;

    currentLevel++;

    createLevel();

    setStatus(

      "✨ ¡Mundo completado: " + previous +

      "! Bienvenida a " + levels[currentLevel].name + "."

    );

  } else {

    gameState = "ending";

    setInterface();

    setStatus(

      `🏆 ¡Aventura completada! Monedas: ${coins}, girasoles: ${flowers}, cofres: ${chests}. Gracias por jugar, princesita. ♡`

    );

    draw();

    return;

  }

  updateHUD();

}

/* =================== PANTALLAS =================== */

function drawMenu() {

  drawBackground();

  drawHill(0, 475, 350, 70, "#48c86d");

  drawHill(480, 475, 500, 75, "#38b963");

  drawGround();

  pixelText("EL MUNDO DE MI", W / 2, 100, 34, "#ffffff", "center");

  pixelText("PRINCESITA", W / 2, 145, 40, "#ffbde2", "center");

  pixelText("UNA AVENTURA CON TURRÓN", W / 2, 190, 17, "#ffffff", "center");

  pixelText("Y MILES DE GIRASOLES", W / 2, 217, 15, "#fff3a6", "center");

  drawPlayer(220, 380);

  drawTurron(700, 380);

  pixelText("🌻", 165, 350, 25, "#ffffff", "center");

  pixelText("PULSA EMPEZAR AVENTURA", W / 2, 280, 16, "#ffffff", "center");

  outlineRect(255, 305, 450, 50, "#ff8ec8", "#ffffff", 4);

  pixelText("★ AVENTURA ★", W / 2, 330, 17, "#ffffff", "center");

  pixelText("4 MUNDOS · MONEDAS · COFRES", W / 2, 475, 13, "#ffffff", "center");

  pixelText("UNA HISTORIA HECHA CON CARIÑO ♡", W / 2, 505, 12, "#fff0c9", "center");

}

function drawGame() {

  drawBackground();

  drawGround();

  drawPlatforms();

  drawBricks();

  for (const it of items) drawItem(it);

  drawDoor();

  drawTurron(bunny.x, bunny.y);

  drawPlayer(player.x, player.y);

  // HUD de videojuego.

  outlineRect(12, 12, 290, 58, "#2c2354cc", "#ffffff", 2);

  pixelText(`MUNDO ${currentLevel + 1}/4`, 25, 31, 14, "#ffffff");

  pixelText(`MONEDAS ${coins}`, 25, 52, 12, "#ffe66e");

  outlineRect(640, 12, 305, 58, "#2c2354cc", "#ffffff", 2);

  pixelText(`GIRASOLES ${flowers}`, 655, 31, 12, "#ffe66e");

  pixelText(`COFRES ${chests}`, 655, 52, 12, "#ffcae5");

  pixelText(levels[currentLevel].name, W / 2, 95, 13, "#ffffff", "center");

  // Mensaje de misión.

  outlineRect(12, 82, 240, 28, "#35264eaa", "#ffffff", 2);

  pixelText("BUSCA LA SALIDA →", 22, 96, 10, "#fff2a9");

  // Consejos.

  pixelText("E: HABLAR", 20, 130, 10, "#ffffff");

  pixelText("SALTA SOBRE LOS BLOQUES", W / 2, 520, 11, "#ffffff", "center");

}

function drawLetterPage() {

  drawBackground();

  drawGround();

  outlineRect(65, 95, 830, 350, "#fff0c9", "#c9854c", 8);

  outlineRect(78, 108, 804, 324, "#fff6db", "#f2c38b", 3);

  drawSunflower(110, 130, 1.2);

  drawSunflower(850, 130, 1.2);

  drawTurron(810, 340);

  pixelText("PARA MI PRINCESITA", W / 2, 155, 24, "#77345d", "center");

  const lines = [

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

    "Puedes descansar, respirar y empezar",

    "despacito.",

    "",

    "Este mundo guarda girasoles para",

    "tus días grises.",

    "",

    "Mereces cariño, paciencia y tranquilidad.",

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

    "Gracias por existir. ♡"

  ];

  lines.forEach((line, i) => {

    pixelText(line, 115, 205 + i * 22, 13, "#542b4c");

  });

  pixelText("UNA FLOR PARA CADA DÍA", W / 2, 415, 12, "#8a4a66", "center");

}

function drawCalmPage() {

  const gradient = ctx.createLinearGradient(0, 0, 0, H);

  gradient.addColorStop(0, "#131b4c");

  gradient.addColorStop(1, "#514e9a");

  rect(0, 0, W, H, gradient);

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

    const x = (i * 197 + 17) % W;

    const y = (i * 83 + 25) % 370;

    circle(x, y, i % 3 === 0 ? 2 : 1, "#ffffff");

  }

  pixelText("☾", 110, 85, 60, "#fff2a6");

  pixelText("TU RINCÓN TRANQUILO", W / 2, 140, 23, "#ffffff", "center");

  const lines = [

    "Inhala despacio…",

    "y suelta el aire.",

    "",

    "No tienes que resolverlo todo ahora.",

    "Puedes descansar.",

    "",

    "Un paso a la vez.",

    "Un momento a la vez.",

    "",

    "Incluso los días nublados",

    "forman parte del cielo. ♡"

  ];

  lines.forEach((line, i) => {

    pixelText(line, W / 2, 200 + i * 25, 14, "#ffffff", "center");

  });

  drawSunflower(80, 450, 1.4);

  drawSunflower(875, 450, 1.4);

  drawTurron(440, 400);

}

function drawJokesPage() {

  drawBackground();

  drawGround();

  pixelText("EL RINCÓN DE LOS CHISTES", W / 2, 100, 21, "#ffffff", "center");

  pixelText("JA, JA, JA", W / 2, 150, 27, "#fff0a5", "center");

  outlineRect(80, 190, 800, 160, "#fff0c9", "#ffb9df", 5);

  const joke = jokes[(jokesSeen - 1 + jokes.length) % jokes.length];

  wrapText(joke, W / 2, 255, 680, 25, "#552c62", "center");

  drawTurron(440, 370);

}

function drawShopPage() {

  drawBackground();

  drawGround();

  pixelText("TIENDA DE RECOMPENSAS", W / 2, 95, 23, "#ffffff", "center");

  pixelText(`TUS MONEDAS: ${coins}`, W / 2, 135, 16, "#ffe66e", "center");

  outlineRect(75, 175, 240, 190, "#fff0c9", "#ffffff", 4);

  outlineRect(360, 175, 240, 190, "#fff0c9", "#ffffff", 4);

  outlineRect(645, 175, 240, 190, "#fff0c9", "#ffffff", 4);

  pixelText("🌻", 195, 220, 28, "#8a4a66", "center");

  pixelText("GIRASOL", 195, 265, 13, "#8a4a66", "center");

  pixelText("3 MONEDAS", 195, 300, 11, "#8a4a66", "center");

  pixelText("♥", 480, 220, 35, "#ff78b6", "center");

  pixelText("CORAZÓN", 480, 265, 13, "#8a4a66", "center");

  pixelText("5 MONEDAS", 480, 300, 11, "#8a4a66", "center");

  pixelText("★", 765, 220, 35, "#ffbf35", "center");

  pixelText("ESTRELLA", 765, 265, 13, "#8a4a66", "center");

  pixelText("8 MONEDAS", 765, 300, 11, "#8a4a66", "center");

  pixelText("ELIGE UNA RECOMPENSA EN LOS BOTONES", W / 2, 420, 12, "#ffffff", "center");

}

function drawEnding() {

  drawBackground();

  drawGround();

  outlineRect(75, 95, 810, 350, "#fff0c9", "#ffffff", 6);

  pixelText("¡AVENTURA COMPLETADA!", W / 2, 140, 23, "#873b6b", "center");

  pixelText("HAS RECORRIDO LOS CUATRO MUNDOS", W / 2, 190, 13, "#873b6b", "center");

  pixelText(`MONEDAS: ${coins}`, W / 2, 245, 15, "#875021", "center");

  pixelText(`GIRASOLES: ${flowers}`, W / 2, 278, 15, "#875021", "center");

  pixelText(`COFRES: ${chests}`, W / 2, 311, 15, "#875021", "center");

  drawPlayer(360, 350);

  drawTurron(530, 350);

  pixelText("GRACIAS POR JUGAR, PRINCESITA. ♡", W / 2, 420, 13, "#873b6b", "center");

}

function drawPaused() {

  drawGame();

  rect(0, 0, W, H, "#1c163cbb");

  outlineRect(260, 200, 440, 130, "#fff0c9", "#ffffff", 5);

  pixelText("JUEGO EN PAUSA", W / 2, 240, 22, "#7a3767", "center");

  pixelText("PULSA E PARA CONTINUAR", W / 2, 285, 13, "#7a3767", "center");

}

function wrapText(text, x, y, maxWidth, lineHeight, color, align = "left") {

  ctx.save();

  ctx.fillStyle = color;

  ctx.font = 'bold 15px "Courier New", monospace';

  ctx.textAlign = align;

  const words = text.split(" ");

  let line = "";

  const lines = [];

  for (const word of words) {

    const test = line ? line + " " + word : word;

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

      lines.push(line);

      line = word;

    } else {

      line = test;

    }

  }

  if (line) lines.push(line);

  lines.forEach((l, i) => {

    ctx.fillText(l, x, y + i * lineHeight);

  });

  ctx.restore();

}

/* =================== RENDERIZADO =================== */

function draw() {

  if (gameState === "menu") {

    drawMenu();

  } else if (gameState === "playing") {

    drawGame();

  } else if (gameState === "paused") {

    drawPaused();

  } else if (gameState === "letter") {

    drawLetterPage();

  } else if (gameState === "calm") {

    drawCalmPage();

  } else if (gameState === "jokes") {

    drawJokesPage();

  } else if (gameState === "shop") {

    drawShopPage();

  } else if (gameState === "ending") {

    drawEnding();

  }

}

function loop(timestamp) {

  if (gameState !== "playing") {

    draw();

    return;

  }

  const dt = lastTime

    ? Math.min((timestamp - lastTime) / 1000, 0.033)

    : 0;

  lastTime = timestamp;

  update(dt);

  draw();

  animationId = requestAnimationFrame(loop);

}

/* =================== CONTROLES =================== */

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

  if (

    ["ArrowLeft", "ArrowRight", "ArrowUp", "Space"].includes(e.code)

  ) {

    e.preventDefault();

  }

  if (e.code === "ArrowLeft" || e.code === "KeyA") keys.left = true;

  if (e.code === "ArrowRight" || e.code === "KeyD") keys.right = true;

  if (

    ["Space", "ArrowUp", "KeyW"].includes(e.code) &&

    !e.repeat

  ) {

    jump();

  }

  if (e.code === "KeyE" || e.code === "Enter") {

    if (gameState === "paused") {

      resumeGame();

    } else {

      interact();

    }

  }

  if (e.code === "Escape") {

    if (gameState === "playing") {

      pauseGame();

    } else if (gameState === "paused") {

      resumeGame();

    } else {

      gameState = "menu";

      setInterface();

      setStatus("Turrón te espera cuando quieras volver. ♡");

      draw();

    }

  }

});

document.addEventListener("keyup", e => {

  if (e.code === "ArrowLeft" || e.code === "KeyA") keys.left = false;

  if (e.code === "ArrowRight" || e.code === "KeyD") keys.right = false;

});

function bindHoldButton(id, key) {

  const button = document.getElementById(id);

  const start = e => {

    e.preventDefault();

    keys[key] = true;

  };

  const stop = e => {

    e.preventDefault();

    keys[key] = false;

  };

  button.addEventListener("pointerdown", start);

  button.addEventListener("pointerup", stop);

  button.addEventListener("pointercancel", stop);

  button.addEventListener("pointerleave", stop);

  button.addEventListener("contextmenu", e => e.preventDefault());

}

bindHoldButton("leftButton", "left");

bindHoldButton("rightButton", "right");

document.getElementById("jumpButton").addEventListener("pointerdown", e => {

  e.preventDefault();

  jump();

});

/* =================== BOTONES DE RECOMPENSAS =================== */

document.getElementById("extraButtons").insertAdjacentHTML(

  "beforeend",

  `

  <button onclick="buyReward('flower')">Comprar 🌻 · 3</button>

  <button onclick="buyReward('heart')">Comprar ♥ · 5</button>

  <button onclick="buyReward('star')">Comprar ★ · 8</button>

  `

);

/* =================== INICIO =================== */

createLevel();

gameState = "menu";

setInterface();

draw();

</script>

</body>

</html>

Game Source: El mundo de mi princesita 🌻

Creator: LunarFlare61

Libraries: none

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