🏛️ Campaña: Llegada de Cortés
1519 - Preparando defensas costeras
🌊 Oleada 1 / 5
🦅 Tenochtitlán
1200 / 1200
⚜️ Fuerte Español
1200 / 1200
📋 Información de Unidad
Selecciona una unidad para ver detalles
⚔️ Ventajas/Débiles:
🗺️ Mapa
🌽 Tributo
50
+2.5/s
⚡ Moral
100
%

Victoria Mexica

Has expulsado a los invasores del Anáhuac. El legado de Cuauhtémoc perdura.

⏸️ Juego Pausado

El tiempo se detiene, pero la resistencia continúa en tu mente.

Presiona P o ESC para reanudar

⚙️ Configuración

Ajusta la experiencia a tu preferencia

🔊 Volumen 40%
🎨 Efectos Visuales
⚡ Dificultad IA
🎯 Mostrar Consejos

🌎 Historia & Créditos

Este juego rinde homenaje a los pueblos originarios de Mesoamérica y su resistencia frente a la conquista. La narrativa alterna explora posibilidades históricas, no busca reescribir la historia real.

📚 Contexto Histórico

1519: Llegada de Hernán Cortés a las costas de lo que hoy es Veracruz.

1520: "Noche Triste" - Los mexicas expulsan temporalmente a los españoles de Tenochtitlán.

1521: Sitio final de Tenochtitlán tras meses de resistencia heroica.

Nota: Las alianzas, batallas y resultados presentados en modo "alternativo" son ficción histórica con fines educativos y de entretenimiento.

🎨 Inspiración Visual

Estilo stickman con elementos de códices aztecas (Mendoza, Florentino). Colores inspirados en pigmentos naturales: cochinilla (rojo), añil (azul), tierra (ocre).

🔧 Desarrollo Técnico

Motor propio en JavaScript vanilla + Canvas 2D. Sin frameworks externos. Código abierto para fines educativos.

🎓 Tutorial: Primeros Pasos

