/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Determine enemy image based on level
var enemyAssetId = 'enemigo'; // Default for level 1
if (level === 2) enemyAssetId = 'enemigo2';else if (level === 3) enemyAssetId = 'enemigo3';else if (level === 4) enemyAssetId = 'enemigo4';else if (level === 5) enemyAssetId = 'enemigo5';
var enemyGraphics = self.attachAsset(enemyAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Health properties
self.health = 100;
self.maxHealth = 100;
// Health bar will be created in GUI instead of attached to enemy
self.healthBarBg = null;
self.healthBar = null;
// Subtle animation properties
self.animTimer = 0;
self.baseX = 0;
self.update = function () {
// Subtle side-to-side movement
self.animTimer += 0.05;
self.x = self.baseX + Math.sin(self.animTimer) * 30;
};
// Method to show attack effect
self.showAttackEffect = function () {
// Flash effect when attacking
tween(enemyGraphics, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
});
// Play attack sound effect
LK.getSound('blaster_spawn').play();
};
// Method to take damage
self.takeDamage = function (amount) {
if (amount === undefined) amount = 20;
self.health = Math.max(0, self.health - amount);
self.updateHealthBar();
// Flash red effect when taking damage
tween(enemyGraphics, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xffffff
}, {
duration: 200
});
}
});
// Check if enemy is defeated
if (self.health <= 0) {
// Hide enemy
self.visible = false;
// Hide health bar elements
if (self.healthBar) self.healthBar.visible = false;
if (self.healthBarBg) self.healthBarBg.visible = false;
// Enemy defeated - advance level
level++;
if (level > maxLevel) {
// Stop music and prepare for victory
LK.stopMusic();
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
} else {
// Play level completion sound effect
LK.getSound('blaster_spawn').play();
// Reset enemy health for next level
self.health = self.maxHealth;
// Update enemy graphics for new level
self.updateEnemyGraphics();
// Show next level dialogue
showDialogue(levelNarratives[level - 1]);
}
}
};
// Method to initialize health bar in GUI
self.initHealthBar = function () {
if (!self.healthBarBg) {
// Health bar background - top center
self.healthBarBg = LK.gui.top.addChild(LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0
}));
self.healthBarBg.y = 130;
self.healthBarBg.visible = true;
// Health bar - top center
self.healthBar = LK.gui.top.addChild(LK.getAsset('enemyHealthBar', {
anchorX: 0.5,
anchorY: 0
}));
self.healthBar.y = 130;
self.healthBar.visible = true;
// Force immediate red color - the asset already has red color but we ensure it's applied
self.healthBar.tint = 0xff0000;
self.healthBar.tintAlpha = 1.0;
self.healthBar.alpha = 1.0;
// Force a visual update by setting width to current health percentage
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = 400 * healthPercentage;
// Enemy health label
var enemyHealthLabel = LK.gui.top.addChild(new Text2('Vida del Enemigo', {
size: 36,
fill: 0xffffff
}));
enemyHealthLabel.anchor.set(0.5, 0);
enemyHealthLabel.y = 70;
// Initialize health bar display immediately after creation
self.updateHealthBar();
}
};
// Method to update health bar visual
self.updateHealthBar = function () {
if (!self.healthBar || !self.healthBarBg) return;
// Make sure health bar is visible
self.healthBar.visible = true;
self.healthBarBg.visible = true;
self.healthBar.alpha = 1.0;
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = 400 * healthPercentage;
// Always set red color for consistency (enemy health bar should be red)
self.healthBar.tint = 0xff0000; // Always red for enemy
// Ensure tint is fully applied
self.healthBar.tintAlpha = 1.0;
};
// Method to update enemy graphics when level changes
self.updateEnemyGraphics = function () {
// Remove current graphics
if (enemyGraphics && enemyGraphics.parent) {
enemyGraphics.parent.removeChild(enemyGraphics);
}
// Determine new enemy image based on level
var enemyAssetId = 'enemigo'; // Default for level 1
if (level === 2) enemyAssetId = 'enemigo2';else if (level === 3) enemyAssetId = 'enemigo3';else if (level === 4) enemyAssetId = 'enemigo4';else if (level === 5) enemyAssetId = 'enemigo5';
// Create new graphics with updated asset
enemyGraphics = self.attachAsset(enemyAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
var GreenTextAttack = Container.expand(function () {
var self = Container.call(this);
// Create random positive text
var positiveTexts = ['CALMA', 'PAZ', 'FUERZA', 'ESPERANZA', 'CONFIANZA'];
var randomText = positiveTexts[Math.floor(Math.random() * positiveTexts.length)];
var textGraphics = self.addChild(new Text2(randomText, {
size: getTextSize(true, level),
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 3
}));
textGraphics.anchor.set(0.5, 0.5);
self.speed = 5;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement properties
self.speed = HEART_SPEED;
self.isMovingLeft = false;
self.isMovingRight = false;
// Health
self.health = 100;
self.update = function () {
// Keyboard movement
if (self.isMovingLeft) {
self.x -= self.speed;
}
if (self.isMovingRight) {
self.x += self.speed;
}
// Boundary checking - constrain to boundary box
if (self.x < BOUNDARY_LEFT + HEART_MARGIN) {
self.x = BOUNDARY_LEFT + HEART_MARGIN;
}
if (self.x > BOUNDARY_RIGHT - HEART_MARGIN) {
self.x = BOUNDARY_RIGHT - HEART_MARGIN;
}
if (self.y < BOUNDARY_TOP + HEART_MARGIN) {
self.y = BOUNDARY_TOP + HEART_MARGIN;
}
if (self.y > BOUNDARY_BOTTOM - HEART_MARGIN) {
self.y = BOUNDARY_BOTTOM - HEART_MARGIN;
}
};
self.takeDamage = function (amount) {
if (amount === undefined) amount = 10;
self.health = Math.max(0, self.health - amount);
// Play hurt sound effect
LK.getSound('Damage').play();
// Flash red effect
tween(heartGraphics, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(heartGraphics, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (self.health <= 0) {
LK.showGameOver();
}
};
return self;
});
var TechniqueMenu = Container.expand(function () {
var self = Container.call(this);
// Background
var bg = self.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
bg.width = 700;
bg.height = 480;
bg.tint = 0x333333;
// Title
var title = self.addChild(new Text2('Técnica de Relajación', {
size: 48,
fill: 0xffffff
}));
title.anchor.set(0.5, 0.5);
title.y = -120;
// Techniques
var techniques = ['Respiración Profunda', 'Mindfulness', 'Relajación Muscular'];
var selectedTechnique = techniques[Math.floor(Math.random() * techniques.length)];
var techniqueText = self.addChild(new Text2(selectedTechnique, {
size: 42,
fill: 0x00ff00
}));
techniqueText.anchor.set(0.5, 0.5);
techniqueText.y = -50;
// Instructions
var instruction = self.addChild(new Text2('Toca para aplicar', {
size: 36,
fill: 0xcccccc
}));
instruction.anchor.set(0.5, 0.5);
instruction.y = 20;
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.update = function () {
self.duration += 16.67;
if (self.duration >= self.maxDuration) {
self.destroy();
}
};
self.down = function () {
// Apply technique - boost credibilidad
credibilidad = Math.min(100, credibilidad + 15);
updateBars();
self.destroy();
};
return self;
});
var TextAttack = Container.expand(function () {
var self = Container.call(this);
// Create random attack text - surprise questions and interruptions
var attackTexts = ['¿Y ESO PARA QUÉ?', '¡INTERRUPTION!', '¿ESTÁS SEGURO?', '¡ESPERA UN MOMENTO!', '¿PUEDES REPETIR?', '¡NO ENTIENDO!', '¿Y SI NO FUNCIONA?', '¡PREGUNTA SORPRESA!'];
var randomText = attackTexts[Math.floor(Math.random() * attackTexts.length)];
var textGraphics = self.addChild(new Text2(randomText, {
size: getTextSize(false, level),
fill: 0xff0000,
stroke: 0x000000,
strokeThickness: 3
}));
textGraphics.anchor.set(0.5, 0.5);
// Enhanced movement properties based on level - moderate difficulty
self.baseSpeed = getAttackSpeed(level);
self.speed = self.baseSpeed;
self.acceleration = 0.08 + (level - 1) * 0.03; // Moderate acceleration
self.maxSpeed = self.baseSpeed * 1.8; // Can accelerate up to 1.8x base speed
// Movement pattern selection (more predictable patterns at lower levels)
self.movementPattern = Math.floor(Math.random() * Math.min(3, level)); // Reduced max patterns
// 0: Basic straight, 1: Zigzag, 2: Pursuit, 3: Spiral (removed erratic)
// Pattern-specific properties
self.speedX = (Math.random() - 0.5) * (3 + level * 1.5); // Moderate horizontal movement
self.speedY = self.speed;
self.waveAmplitude = Math.random() * (15 + level * 8) + 8; // Moderate wave amplitude
self.waveFrequency = Math.random() * 0.06 + 0.03 + level * 0.015; // Moderate wave frequency
self.animTimer = Math.random() * Math.PI * 2;
self.directionChangeTimer = Math.random() * (1000 - level * 60) + 350; // Moderate direction changes
self.maxDirectionChangeTimer = self.directionChangeTimer;
self.pursuitStrength = 0.25 + (level - 1) * 0.12; // Moderate pursuit strength
self.lastHeartX = heart.x;
self.lastHeartY = heart.y;
self.update = function () {
// Get current heart position for pursuit calculations
var heartX = heart.x;
var heartY = heart.y;
// Accelerate over time (attacks get faster)
if (self.speed < self.maxSpeed) {
self.speed += self.acceleration;
self.speedY = self.speed;
}
// Movement pattern execution - simplified and less aggressive
switch (self.movementPattern) {
case 0:
// Basic straight movement with moderate tracking
self.y += self.speedY;
// Moderate horizontal drift toward heart
var heartDirection = heartX > self.x ? 1 : -1;
self.x += heartDirection * (level * 0.35); // Moderate tracking
break;
case 1:
// Zigzag with moderate pursuit
self.y += self.speedY;
self.animTimer += self.waveFrequency;
self.x += Math.sin(self.animTimer) * self.waveAmplitude * 0.15; // Moderate zigzag intensity
// Moderate pursuit element
var pursuitX = (heartX - self.x) * self.pursuitStrength * 0.08; // Moderate pursuit
self.x += pursuitX;
break;
case 2:
// Moderate pursuit - balanced aggression
var deltaX = heartX - self.x;
var deltaY = heartY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.x += deltaX / distance * self.speed * self.pursuitStrength * 0.7; // Moderate pursuit
self.y += deltaY / distance * self.speed * self.pursuitStrength * 0.45; // Moderate vertical pursuit
}
// Still move generally downward but less predictable
self.y += self.speedY * 0.75; // Moderate downward movement
break;
case 3:
// Spiral pattern with moderate pursuit
self.y += self.speedY * 0.85; // Moderate downward movement
self.animTimer += self.waveFrequency;
var spiralRadius = 25 + Math.sin(self.animTimer * 0.3) * 15; // Moderate spiral
self.x += Math.cos(self.animTimer) * spiralRadius * 0.12; // Moderate spiral intensity
// Moderate drift toward heart
var driftX = (heartX - self.x) * 0.035; // Moderate drift
self.x += driftX;
break;
}
// Enhanced boundary behavior - some attacks can bounce
if (self.x < BOUNDARY_LEFT - 50) {
if (level >= 3 && Math.random() < 0.4) {
// Bounce off left wall
self.speedX = Math.abs(self.speedX) * 1.2;
} else {
self.speedX = Math.abs(self.speedX);
}
}
if (self.x > BOUNDARY_RIGHT + 50) {
if (level >= 3 && Math.random() < 0.4) {
// Bounce off right wall
self.speedX = -Math.abs(self.speedX) * 1.2;
} else {
self.speedX = -Math.abs(self.speedX);
}
}
// Store last heart position for next frame
self.lastHeartX = heartX;
self.lastHeartY = heartY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Turn-based constants
// Game Constants
var ENEMY_ATTACK_DURATION = 3000; // 3 seconds for enemy attack phase
var PLAYER_ATTACK_DURATION = 3000; // 3 seconds for player attack phase
var ATTACKS_PER_TURN = 3; // Number of attacks enemy launches per turn
var LEVEL_DIFF = 5;
var MAX_LEVEL = 10;
var WIN_CRED = 60;
var LEVEL_CRED_BOOST = 5;
var GAME_LEFT = 300;
var GAME_RIGHT = 1748;
var GAME_TOP = 200;
var GAME_BOTTOM = 2732;
var GAME_WIDTH = GAME_RIGHT - GAME_LEFT;
// Heart movement constants
var HEART_SPEED = 8;
var HEART_MARGIN = 60;
// Boundary box constants
var BOUNDARY_WIDTH = 800;
var BOUNDARY_HEIGHT = 400;
var BOUNDARY_LEFT = (2048 - BOUNDARY_WIDTH) / 2;
var BOUNDARY_RIGHT = BOUNDARY_LEFT + BOUNDARY_WIDTH;
var BOUNDARY_TOP = GAME_BOTTOM - 900;
var BOUNDARY_BOTTOM = BOUNDARY_TOP + BOUNDARY_HEIGHT;
// Text size scaling function
function getTextSize(isGreen, currentLevel) {
var baseSize = 20;
var sizeMultiplier = 10;
var scale;
if (isGreen) {
// Green words: size 5 at level 1, size 1 at level 5
scale = 6 - currentLevel;
} else {
// Red words: size 1 at level 1, size 5 at level 5
scale = currentLevel;
}
// Ensure scale is between 1 and 5
scale = Math.max(1, Math.min(5, scale));
return baseSize + scale * sizeMultiplier;
}
// Attack difficulty scaling function
function getAttackSpeed(currentLevel) {
// Base speed increases more gradually with level - reduced difficulty
return 4 + (currentLevel - 1) * 2; // Level 1: 4, Level 5: 12
}
function getAttackFrequency(currentLevel) {
// Attack interval decreases (more frequent attacks) with level
return Math.max(300, 1000 - (currentLevel - 1) * 175); // Level 1: 1000ms, Level 5: 300ms
}
function getMaxSimultaneousAttacks(currentLevel) {
// More attacks on screen at once as level increases
return Math.min(8, 2 + currentLevel); // Level 1: 3, Level 5: 7
}
// Game state management
var gameState = 'menu'; // 'menu', 'dialogue', 'enemyTurn', 'playerTurn', 'transition'
var menuContainer = game.addChild(new Container());
// Turn-based variables
var currentTurn = 'enemy'; // 'enemy' or 'player'
var turnTimer = 0;
var attacksLaunched = 0;
var attackTimer = 0;
// Game variables
var level = 1;
var maxLevel = 5;
var dialogueContainer = game.addChild(new Container());
dialogueContainer.visible = false;
// Game rules narrative
var gameRules = "¡Bienvenido a Corazón de Tutor!\n\n" + "CÓMO JUGAR:\n\n" + "🎯 OBJETIVO:\n" + "Supera los 4 niveles manteniendo baja tu ansiedad y alta tu credibilidad.\n\n" + "⚡ CONTROLES:\n" + "• En móvil: Toca y arrastra para mover tu corazón\n" + "• En PC: Usa las flechas izquierda/derecha\n\n" + "💔 ESQUIVA:\n" + "Evita las preguntas sorpresa e interrupciones rojas que aumentan tu ansiedad.\n\n" + "💚 COLECTA:\n" + "Toca las palabras verdes de calma para aumentar tu credibilidad.\n\n" + "🛡️ TÉCNICAS:\n" + "En tu turno, usa técnicas de tutoría para atacar al enemigo:\n" + "• Redirigir: Daña al enemigo y sube credibilidad\n" + "• Respiración: Reduce ansiedad y sube credibilidad\n" + "• Reformulación: Equilibra ambas barras y daña\n\n" + "¡Conviértete en un verdadero Corazón de Tutor!";
// Level narratives
var levelNarratives = ["Nivel 1 – \"Primer Latido\"\n¡Lo lograste!\nHas esquivado con éxito las primeras preguntas sorpresa y aprendido tu primera técnica de Pausa. Síntoma de nervios: la ansiedad cayó un 20 % y tu credibilidad subió un 10 %.\n\"Siente el ritmo de tu corazón. Cada respiro te hace más fuerte.\"", "Nivel 2 – \"Pulso Controlado\"\n¡Buen trabajo!\nAhora dominas la Respiración: mantuviste la calma frente a interrupciones múltiples. Has reducido tu ansiedad un 15 % adicional y tus tutorados confían más en ti.\n\"Cuando el mundo acelera, tú decides pausar.\"", "Nivel 3 – \"Voz Sólida\"\n¡Impresionante!\nHas practicado la Reformulación bajo presión. Tus explicaciones fluyen con claridad, tu ansiedad alcanza su punto más bajo y tu credibilidad roza el 80 %.\n\"No sólo respondas: reformula, conecta y convence.\"", "Nivel 4 – \"Corazón de Tutor\"\n¡Victoria Final!\nTras usar la técnica Redirigir estratégicamente, superaste el desafío y alcanzaste el dominio comunicativo. Ansiedad mínima, credibilidad máxima: eres un Corazón de Tutor.\n\"Comunicar es un arte: lo llevas en el corazón.\""];
// Track if rules have been shown
var rulesShown = false;
var score = 0;
var ansiedad = 0;
var credibilidad = 50;
// UI Elements
var ansiedadBarBg = null,
ansiedadBar = null,
credibilidadBarBg = null,
credibilidadBar = null;
var levelText = null,
scoreText = null;
// Initialize UI
function initializeUI() {
// Ansiedad bar - bottom left
ansiedadBarBg = LK.gui.bottomLeft.addChild(LK.getAsset('ansiedadBarBg', {
anchorX: 0,
anchorY: 1
}));
ansiedadBarBg.x = 120;
ansiedadBarBg.y = -150;
ansiedadBar = LK.gui.bottomLeft.addChild(LK.getAsset('ansiedadBar', {
anchorX: 0,
anchorY: 1
}));
ansiedadBar.x = 120;
ansiedadBar.y = -150;
// Credibilidad bar - bottom right
credibilidadBarBg = LK.gui.bottomRight.addChild(LK.getAsset('credibilidadBarBg', {
anchorX: 1,
anchorY: 1
}));
credibilidadBarBg.x = -120;
credibilidadBarBg.y = -150;
credibilidadBar = LK.gui.bottomRight.addChild(LK.getAsset('credibilidadBar', {
anchorX: 1,
anchorY: 1
}));
credibilidadBar.x = -120;
credibilidadBar.y = -150;
// Text labels
var ansiedadLabel = LK.gui.bottomLeft.addChild(new Text2('Ansiedad', {
size: 30,
fill: 0xffffff
}));
ansiedadLabel.x = 120;
ansiedadLabel.y = -110;
var credibilidadLabel = LK.gui.bottomRight.addChild(new Text2('Credibilidad', {
size: 30,
fill: 0xffffff
}));
credibilidadLabel.anchor.set(1, 1);
credibilidadLabel.x = -120;
credibilidadLabel.y = -110;
// Level and score - keep at top
levelText = LK.gui.topRight.addChild(new Text2('Nivel: 1', {
size: 36,
fill: 0xffffff
}));
levelText.anchor.set(1, 0);
levelText.x = -20;
levelText.y = 50;
}
function updateBars() {
// Update bar widths based on values
var ansiedadWidth = ansiedad / 100 * 300;
var credibilidadWidth = credibilidad / 100 * 300;
ansiedadBar.width = ansiedadWidth;
credibilidadBar.width = credibilidadWidth;
// Adjust credibilidad bar position since it's right-anchored
credibilidadBar.x = -120 - (300 - credibilidadWidth);
// Update text
levelText.setText('Nivel: ' + level);
}
function showRulesDialogue() {
gameState = 'dialogue';
dialogueContainer.visible = true;
// Clear previous dialogue
dialogueContainer.removeChildren();
// Rules text with word wrapping and smaller size
var rulesText = dialogueContainer.addChild(new Text2(gameRules, {
size: 28,
fill: 0xffffff,
wordWrap: true,
wordWrapWidth: 1800
}));
rulesText.anchor.set(0.5, 0.5);
rulesText.x = 1024;
rulesText.y = 1500; // Match moved background position
// Continue button
var continueBtn = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
continueBtn.width = 380;
continueBtn.height = 100;
continueBtn.tint = 0x00aa00;
continueBtn.x = 1024;
continueBtn.y = 2000; // Moved further down from background
var continueText = dialogueContainer.addChild(new Text2('¡Empezar!', {
size: 48,
fill: 0xffffff
}));
continueText.anchor.set(0.5, 0.5);
continueText.x = continueBtn.x;
continueText.y = continueBtn.y;
continueBtn.down = function () {
dialogueContainer.visible = false;
// Now show the first level dialogue
showDialogue(levelNarratives[level - 1]);
};
}
function showDialogue(text) {
gameState = 'dialogue';
dialogueContainer.visible = true;
// Clear previous dialogue
dialogueContainer.removeChildren();
// Dialogue background
var dialogueBg = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
dialogueBg.width = 1900;
dialogueBg.height = 600;
dialogueBg.tint = 0x333333;
dialogueBg.x = 1024;
dialogueBg.y = 1366; // Center vertically (2732/2)
// Dialogue text with word wrapping and smaller size
var dialogueText = dialogueContainer.addChild(new Text2(text, {
size: 32,
fill: 0xffffff,
wordWrap: true,
wordWrapWidth: 1800
}));
dialogueText.anchor.set(0.5, 0.5);
dialogueText.x = 1024;
dialogueText.y = 1366; // Center vertically (2732/2)
// Continue button
var continueBtn = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
continueBtn.width = 380;
continueBtn.height = 100;
continueBtn.tint = 0x00aa00;
continueBtn.x = 1024;
continueBtn.y = 1750; // Moved further down from centered dialogue
var continueText = dialogueContainer.addChild(new Text2('Continuar', {
size: 48,
fill: 0xffffff
}));
continueText.anchor.set(0.5, 0.5);
continueText.x = continueBtn.x;
continueText.y = continueBtn.y;
continueBtn.down = function () {
dialogueContainer.visible = false;
gameState = 'enemyTurn'; // Start with enemy turn
// Initialize and show UI if not already done
if (!ansiedadBarBg || ansiedadBarBg && !ansiedadBarBg.parent) {
initializeUI();
}
// Initialize enemy health bar
enemy.initHealthBar();
// Show enemy and health bar again
enemy.visible = true;
// Ensure health bar is visible and properly updated
enemy.updateHealthBar();
updateBars();
heart.visible = true;
boundaryBox.visible = true;
boundaryBoxBorder.visible = true;
// Adjust music volume based on level intensity
var musicVolume = 0.4 + level * 0.1; // Volume increases with level
LK.playMusic('fondo', {
fade: {
start: LK.getCurrentMusicVolume ? LK.getCurrentMusicVolume() : 0.6,
end: musicVolume,
duration: 800
}
});
};
}
// Reset button functionality
function resetGame() {
// Stop all current music
LK.stopMusic();
// Reset all game variables to initial state
level = 1;
ansiedad = 0;
credibilidad = 50;
score = 0;
gameState = 'menu';
currentTurn = 'enemy';
turnTimer = 0;
attacksLaunched = 0;
attackTimer = 0;
greenAttackTimer = 0;
ansiedadTimer = 0;
scoreTimer = 0;
// Destroy all active attacks
for (var i = textAttacks.length - 1; i >= 0; i--) {
textAttacks[i].destroy();
}
textAttacks = [];
for (var j = greenTextAttacks.length - 1; j >= 0; j--) {
greenTextAttacks[j].destroy();
}
greenTextAttacks = [];
// Reset enemy to initial state
if (enemy) {
enemy.visible = false;
enemy.health = enemy.maxHealth;
// Reset enemy graphics to level 1
enemy.updateEnemyGraphics();
}
// Hide heart character
if (heart) {
heart.visible = false;
heart.health = 100;
heart.x = 2048 / 2;
heart.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
heart.isMovingLeft = false;
heart.isMovingRight = false;
}
// Hide boundary box
if (boundaryBox) {
boundaryBox.visible = false;
}
// Hide boundary box border
if (boundaryBoxBorder) {
boundaryBoxBorder.visible = false;
}
// Hide dialogue
dialogueContainer.visible = false;
// Hide player turn menu
hidePlayerTurnMenu();
// Hide and reset UI bars
if (ansiedadBarBg) {
ansiedadBarBg.visible = false;
ansiedadBarBg.parent.removeChild(ansiedadBarBg);
ansiedadBarBg = null;
}
if (ansiedadBar) {
ansiedadBar.visible = false;
ansiedadBar.parent.removeChild(ansiedadBar);
ansiedadBar = null;
}
if (credibilidadBarBg) {
credibilidadBarBg.visible = false;
credibilidadBarBg.parent.removeChild(credibilidadBarBg);
credibilidadBarBg = null;
}
if (credibilidadBar) {
credibilidadBar.visible = false;
credibilidadBar.parent.removeChild(credibilidadBar);
credibilidadBar = null;
}
if (levelText) {
levelText.visible = false;
levelText.parent.removeChild(levelText);
levelText = null;
}
// Hide enemy health bar
if (enemy && enemy.healthBar) {
enemy.healthBar.visible = false;
enemy.healthBar.parent.removeChild(enemy.healthBar);
enemy.healthBar = null;
}
if (enemy && enemy.healthBarBg) {
enemy.healthBarBg.visible = false;
enemy.healthBarBg.parent.removeChild(enemy.healthBarBg);
enemy.healthBarBg = null;
}
// Show main menu
menuContainer.visible = true;
// Start menu music from beginning
LK.playMusic('menumusic');
}
// Start menu music
LK.playMusic('menumusic');
// Create reset button
var resetButton = LK.gui.topRight.addChild(LK.getAsset('resetButton', {
anchorX: 1,
anchorY: 0
}));
resetButton.x = -20;
resetButton.y = 120;
var resetText = LK.gui.topRight.addChild(new Text2('RESET', {
size: 24,
fill: 0xffffff
}));
resetText.anchor.set(0.5, 0.5);
resetText.x = resetButton.x - resetButton.width / 2;
resetText.y = resetButton.y + resetButton.height / 2;
// Reset button interaction
resetButton.down = function () {
// Flash button on press
tween(resetButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(resetButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
// Reset the game
resetGame();
};
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.width = 500;
playButton.height = 150;
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 300;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 90,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Play button interaction
playButton.down = function (x, y, obj) {
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Switch to background music
LK.playMusic('fondo', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
// Show rules first if not shown, then level dialogue
menuContainer.visible = false;
if (!rulesShown) {
rulesShown = true;
showRulesDialogue();
} else {
showDialogue(levelNarratives[level - 1]);
}
};
// Create the enemy
var enemy = game.addChild(new Enemy());
enemy.x = 2048 / 2;
enemy.baseX = enemy.x;
enemy.y = GAME_TOP + 300; // Moved down further to avoid health bar overlap
enemy.visible = false;
// Create boundary box with white border (border first, then main box on top)
var boundaryBoxBorder = game.addChild(LK.getAsset('boundaryBoxBorder', {
anchorX: 0.5,
anchorY: 0.5
}));
boundaryBoxBorder.x = 2048 / 2;
boundaryBoxBorder.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
boundaryBoxBorder.alpha = 1.0; // Fully visible white border
boundaryBoxBorder.visible = false;
// Create boundary box visual (black box on top of white border)
var boundaryBox = game.addChild(LK.getAsset('boundaryBox', {
anchorX: 0.5,
anchorY: 0.5
}));
boundaryBox.x = 2048 / 2;
boundaryBox.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
boundaryBox.alpha = 1.0; // Fully opaque black background
boundaryBox.visible = false;
// Create the heart character
var heart = game.addChild(new Heart());
heart.x = 2048 / 2;
heart.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2; // Position centered in boundary box
heart.visible = false;
// Keyboard controls - using LK event system instead of window
LK.on('keydown', function (event) {
if (gameState !== 'enemyTurn') return;
if (event.key === 'ArrowLeft') {
heart.isMovingLeft = true;
}
if (event.key === 'ArrowRight') {
heart.isMovingRight = true;
}
});
LK.on('keyup', function (event) {
if (gameState !== 'enemyTurn') return;
if (event.key === 'ArrowLeft') {
heart.isMovingLeft = false;
}
if (event.key === 'ArrowRight') {
heart.isMovingRight = false;
}
});
// Touch controls for mobile
game.move = function (x, y, obj) {
if (gameState === 'enemyTurn') {
// Move heart towards touch position
if (x < heart.x) {
heart.isMovingLeft = true;
heart.isMovingRight = false;
} else if (x > heart.x) {
heart.isMovingRight = true;
heart.isMovingLeft = false;
}
}
};
game.up = function (x, y, obj) {
if (gameState === 'enemyTurn') {
heart.isMovingLeft = false;
heart.isMovingRight = false;
}
};
// Game arrays and timers
var textAttacks = [];
var greenTextAttacks = [];
var attackTimer = 0;
var greenAttackTimer = 0;
var ansiedadTimer = 0;
var scoreTimer = 0;
// Player turn menu
var playerTurnMenu = null;
function showPlayerTurnMenu() {
if (playerTurnMenu) return; // Already showing
playerTurnMenu = game.addChild(new Container());
playerTurnMenu.x = 1024;
playerTurnMenu.y = 1800;
// Background
var menuBg = playerTurnMenu.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
menuBg.width = 1600;
menuBg.height = 600;
menuBg.tint = 0x333333;
// Title
var title = playerTurnMenu.addChild(new Text2('Tu Turno - Técnicas de Tutoría', {
size: 40,
fill: 0xffffff
}));
title.anchor.set(0.5, 0.5);
title.y = -100;
// Technique buttons
var techniques = [{
text: 'Redirigir',
effect: function effect() {
credibilidad += 5;
enemy.takeDamage(30);
}
}, {
text: 'Respiración',
effect: function effect() {
credibilidad += 30;
ansiedad = Math.max(0, ansiedad - 10);
}
}, {
text: 'Reformulación',
effect: function effect() {
credibilidad += 15;
ansiedad = Math.max(0, ansiedad - 15);
enemy.takeDamage(15);
}
}];
for (var i = 0; i < techniques.length; i++) {
var btn = playerTurnMenu.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
btn.width = 450;
btn.height = 120;
btn.tint = 0x00aa00;
btn.x = (i - 1) * 420;
btn.y = 40;
btn.techniqueIndex = i;
var btnText = playerTurnMenu.addChild(new Text2(techniques[i].text, {
size: 38,
fill: 0xffffff
}));
btnText.anchor.set(0.5, 0.5);
btnText.x = btn.x;
btnText.y = btn.y;
btn.down = function () {
var technique = techniques[this.techniqueIndex];
technique.effect();
credibilidad = Math.min(100, credibilidad);
ansiedad = Math.max(0, ansiedad);
updateBars();
// Flash effect
tween(this, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
tint: 0x00aa00
}, {
duration: 100
});
}
});
// End player turn and prepare for enemy turn
hidePlayerTurnMenu();
// Set a brief delay before enemy turn starts
LK.setTimeout(function () {
gameState = 'enemyTurn';
turnTimer = 0;
attacksLaunched = 0;
attackTimer = 0;
}, 500); // 500ms delay before enemy can start attacking
};
}
}
function hidePlayerTurnMenu() {
if (playerTurnMenu) {
playerTurnMenu.destroy();
playerTurnMenu = null;
}
}
// Main game update loop
game.update = function () {
// Only update game logic when in turn-based states
if (gameState !== 'enemyTurn' && gameState !== 'playerTurn') return;
// Allow heart movement during gameplay
if (gameState === 'enemyTurn' || gameState === 'playerTurn') {
heart.update();
}
// Turn-based system
if (gameState === 'enemyTurn') {
// Only allow enemy attacks if enemy is alive (visible)
if (enemy.visible) {
// Enemy's turn - launch attacks
turnTimer += 16.67; // Add frame time
attackTimer += 16.67;
// Enhanced attack system with level-based difficulty
var attackInterval = getAttackFrequency(level);
var maxAttacksPerTurn = getMaxSimultaneousAttacks(level);
var maxSimultaneousAttacks = getMaxSimultaneousAttacks(level);
if (attackTimer >= attackInterval && attacksLaunched < maxAttacksPerTurn && textAttacks.length < maxSimultaneousAttacks) {
// Launch multiple attacks in formation for higher levels
var attacksToLaunch = level >= 3 ? Math.floor(Math.random() * 2) + 1 : 1;
for (var attackIndex = 0; attackIndex < attacksToLaunch && attacksLaunched < maxAttacksPerTurn; attackIndex++) {
var textAttack = new TextAttack();
// Varied spawn positions based on level
if (level >= 4) {
// Higher levels: attacks can spawn from sides too
var spawnSide = Math.random();
if (spawnSide < 0.6) {
// From above (normal)
textAttack.x = enemy.x + (Math.random() - 0.5) * 200;
textAttack.y = enemy.y + 50;
} else if (spawnSide < 0.8) {
// From left side
textAttack.x = BOUNDARY_LEFT - 50;
textAttack.y = BOUNDARY_TOP + Math.random() * BOUNDARY_HEIGHT;
} else {
// From right side
textAttack.x = BOUNDARY_RIGHT + 50;
textAttack.y = BOUNDARY_TOP + Math.random() * BOUNDARY_HEIGHT;
}
} else {
// Lower levels: spawn from above only
textAttack.x = enemy.x + (Math.random() - 0.5) * (100 + level * 50);
textAttack.y = enemy.y + 50;
}
textAttacks.push(textAttack);
game.addChild(textAttack);
attacksLaunched++;
}
enemy.showAttackEffect();
attackTimer = 0;
}
// End enemy turn only when all attacks are launched AND all attacks are processed (hit or missed)
if (attacksLaunched >= maxAttacksPerTurn && textAttacks.length === 0) {
gameState = 'playerTurn';
turnTimer = 0;
// Show player turn interface
showPlayerTurnMenu();
}
}
} else if (gameState === 'playerTurn') {
// Player's turn - wait for player action or timeout
turnTimer += 16.67;
// Player turn ends only when technique is selected (handled in showPlayerTurnMenu)
// No automatic timeout - player must make a choice
}
// Update and check TextAttack collisions
for (var i = textAttacks.length - 1; i >= 0; i--) {
var atk = textAttacks[i];
// Initialize collision tracking if not present
if (atk.lastIntersecting === undefined) {
atk.lastIntersecting = false;
}
// Check for NEW collision with heart (transition from not intersecting to intersecting)
var currentIntersecting = atk.intersects(heart);
if (!atk.lastIntersecting && currentIntersecting) {
// Only damage on the first frame of collision
heart.takeDamage(15);
ansiedad = Math.min(100, ansiedad + 15);
credibilidad = Math.max(0, credibilidad - 10);
// Play hurt sound when anxiety increases
LK.getSound('Damage').play();
updateBars();
atk.destroy();
textAttacks.splice(i, 1);
continue;
}
// Update last intersection state
atk.lastIntersecting = currentIntersecting;
// Remove off-screen attacks
if (atk.y > GAME_BOTTOM + 100) {
atk.destroy();
textAttacks.splice(i, 1);
}
}
// Update and check GreenTextAttack collisions
for (var k = greenTextAttacks.length - 1; k >= 0; k--) {
var greenAtk = greenTextAttacks[k];
// Initialize collision tracking if not present
if (greenAtk.lastIntersecting === undefined) {
greenAtk.lastIntersecting = false;
}
// Check for NEW collision with heart (transition from not intersecting to intersecting)
var currentIntersecting = greenAtk.intersects(heart);
if (!greenAtk.lastIntersecting && currentIntersecting) {
// Only give benefit on the first frame of collision
credibilidad = Math.min(100, credibilidad + 8);
updateBars();
greenAtk.destroy();
greenTextAttacks.splice(k, 1);
continue;
}
// Update last intersection state
greenAtk.lastIntersecting = currentIntersecting;
// Remove off-screen green attacks
if (greenAtk.y > GAME_BOTTOM + 100) {
greenAtk.destroy();
greenTextAttacks.splice(k, 1);
}
}
// Check game over conditions
if (ansiedad >= 100 || credibilidad <= 0) {
// Fade out music before game over
LK.playMusic('fondo', {
fade: {
start: LK.getCurrentMusicVolume ? LK.getCurrentMusicVolume() : 0.6,
end: 0,
duration: 500
}
});
LK.setTimeout(function () {
LK.showGameOver();
}, 500);
}
// Check win condition
if (level >= maxLevel && credibilidad >= WIN_CRED) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Determine enemy image based on level
var enemyAssetId = 'enemigo'; // Default for level 1
if (level === 2) enemyAssetId = 'enemigo2';else if (level === 3) enemyAssetId = 'enemigo3';else if (level === 4) enemyAssetId = 'enemigo4';else if (level === 5) enemyAssetId = 'enemigo5';
var enemyGraphics = self.attachAsset(enemyAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Health properties
self.health = 100;
self.maxHealth = 100;
// Health bar will be created in GUI instead of attached to enemy
self.healthBarBg = null;
self.healthBar = null;
// Subtle animation properties
self.animTimer = 0;
self.baseX = 0;
self.update = function () {
// Subtle side-to-side movement
self.animTimer += 0.05;
self.x = self.baseX + Math.sin(self.animTimer) * 30;
};
// Method to show attack effect
self.showAttackEffect = function () {
// Flash effect when attacking
tween(enemyGraphics, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xffffff
}, {
duration: 150
});
}
});
// Play attack sound effect
LK.getSound('blaster_spawn').play();
};
// Method to take damage
self.takeDamage = function (amount) {
if (amount === undefined) amount = 20;
self.health = Math.max(0, self.health - amount);
self.updateHealthBar();
// Flash red effect when taking damage
tween(enemyGraphics, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xffffff
}, {
duration: 200
});
}
});
// Check if enemy is defeated
if (self.health <= 0) {
// Hide enemy
self.visible = false;
// Hide health bar elements
if (self.healthBar) self.healthBar.visible = false;
if (self.healthBarBg) self.healthBarBg.visible = false;
// Enemy defeated - advance level
level++;
if (level > maxLevel) {
// Stop music and prepare for victory
LK.stopMusic();
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
} else {
// Play level completion sound effect
LK.getSound('blaster_spawn').play();
// Reset enemy health for next level
self.health = self.maxHealth;
// Update enemy graphics for new level
self.updateEnemyGraphics();
// Show next level dialogue
showDialogue(levelNarratives[level - 1]);
}
}
};
// Method to initialize health bar in GUI
self.initHealthBar = function () {
if (!self.healthBarBg) {
// Health bar background - top center
self.healthBarBg = LK.gui.top.addChild(LK.getAsset('enemyHealthBarBg', {
anchorX: 0.5,
anchorY: 0
}));
self.healthBarBg.y = 130;
self.healthBarBg.visible = true;
// Health bar - top center
self.healthBar = LK.gui.top.addChild(LK.getAsset('enemyHealthBar', {
anchorX: 0.5,
anchorY: 0
}));
self.healthBar.y = 130;
self.healthBar.visible = true;
// Force immediate red color - the asset already has red color but we ensure it's applied
self.healthBar.tint = 0xff0000;
self.healthBar.tintAlpha = 1.0;
self.healthBar.alpha = 1.0;
// Force a visual update by setting width to current health percentage
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = 400 * healthPercentage;
// Enemy health label
var enemyHealthLabel = LK.gui.top.addChild(new Text2('Vida del Enemigo', {
size: 36,
fill: 0xffffff
}));
enemyHealthLabel.anchor.set(0.5, 0);
enemyHealthLabel.y = 70;
// Initialize health bar display immediately after creation
self.updateHealthBar();
}
};
// Method to update health bar visual
self.updateHealthBar = function () {
if (!self.healthBar || !self.healthBarBg) return;
// Make sure health bar is visible
self.healthBar.visible = true;
self.healthBarBg.visible = true;
self.healthBar.alpha = 1.0;
var healthPercentage = self.health / self.maxHealth;
self.healthBar.width = 400 * healthPercentage;
// Always set red color for consistency (enemy health bar should be red)
self.healthBar.tint = 0xff0000; // Always red for enemy
// Ensure tint is fully applied
self.healthBar.tintAlpha = 1.0;
};
// Method to update enemy graphics when level changes
self.updateEnemyGraphics = function () {
// Remove current graphics
if (enemyGraphics && enemyGraphics.parent) {
enemyGraphics.parent.removeChild(enemyGraphics);
}
// Determine new enemy image based on level
var enemyAssetId = 'enemigo'; // Default for level 1
if (level === 2) enemyAssetId = 'enemigo2';else if (level === 3) enemyAssetId = 'enemigo3';else if (level === 4) enemyAssetId = 'enemigo4';else if (level === 5) enemyAssetId = 'enemigo5';
// Create new graphics with updated asset
enemyGraphics = self.attachAsset(enemyAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
var GreenTextAttack = Container.expand(function () {
var self = Container.call(this);
// Create random positive text
var positiveTexts = ['CALMA', 'PAZ', 'FUERZA', 'ESPERANZA', 'CONFIANZA'];
var randomText = positiveTexts[Math.floor(Math.random() * positiveTexts.length)];
var textGraphics = self.addChild(new Text2(randomText, {
size: getTextSize(true, level),
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 3
}));
textGraphics.anchor.set(0.5, 0.5);
self.speed = 5;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement properties
self.speed = HEART_SPEED;
self.isMovingLeft = false;
self.isMovingRight = false;
// Health
self.health = 100;
self.update = function () {
// Keyboard movement
if (self.isMovingLeft) {
self.x -= self.speed;
}
if (self.isMovingRight) {
self.x += self.speed;
}
// Boundary checking - constrain to boundary box
if (self.x < BOUNDARY_LEFT + HEART_MARGIN) {
self.x = BOUNDARY_LEFT + HEART_MARGIN;
}
if (self.x > BOUNDARY_RIGHT - HEART_MARGIN) {
self.x = BOUNDARY_RIGHT - HEART_MARGIN;
}
if (self.y < BOUNDARY_TOP + HEART_MARGIN) {
self.y = BOUNDARY_TOP + HEART_MARGIN;
}
if (self.y > BOUNDARY_BOTTOM - HEART_MARGIN) {
self.y = BOUNDARY_BOTTOM - HEART_MARGIN;
}
};
self.takeDamage = function (amount) {
if (amount === undefined) amount = 10;
self.health = Math.max(0, self.health - amount);
// Play hurt sound effect
LK.getSound('Damage').play();
// Flash red effect
tween(heartGraphics, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(heartGraphics, {
tint: 0xffffff
}, {
duration: 200
});
}
});
if (self.health <= 0) {
LK.showGameOver();
}
};
return self;
});
var TechniqueMenu = Container.expand(function () {
var self = Container.call(this);
// Background
var bg = self.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
bg.width = 700;
bg.height = 480;
bg.tint = 0x333333;
// Title
var title = self.addChild(new Text2('Técnica de Relajación', {
size: 48,
fill: 0xffffff
}));
title.anchor.set(0.5, 0.5);
title.y = -120;
// Techniques
var techniques = ['Respiración Profunda', 'Mindfulness', 'Relajación Muscular'];
var selectedTechnique = techniques[Math.floor(Math.random() * techniques.length)];
var techniqueText = self.addChild(new Text2(selectedTechnique, {
size: 42,
fill: 0x00ff00
}));
techniqueText.anchor.set(0.5, 0.5);
techniqueText.y = -50;
// Instructions
var instruction = self.addChild(new Text2('Toca para aplicar', {
size: 36,
fill: 0xcccccc
}));
instruction.anchor.set(0.5, 0.5);
instruction.y = 20;
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.update = function () {
self.duration += 16.67;
if (self.duration >= self.maxDuration) {
self.destroy();
}
};
self.down = function () {
// Apply technique - boost credibilidad
credibilidad = Math.min(100, credibilidad + 15);
updateBars();
self.destroy();
};
return self;
});
var TextAttack = Container.expand(function () {
var self = Container.call(this);
// Create random attack text - surprise questions and interruptions
var attackTexts = ['¿Y ESO PARA QUÉ?', '¡INTERRUPTION!', '¿ESTÁS SEGURO?', '¡ESPERA UN MOMENTO!', '¿PUEDES REPETIR?', '¡NO ENTIENDO!', '¿Y SI NO FUNCIONA?', '¡PREGUNTA SORPRESA!'];
var randomText = attackTexts[Math.floor(Math.random() * attackTexts.length)];
var textGraphics = self.addChild(new Text2(randomText, {
size: getTextSize(false, level),
fill: 0xff0000,
stroke: 0x000000,
strokeThickness: 3
}));
textGraphics.anchor.set(0.5, 0.5);
// Enhanced movement properties based on level - moderate difficulty
self.baseSpeed = getAttackSpeed(level);
self.speed = self.baseSpeed;
self.acceleration = 0.08 + (level - 1) * 0.03; // Moderate acceleration
self.maxSpeed = self.baseSpeed * 1.8; // Can accelerate up to 1.8x base speed
// Movement pattern selection (more predictable patterns at lower levels)
self.movementPattern = Math.floor(Math.random() * Math.min(3, level)); // Reduced max patterns
// 0: Basic straight, 1: Zigzag, 2: Pursuit, 3: Spiral (removed erratic)
// Pattern-specific properties
self.speedX = (Math.random() - 0.5) * (3 + level * 1.5); // Moderate horizontal movement
self.speedY = self.speed;
self.waveAmplitude = Math.random() * (15 + level * 8) + 8; // Moderate wave amplitude
self.waveFrequency = Math.random() * 0.06 + 0.03 + level * 0.015; // Moderate wave frequency
self.animTimer = Math.random() * Math.PI * 2;
self.directionChangeTimer = Math.random() * (1000 - level * 60) + 350; // Moderate direction changes
self.maxDirectionChangeTimer = self.directionChangeTimer;
self.pursuitStrength = 0.25 + (level - 1) * 0.12; // Moderate pursuit strength
self.lastHeartX = heart.x;
self.lastHeartY = heart.y;
self.update = function () {
// Get current heart position for pursuit calculations
var heartX = heart.x;
var heartY = heart.y;
// Accelerate over time (attacks get faster)
if (self.speed < self.maxSpeed) {
self.speed += self.acceleration;
self.speedY = self.speed;
}
// Movement pattern execution - simplified and less aggressive
switch (self.movementPattern) {
case 0:
// Basic straight movement with moderate tracking
self.y += self.speedY;
// Moderate horizontal drift toward heart
var heartDirection = heartX > self.x ? 1 : -1;
self.x += heartDirection * (level * 0.35); // Moderate tracking
break;
case 1:
// Zigzag with moderate pursuit
self.y += self.speedY;
self.animTimer += self.waveFrequency;
self.x += Math.sin(self.animTimer) * self.waveAmplitude * 0.15; // Moderate zigzag intensity
// Moderate pursuit element
var pursuitX = (heartX - self.x) * self.pursuitStrength * 0.08; // Moderate pursuit
self.x += pursuitX;
break;
case 2:
// Moderate pursuit - balanced aggression
var deltaX = heartX - self.x;
var deltaY = heartY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.x += deltaX / distance * self.speed * self.pursuitStrength * 0.7; // Moderate pursuit
self.y += deltaY / distance * self.speed * self.pursuitStrength * 0.45; // Moderate vertical pursuit
}
// Still move generally downward but less predictable
self.y += self.speedY * 0.75; // Moderate downward movement
break;
case 3:
// Spiral pattern with moderate pursuit
self.y += self.speedY * 0.85; // Moderate downward movement
self.animTimer += self.waveFrequency;
var spiralRadius = 25 + Math.sin(self.animTimer * 0.3) * 15; // Moderate spiral
self.x += Math.cos(self.animTimer) * spiralRadius * 0.12; // Moderate spiral intensity
// Moderate drift toward heart
var driftX = (heartX - self.x) * 0.035; // Moderate drift
self.x += driftX;
break;
}
// Enhanced boundary behavior - some attacks can bounce
if (self.x < BOUNDARY_LEFT - 50) {
if (level >= 3 && Math.random() < 0.4) {
// Bounce off left wall
self.speedX = Math.abs(self.speedX) * 1.2;
} else {
self.speedX = Math.abs(self.speedX);
}
}
if (self.x > BOUNDARY_RIGHT + 50) {
if (level >= 3 && Math.random() < 0.4) {
// Bounce off right wall
self.speedX = -Math.abs(self.speedX) * 1.2;
} else {
self.speedX = -Math.abs(self.speedX);
}
}
// Store last heart position for next frame
self.lastHeartX = heartX;
self.lastHeartY = heartY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Turn-based constants
// Game Constants
var ENEMY_ATTACK_DURATION = 3000; // 3 seconds for enemy attack phase
var PLAYER_ATTACK_DURATION = 3000; // 3 seconds for player attack phase
var ATTACKS_PER_TURN = 3; // Number of attacks enemy launches per turn
var LEVEL_DIFF = 5;
var MAX_LEVEL = 10;
var WIN_CRED = 60;
var LEVEL_CRED_BOOST = 5;
var GAME_LEFT = 300;
var GAME_RIGHT = 1748;
var GAME_TOP = 200;
var GAME_BOTTOM = 2732;
var GAME_WIDTH = GAME_RIGHT - GAME_LEFT;
// Heart movement constants
var HEART_SPEED = 8;
var HEART_MARGIN = 60;
// Boundary box constants
var BOUNDARY_WIDTH = 800;
var BOUNDARY_HEIGHT = 400;
var BOUNDARY_LEFT = (2048 - BOUNDARY_WIDTH) / 2;
var BOUNDARY_RIGHT = BOUNDARY_LEFT + BOUNDARY_WIDTH;
var BOUNDARY_TOP = GAME_BOTTOM - 900;
var BOUNDARY_BOTTOM = BOUNDARY_TOP + BOUNDARY_HEIGHT;
// Text size scaling function
function getTextSize(isGreen, currentLevel) {
var baseSize = 20;
var sizeMultiplier = 10;
var scale;
if (isGreen) {
// Green words: size 5 at level 1, size 1 at level 5
scale = 6 - currentLevel;
} else {
// Red words: size 1 at level 1, size 5 at level 5
scale = currentLevel;
}
// Ensure scale is between 1 and 5
scale = Math.max(1, Math.min(5, scale));
return baseSize + scale * sizeMultiplier;
}
// Attack difficulty scaling function
function getAttackSpeed(currentLevel) {
// Base speed increases more gradually with level - reduced difficulty
return 4 + (currentLevel - 1) * 2; // Level 1: 4, Level 5: 12
}
function getAttackFrequency(currentLevel) {
// Attack interval decreases (more frequent attacks) with level
return Math.max(300, 1000 - (currentLevel - 1) * 175); // Level 1: 1000ms, Level 5: 300ms
}
function getMaxSimultaneousAttacks(currentLevel) {
// More attacks on screen at once as level increases
return Math.min(8, 2 + currentLevel); // Level 1: 3, Level 5: 7
}
// Game state management
var gameState = 'menu'; // 'menu', 'dialogue', 'enemyTurn', 'playerTurn', 'transition'
var menuContainer = game.addChild(new Container());
// Turn-based variables
var currentTurn = 'enemy'; // 'enemy' or 'player'
var turnTimer = 0;
var attacksLaunched = 0;
var attackTimer = 0;
// Game variables
var level = 1;
var maxLevel = 5;
var dialogueContainer = game.addChild(new Container());
dialogueContainer.visible = false;
// Game rules narrative
var gameRules = "¡Bienvenido a Corazón de Tutor!\n\n" + "CÓMO JUGAR:\n\n" + "🎯 OBJETIVO:\n" + "Supera los 4 niveles manteniendo baja tu ansiedad y alta tu credibilidad.\n\n" + "⚡ CONTROLES:\n" + "• En móvil: Toca y arrastra para mover tu corazón\n" + "• En PC: Usa las flechas izquierda/derecha\n\n" + "💔 ESQUIVA:\n" + "Evita las preguntas sorpresa e interrupciones rojas que aumentan tu ansiedad.\n\n" + "💚 COLECTA:\n" + "Toca las palabras verdes de calma para aumentar tu credibilidad.\n\n" + "🛡️ TÉCNICAS:\n" + "En tu turno, usa técnicas de tutoría para atacar al enemigo:\n" + "• Redirigir: Daña al enemigo y sube credibilidad\n" + "• Respiración: Reduce ansiedad y sube credibilidad\n" + "• Reformulación: Equilibra ambas barras y daña\n\n" + "¡Conviértete en un verdadero Corazón de Tutor!";
// Level narratives
var levelNarratives = ["Nivel 1 – \"Primer Latido\"\n¡Lo lograste!\nHas esquivado con éxito las primeras preguntas sorpresa y aprendido tu primera técnica de Pausa. Síntoma de nervios: la ansiedad cayó un 20 % y tu credibilidad subió un 10 %.\n\"Siente el ritmo de tu corazón. Cada respiro te hace más fuerte.\"", "Nivel 2 – \"Pulso Controlado\"\n¡Buen trabajo!\nAhora dominas la Respiración: mantuviste la calma frente a interrupciones múltiples. Has reducido tu ansiedad un 15 % adicional y tus tutorados confían más en ti.\n\"Cuando el mundo acelera, tú decides pausar.\"", "Nivel 3 – \"Voz Sólida\"\n¡Impresionante!\nHas practicado la Reformulación bajo presión. Tus explicaciones fluyen con claridad, tu ansiedad alcanza su punto más bajo y tu credibilidad roza el 80 %.\n\"No sólo respondas: reformula, conecta y convence.\"", "Nivel 4 – \"Corazón de Tutor\"\n¡Victoria Final!\nTras usar la técnica Redirigir estratégicamente, superaste el desafío y alcanzaste el dominio comunicativo. Ansiedad mínima, credibilidad máxima: eres un Corazón de Tutor.\n\"Comunicar es un arte: lo llevas en el corazón.\""];
// Track if rules have been shown
var rulesShown = false;
var score = 0;
var ansiedad = 0;
var credibilidad = 50;
// UI Elements
var ansiedadBarBg = null,
ansiedadBar = null,
credibilidadBarBg = null,
credibilidadBar = null;
var levelText = null,
scoreText = null;
// Initialize UI
function initializeUI() {
// Ansiedad bar - bottom left
ansiedadBarBg = LK.gui.bottomLeft.addChild(LK.getAsset('ansiedadBarBg', {
anchorX: 0,
anchorY: 1
}));
ansiedadBarBg.x = 120;
ansiedadBarBg.y = -150;
ansiedadBar = LK.gui.bottomLeft.addChild(LK.getAsset('ansiedadBar', {
anchorX: 0,
anchorY: 1
}));
ansiedadBar.x = 120;
ansiedadBar.y = -150;
// Credibilidad bar - bottom right
credibilidadBarBg = LK.gui.bottomRight.addChild(LK.getAsset('credibilidadBarBg', {
anchorX: 1,
anchorY: 1
}));
credibilidadBarBg.x = -120;
credibilidadBarBg.y = -150;
credibilidadBar = LK.gui.bottomRight.addChild(LK.getAsset('credibilidadBar', {
anchorX: 1,
anchorY: 1
}));
credibilidadBar.x = -120;
credibilidadBar.y = -150;
// Text labels
var ansiedadLabel = LK.gui.bottomLeft.addChild(new Text2('Ansiedad', {
size: 30,
fill: 0xffffff
}));
ansiedadLabel.x = 120;
ansiedadLabel.y = -110;
var credibilidadLabel = LK.gui.bottomRight.addChild(new Text2('Credibilidad', {
size: 30,
fill: 0xffffff
}));
credibilidadLabel.anchor.set(1, 1);
credibilidadLabel.x = -120;
credibilidadLabel.y = -110;
// Level and score - keep at top
levelText = LK.gui.topRight.addChild(new Text2('Nivel: 1', {
size: 36,
fill: 0xffffff
}));
levelText.anchor.set(1, 0);
levelText.x = -20;
levelText.y = 50;
}
function updateBars() {
// Update bar widths based on values
var ansiedadWidth = ansiedad / 100 * 300;
var credibilidadWidth = credibilidad / 100 * 300;
ansiedadBar.width = ansiedadWidth;
credibilidadBar.width = credibilidadWidth;
// Adjust credibilidad bar position since it's right-anchored
credibilidadBar.x = -120 - (300 - credibilidadWidth);
// Update text
levelText.setText('Nivel: ' + level);
}
function showRulesDialogue() {
gameState = 'dialogue';
dialogueContainer.visible = true;
// Clear previous dialogue
dialogueContainer.removeChildren();
// Rules text with word wrapping and smaller size
var rulesText = dialogueContainer.addChild(new Text2(gameRules, {
size: 28,
fill: 0xffffff,
wordWrap: true,
wordWrapWidth: 1800
}));
rulesText.anchor.set(0.5, 0.5);
rulesText.x = 1024;
rulesText.y = 1500; // Match moved background position
// Continue button
var continueBtn = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
continueBtn.width = 380;
continueBtn.height = 100;
continueBtn.tint = 0x00aa00;
continueBtn.x = 1024;
continueBtn.y = 2000; // Moved further down from background
var continueText = dialogueContainer.addChild(new Text2('¡Empezar!', {
size: 48,
fill: 0xffffff
}));
continueText.anchor.set(0.5, 0.5);
continueText.x = continueBtn.x;
continueText.y = continueBtn.y;
continueBtn.down = function () {
dialogueContainer.visible = false;
// Now show the first level dialogue
showDialogue(levelNarratives[level - 1]);
};
}
function showDialogue(text) {
gameState = 'dialogue';
dialogueContainer.visible = true;
// Clear previous dialogue
dialogueContainer.removeChildren();
// Dialogue background
var dialogueBg = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
dialogueBg.width = 1900;
dialogueBg.height = 600;
dialogueBg.tint = 0x333333;
dialogueBg.x = 1024;
dialogueBg.y = 1366; // Center vertically (2732/2)
// Dialogue text with word wrapping and smaller size
var dialogueText = dialogueContainer.addChild(new Text2(text, {
size: 32,
fill: 0xffffff,
wordWrap: true,
wordWrapWidth: 1800
}));
dialogueText.anchor.set(0.5, 0.5);
dialogueText.x = 1024;
dialogueText.y = 1366; // Center vertically (2732/2)
// Continue button
var continueBtn = dialogueContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
continueBtn.width = 380;
continueBtn.height = 100;
continueBtn.tint = 0x00aa00;
continueBtn.x = 1024;
continueBtn.y = 1750; // Moved further down from centered dialogue
var continueText = dialogueContainer.addChild(new Text2('Continuar', {
size: 48,
fill: 0xffffff
}));
continueText.anchor.set(0.5, 0.5);
continueText.x = continueBtn.x;
continueText.y = continueBtn.y;
continueBtn.down = function () {
dialogueContainer.visible = false;
gameState = 'enemyTurn'; // Start with enemy turn
// Initialize and show UI if not already done
if (!ansiedadBarBg || ansiedadBarBg && !ansiedadBarBg.parent) {
initializeUI();
}
// Initialize enemy health bar
enemy.initHealthBar();
// Show enemy and health bar again
enemy.visible = true;
// Ensure health bar is visible and properly updated
enemy.updateHealthBar();
updateBars();
heart.visible = true;
boundaryBox.visible = true;
boundaryBoxBorder.visible = true;
// Adjust music volume based on level intensity
var musicVolume = 0.4 + level * 0.1; // Volume increases with level
LK.playMusic('fondo', {
fade: {
start: LK.getCurrentMusicVolume ? LK.getCurrentMusicVolume() : 0.6,
end: musicVolume,
duration: 800
}
});
};
}
// Reset button functionality
function resetGame() {
// Stop all current music
LK.stopMusic();
// Reset all game variables to initial state
level = 1;
ansiedad = 0;
credibilidad = 50;
score = 0;
gameState = 'menu';
currentTurn = 'enemy';
turnTimer = 0;
attacksLaunched = 0;
attackTimer = 0;
greenAttackTimer = 0;
ansiedadTimer = 0;
scoreTimer = 0;
// Destroy all active attacks
for (var i = textAttacks.length - 1; i >= 0; i--) {
textAttacks[i].destroy();
}
textAttacks = [];
for (var j = greenTextAttacks.length - 1; j >= 0; j--) {
greenTextAttacks[j].destroy();
}
greenTextAttacks = [];
// Reset enemy to initial state
if (enemy) {
enemy.visible = false;
enemy.health = enemy.maxHealth;
// Reset enemy graphics to level 1
enemy.updateEnemyGraphics();
}
// Hide heart character
if (heart) {
heart.visible = false;
heart.health = 100;
heart.x = 2048 / 2;
heart.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
heart.isMovingLeft = false;
heart.isMovingRight = false;
}
// Hide boundary box
if (boundaryBox) {
boundaryBox.visible = false;
}
// Hide boundary box border
if (boundaryBoxBorder) {
boundaryBoxBorder.visible = false;
}
// Hide dialogue
dialogueContainer.visible = false;
// Hide player turn menu
hidePlayerTurnMenu();
// Hide and reset UI bars
if (ansiedadBarBg) {
ansiedadBarBg.visible = false;
ansiedadBarBg.parent.removeChild(ansiedadBarBg);
ansiedadBarBg = null;
}
if (ansiedadBar) {
ansiedadBar.visible = false;
ansiedadBar.parent.removeChild(ansiedadBar);
ansiedadBar = null;
}
if (credibilidadBarBg) {
credibilidadBarBg.visible = false;
credibilidadBarBg.parent.removeChild(credibilidadBarBg);
credibilidadBarBg = null;
}
if (credibilidadBar) {
credibilidadBar.visible = false;
credibilidadBar.parent.removeChild(credibilidadBar);
credibilidadBar = null;
}
if (levelText) {
levelText.visible = false;
levelText.parent.removeChild(levelText);
levelText = null;
}
// Hide enemy health bar
if (enemy && enemy.healthBar) {
enemy.healthBar.visible = false;
enemy.healthBar.parent.removeChild(enemy.healthBar);
enemy.healthBar = null;
}
if (enemy && enemy.healthBarBg) {
enemy.healthBarBg.visible = false;
enemy.healthBarBg.parent.removeChild(enemy.healthBarBg);
enemy.healthBarBg = null;
}
// Show main menu
menuContainer.visible = true;
// Start menu music from beginning
LK.playMusic('menumusic');
}
// Start menu music
LK.playMusic('menumusic');
// Create reset button
var resetButton = LK.gui.topRight.addChild(LK.getAsset('resetButton', {
anchorX: 1,
anchorY: 0
}));
resetButton.x = -20;
resetButton.y = 120;
var resetText = LK.gui.topRight.addChild(new Text2('RESET', {
size: 24,
fill: 0xffffff
}));
resetText.anchor.set(0.5, 0.5);
resetText.x = resetButton.x - resetButton.width / 2;
resetText.y = resetButton.y + resetButton.height / 2;
// Reset button interaction
resetButton.down = function () {
// Flash button on press
tween(resetButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(resetButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
// Reset the game
resetGame();
};
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.width = 500;
playButton.height = 150;
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 300;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 90,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Play button interaction
playButton.down = function (x, y, obj) {
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Switch to background music
LK.playMusic('fondo', {
fade: {
start: 0,
end: 0.6,
duration: 1000
}
});
// Show rules first if not shown, then level dialogue
menuContainer.visible = false;
if (!rulesShown) {
rulesShown = true;
showRulesDialogue();
} else {
showDialogue(levelNarratives[level - 1]);
}
};
// Create the enemy
var enemy = game.addChild(new Enemy());
enemy.x = 2048 / 2;
enemy.baseX = enemy.x;
enemy.y = GAME_TOP + 300; // Moved down further to avoid health bar overlap
enemy.visible = false;
// Create boundary box with white border (border first, then main box on top)
var boundaryBoxBorder = game.addChild(LK.getAsset('boundaryBoxBorder', {
anchorX: 0.5,
anchorY: 0.5
}));
boundaryBoxBorder.x = 2048 / 2;
boundaryBoxBorder.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
boundaryBoxBorder.alpha = 1.0; // Fully visible white border
boundaryBoxBorder.visible = false;
// Create boundary box visual (black box on top of white border)
var boundaryBox = game.addChild(LK.getAsset('boundaryBox', {
anchorX: 0.5,
anchorY: 0.5
}));
boundaryBox.x = 2048 / 2;
boundaryBox.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2;
boundaryBox.alpha = 1.0; // Fully opaque black background
boundaryBox.visible = false;
// Create the heart character
var heart = game.addChild(new Heart());
heart.x = 2048 / 2;
heart.y = BOUNDARY_TOP + BOUNDARY_HEIGHT / 2; // Position centered in boundary box
heart.visible = false;
// Keyboard controls - using LK event system instead of window
LK.on('keydown', function (event) {
if (gameState !== 'enemyTurn') return;
if (event.key === 'ArrowLeft') {
heart.isMovingLeft = true;
}
if (event.key === 'ArrowRight') {
heart.isMovingRight = true;
}
});
LK.on('keyup', function (event) {
if (gameState !== 'enemyTurn') return;
if (event.key === 'ArrowLeft') {
heart.isMovingLeft = false;
}
if (event.key === 'ArrowRight') {
heart.isMovingRight = false;
}
});
// Touch controls for mobile
game.move = function (x, y, obj) {
if (gameState === 'enemyTurn') {
// Move heart towards touch position
if (x < heart.x) {
heart.isMovingLeft = true;
heart.isMovingRight = false;
} else if (x > heart.x) {
heart.isMovingRight = true;
heart.isMovingLeft = false;
}
}
};
game.up = function (x, y, obj) {
if (gameState === 'enemyTurn') {
heart.isMovingLeft = false;
heart.isMovingRight = false;
}
};
// Game arrays and timers
var textAttacks = [];
var greenTextAttacks = [];
var attackTimer = 0;
var greenAttackTimer = 0;
var ansiedadTimer = 0;
var scoreTimer = 0;
// Player turn menu
var playerTurnMenu = null;
function showPlayerTurnMenu() {
if (playerTurnMenu) return; // Already showing
playerTurnMenu = game.addChild(new Container());
playerTurnMenu.x = 1024;
playerTurnMenu.y = 1800;
// Background
var menuBg = playerTurnMenu.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
menuBg.width = 1600;
menuBg.height = 600;
menuBg.tint = 0x333333;
// Title
var title = playerTurnMenu.addChild(new Text2('Tu Turno - Técnicas de Tutoría', {
size: 40,
fill: 0xffffff
}));
title.anchor.set(0.5, 0.5);
title.y = -100;
// Technique buttons
var techniques = [{
text: 'Redirigir',
effect: function effect() {
credibilidad += 5;
enemy.takeDamage(30);
}
}, {
text: 'Respiración',
effect: function effect() {
credibilidad += 30;
ansiedad = Math.max(0, ansiedad - 10);
}
}, {
text: 'Reformulación',
effect: function effect() {
credibilidad += 15;
ansiedad = Math.max(0, ansiedad - 15);
enemy.takeDamage(15);
}
}];
for (var i = 0; i < techniques.length; i++) {
var btn = playerTurnMenu.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
btn.width = 450;
btn.height = 120;
btn.tint = 0x00aa00;
btn.x = (i - 1) * 420;
btn.y = 40;
btn.techniqueIndex = i;
var btnText = playerTurnMenu.addChild(new Text2(techniques[i].text, {
size: 38,
fill: 0xffffff
}));
btnText.anchor.set(0.5, 0.5);
btnText.x = btn.x;
btnText.y = btn.y;
btn.down = function () {
var technique = techniques[this.techniqueIndex];
technique.effect();
credibilidad = Math.min(100, credibilidad);
ansiedad = Math.max(0, ansiedad);
updateBars();
// Flash effect
tween(this, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
tint: 0x00aa00
}, {
duration: 100
});
}
});
// End player turn and prepare for enemy turn
hidePlayerTurnMenu();
// Set a brief delay before enemy turn starts
LK.setTimeout(function () {
gameState = 'enemyTurn';
turnTimer = 0;
attacksLaunched = 0;
attackTimer = 0;
}, 500); // 500ms delay before enemy can start attacking
};
}
}
function hidePlayerTurnMenu() {
if (playerTurnMenu) {
playerTurnMenu.destroy();
playerTurnMenu = null;
}
}
// Main game update loop
game.update = function () {
// Only update game logic when in turn-based states
if (gameState !== 'enemyTurn' && gameState !== 'playerTurn') return;
// Allow heart movement during gameplay
if (gameState === 'enemyTurn' || gameState === 'playerTurn') {
heart.update();
}
// Turn-based system
if (gameState === 'enemyTurn') {
// Only allow enemy attacks if enemy is alive (visible)
if (enemy.visible) {
// Enemy's turn - launch attacks
turnTimer += 16.67; // Add frame time
attackTimer += 16.67;
// Enhanced attack system with level-based difficulty
var attackInterval = getAttackFrequency(level);
var maxAttacksPerTurn = getMaxSimultaneousAttacks(level);
var maxSimultaneousAttacks = getMaxSimultaneousAttacks(level);
if (attackTimer >= attackInterval && attacksLaunched < maxAttacksPerTurn && textAttacks.length < maxSimultaneousAttacks) {
// Launch multiple attacks in formation for higher levels
var attacksToLaunch = level >= 3 ? Math.floor(Math.random() * 2) + 1 : 1;
for (var attackIndex = 0; attackIndex < attacksToLaunch && attacksLaunched < maxAttacksPerTurn; attackIndex++) {
var textAttack = new TextAttack();
// Varied spawn positions based on level
if (level >= 4) {
// Higher levels: attacks can spawn from sides too
var spawnSide = Math.random();
if (spawnSide < 0.6) {
// From above (normal)
textAttack.x = enemy.x + (Math.random() - 0.5) * 200;
textAttack.y = enemy.y + 50;
} else if (spawnSide < 0.8) {
// From left side
textAttack.x = BOUNDARY_LEFT - 50;
textAttack.y = BOUNDARY_TOP + Math.random() * BOUNDARY_HEIGHT;
} else {
// From right side
textAttack.x = BOUNDARY_RIGHT + 50;
textAttack.y = BOUNDARY_TOP + Math.random() * BOUNDARY_HEIGHT;
}
} else {
// Lower levels: spawn from above only
textAttack.x = enemy.x + (Math.random() - 0.5) * (100 + level * 50);
textAttack.y = enemy.y + 50;
}
textAttacks.push(textAttack);
game.addChild(textAttack);
attacksLaunched++;
}
enemy.showAttackEffect();
attackTimer = 0;
}
// End enemy turn only when all attacks are launched AND all attacks are processed (hit or missed)
if (attacksLaunched >= maxAttacksPerTurn && textAttacks.length === 0) {
gameState = 'playerTurn';
turnTimer = 0;
// Show player turn interface
showPlayerTurnMenu();
}
}
} else if (gameState === 'playerTurn') {
// Player's turn - wait for player action or timeout
turnTimer += 16.67;
// Player turn ends only when technique is selected (handled in showPlayerTurnMenu)
// No automatic timeout - player must make a choice
}
// Update and check TextAttack collisions
for (var i = textAttacks.length - 1; i >= 0; i--) {
var atk = textAttacks[i];
// Initialize collision tracking if not present
if (atk.lastIntersecting === undefined) {
atk.lastIntersecting = false;
}
// Check for NEW collision with heart (transition from not intersecting to intersecting)
var currentIntersecting = atk.intersects(heart);
if (!atk.lastIntersecting && currentIntersecting) {
// Only damage on the first frame of collision
heart.takeDamage(15);
ansiedad = Math.min(100, ansiedad + 15);
credibilidad = Math.max(0, credibilidad - 10);
// Play hurt sound when anxiety increases
LK.getSound('Damage').play();
updateBars();
atk.destroy();
textAttacks.splice(i, 1);
continue;
}
// Update last intersection state
atk.lastIntersecting = currentIntersecting;
// Remove off-screen attacks
if (atk.y > GAME_BOTTOM + 100) {
atk.destroy();
textAttacks.splice(i, 1);
}
}
// Update and check GreenTextAttack collisions
for (var k = greenTextAttacks.length - 1; k >= 0; k--) {
var greenAtk = greenTextAttacks[k];
// Initialize collision tracking if not present
if (greenAtk.lastIntersecting === undefined) {
greenAtk.lastIntersecting = false;
}
// Check for NEW collision with heart (transition from not intersecting to intersecting)
var currentIntersecting = greenAtk.intersects(heart);
if (!greenAtk.lastIntersecting && currentIntersecting) {
// Only give benefit on the first frame of collision
credibilidad = Math.min(100, credibilidad + 8);
updateBars();
greenAtk.destroy();
greenTextAttacks.splice(k, 1);
continue;
}
// Update last intersection state
greenAtk.lastIntersecting = currentIntersecting;
// Remove off-screen green attacks
if (greenAtk.y > GAME_BOTTOM + 100) {
greenAtk.destroy();
greenTextAttacks.splice(k, 1);
}
}
// Check game over conditions
if (ansiedad >= 100 || credibilidad <= 0) {
// Fade out music before game over
LK.playMusic('fondo', {
fade: {
start: LK.getCurrentMusicVolume ? LK.getCurrentMusicVolume() : 0.6,
end: 0,
duration: 500
}
});
LK.setTimeout(function () {
LK.showGameOver();
}, 500);
}
// Check win condition
if (level >= maxLevel && credibilidad >= WIN_CRED) {
LK.showYouWin();
}
};