Mobile-emulator / infinityevo.html
ngiactcp
Add several new playable games to the application
61bdef8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Infinity Evolution Dino</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root {
--bg-color: #f7f7f7;
--text-color: #535353;
}
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 2s, color 2s;
font-family: 'Courier New', Courier, monospace;
touch-action: none;
user-select: none;
width: 100vw;
height: 100vh;
}
#game-container {
position: relative;
width: 100vw;
height: 380px;
background-color: transparent;
border-bottom: 2px solid var(--text-color);
overflow: hidden;
margin-top: 20px;
}
canvas { display: block; }
#ui-layer {
position: absolute;
top: 10px;
left: 20px;
right: 20px;
display: flex;
justify-content: space-between;
font-weight: bold;
pointer-events: none;
z-index: 5;
}
#overlay {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
display: flex; flex-direction: column; justify-content: center; align-items: center;
background: rgba(255, 255, 255, 0.8);
z-index: 20;
}
.btn {
padding: 12px 24px;
background: #535353;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 18px;
}
.evolution-popup {
position: absolute;
top: 40%; left: 50%; transform: translate(-50%, -50%);
background: #fff; border: 2px solid #535353; padding: 15px 25px;
border-radius: 10px; display: none; pointer-events: none;
z-index: 30;
animation: fadeUp 2s forwards;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
@keyframes fadeUp {
0% { opacity: 1; transform: translate(-50%, -50%); }
100% { opacity: 0; transform: translate(-50%, -200%); }
}
</style>
</head>
<body>
<div id="game-container">
<div id="ui-layer">
<div>STAGE: <span id="evo-level">Baby</span></div>
<div>HI <span id="high-score">000000</span> | <span id="score">000000</span></div>
</div>
<div id="overlay">
<h1 id="main-title" class="text-3xl font-bold mb-2">INFINITY EVO</h1>
<p id="sub-title" class="mb-4 text-sm text-gray-500 text-center px-4">Tap anywhere to Jump<br>Swipe/Pull Down to Duck</p>
<button id="start-btn" class="btn">BEGIN EVOLUTION</button>
</div>
<div id="evo-msg" class="evolution-popup font-bold text-center">
<div class="text-green-600 text-xl">EVOLVED!</div>
<div id="evo-name-popup" class="text-gray-700"></div>
</div>
<canvas id="gameCanvas"></canvas>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const highScoreEl = document.getElementById('high-score');
const evoLevelEl = document.getElementById('evo-level');
const overlay = document.getElementById('overlay');
const startBtn = document.getElementById('start-btn');
const evoMsg = document.getElementById('evo-msg');
const evoNamePopup = document.getElementById('evo-name-popup');
// Balanced Constants
const GRAVITY = 0.5;
const JUMP_FORCE = -11;
const GROUND_Y_OFFSET = 40;
const INITIAL_SPEED = 5;
const SPEED_INCREMENT = 0.0003;
let isPlaying = false;
let score = 0;
let highScore = 0;
let speed = INITIAL_SPEED;
let animationFrameId;
let isNight = false;
// EXTENDED EVOLUTIONS
const evolutions = [
{ threshold: 0, name: "Baby", color: "#535353", size: 0.8 },
{ threshold: 500, name: "Teen", color: "#3498db", size: 1.0 },
{ threshold: 2000, name: "Hunter", color: "#e67e22", size: 1.1 },
{ threshold: 5000, name: "Titan", color: "#c0392b", size: 1.2 },
{ threshold: 10000, name: "Ancient", color: "#27ae60", size: 1.3 },
{ threshold: 25000, name: "Celestial", color: "#9b59b6", size: 1.4 },
{ threshold: 50000, name: "Godzilla", color: "#1abc9c", size: 1.5 },
{ threshold: 100000, name: "Eternal", color: "#f1c40f", size: 1.6 }
];
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSfx(f, t, d) {
try {
if (audioCtx.state === 'suspended') audioCtx.resume();
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.type = t; o.frequency.value = f;
g.gain.setValueAtTime(0.05, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + d);
o.connect(g); g.connect(audioCtx.destination);
o.start(); o.stop(audioCtx.currentTime + d);
} catch(e) {}
}
class Dino {
constructor() {
this.baseW = 44;
this.baseH = 47;
this.x = 60;
this.y = 0;
this.dy = 0;
this.isJumping = false;
this.isDucking = false;
this.tick = 0;
this.currentEvo = evolutions[0];
}
update(currentScore) {
// Check for new evolution
const nextEvo = [...evolutions].reverse().find(e => currentScore >= e.threshold);
if (nextEvo && nextEvo.name !== this.currentEvo.name) {
this.currentEvo = nextEvo;
evoLevelEl.innerText = this.currentEvo.name;
evoNamePopup.innerText = this.currentEvo.name + " Stage";
evoMsg.style.display = 'block';
setTimeout(() => { evoMsg.style.display = 'none'; }, 2000);
playSfx(800, 'sine', 0.3);
}
this.width = this.baseW * this.currentEvo.size;
this.height = this.baseH * this.currentEvo.size;
if (this.isJumping) {
this.y += this.dy;
this.dy += GRAVITY;
const floor = canvas.height - this.height - GROUND_Y_OFFSET;
if (this.y > floor) {
this.y = floor;
this.dy = 0;
this.isJumping = false;
}
} else {
const drawH = this.isDucking ? this.height * 0.6 : this.height;
this.y = canvas.height - drawH - GROUND_Y_OFFSET;
}
}
draw() {
const drawH = this.isDucking ? this.height * 0.6 : this.height;
ctx.fillStyle = this.currentEvo.color;
// Draw Body
ctx.beginPath();
ctx.roundRect(this.x, this.y, this.width, drawH, 6);
ctx.fill();
// Eye
ctx.fillStyle = isNight ? '#000' : '#fff';
ctx.fillRect(this.x + this.width - 12, this.y + 10, 5, 5);
// Legs
if (!this.isJumping) {
this.tick++;
const legToggle = Math.floor(this.tick / 6) % 2;
ctx.fillStyle = this.currentEvo.color;
if (legToggle) {
ctx.fillRect(this.x + 5, this.y + drawH, 12, 6);
} else {
ctx.fillRect(this.x + this.width - 17, this.y + drawH, 12, 6);
}
}
}
jump() {
if (!this.isJumping && !this.isDucking) {
this.isJumping = true;
this.dy = JUMP_FORCE;
playSfx(400, 'square', 0.1);
}
}
duck(state) {
if (!this.isJumping) {
this.isDucking = state;
}
}
}
class Obstacle {
constructor() {
this.type = (score > 1000 && Math.random() < 0.25) ? 'bird' : 'cactus';
this.width = this.type === 'cactus' ? 20 + Math.random() * 30 : 45;
this.height = this.type === 'cactus' ? 30 + Math.random() * 40 : 25;
this.x = canvas.width;
// High birds require ducking, low cacti require jumping
this.y = this.type === 'cactus'
? canvas.height - this.height - GROUND_Y_OFFSET
: canvas.height - GROUND_Y_OFFSET - 85;
}
update() {
this.x -= speed;
ctx.fillStyle = isNight ? '#ecf0f1' : '#535353';
ctx.beginPath();
ctx.roundRect(this.x, this.y, this.width, this.height, 4);
ctx.fill();
}
}
let dino;
let obstacles = [];
let nextObstacleTimer = 0;
function drawEnvironment() {
// Day/Night Cycle Logic
const cyclePoints = 2000;
const cycleProgress = (score % cyclePoints) / cyclePoints;
isNight = cycleProgress > 0.5;
if (isNight) {
document.body.style.backgroundColor = "#2c3e50";
document.body.style.color = "#ecf0f1";
overlay.style.background = "rgba(0,0,0,0.8)";
} else {
document.body.style.backgroundColor = "#f7f7f7";
document.body.style.color = "#535353";
overlay.style.background = "rgba(255,255,255,0.8)";
}
// Draw Sun or Moon
const centerX = canvas.width * 0.8;
const centerY = 80;
ctx.beginPath();
if (!isNight) {
// Sun
ctx.fillStyle = "#f1c40f";
ctx.arc(centerX, centerY, 30, 0, Math.PI * 2);
ctx.fill();
} else {
// Moon
ctx.fillStyle = "#f1c40f";
ctx.arc(centerX, centerY, 25, 0, Math.PI * 2);
ctx.fill();
// Moon shadow
ctx.fillStyle = isNight ? "#2c3e50" : "#f7f7f7";
ctx.beginPath();
ctx.arc(centerX + 10, centerY - 5, 25, 0, Math.PI * 2);
ctx.fill();
}
// Ground
ctx.strokeStyle = isNight ? "#555" : "#ccc";
ctx.beginPath();
ctx.moveTo(0, canvas.height - GROUND_Y_OFFSET);
ctx.lineTo(canvas.width, canvas.height - GROUND_Y_OFFSET);
ctx.stroke();
}
function init() {
canvas.width = window.innerWidth;
canvas.height = 380;
dino = new Dino();
obstacles = [];
score = 0;
speed = INITIAL_SPEED;
nextObstacleTimer = 60;
}
function gameOver() {
isPlaying = false;
cancelAnimationFrame(animationFrameId);
playSfx(100, 'sawtooth', 0.5);
overlay.style.display = 'flex';
document.getElementById('main-title').innerText = "EXTINCT!";
document.getElementById('sub-title').innerText = `Survived as ${dino.currentEvo.name}\nScore: ${Math.floor(score)}`;
startBtn.innerText = "EVOLVE AGAIN";
}
function update() {
if (!isPlaying) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawEnvironment();
dino.update(score);
dino.draw();
if (nextObstacleTimer <= 0) {
obstacles.push(new Obstacle());
nextObstacleTimer = 70 + Math.random() * 100 / (speed / 5);
}
nextObstacleTimer--;
for (let i = obstacles.length - 1; i >= 0; i--) {
const obs = obstacles[i];
obs.update();
// Collision
const dH = dino.isDucking ? dino.height * 0.6 : dino.height;
const pad = 8;
if (dino.x + pad < obs.x + obs.width &&
dino.x + dino.width - pad > obs.x &&
dino.y + pad < obs.y + obs.height &&
dino.y + dH - pad > obs.y) {
gameOver();
return;
}
if (obs.x + obs.width < 0) obstacles.splice(i, 1);
}
score += 0.2;
scoreEl.innerText = Math.floor(score).toString().padStart(6, '0');
if (score > highScore) {
highScore = Math.floor(score);
highScoreEl.innerText = highScore.toString().padStart(6, '0');
}
speed += SPEED_INCREMENT;
animationFrameId = requestAnimationFrame(update);
}
function startGame() {
if (audioCtx.state === 'suspended') audioCtx.resume();
init();
isPlaying = true;
overlay.style.display = 'none';
update();
}
// CONTROL LOGIC
const triggerJump = () => {
if (!isPlaying) startGame();
else dino.jump();
};
// Touch Interaction
let touchStartY = 0;
let isDuckingByTouch = false;
document.body.addEventListener('touchstart', (e) => {
touchStartY = e.touches[0].clientY;
// Always treat a tap as a jump first
triggerJump();
}, { passive: false });
document.body.addEventListener('touchmove', (e) => {
const currentY = e.touches[0].clientY;
const diffY = currentY - touchStartY;
// Swiping down to duck
if (diffY > 30) {
dino.duck(true);
isDuckingByTouch = true;
} else if (diffY < -10) {
// Ignore small movements or upward swipes
dino.duck(false);
isDuckingByTouch = false;
}
e.preventDefault();
}, { passive: false });
document.body.addEventListener('touchend', () => {
if (isDuckingByTouch) {
dino.duck(false);
isDuckingByTouch = false;
}
});
// Mouse/Keys
window.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.code === 'ArrowUp') { e.preventDefault(); triggerJump(); }
if (e.code === 'ArrowDown') { e.preventDefault(); dino.duck(true); }
});
window.addEventListener('keyup', (e) => { if (e.code === 'ArrowDown') dino.duck(false); });
window.addEventListener('mousedown', (e) => {
// Only handle left clicks as jumps if not a touch-enabled browser (prevents double triggers)
if (e.button === 0 && !('ontouchstart' in window)) {
triggerJump();
}
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = 380;
});
init();
// Ensure start button works even if listeners fail
startBtn.onclick = startGame;
</script>
</body>
</html>