Skip to content

Commit bf3fbc4

Browse files
committed
Content update
1 parent 4ac812b commit bf3fbc4

1 file changed

Lines changed: 352 additions & 0 deletions

File tree

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
<!DOCTYPE html>
2+
<html lang="es">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Rubber Rocket Arena - Procedural Loop Edition</title>
6+
<style>
7+
body { margin: 0; background: #000; overflow: hidden; font-family: 'Segoe UI', Tahoma, sans-serif; color: white; user-select: none;}
8+
canvas { display: block; background: #0a0a0f; margin: 0 auto; }
9+
10+
.ui { position: absolute; top: 15px; left: 15px; background: rgba(0,0,0,0.85); padding: 15px; border-radius: 12px; border: 1px solid #444; width: 220px; pointer-events: none; }
11+
.stat-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; font-size: 13px; font-weight: bold; }
12+
.bar-container { width: 130px; height: 12px; background: #222; border-radius: 6px; overflow: hidden; border: 1px solid #555; }
13+
.bar-fill { height: 100%; transition: width 0.15s ease-out; }
14+
.level-card { text-align: center; margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 10px; }
15+
.kdr-box { font-size: 16px; font-weight: bold; color: #ffcc00; text-align: center; margin-top: 5px; }
16+
17+
.sandbox-panel {
18+
position: absolute; top: 15px; right: 15px;
19+
background: rgba(20, 20, 25, 0.95); padding: 15px;
20+
border-radius: 12px; border: 1px solid #00ffaa;
21+
width: 260px; pointer-events: all; font-size: 12px;
22+
}
23+
.sandbox-panel h3 { margin: 0 0 10px 0; color: #00ffaa; text-align: center; font-size: 14px; text-transform: uppercase; }
24+
.control-group { margin-bottom: 10px; }
25+
.control-group label { display: block; margin-bottom: 3px; color: #ccc; }
26+
.control-group input { width: 100%; cursor: pointer; accent-color: #00ffaa; }
27+
.val-display { float: right; color: #00ffaa; font-family: monospace; }
28+
</style>
29+
</head>
30+
<body>
31+
<div class="ui">
32+
<div class="level-card">
33+
<h2 id="level-title" style="margin:0; color:#00ffaa; font-size: 18px;">NIVEL 1</h2>
34+
<div style="font-size:10px; color:#888;">OBJETIVO: <span id="goal-text">10</span> BAJAS</div>
35+
</div>
36+
<div class="stat-row"><span>HP</span><div class="bar-container"><div id="hp-bar" class="bar-fill" style="background: linear-gradient(90deg, #ff416c, #ff4b2b); width:100%;"></div></div></div>
37+
<div class="stat-row"><span>JET</span><div class="bar-container"><div id="jet-bar" class="bar-fill" style="background: linear-gradient(90deg, #00d2ff, #3a7bd5); width:100%;"></div></div></div>
38+
<div class="kdr-box">K: <span id="k-val">0</span> | D: <span id="d-val">0</span> | KDR: <span id="kdr-val">0.00</span><br><h5>CONTROLES: WASD + ESPACIO<h5></div>
39+
</div>
40+
41+
<div class="sandbox-panel">
42+
<h3>Physics Sandbox</h3>
43+
<div class="control-group"><label>Gravedad <span class="val-display" id="v-grav">0.60</span></label><input type="range" min="0" max="2" step="0.05" value="0.6" oninput="updatePhys('gravity', this.value, 'v-grav')"></div>
44+
<div class="control-group"><label>Elasticidad <span class="val-display" id="v-elast">0.80</span></label><input type="range" min="0" max="1.5" step="0.05" value="0.8" oninput="updatePhys('elasticity', this.value, 'v-elast')"></div>
45+
<div class="control-group"><label>Fricción Aire <span class="val-display" id="v-fric">0.985</span></label><input type="range" min="0.9" max="1" step="0.005" value="0.985" oninput="updatePhys('friction', this.value, 'v-fric')"></div>
46+
<div class="control-group"><label>Gravedad Proyectil <span class="val-display" id="v-rgrav">0.15</span></label><input type="range" min="-0.1" max="1" step="0.05" value="0.15" oninput="updatePhys('rocketGravity', this.value, 'v-rgrav')"></div>
47+
<div class="control-group"><label>Velocidad Proyectil <span class="val-display" id="v-rspd">25</span></label><input type="range" min="5" max="60" step="1" value="25" oninput="updatePhys('rocketSpeed', this.value, 'v-rspd')"></div>
48+
</div>
49+
50+
<canvas id="gameCanvas"></canvas>
51+
52+
<script>
53+
const canvas = document.getElementById('gameCanvas');
54+
const ctx = canvas.getContext('2d');
55+
canvas.width = window.innerWidth;
56+
canvas.height = window.innerHeight;
57+
58+
// --- GENERADOR PSEUDO-ALEATORIO (Seed) ---
59+
let seed = 12345;
60+
function mulberry32() {
61+
return function() {
62+
let t = seed += 0x6D2B79F5;
63+
t = Math.imul(t ^ t >>> 15, t | 1);
64+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
65+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
66+
}
67+
}
68+
let random = mulberry32();
69+
70+
const PHYS = {
71+
gravity: 0.6, friction: 0.985, elasticity: 0.8,
72+
squishRecover: 0.12, baseRadius: 25,
73+
rocketGravity: 0.15, rocketSpeed: 25
74+
};
75+
76+
function updatePhys(param, val, displayId) {
77+
PHYS[param] = parseFloat(val);
78+
document.getElementById(displayId).innerText = PHYS[param].toFixed(param === 'rocketSpeed' ? 0 : 2);
79+
}
80+
81+
let gameState = {
82+
level: 1, kills: 0, deaths: 0,
83+
targetKills: 10, enemiesCount: 2, isTransitioning: false,
84+
shake: 0
85+
};
86+
87+
const keys = {};
88+
let entities = [];
89+
let rockets = [];
90+
let particles = [];
91+
let map = [];
92+
93+
// --- CLASE PLATAFORMA MÓVIL ---
94+
class Platform {
95+
constructor(x1, y1, x2, y2, w, h, speed) {
96+
this.x1 = x1; this.y1 = y1;
97+
this.x2 = x2; this.y2 = y2;
98+
this.w = w; this.h = h;
99+
this.speed = speed;
100+
this.progress = 0;
101+
this.dir = 1;
102+
this.x = x1; this.y = y1;
103+
}
104+
update() {
105+
this.progress += this.speed * this.dir;
106+
if(this.progress >= 1 || this.progress <= 0) this.dir *= -1;
107+
// Interpolación lineal (LERP) para el movimiento suave
108+
this.x = this.x1 + (this.x2 - this.x1) * this.progress;
109+
this.y = this.y1 + (this.y2 - this.y1) * this.progress;
110+
}
111+
draw() {
112+
ctx.fillStyle = '#1e1e2f';
113+
ctx.fillRect(this.x, this.y, this.w, this.h);
114+
ctx.strokeStyle = "#3a3a5a";
115+
ctx.lineWidth = 2;
116+
ctx.strokeRect(this.x, this.y, this.w, this.h);
117+
}
118+
}
119+
120+
function generateProceduralLevel(levelNum) {
121+
seed = 12345 + (levelNum * 777); // Cambia la semilla por nivel
122+
random = mulberry32();
123+
map = [];
124+
125+
// Suelo estático
126+
map.push(new Platform(0, canvas.height - 40, 0, canvas.height - 40, canvas.width, 40, 0));
127+
128+
// Generar plataformas según el nivel
129+
const platformCount = 3 + levelNum;
130+
for(let i=0; i<platformCount; i++) {
131+
let w = 50 + random() * 100;
132+
let h = 20;
133+
let x1 = random() * (canvas.width - w);
134+
let y1 = 150 + random() * (canvas.height - 300);
135+
136+
// El loop final (x2, y2)
137+
let x2 = x1 + (random() - 0.5) * 300;
138+
let y2 = y1 + (random() - 0.5) * 200;
139+
140+
// Limitar bordes
141+
x2 = Math.max(0, Math.min(canvas.width - w, x2));
142+
y2 = Math.max(100, Math.min(canvas.height - 100, y2));
143+
144+
let speed = 0.002 + random() * 0.008 + (levelNum * 0.0005);
145+
if (levelNum <= 2){
146+
speed = 0;
147+
}
148+
map.push(new Platform(x1, y1, x2, y2, w, h, speed));
149+
}
150+
}
151+
152+
// (Las clases Particle, Rocket y Soldier se mantienen con ajustes para detectar 'map[i].x/y' dinámico)
153+
154+
class Particle {
155+
constructor(x, y, vx, vy, color, life, size, bouncy = false) {
156+
this.x = x; this.y = y; this.vx = vx; this.vy = vy;
157+
this.color = color; this.life = life; this.maxLife = life;
158+
this.size = size; this.bouncy = bouncy;
159+
}
160+
update() {
161+
if(this.bouncy) this.vy += PHYS.gravity;
162+
this.x += this.vx; this.y += this.vy;
163+
if(this.bouncy) {
164+
if(this.y > canvas.height-40) { this.vy *= -PHYS.elasticity * 0.7; this.y = canvas.height-41; }
165+
}
166+
this.life--;
167+
}
168+
draw() {
169+
ctx.globalAlpha = Math.max(0, this.life / this.maxLife);
170+
ctx.fillStyle = this.color;
171+
ctx.fillRect(this.x, this.y, this.size, this.size);
172+
ctx.globalAlpha = 1;
173+
}
174+
}
175+
176+
class Rocket {
177+
constructor(x, y, tx, ty, owner) {
178+
this.x = x; this.y = y; this.owner = owner;
179+
const angle = Math.atan2(ty - y, tx - x);
180+
this.vx = Math.cos(angle) * PHYS.rocketSpeed;
181+
this.vy = Math.sin(angle) * PHYS.rocketSpeed;
182+
this.dead = false;
183+
}
184+
update() {
185+
this.vy += PHYS.rocketGravity;
186+
this.x += this.vx; this.y += this.vy;
187+
if(this.y < 0 || this.y > canvas.height - 40 || this.x < 0 || this.x > canvas.width) {
188+
this.explode(); return;
189+
}
190+
for(let p of map) {
191+
if(this.x > p.x && this.x < p.x + p.w && this.y > p.y && this.y < p.y + p.h) {
192+
this.explode(); return;
193+
}
194+
}
195+
for(let e of entities) {
196+
if(e !== this.owner && Math.hypot(e.x - this.x, e.y - this.y) < e.radius) {
197+
this.explode(); return;
198+
}
199+
}
200+
}
201+
explode() {
202+
this.dead = true;
203+
for(let i=0; i<15; i++) particles.push(new Particle(this.x, this.y, (Math.random()-0.5)*12, (Math.random()-0.5)*12, '#ff9900', 20, 4));
204+
entities.forEach(e => {
205+
let dist = Math.hypot(e.x - this.x, e.y - this.y);
206+
if(dist < 120) {
207+
let pct = (1 - dist/120);
208+
let ang = Math.atan2(e.y - this.y, e.x - this.x);
209+
e.vx += Math.cos(ang) * pct * 32 * (PHYS.baseRadius / e.radius);
210+
e.vy += Math.sin(ang) * pct * 32 * (PHYS.baseRadius / e.radius);
211+
e.hp -= pct * 115;
212+
if(e.hp <= 0 && this.owner.isPlayer) updateStats(true);
213+
}
214+
});
215+
}
216+
}
217+
218+
class Soldier {
219+
constructor(x, y, color, isPlayer = false) {
220+
this.x = x; this.y = y; this.color = color;
221+
this.vx = 0; this.vy = 0; this.hp = 100; this.jet = 100;
222+
this.isPlayer = isPlayer; this.lastShot = 0;
223+
this.radius = PHYS.baseRadius;
224+
this.sqX = 1; this.sqY = 1;
225+
}
226+
update() {
227+
this.radius = Math.max(6, (this.hp / 100) * PHYS.baseRadius);
228+
this.vy += PHYS.gravity;
229+
if(this.isPlayer) {
230+
if(keys['KeyA']) this.vx -= 1.3; if(keys['KeyD']) this.vx += 1.3;
231+
if(keys['KeyW'] && this.jet > 0) { this.vy -= 1.8; this.jet -= 2.8; }
232+
else { this.jet = Math.min(100, this.jet + 1.4); }
233+
if(keys['Space']) this.shoot();
234+
} else {
235+
if(Math.random() < 0.02) this.shoot();
236+
this.vx += (player.x > this.x ? 0.35 : -0.35);
237+
}
238+
this.vx *= PHYS.friction;
239+
this.x += this.vx; this.y += this.vy;
240+
241+
if(this.y + this.radius > canvas.height - 40) {
242+
this.y = canvas.height - 40 - this.radius; this.vy *= -PHYS.elasticity; this.sqY = 0.5; this.sqX = 1.4;
243+
} else if(this.y - this.radius < 0) {
244+
this.y = this.radius; this.vy *= -PHYS.elasticity; this.sqY = 0.5; this.sqX = 1.4;
245+
}
246+
if(this.x - this.radius < 0) { this.x = this.radius; this.vx *= -PHYS.elasticity; }
247+
if(this.x + this.radius > canvas.width) { this.x = canvas.width - this.radius; this.vx *= -PHYS.elasticity; }
248+
249+
map.forEach(p => {
250+
if(this.x + this.radius > p.x && this.x - this.radius < p.x + p.w && this.y + this.radius > p.y && this.y - this.radius < p.y + p.h) {
251+
if(this.y < p.y + p.h/2) {
252+
this.y = p.y - this.radius;
253+
this.vy *= -PHYS.elasticity;
254+
// Si la plataforma se mueve, arrastramos levemente al personaje
255+
this.x += (p.x2 - p.x1) * p.speed * p.dir;
256+
} else {
257+
this.y = p.y + p.h + this.radius; this.vy *= -PHYS.elasticity;
258+
}
259+
}
260+
});
261+
262+
this.sqX += (1 - this.sqX) * PHYS.squishRecover;
263+
this.sqY += (1 - this.sqY) * PHYS.squishRecover;
264+
265+
if(this.hp <= 0) this.die();
266+
}
267+
shoot() {
268+
if(Date.now() - this.lastShot < 650) return;
269+
let tx = this.isPlayer ? (entities.find(e=>!e.isPlayer)?.x || this.x+100) : player.x;
270+
let ty = this.isPlayer ? (entities.find(e=>!e.isPlayer)?.y || this.y) : player.y;
271+
rockets.push(new Rocket(this.x, this.y, tx, ty, this));
272+
this.lastShot = Date.now();
273+
}
274+
die() {
275+
for(let i=0; i<30; i++) particles.push(new Particle(this.x, this.y, (Math.random()-0.5)*15, (Math.random()-0.5)*15, '#ff0000', 15, Math.random()*10));
276+
if(this.isPlayer) {
277+
gameState.shake = 35; updateStats(false);
278+
this.hp = 100; this.x = canvas.width/2; this.y = 100; this.vx = 0; this.vy = 0;
279+
} else {
280+
this.hp = 100; this.x = Math.random()*canvas.width; this.y = -100;
281+
}
282+
}
283+
draw() {
284+
ctx.save();
285+
ctx.translate(this.x, this.y);
286+
ctx.scale(this.sqX, this.sqY);
287+
ctx.fillStyle = this.color;
288+
ctx.beginPath(); ctx.arc(0, 0, this.radius, 0, Math.PI*2); ctx.fill();
289+
ctx.strokeStyle = "white"; ctx.stroke();
290+
ctx.restore();
291+
}
292+
}
293+
294+
function updateStats(isKill) {
295+
if(isKill) gameState.kills++; else gameState.deaths++;
296+
document.getElementById('k-val').innerText = gameState.kills;
297+
document.getElementById('d-val').innerText = gameState.deaths;
298+
let kdr = gameState.deaths === 0 ? gameState.kills : (gameState.kills / gameState.deaths);
299+
document.getElementById('kdr-val').innerText = kdr.toFixed(2);
300+
if(gameState.kills >= gameState.targetKills && !gameState.isTransitioning) nextLevel();
301+
}
302+
303+
function nextLevel() {
304+
gameState.isTransitioning = true;
305+
gameState.level++;
306+
gameState.targetKills += 5;
307+
gameState.enemiesCount++;
308+
document.getElementById('level-title').innerText = "Nivel " + gameState.level;
309+
document.getElementById('goal-text').innerText = gameState.targetKills;
310+
initLevel();
311+
setTimeout(() => { gameState.isTransitioning = false; }, 1000);
312+
}
313+
314+
const player = new Soldier(canvas.width/2, 100, '#00ff00', true);
315+
316+
function initLevel() {
317+
generateProceduralLevel(gameState.level);
318+
entities = [player];
319+
for(let i=0; i < gameState.enemiesCount; i++) {
320+
entities.push(new Soldier(random()*canvas.width, -100, '#ff4444'));
321+
}
322+
}
323+
324+
window.addEventListener('keydown', e => keys[e.code] = true);
325+
window.addEventListener('keyup', e => keys[e.code] = false);
326+
327+
function loop() {
328+
ctx.save();
329+
if (gameState.shake > 0) {
330+
ctx.translate((Math.random()-0.5)*gameState.shake, (Math.random()-0.5)*gameState.shake);
331+
gameState.shake *= 0.85;
332+
}
333+
ctx.clearRect(-100, -100, canvas.width+200, canvas.height+200);
334+
335+
map.forEach(p => { p.update(); p.draw(); });
336+
rockets = rockets.filter(r => !r.dead);
337+
rockets.forEach(r => { r.update(); ctx.fillStyle="white"; ctx.fillRect(r.x-3, r.y-3, 6, 6); });
338+
particles = particles.filter(p => p.life > 0);
339+
particles.forEach(p => { p.update(); p.draw(); });
340+
entities.forEach(e => { e.update(); e.draw(); });
341+
342+
ctx.restore();
343+
document.getElementById('hp-bar').style.width = player.hp + '%';
344+
document.getElementById('jet-bar').style.width = player.jet + '%';
345+
requestAnimationFrame(loop);
346+
}
347+
348+
initLevel();
349+
loop();
350+
</script>
351+
</body>
352+
</html>

0 commit comments

Comments
 (0)