Paso 1 de 5
// Analizar tácticas: ¿el jugador ataca temprano? ¿defiende? const earlyAggression = world.time < 60 && playerUnits.some(u => u.x > world.width * 0.6); if (earlyAggression) stats.tactics.push('early_rush'); const defensivePosture = playerUnits.filter(u => u.x < world.width * 0.4).length > playerUnits.length * 0.7; if (defensivePosture) stats.tactics.push('turtle'); return stats; }, // Seleccionar estrategia basada en análisis selectStrategy(playerStats) { if (playerStats.aggression > 0.6) return 'defensive'; if (playerStats.composition.ranged > (playerStats.composition.melee || 0) * 2) return 'aggressive'; if (playerStats.tactics.includes('early_rush')) return 'counter'; if (Object.keys(playerStats.composition).length > 3) return 'balanced'; return 'balanced'; }, // Generar oleada inteligente generateWave(world, waveNum) { const playerStats = AISystem.analyzePlayerBehavior(world); const strategy = this.strategies[this.selectStrategy(playerStats)]; const baseCount = 3 + Math.floor(waveNum * 0.8); const units = []; // Distribuir unidades según estrategia Object.entries(strategy).forEach(([role, weight]) => { const count = Math.floor(baseCount * weight); const types = Object.entries(UNIT_DATA) .filter(([k,v]) => v.role === role && !k.includes('minero')) .map(([k,v]) => k); for (let i = 0; i < count && types.length > 0; i++) { units.push(Utils.randChoice(types)); } }); return units; }, // Comportamiento táctico de grupo updateGroupBehavior(units, world) { const groups = this.groupByRole(units); // Ranged se posiciona detrás de melee if (groups.melee?.length && groups.ranged?.length) { groups.ranged.forEach(r => { const front = groups.melee[Math.floor(Math.random() * groups.melee.length)]; if (front && r.x < front.x - 40) { r.vx = Math.max(r.vx || 0, 0.3); } }); } // Foco en héroes o unidades élite const priorityTargets = world.entities.filter(e => e.team === 'player' && !e.isDead && (e.data.role === 'heavy' || e.typeKey === 'jaguar') ); if (priorityTargets.length > 0) { units.filter(u => u.data.role === 'ranged' || u.data.role === 'siege').forEach(u => { const closest = priorityTargets.reduce((a,b) => Utils.dist(u.x,u.y,a.x,a.y) < Utils.dist(u.x,u.y,b.x,b.y) ? a : b ); if (closest) u.target = closest; }); } }, groupByRole(units) { const groups = {}; units.forEach(u => { groups[u.data.role] = groups[u.data.role] || []; groups[u.data.role].push(u); }); return groups; }, // Adaptar dificultad dinámicamente adjustDifficulty(world, playerPerformance) { // Si el jugador domina, aumentar dificultad gradualmente if (playerPerformance.winRate > 0.8 && world.difficultyModifier < 1.5) { world.difficultyModifier += 0.05; world.notifyPlayer('⚠️ La resistencia se fortalece...', 'Los invasores adaptan sus tácticas', 'warning'); } // Si el jugador sufre, dar pequeña ayuda else if (playerPerformance.winRate < 0.3 && world.difficultyModifier > 0.8) { world.difficultyModifier -= 0.03; } } }; // ======================================================================== // 14. SISTEMA DE LOG & DEBUG (Desarrollo) // ======================================================================== const Debug = { enabled: false, logs: [], maxLogs: 50, toggle() { this.enabled = !this.enabled; console.log(`[DEBUG] ${this.enabled ? 'Activado' : 'Desactivado'}`); }, log(category, message, data = null) { if (!this.enabled) return; const entry = { time: Date.now(), category, message, data }; this.logs.push(entry); if (this.logs.length > this.maxLogs) this.logs.shift(); console.log(`[RM:${category}] ${message}`, data || ''); }, getStats(world) { if (!world) return {}; return { entities: world.entities.length, projectiles: world.projectiles.length, particles: world.particles.length, fps: Math.round(1000 / (performance.now() - (Debug._lastTime || performance.now()))), memory: performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + 'MB' : 'N/A' }; }, render(ctx, world) { if (!this.enabled || !world) return; const stats = this.getStats(world); ctx.save(); ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(10, 10, 200, 100); ctx.fillStyle = '#0f0'; ctx.font = '11px monospace'; ctx.fillText(`FPS: ${stats.fps || '---'}`, 15, 25); ctx.fillText(`Entidades: ${stats.entities}`, 15, 40); ctx.fillText(`Proyectiles: ${stats.projectiles}`, 15, 55); ctx.fillText(`Partículas: ${stats.particles}`, 15, 70); ctx.fillText(`Memoria: ${stats.memory}`, 15, 85); ctx.restore(); } }; // ======================================================================== // 15. INICIALIZACIÓN FINAL & EVENTOS GLOBALES // ======================================================================== // Función de notificación global accesible desde consola window.notify = function(msg, icon = '📜', type = 'info') { UI.showNotification({title: icon, message: msg, type}); }; // Atajos de teclado globales window.addEventListener('keydown', (e) => { // Toggle debug con F12 o Ctrl+Shift+D if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'd')) { e.preventDefault(); Debug.toggle(); } // Reinicio rápido con R (solo si no está en input) if (e.key.toLowerCase() === 'r' && !e.target.matches('input, textarea')) { if (e.ctrlKey) { e.preventDefault(); if (Game.world && !Game.world.gameOver) { Game.restartLevel(); notify('🔄 Nivel reiniciado', '', 'info'); } } } }); // Manejo de visibilidad (pausar automáticamente) document.addEventListener('visibilitychange', () => { if (document.hidden && Game.world && !Game.world?.paused && !Game.world?.gameOver) { Game.togglePause(); notify('⏸️ Juego pausado', 'Cambiaste de pestaña', 'info'); } }); // Prevenir scroll/zoom en móvil sobre el canvas document.addEventListener('touchmove', (e) => { if (e.target.closest('#gameCanvas')) { e.preventDefault(); } }, { passive: false }); // Manejar redimensionamiento de ventana let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (Game.world) { Game.world.resize(window.innerWidth, window.innerHeight - 90); UI.setupCanvas(); } }, 150); }); // Inicialización cuando el DOM está listo if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => Game.init()); } else { Game.init(); } // Exponer APIs globales para debugging/extensiones window.ResistenciaMexica = { Game, World, Entity, Projectile, Particle, Economy, EventBus, CONFIG, UNIT_DATA, ABILITIES_DATA, Utils, Audio, UI, AISystem, SaveSystem, Tutorial, Settings, Debug, // Helpers útiles para consola spawn: (type, team = 'player') => Game.world?.spawnEntity(type, team), kill: (team) => Game.world?.entities.filter(e => e.team === team).forEach(e => e.hp = 0), win: () => { if (Game.world) Game.world.enemyBaseHP = 0; }, lose: () => { if (Game.world) Game.world.playerBaseHP = 0; }, godmode: (on) => { if (Game.world) Game.world.entities.filter(e => e.team === 'player').forEach(e => e.maxHp = on ? 9999 : UNIT_DATA[e.typeKey]?.hp || 50); } }; /* * ======================================================================== * 📜 DOCUMENTACIÓN FINAL & GUÍA DE EXTENSIÓN * ======================================================================== * * ✅ CARACTERÍSTICAS COMPLETAS: * • Core Loop Stick War: recolectar → entrenar → combatir → victoria * • Sistema RPS histórico con multiplicadores 1.65x / 0.55x * • Economía dinámica: tributo pasivo + mineros + moral * • IA adaptativa: counter-picking, estrategias, dificultad escalable * • Terreno táctico: agua ralentiza, bosque buffea ranged, chinampas defensivas * • 5 habilidades especiales con cooldowns visuales y efectos * • Campaña de 4 niveles con narrativa histórica alternativa * • Modo infinito con oleadas progresivas y récords * • Tutorial interactivo de 5 pasos * • UI completa: minimapa, panel de unidad, tooltips, notificaciones * • Audio procedural sin assets externos (Web Audio API) * • Guardado persistente con localStorage * • Accesibilidad: reduced-motion, alto contraste, navegación por teclado * • Debug mode con estadísticas en tiempo real * * 🔧 CÓMO EXTENDER: * * 1. AGREGAR NUEVA UNIDAD: * a) Añadir entrada en UNIT_DATA con: cost, hp, dmg, speed, range, cd, color, role, label, tooltip, icon * b) Definir ventajas/débiles en CONFIG.RPS[nuevaUnidad] = { strong: [...], weak: [...] } * c) Implementar dibujo en Entity.drawTypeDetails(): case 'nuevaUnidad': { ... } * d) (Opcional) Añadir comportamiento especial en runCombatAI() o crear nueva clase * * 2. CREAR NUEVO NIVEL DE CAMPAÑA: * a) Añadir objeto en CAMPAIGN_DATA con: id, name, desc, waves, reward, story, objectives, terrain, enemyFaction, difficulty * b) Implementar lógica de terreno especial en World.generateTerrain() si terrain es personalizado * c) (Opcional) Añadir eventos únicos en World.checkEvents() * * 3. AJUSTAR BALANCE: * • Economía: CONFIG.ECONOMY.{startTribute, passiveIncome, minerReward, baseHP} * • Unidades: UNIT_DATA[*].{cost, hp, dmg, speed, range, cd} * • RPS: CONFIG.RPS[*].{strong, weak} y multiplicadores en Entity.getRPSMultiplier() * • IA: CONFIG.AI.{spawnBudgetBase, counterPickWeight, difficultyMultipliers} * * 4. MEJORAR GRÁFICOS: * • Reemplazar Entity.drawBody() con ctx.drawImage() usando sprites * • Añadir capas de parallax en World.drawBackground() * • Implementar partículas más complejas en Particle class * • Usar WebGL/Three.js para efectos 3D (requiere reestructurar renderer) * * 5. AÑADIR MULTIJUGADOR: * • Implementar WebSocket server para sincronización de estado * • Separar lógica de simulación (determinista) del renderizado * • Añadir sistema de input buffering y reconciliación * • Considerar lockstep o rollback netcode para RTS * * 🎨 PALETA DE COLORES TEMÁTICA: * • Rojo cochinilla: #c0392b (guerra, sangre, sacrificio) * • Oro azteca: #d4af37 (divinidad, poder, tributo) * • Verde jade: #27ae60 (vida, fertilidad, chinampas) * • Azul añil: #2980b9 (agua, cielo, lago de Texcoco) * • Púrpura real: #8e44ad (nobleza, sacerdotes, élite) * • Marrón tierra: #5d4037 (barro, pirámides, obsidiana) * * 📚 REFERENCIAS HISTÓRICAS: * • "Visión de los Vencidos" - Miguel León-Portilla * • "Códice Florentino" - Bernardino de Sahagún * • "La Conquista de México" - Hugh Thomas * • "Armas, Gérmenes y Acero" - Jared Diamond (contexto) * * ⚠️ NOTA ÉTICA: * Este juego presenta una narrativa de "historia alternativa" con fines * educativos y de entretenimiento. No busca negar ni minimizar el impacto * real de la conquista, sino explorar posibilidades históricas y honrar la * resistencia de los pueblos originarios de Mesoamérica. Se recomienda * complementar con fuentes históricas académicas. * * 🏛️ LICENCIA: * Código abierto bajo licencia MIT para fines educativos. * Los elementos históricos y culturales deben ser tratados con respeto. * Se anima a contribuir con mejoras, correcciones y traducciones. * * ======================================================================== * ¡Gracias por jugar Resistencia Mexica: Tenochtitlán Legacy! * Que el espíritu de Cuauhtémoc inspire tu estrategia. 🦅⚔️ * ======================================================================== */