User prompt
move player and monster a little hight in the battle scree, so there is more room for the actions.
User prompt
Now they are a little to spearated, move them little higher closer to the image
User prompt
hp and dmg of monster and player a little below the images so they don't overlap
User prompt
Monster and player in battle screen shouldbe bigger
User prompt
Modify MonsterTile down handler to allow re-engaging undefeated monsters after running away or for initial engagement
User prompt
Make sure player can click again on a cell where a monster is still alive
User prompt
Tiles that have been open grey is too dark, make it ligher
User prompt
when a cell is cleared make the grey ligher
User prompt
Player should be able to clilck on a cell that has been uncovered again, as long as it has an enemy that has not been defeated (since the p layer run away)
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'width')' in or related to this line: 'var totalButtonWidth = self.attackBtn.width + self.runBtn.width + self.itemsBtn.width + 80; // Widths + 2*40px spacing' Line Number: 79
User prompt
Reenagging monsters is not working,can you fix it
User prompt
make sure the action on the battle screen are clearly separated
User prompt
Monsters that have not been defeated beacuse player run away, can be fought again by player by clicking on them again.
User prompt
Player should be able to try to fight an monster they runed away from by clicking on it on the grid
User prompt
also add a new action called items. it will only be enabled if player has consumables like a life potion. if not it will be greyed out. if the payer has consumables, player should be able to select one of them and used them
User prompt
attack and run actiono whouldlbe next to each other
User prompt
add more action for the battle screen. First lets add a new RUN action. Player can run the battle when clicking it. However if the enemy is a higher level than the player, there will be a chance the player can't run, and has to try again. If he runs, then the monster will be back to full life and will be alive in the grid.
User prompt
Change all the game font to a font more retro pixelart that lk supports
User prompt
EXP is always fixed at 3 to level up. IT should not be this way. Once the player levels up, next time it will need more EXP to level up again.
User prompt
Every time the in stats, show the player level. every time the player increases its level, make sure he willneed more EXP to level up again
User prompt
Stats+ shoudl also cahnge its tint to yellow
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'statsBtn.style.fill = "#fff"; // Default white color' Line Number: 2121
User prompt
Make sure stats button gets a + next to it and yellow color when player has skill points to use. Either that or an indicator you would think works best
User prompt
Instead of FOUND, use Loot
User prompt
Enemies should drop loot. mainly coins depending on their level, but rarely can drop other loot like gear. we willdefine the % for drops later.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BattleScreen: Handles the turn-based combat var BattleScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.8 }); // Monster display self.monsterDisplay = self.attachAsset('monsterTileMonster', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 300, // Position near top-right corner within the battle area y: 2732 * 0.2 + 200, // Position near top-right corner within the battle area scaleX: 2, scaleY: 2 }); // Monster stats text self.monsterStatText = new Text2('', { size: 60, fill: "#fff" }); self.monsterStatText.anchor.set(0.5, 0); // Anchor to top-center self.monsterStatText.x = self.monsterDisplay.x; self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height / 2 + 60; // Move further below monster image self.addChild(self.monsterStatText); // Player stats text (in battle) self.playerBattleStatText = new Text2('', { size: 60, fill: "#fff" }); self.playerDisplay = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, x: 300, // Position near bottom-left corner within the battle area y: 2732 * 0.8 - 200, // Position near bottom-left corner within the battle area scaleX: 2, scaleY: 2 }); self.playerBattleStatText.anchor.set(0.5, 0); // Anchor to top-center // Set stat text position to where hero will appear self.playerBattleStatText.x = 300; self.playerBattleStatText.y = 2732 * 0.8 - 200 + 100 + 60; // 100 is half hero height at scale 2 self.addChild(self.playerBattleStatText); // Action button (Attack) self.attackBtn = new Text2('ATTACK', { size: 80, fill: "#fff", font: "Impact" }); self.attackBtn.anchor.set(0.5, 0.5); self.attackBtn.x = 2048 / 2; self.attackBtn.y = 2732 - 300; // Position near bottom self.addChild(self.attackBtn); self.attackBtn.down = function () { if (!self.visible) return; self.playerTurn(); }; self.currentMonster = null; // Reference to the monster tile being fought // Start battle self.startBattle = function (monsterTile) { self.currentMonster = monsterTile; self.visible = true; self.alpha = 0; game.addChild(self); // Add battle screen to game updateGUI(); // Ensure main GUI elements are hidden // Hide hero/monster and attack button for intro animation self.monsterDisplay.visible = false; self.playerDisplay.visible = false; self.monsterStatText.visible = false; self.playerBattleStatText.visible = false; self.attackBtn.visible = false; // Fade in battle screen tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Show "You encountered a monster!" message var encounterText = new Text2('You encountered a monster!', { size: 90, fill: 0xFFE066 }); encounterText.anchor.set(0.5, 0.5); encounterText.x = 2048 / 2; encounterText.y = 2732 / 2 - 200; self.addChild(encounterText); tween(encounterText, { alpha: 0 }, { duration: 900, delay: 900, easing: tween.easeOut, onFinish: function onFinish() { encounterText.destroy(); // Prepare hero/monster offscreen for slide-in // Save original positions var heroTargetX = 300; var heroTargetY = 2732 * 0.8 - 200; var monsterTargetX = 2048 - 300; var monsterTargetY = 2732 * 0.2 + 200; self.playerDisplay.x = -self.playerDisplay.width; // Offscreen left self.playerDisplay.y = heroTargetY; self.monsterDisplay.x = 2048 + self.monsterDisplay.width; // Offscreen right self.monsterDisplay.y = monsterTargetY; self.playerDisplay.visible = true; self.monsterDisplay.visible = true; self.monsterStatText.visible = false; self.playerBattleStatText.visible = false; // Slide in hero and monster simultaneously var slideInCount = 0; function afterSlideIn() { slideInCount++; if (slideInCount === 2) { // Show stats and decide who attacks first self.monsterStatText.visible = true; self.playerBattleStatText.visible = true; self.updateBattleStats(); // Randomize who attacks first: 0 = player, 1 = monster var firstAttacker = Math.random() < 0.5 ? "player" : "monster"; var firstText = firstAttacker === "monster" ? "Monster strikes first!" : "You start!"; var firstTextColor = firstAttacker === "monster" ? 0xff0000 : 0xFFE066; var whoFirstText = new Text2(firstText, { size: 90, fill: firstTextColor }); whoFirstText.anchor.set(0.5, 0.5); whoFirstText.x = 2048 / 2; whoFirstText.y = 2732 / 2; self.addChild(whoFirstText); tween(whoFirstText, { alpha: 0 }, { duration: 900, delay: 900, easing: tween.easeOut, onFinish: function onFinish() { whoFirstText.destroy(); // Show attack button and start the correct turn after a short delay self.attackBtn.visible = true; if (firstAttacker === "monster") { LK.setTimeout(self.monsterTurn, 500); } // If player starts, do nothing: player must press attackBtn } }); } } tween(self.playerDisplay, { x: heroTargetX }, { duration: 600, easing: tween.cubicOut, onFinish: afterSlideIn }); tween(self.monsterDisplay, { x: monsterTargetX }, { duration: 600, easing: tween.cubicOut, onFinish: afterSlideIn }); } }); } }); }; // End battle self.endBattle = function (win) { self.visible = false; self.alpha = 0; // Reset alpha self.attackBtn.visible = false; if (win) { // expGained and player.gainExp(expGained) are now handled in playerTurn's onFinish monster death animation self.currentMonster.defeat(); monstersLeft--; updateGUI(); // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); if (monstersLeft <= 0) { // If not last level, advance to next level if (currentLevel < LEVELS.length - 1) { // Show level end screen levelEndScreen.startLevelEndScreen(); } else { LK.showYouWin(); gameOver = true; } } else {} // Player lost, game over is handled in handleTileDown } self.currentMonster = null; self.parent.removeChild(self); // Remove battle screen from game }; // Update battle stats display self.updateBattleStats = function () { if (self.currentMonster) { self.monsterStatText.setText('HP:' + self.currentMonster.hp + '/' + self.currentMonster.maxHp + ' DMG:' + self.currentMonster.damage); self.playerBattleStatText.setText('HP:' + player.hp + '/' + player.maxHp + ' DMG:' + player.damage); } }; // Player's turn self.playerTurn = function () { if (!self.currentMonster) return; // Animate player attack: slide playerDisplay forward, then back, then apply damage var originalX = self.playerDisplay.x; var attackX = originalX + 220; self.attackBtn.visible = false; // Hide attack button during animation // Show "Player Attacks!" text var playerAttackText = new Text2('Player Attacks!', { size: 80, fill: 0xffe066 }); playerAttackText.anchor.set(0.5, 0.5); playerAttackText.x = 2048 / 2; playerAttackText.y = self.monsterStatText.y - 100; self.addChild(playerAttackText); function doAttack() { // Damage and flash self.currentMonster.hp -= player.damage; LK.effects.flashObject(self.monsterDisplay, 0xffe066, 300); // Show animated -X text above monster in battle if (self.currentMonster && self.monsterDisplay) { var monsterDmgText = new Text2('-' + player.damage, { size: 90, fill: 0xff0000 }); monsterDmgText.anchor.set(0.5, 1); // Position above monster sprite monsterDmgText.x = self.monsterDisplay.x; monsterDmgText.y = self.monsterDisplay.y - self.monsterDisplay.height / 2 - 20; self.addChild(monsterDmgText); // Animate: pop up and fade out tween(monsterDmgText, { y: monsterDmgText.y - 80, alpha: 0 }, { duration: 700, easing: tween.cubicOut, onFinish: function onFinish() { monsterDmgText.destroy(); } }); } self.updateBattleStats(); // Fade out attack text tween(playerAttackText, { alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { playerAttackText.destroy(); } }); // If monster dies, animate monster death if (self.currentMonster.hp <= 0) { // Monster death animation: fade out and scale down tween(self.monsterDisplay, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 600, easing: tween.cubicIn, onFinish: function onFinish() { var expGained = self.currentMonster.exp; var lootMessages = []; // Award EXP player.gainExp(expGained); // Coin Drop var coinsDropped = Math.floor(self.currentMonster.exp * (2 + Math.random() * 4)); // 2-5 coins per EXP point if (coinsDropped > 0) { player.gold += coinsDropped; lootMessages.push(coinsDropped + ' Coins'); } // Gear Drop (Rare) var gearDropChance = 0.1; // 10% chance for now, we can adjust this later! if (Math.random() < gearDropChance) { if (chestScreen && chestScreen._rewardTypes && chestScreen._rewardTypes.length > 2 && chestScreen._lootQualities && chestScreen._lootQualities.length > 0) { var potentialGearRewards = chestScreen._rewardTypes.slice(2); // Exclude potion and coins if (potentialGearRewards.length > 0) { var gearReward = potentialGearRewards[Math.floor(Math.random() * potentialGearRewards.length)]; var gearQuality = chestScreen._lootQualities[Math.floor(Math.random() * chestScreen._lootQualities.length)]; player.equipGear(gearReward.type, gearQuality); // This also calls player.updateEquippedStats() var gearLabel = gearQuality.label + ' ' + gearReward.label; lootMessages.push(gearLabel); // playerStatsScreen and playerInventoryScreen will be updated via updateGUI() when battle ends } } } // Construct Victory Message var victoryMessage = 'Victory!\nGained ' + expGained + ' EXP!'; if (lootMessages.length > 0) { victoryMessage += '\n\nFound:\n' + lootMessages.join('\n'); } // Show "Victory!" message before closing battle screen var victoryText = new Text2(victoryMessage, { size: 100, fill: 0xA3E635, align: 'center' // Ensure multi-line text is centered }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 2048 / 2; victoryText.y = 2732 / 2; self.addChild(victoryText); victoryText.alpha = 0; // Fade in, hold, then fade out tween(victoryText, { alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(victoryText, { alpha: 0 }, { duration: 500, delay: 1000, // Increased delay to read loot easing: tween.easeIn, onFinish: function onFinish() { victoryText.destroy(); self.endBattle(true); // Player wins // Reset monsterDisplay for next battle self.monsterDisplay.alpha = 1; self.monsterDisplay.scaleX = 2; self.monsterDisplay.scaleY = 2; } }); } }); } }); } else { // Monster survives, monster's turn after a delay LK.setTimeout(self.monsterTurn, 500); self.attackBtn.visible = true; } } // Animate player forward, then back, then do attack tween(self.playerDisplay, { x: attackX }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.playerDisplay, { x: originalX }, { duration: 180, easing: tween.cubicIn, onFinish: doAttack }); } }); }; // Monster's turn self.monsterTurn = function () { if (!self.currentMonster) return; // Animate monster attack: slide monsterDisplay forward, then back, then apply damage var originalX = self.monsterDisplay.x; var attackX = originalX - 220; // Slide left toward player self.attackBtn.visible = false; // Hide attack button during animation // Show "Monster Attacks!" text var monsterAttackText = new Text2('Monster Attacks!', { size: 80, fill: 0xff0000 }); monsterAttackText.anchor.set(0.5, 0.5); monsterAttackText.x = 2048 / 2; monsterAttackText.y = self.playerBattleStatText.y + 100; self.addChild(monsterAttackText); function doMonsterAttack() { player.takeDamage(self.currentMonster.damage); // Flash monster and player LK.effects.flashObject(self.monsterDisplay, 0xff0000, 300); // Flash monster indicating it attacked LK.effects.flashObject(self.playerDisplay, 0xff0000, 300); // Flash player sprite LK.effects.flashObject(self.playerBattleStatText, 0xff0000, 300); // Flash player stats indicating damage // Show animated -X text above player in battle (damage taken) or show "Blocked!" text if (player && self.playerDisplay) { var damageTaken = Math.max(0, self.currentMonster.damage - player.defense); var playerDmgText; var textColor; if (damageTaken > 0) { playerDmgText = new Text2('-' + damageTaken, { size: 90, fill: 0xff0000 // Red for damage }); textColor = 0xff0000; } else { playerDmgText = new Text2('Blocked!', { size: 80, fill: 0x7DD3FC // Light blue for blocked }); textColor = 0x7DD3FC; } playerDmgText.anchor.set(0.5, 1); // Position above player sprite playerDmgText.x = self.playerDisplay.x; playerDmgText.y = self.playerDisplay.y - self.playerDisplay.height / 2 - 20; self.addChild(playerDmgText); // Animate: pop up and fade out tween(playerDmgText, { y: playerDmgText.y - 80, alpha: 0 }, { duration: 700, easing: tween.cubicOut, onFinish: function onFinish() { playerDmgText.destroy(); } }); } // Fade out attack text tween(monsterAttackText, { alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { monsterAttackText.destroy(); // Show attack button again if player is still alive and battle is ongoing if (player.hp > 0 && self.currentMonster && !gameOver) { self.attackBtn.visible = true; } } }); self.updateBattleStats(); if (player.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; revealAllMonsters(); self.endBattle(false); // Player loses } } // Animate monster forward, then back, then do attack tween(self.monsterDisplay, { x: attackX }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.monsterDisplay, { x: originalX }, { duration: 180, easing: tween.cubicIn, onFinish: doMonsterAttack }); } }); }; return self; }); // ChestScreen: Handles the chest found popup var ChestScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.8 }); // Chest display (center) - use chest image asset self.chestDisplay = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, scaleX: 2, scaleY: 2 }); // "You found a chest!" text self.chestText = new Text2('You found a chest!', { size: 90, fill: 0xFFD700 }); self.chestText.anchor.set(0.5, 0.5); self.chestText.x = 2048 / 2; self.chestText.y = 2732 / 2 - 350; self.addChild(self.chestText); // Player display (bottom, like battle screen) self.playerDisplay = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 * 0.8, scaleX: 2, scaleY: 2 }); // --- Double-tap to open chest logic --- self._lastTapTime = 0; self._tapCount = 0; self._rewardPopup = null; // Chest reward types self._rewardTypes = [{ type: "potion", label: "Life Potion", color: 0xA3E635 }, { type: "coins", label: "Coins", color: 0xFFD700 }, { type: "shield", label: "Shield", color: 0x7DD3FC }, { type: "sword", label: "Sword", color: 0xF87171 }, { type: "helmet", label: "Helmet", color: 0xFACC15 }, { type: "armour", label: "Armour", color: 0xA78BFA }]; // Loot qualities self._lootQualities = [{ label: "Wooden", modifier: 1, color: 0x8B4513 // SaddleBrown }, { label: "Bronze", modifier: 2, color: 0xCD7F32 // Bronze }, { label: "Silver", modifier: 3, color: 0xC0C0C0 // Silver }]; // Helper to show reward popup self._showRewardPopup = function (reward) { if (self._rewardPopup) { self._rewardPopup.destroy(); self._rewardPopup = null; } var popup = new Container(); // Overlay var overlay = LK.getAsset('emptyTileCover', { width: 900, height: 400, alpha: 0.95, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); popup.addChild(overlay); // Reward text var rewardText = new Text2('You found: ' + reward.label + '!', { size: 80, fill: reward.color }); rewardText.anchor.set(0.5, 0.5); rewardText.x = 2048 / 2; rewardText.y = 2732 / 2 - 40; popup.addChild(rewardText); // "OK" button var okBtn = new Text2('OK', { size: 70, fill: "#fff", font: "Impact" }); okBtn.anchor.set(0.5, 0.5); okBtn.x = 2048 / 2; okBtn.y = 2732 / 2 + 80; okBtn.down = function () { popup.destroy(); self._rewardPopup = null; self.endChest(); }; popup.addChild(okBtn); self._rewardPopup = popup; game.addChild(popup); }; // Double-tap handler for chest self.chestDisplay.down = function (x, y, obj) { var now = Date.now(); if (now - self._lastTapTime < 400) { self._tapCount++; } else { self._tapCount = 1; } self._lastTapTime = now; if (self._tapCount === 2 && self.visible && !self._rewardPopup) { // Open chest if (self.currentChest && !self.currentChest.opened) { self.currentChest.open(); // Pick a random reward var reward = self._rewardTypes[Math.floor(Math.random() * self._rewardTypes.length)]; var quality = null; if (reward.type !== "potion" && reward.type !== "coins") { // For gear, pick a random quality quality = self._lootQualities[Math.floor(Math.random() * self._lootQualities.length)]; } // Apply reward effect (for now, just heal for potion, add coins, or do nothing for gear) if (reward.type === "potion") { player.inventory.potionCount++; // Add potion to inventory self._showRewardPopup(reward); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } else if (reward.type === "coins") { player.gold += 10 + Math.floor(Math.random() * 10); // Add 10-20 coins self._showRewardPopup(reward); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } else if (quality) { // Add gear to player inventory with quality modifier player.equipGear(reward.type, quality); var lootLabel = quality.label + ' ' + reward.label; self._showRewardPopup({ label: lootLabel, color: quality.color }); playerStatsScreen.updateStatsDisplay(); // Update stats screen if open playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open } } self._tapCount = 0; } }; // Start chest screen self.startChest = function (chestTile) { self.currentChest = chestTile; self.visible = true; self.alpha = 0; game.addChild(self); // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End chest screen self.endChest = function () { self.visible = false; self.alpha = 0; self.currentChest = null; if (self._rewardPopup) { self._rewardPopup.destroy(); self._rewardPopup = null; } if (self.parent) self.parent.removeChild(self); updateGUI(); // Ensure main GUI is updated after closing chest screen // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); }; return self; }); // ChestTile: Represents a chest hidden under a tile var ChestTile = Container.expand(function () { var self = Container.call(this); self.revealed = false; self.opened = false; self.cover = self.attachAsset('emptyTileCover', { anchorX: 0.5, anchorY: 0.5 }); // Use a chest image asset self.chest = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); // Reveal the chest self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.chest.alpha = 1; }; // Mark as opened self.open = function () { self.opened = true; self.cover.alpha = 0.1; // Grey out cover self.chest.alpha = 0.5; // Grey out chest image // Optionally animate chest opening here }; return self; }); // EmptyTile: Represents a safe tile (no monster) var EmptyTile = Container.expand(function () { var self = Container.call(this); self.revealed = false; self.adjacentMonsterDamage = 0; // Change to store damage sum self.cover = self.attachAsset('emptyTileCover', { anchorX: 0.5, anchorY: 0.5 }); self.bg = self.attachAsset('emptyTileBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); self.adjText = new Text2('', { size: 48, fill: "#fff" }); self.adjText.anchor.set(0.5, 0.5); self.adjText.alpha = 0; self.addChild(self.adjText); self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.bg.alpha = 1; if (self.adjacentMonsterDamage > 0) { // Check damage sum self.adjText.setText(self.adjacentMonsterDamage + ''); // Display damage sum self.adjText.alpha = 1; } }; return self; }); // LevelEndScreen: Appears after clearing a level, offering Next Level and Shop options var LevelEndScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1000, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Level Complete!', { size: 100, fill: 0xA3E635 }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Next Level Button self.nextLevelBtn = new Text2('NEXT LEVEL', { size: 80, fill: "#fff", font: "Impact" }); self.nextLevelBtn.anchor.set(0.5, 0.5); self.nextLevelBtn.x = 2048 / 2; self.nextLevelBtn.y = self.panel.y + 100; self.nextLevelBtn.down = function () { if (!self.visible) return; self.endLevelEndScreen(); currentLevel++; MONSTER_COUNT = LEVELS[currentLevel].monsters; generateBoard(); updateGUI(); levelText.setText('LVL: ' + (currentLevel + 1)); gameOver = false; }; self.addChild(self.nextLevelBtn); // Shop Button self.shopBtn = new Text2('SHOP', { size: 80, fill: "#fff", font: "Impact" }); self.shopBtn.anchor.set(0.5, 0.5); self.shopBtn.x = 2048 / 2; self.shopBtn.y = self.panel.y + 300; self.shopBtn.down = function () { if (!self.visible) return; self.endLevelEndScreen(); shopScreen.startShopScreen(); }; self.addChild(self.shopBtn); // Start the level end screen self.startLevelEndScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the level end screen self.endLevelEndScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); updateGUI(); // Ensure main GUI is updated when closing // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); }; return self; }); var MainScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1000, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Dungeon Sweeper', { size: 150, fill: 0xFFE066 // Gold }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 150; self.addChild(self.titleText); // Start Game Button self.startGameBtn = new Text2('START GAME', { size: 80, fill: "#fff", font: "Impact" }); self.startGameBtn.anchor.set(0.5, 0.5); self.startGameBtn.x = 2048 / 2; self.startGameBtn.y = self.panel.y + 100; self.startGameBtn.down = function () { if (!self.visible) return; self.endMainScreen(); // Start the game logic here (e.g., generateBoard) startGame(); // Call a new function to start the game }; self.addChild(self.startGameBtn); // (Reset Progress Button removed) // Start the main screen self.startMainScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the main screen self.endMainScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); }; return self; }); // MonsterTile: Represents a monster hidden under a tile var MonsterTile = Container.expand(function () { var self = Container.call(this); // Monster stats self.hp = 1; self.maxHp = 1; self.damage = 1; self.exp = 1; self.revealed = false; self.defeated = false; // Visuals self.cover = self.attachAsset('monsterTileCover', { anchorX: 0.5, anchorY: 0.5 }); self.monster = self.attachAsset('monsterTileMonster', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Hidden until revealed }); // Show monster stats as text (hidden until revealed) self.statText = new Text2('', { size: 48, fill: "#fff" }); self.statText.anchor.set(0.5, 0.5); self.statText.alpha = 0; self.addChild(self.statText); // Reveal the monster self.reveal = function () { if (self.revealed) return; self.revealed = true; self.cover.alpha = 0.2; self.monster.alpha = 1; self.statText.setText(''); self.statText.alpha = 0; }; // Mark as defeated self.defeat = function () { self.defeated = true; self.monster.alpha = 0.3; self.cover.alpha = 0.1; self.statText.setText(''); self.statText.alpha = 0; // Update adjacent empty tiles' numbers since this monster is no longer generating damage if (typeof grid !== "undefined") { // Find this monster's position in the grid for (var row = 0; row < grid.length; row++) { for (var col = 0; col < grid[row].length; col++) { if (grid[row][col] === self) { var adj = getAdjacent(row, col); for (var i = 0; i < adj.length; i++) { var nr = adj[i][0], nc = adj[i][1]; var t = grid[nr][nc]; if (t instanceof EmptyTile) { // Recalculate adjacentMonsterDamage for this empty tile var adj2 = getAdjacent(nr, nc); var damageSum = 0; for (var j = 0; j < adj2.length; j++) { var ar = adj2[j][0], ac = adj2[j][1]; var adjTile = grid[ar][ac]; if (adjTile instanceof MonsterTile && !adjTile.defeated) { damageSum += adjTile.damage; } } t.adjacentMonsterDamage = damageSum; // If revealed, update the displayed number if (t.revealed) { if (damageSum > 0) { t.adjText.setText(damageSum + ''); t.adjText.alpha = 1; } else { t.adjText.setText(''); t.adjText.alpha = 0; } } } } // Only need to update once for the found monster row = grid.length; // break outer loop break; } } } } }; return self; }); // Player: Holds player stats and methods var Player = Container.expand(function () { var self = Container.call(this); // Player stats self.level = 1; self.exp = 0; self.expToLevel = 3; // Start with 3 exp needed for level up self.maxHp = 10; self.hp = self.maxHp; self.damage = 1; // Start with 1 damage self.defense = 0; // Initial defense is 0 self.skillPoints = 0; // Points to spend on upgrades self.gold = 0; // Player's currency self.inventory = { // Player inventory potionCount: 0, sword: null, // Equipped items shield: null, helmet: null, armour: null }; self.baseDamage = 1; // Base damage before gear self.baseDefense = 0; // Base defense before gear // Gain experience self.gainExp = function (amount) { self.exp += amount; while (self.exp >= self.expToLevel) { self.exp -= self.expToLevel; self.levelUp(); } }; // Level up player self.levelUp = function () { self.level++; self.skillPoints++; self.expToLevel = Math.floor(self.expToLevel * 1.2); // Increase EXP needed for next level // Increase base stats on level up // self.maxHp += 5; // Example stat increase // self.damage += 1; // Example stat increase self.hp = self.maxHp; // Heal to full HP on level up self.updateEquippedStats(); // Update stats after base increase }; // Take damage self.takeDamage = function (amount) { var damageTaken = Math.max(0, amount - self.defense); // Damage reduced by defense self.hp -= damageTaken; if (self.hp < 0) self.hp = 0; }; // Spend skill points self.spendSkillPoint = function (stat) { if (self.skillPoints <= 0) return; self.skillPoints--; if (stat === "hp") { self.maxHp += 10; // More HP per point self.hp = self.maxHp; // Heal to full } else if (stat === "damage") { self.baseDamage += 2; // Increase base damage } else if (stat === "defense") { self.baseDefense += 1; // Increase base defense } self.updateEquippedStats(); // Update stats after spending points }; // Equip gear self.equipGear = function (type, quality) { self.inventory[type] = { type: type, label: quality.label, modifier: quality.modifier, color: quality.color }; self.updateEquippedStats(); // Update stats after equipping }; // Update stats based on equipped gear self.updateEquippedStats = function () { self.damage = self.baseDamage; self.defense = self.baseDefense; if (self.inventory.sword) self.damage += self.inventory.sword.modifier; if (self.inventory.shield) self.defense += self.inventory.shield.modifier; if (self.inventory.helmet) self.defense += Math.floor(self.inventory.helmet.modifier / 2); // Helmet gives half defense if (self.inventory.armour) self.defense += self.inventory.armour.modifier; // Armour gives full defense if (self.inventory.armour && self.inventory.armour.modifier > 0) { // Add an extra HP bonus for armour self.maxHp += self.inventory.armour.modifier * 5; // Example HP bonus } // Ensure current HP doesn't exceed new max HP after stat changes if (self.hp > self.maxHp) self.hp = self.maxHp; }; return self; }); // PlayerInventoryScreen: Displays player's inventory and allows using potions var PlayerInventoryScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; // Initially hidden self.alpha = 0; // Set alpha to 0 for fade-in animation // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1600, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('INVENTORY', { size: 100, fill: 0x7DD3FC // Light Blue }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Gold display self.goldText = new Text2('', { size: 60, fill: 0xFFD700 // Gold }); self.goldText.anchor.set(0, 0.5); self.goldText.x = self.panel.x - self.panel.width / 2 + 80; self.goldText.y = self.panel.y - self.panel.height / 2 + 220; self.addChild(self.goldText); // Inventory items display self.inventoryItemsText = new Text2('', { size: 60, fill: "#fff" }); self.inventoryItemsText.anchor.set(0, 0); // Anchor to top-left self.inventoryItemsText.x = self.panel.x - self.panel.width / 2 + 80; self.inventoryItemsText.y = self.goldText.y + 80; self.addChild(self.inventoryItemsText); // Use Potion button self.usePotionBtn = new Text2('USE POTION', { size: 70, fill: 0xA3E635, // Green font: "Impact" }); self.usePotionBtn.anchor.set(0.5, 0.5); self.usePotionBtn.x = 2048 / 2; self.usePotionBtn.y = self.panel.y + self.panel.height / 2 - 250; self.usePotionBtn.visible = false; // Initially hidden self.usePotionBtn.down = function () { if (!self.visible || player.inventory.potionCount <= 0 || player.hp >= player.maxHp) return; player.inventory.potionCount--; player.hp += 20; // Heal amount if (player.hp > player.maxHp) player.hp = player.maxHp; self.updateInventoryDisplay(); playerStatsScreen.updateStatsDisplay(); // Update stats screen if open updateGUI(); // Update main GUI HP display }; self.addChild(self.usePotionBtn); // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endInventoryScreen(); }; self.addChild(self.closeBtn); // Update the displayed inventory self.updateInventoryDisplay = function () { self.goldText.setText('Coins: ' + player.gold); var inventoryText = ''; inventoryText += 'Life Potions: ' + player.inventory.potionCount + '\n\n'; var gearTypes = ['sword', 'shield', 'helmet', 'armour']; for (var i = 0; i < gearTypes.length; i++) { var type = gearTypes[i]; var item = player.inventory[type]; if (item) { var attributeText = ''; if (type === 'sword') { attributeText = '(+' + item.modifier + ' DMG)'; } else if (type === 'shield' || type === 'helmet' || type === 'armour') { // Calculate defense bonus based on type (matching player.updateEquippedStats) var defenseBonus = 0; if (type === 'shield') defenseBonus = item.modifier; if (type === 'helmet') defenseBonus = Math.floor(item.modifier / 2); if (type === 'armour') defenseBonus = item.modifier; attributeText = '(+' + defenseBonus + ' DEF)'; } inventoryText += item.label + ' ' + type.charAt(0).toUpperCase() + type.slice(1) + ' ' + attributeText + '\n'; } else { inventoryText += type.charAt(0).toUpperCase() + type.slice(1) + ': -\n'; } } self.inventoryItemsText.setText(inventoryText); // Show/hide use potion button if (player.inventory.potionCount > 0 && player.hp < player.maxHp) { self.usePotionBtn.visible = true; } else { self.usePotionBtn.visible = false; } }; // Start the inventory screen self.startInventoryScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); self.updateInventoryDisplay(); // Update inventory when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the inventory screen self.endInventoryScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); updateGUI(); // Ensure main GUI is updated when closing // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); }; return self; }); // PlayerStatsScreen: Displays and allows spending skill points var PlayerStatsScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1200, height: 1600, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Player Stats', { size: 100, fill: 0xFFE066 }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Stat texts self.hpStatText = new Text2('', { size: 60, fill: 0xA3E635 }); self.hpStatText.anchor.set(0, 0.5); self.hpStatText.x = self.panel.x - self.panel.width / 2 + 80; self.hpStatText.y = self.panel.y - 200; self.addChild(self.hpStatText); self.dmgStatText = new Text2('', { size: 60, fill: 0xF87171 }); self.dmgStatText.anchor.set(0, 0.5); self.dmgStatText.x = self.panel.x - self.panel.width / 2 + 80; self.dmgStatText.y = self.panel.y; self.addChild(self.dmgStatText); self.defStatText = new Text2('', { size: 60, fill: 0x7DD3FC }); self.defStatText.anchor.set(0, 0.5); self.defStatText.x = self.panel.x - self.panel.width / 2 + 80; self.defStatText.y = self.panel.y + 200; self.addChild(self.defStatText); self.skillPointsText = new Text2('', { size: 60, fill: 0xFFE066 }); self.skillPointsText.anchor.set(0.5, 0.5); self.skillPointsText.x = 2048 / 2; self.skillPointsText.y = self.panel.y + self.panel.height / 2 - 200; self.addChild(self.skillPointsText); // Upgrade buttons self.upgradeBtns = []; var upgradeOptions = [{ label: "+HP & Heal", stat: "hp", color: 0xA3E635 }, { label: "+DMG", stat: "damage", color: 0xF87171 }, { label: "+DEF", stat: "defense", color: 0x7DD3FC }]; for (var i = 0; i < upgradeOptions.length; i++) { var option = upgradeOptions[i]; var btn = new Text2(option.label, { size: 60, fill: option.color, font: "Impact" }); btn.anchor.set(0.5, 0.5); btn.x = self.panel.x + self.panel.width / 2 - 150; btn.y = self.panel.y - 200 + i * 200; btn.optionStat = option.stat; // Store the stat this button upgrades btn.visible = false; // Initially hidden btn.down = function () { if (!self.visible || player.skillPoints <= 0) return; player.spendSkillPoint(this.optionStat); self.updateStatsDisplay(); updateGUI(); // Update main GUI as well // Hide buttons if no skill points left if (player.skillPoints <= 0) { self.hideUpgradeButtons(); } }; self.addChild(btn); self.upgradeBtns.push(btn); } // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endStatsScreen(); }; self.addChild(self.closeBtn); // Update the displayed stats self.updateStatsDisplay = function () { self.hpStatText.setText('HP: ' + player.hp + '/' + player.maxHp); self.dmgStatText.setText('DMG: ' + player.damage); self.defStatText.setText('DEF: ' + player.defense); self.skillPointsText.setText('Skill Points: ' + player.skillPoints); if (player.skillPoints > 0) { self.showUpgradeButtons(); } else { self.hideUpgradeButtons(); } }; // Show upgrade buttons self.showUpgradeButtons = function () { for (var i = 0; i < self.upgradeBtns.length; i++) { self.upgradeBtns[i].visible = true; } }; // Hide upgrade buttons self.hideUpgradeButtons = function () { for (var i = 0; i < self.upgradeBtns.length; i++) { self.upgradeBtns[i].visible = false; } }; // Start the stats screen self.startStatsScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); self.updateStatsDisplay(); // Update stats when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the stats screen self.endStatsScreen = function () { self.visible = false; self.alpha = 0; if (self.parent) self.parent.removeChild(self); updateGUI(); // Ensure main GUI is updated when closing // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); }; return self; }); // ShopScreen: Allows buying and selling items var ShopScreen = Container.expand(function () { var self = Container.call(this); self.visible = false; self.alpha = 0; // Background overlay self.overlay = self.attachAsset('emptyTileCover', { width: 2048, height: 2732, alpha: 0.9 }); // Main panel background self.panel = self.attachAsset('emptyTileBg', { width: 1400, height: 2000, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Title self.titleText = new Text2('Shop', { size: 100, fill: 0xADD8E6 // LightBlue }); self.titleText.anchor.set(0.5, 0.5); self.titleText.x = 2048 / 2; self.titleText.y = self.panel.y - self.panel.height / 2 + 120; self.addChild(self.titleText); // Coins display self.coinsText = new Text2('Coins: ' + player.gold, { size: 60, fill: 0xFFD700 }); self.coinsText.anchor.set(0, 0.5); self.coinsText.x = self.panel.x - self.panel.width / 2 + 80; self.coinsText.y = self.panel.y - self.panel.height / 2 + 220; self.addChild(self.coinsText); // Items for Sale (simplified for now: only potions) self.itemsForSale = [{ type: "potion", label: "Life Potion", color: 0xA3E635, cost: 15 }]; self.saleButtons = []; var startY = self.coinsText.y + 100; for (var i = 0; i < self.itemsForSale.length; i++) { var item = self.itemsForSale[i]; var buyBtn = new Text2('BUY ' + item.label + ' (' + item.cost + ' Coins)', { size: 60, fill: item.color, font: "Impact" }); buyBtn.anchor.set(0, 0.5); buyBtn.x = self.panel.x - self.panel.width / 2 + 80; buyBtn.y = startY + i * 100; buyBtn.item = item; buyBtn.down = function () { if (!self.visible) return; var itemToBuy = this.item; if (player.gold >= itemToBuy.cost) { player.gold -= itemToBuy.cost; if (itemToBuy.type === "potion") { player.inventory.potionCount++; } self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open // Show confirmation self.showConfirmationText('Bought ' + itemToBuy.label + '!', itemToBuy.color); } else { // Show insufficient funds message self.showConfirmationText('Not enough coins!', 0xFF0000); } }; self.addChild(buyBtn); self.saleButtons.push(buyBtn); } // Items to Sell (simplified for now: gear and potions) self.sellButtons = []; var sellTitle = new Text2('Sell Items:', { size: 70, fill: 0xFFE066 // Yellow }); sellTitle.anchor.set(0, 0.5); sellTitle.x = self.panel.x - self.panel.width / 2 + 80; sellTitle.y = startY + self.itemsForSale.length * 100 + 80; self.addChild(sellTitle); self.updateSellButtons = function () { // Clear existing sell buttons for (var i = 0; i < self.sellButtons.length; i++) { self.sellButtons[i].destroy(); } self.sellButtons = []; var currentSellY = sellTitle.y + 80; // Sell gear var gearTypes = ['sword', 'shield', 'helmet', 'armour']; for (var i = 0; i < gearTypes.length; i++) { var gearType = gearTypes[i]; var item = player.inventory[gearType]; if (item) { var sellPrice = item.modifier * 5; // Example sell price var sellBtn = new Text2('SELL ' + item.label + ' ' + gearType.charAt(0).toUpperCase() + gearType.slice(1) + ' (' + sellPrice + ' Coins)', { size: 50, fill: item.color, font: "Impact" }); sellBtn.anchor.set(0, 0.5); sellBtn.x = self.panel.x - self.panel.width / 2 + 80; sellBtn.y = currentSellY; sellBtn.itemType = gearType; sellBtn.sellPrice = sellPrice; sellBtn.down = function () { if (!self.visible) return; var type = this.itemType; var price = this.sellPrice; player.gold += price; player.inventory[type] = null; // Remove item from inventory player.updateEquippedStats(); // Update player stats self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open playerStatsScreen.updateStatsDisplay(); // Update stats screen if open self.updateSellButtons(); // Refresh sell buttons self.showConfirmationText('Sold ' + type.charAt(0).toUpperCase() + type.slice(1) + ' for ' + price + ' coins!', 0xFFD700); }; self.addChild(sellBtn); self.sellButtons.push(sellBtn); currentSellY += 60; } } // Sell potions if (player.inventory.potionCount > 0) { var potionSellPrice = 10; // Example sell price for potion var sellPotionBtn = new Text2('SELL Life Potion (' + potionSellPrice + ' Coins)', { size: 50, fill: 0xA3E635, font: "Impact" }); sellPotionBtn.anchor.set(0, 0.5); sellPotionBtn.x = self.panel.x - self.panel.width / 2 + 80; sellPotionBtn.y = currentSellY; sellPotionBtn.sellPrice = potionSellPrice; sellPotionBtn.down = function () { if (!self.visible || player.inventory.potionCount <= 0) return; var price = this.sellPrice; player.gold += price; player.inventory.potionCount--; // Remove potion self.updateShopDisplay(); playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open self.updateSellButtons(); // Refresh sell buttons self.showConfirmationText('Sold a Life Potion for ' + price + ' coins!', 0xFFD700); }; self.addChild(sellPotionBtn); self.sellButtons.push(sellPotionBtn); currentSellY += 60; } }; // Confirmation text display self._confirmationText = null; self.showConfirmationText = function (message, color) { if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); } self._confirmationText = new Text2(message, { size: 60, fill: color }); self._confirmationText.anchor.set(0.5, 0.5); self._confirmationText.x = 2048 / 2; self._confirmationText.y = self.panel.y + self.panel.height / 2 - 150; self.addChild(self._confirmationText); tween(self._confirmationText, { alpha: 0 }, { duration: 1000, delay: 800, onFinish: function onFinish() { self._confirmationText.destroy(); self._confirmationText = null; } }); }; // Close button self.closeBtn = new Text2('CLOSE', { size: 70, fill: "#fff", font: "Impact" }); self.closeBtn.anchor.set(0.5, 0.5); self.closeBtn.x = 2048 / 2; self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80; self.closeBtn.down = function () { if (!self.visible) return; self.endShopScreen(); }; self.addChild(self.closeBtn); // Update the displayed shop info self.updateShopDisplay = function () { self.coinsText.setText('Coins: ' + player.gold); self.updateSellButtons(); // Refresh sell buttons }; // Start the shop screen self.startShopScreen = function () { self.visible = true; self.alpha = 0; game.addChild(self); self.updateShopDisplay(); // Update shop info when opening // Fade in tween(self, { alpha: 1 }, { duration: 300, easing: tween.easeOut }); }; // End the shop screen self.endShopScreen = function () { self.visible = false; self.alpha = 0; if (self._confirmationText && typeof self._confirmationText.destroy === 'function') { self._confirmationText.destroy(); self._confirmationText = null; } if (self.parent) self.parent.removeChild(self); if (monstersLeft <= 0 && currentLevel < LEVELS.length - 1) { levelEndScreen.startLevelEndScreen(); } else { updateGUI(); // Ensure main GUI is updated when closing // Add transition for main GUI elements tween(hpText, { alpha: 1 }, { duration: 300 }); tween(expText, { alpha: 1 }, { duration: 300 }); tween(levelText, { alpha: 1 }, { duration: 300 }); tween(statsBtn, { alpha: 1 }, { duration: 300 }); tween(inventoryBtn, { alpha: 1 }, { duration: 300 }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x22223a }); /**** * Game Code ****/ // --- Game Constants --- function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } var GRID_COLS = 8; var GRID_ROWS = 8; var TILE_SIZE = 180; var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * TILE_SIZE) / 2); var GRID_OFFSET_Y = Math.floor(2732 * 0.2 + (2732 * 0.6 - GRID_ROWS * TILE_SIZE) / 2); // Center the grid vertically within the middle 60% // --- Level System --- var LEVELS = [{ monsters: 3, monsterStats: { minHp: 1, maxHp: 2, minDmg: 1, maxDmg: 1, minExp: 1, maxExp: 1 } }, { monsters: 5, monsterStats: { minHp: 3, maxHp: 5, minDmg: 2, maxDmg: 3, minExp: 2, maxExp: 3 } }, { monsters: 7, monsterStats: { minHp: 5, maxHp: 8, minDmg: 3, maxDmg: 5, minExp: 3, maxExp: 4 } }]; var currentLevel = 0; var MONSTER_COUNT = LEVELS[currentLevel].monsters; // --- Asset Initialization --- // --- Game State --- var player = new Player(); player.damage = 1; player.baseDamage = 1; player.exp = 0; player.expToLevel = 3; var grid = []; // 2D array [row][col] var tileObjs = []; // Flat array of all tile objects for easy iteration var monstersLeft = MONSTER_COUNT; var revealedTiles = 0; var totalSafeTiles = GRID_COLS * GRID_ROWS - MONSTER_COUNT; var gameOver = false; var mainScreen = new MainScreen(); // Initialize main screen var battleScreen = new BattleScreen(); var chestScreen = new ChestScreen(); var playerStatsScreen = new PlayerStatsScreen(); // Initialize stats screen var playerInventoryScreen = new PlayerInventoryScreen(); // Initialize inventory screen var levelEndScreen = new LevelEndScreen(); // Initialize level end screen var shopScreen = new ShopScreen(); // Initialize shop screen // --- GUI Elements --- var hpText = new Text2('', { size: 40, fill: 0xFF6666 }); hpText.anchor.set(0.5, 0); // Anchor to top-center LK.gui.top.addChild(hpText); var expText = new Text2('', { size: 40, fill: 0xFFE066 }); expText.anchor.set(0.5, 0); // Anchor to top-center LK.gui.top.addChild(expText); var levelText = new Text2('', { size: 40, fill: 0xA3E635 }); levelText.anchor.set(0, 0.5); // Anchor to left-middle LK.gui.top.addChild(levelText); // var monstersLeftText = new Text2('', { // size: 40, // fill: "#fff" // }); // monstersLeftText.anchor.set(0, 0.5); // Anchor to left-middle // LK.gui.top.addChild(monstersLeftText); var statsBtn = new Text2('STATS', { size: 70, fill: "#fff", font: "Impact" }); statsBtn.anchor.set(1, 1); // Anchor to right-bottom statsBtn.x = -100; // Position to the left of the right edge (adjusted for safe area) statsBtn.y = -40; // Position near the bottom edge statsBtn.visible = false; // Set initial visibility to false statsBtn.down = function () { if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active playerStatsScreen.startStatsScreen(); }; LK.gui.bottom.addChild(statsBtn); var inventoryBtn = new Text2('INVENTORY', { size: 70, fill: "#fff", font: "Impact" }); inventoryBtn.anchor.set(1, 1); // Anchor to right-bottom inventoryBtn.x = -100 - statsBtn.width - 40; // Position to the left of the right edge (adjusted for safe area and other button) inventoryBtn.y = -40; // Position near the bottom edge inventoryBtn.visible = false; // Set initial visibility to false inventoryBtn.down = function () { if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active playerInventoryScreen.startInventoryScreen(); }; LK.gui.bottom.addChild(inventoryBtn); var levelUpBtn = new Text2('LEVEL UP', { size: 70, fill: "#fff", font: "Impact" }); levelUpBtn.anchor.set(0.5, 0.5); levelUpBtn.alpha = 0.7; levelUpBtn.visible = false; levelUpBtn.down = function () { if (!this.visible) return; // Old level up choices removed }; LK.gui.top.addChild(levelUpBtn); // Old level up choice buttons and logic removed // --- Helper Functions --- function updateGUI() { if (mainScreen.visible) { hpText.visible = false; expText.visible = false; levelText.visible = false; hpText.alpha = 0; // Set alpha to 0 for fade-in expText.alpha = 0; // Set alpha to 0 for fade-in levelText.alpha = 0; // Set alpha to 0 for fade-in statsBtn.alpha = 0; // Set alpha to 0 for fade-in inventoryBtn.alpha = 0; // Set alpha to 0 for fade-in // monstersLeftText.visible = false; levelUpBtn.visible = false; statsBtn.visible = false; // Hide stats button inventoryBtn.visible = false; // Hide inventory button return; } if (battleScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) { hpText.visible = false; expText.visible = false; levelText.visible = false; hpText.alpha = 0; // Set alpha to 0 for fade-in expText.alpha = 0; // Set alpha to 0 for fade-in levelText.alpha = 0; // Set alpha to 0 for fade-in statsBtn.alpha = 0; // Set alpha to 0 for fade-in inventoryBtn.alpha = 0; // Set alpha to 0 for fade-in // monstersLeftText.visible = false; levelUpBtn.visible = false; statsBtn.visible = false; // Hide stats button inventoryBtn.visible = false; // Hide inventory button if (battleScreen.visible) battleScreen.updateBattleStats(); // Update battle screen stats when visible return; } else { hpText.visible = true; expText.visible = true; levelText.visible = true; // monstersLeftText.visible = true; statsBtn.visible = true; // Show stats button inventoryBtn.visible = true; // Show inventory button } hpText.setText('HP: ' + player.hp + '/' + player.maxHp); hpText.x = 450; // Position HP more to the right hpText.y = 60; // Position near the top edge // Show yellow + if enough EXP to level up var canLevelUp = player.exp >= player.expToLevel; var plusStr = canLevelUp ? " [color=#FFE066]+[/color]" : ""; expText.setText('EXP: ' + player.exp + '/' + player.expToLevel + plusStr); expText.x = 450; // Position EXP more to the right expText.y = 110; // Position below HP // Show yellow + if enough EXP to level up next to LVL var lvlPlusStr = canLevelUp ? " [color=#FFE066]+[/color]" : ""; levelText.setText('LVL: ' + player.level + lvlPlusStr); levelText.x = 0; // Position LVL in the center of the top gui levelText.y = 60; // Position near the top edge levelText.anchor.set(0.5, 0.5); // monstersLeftText.setText('Monsters: ' + monstersLeft); // monstersLeftText.x = levelText.x + levelText.width + 40; // Position to the right of the level text // monstersLeftText.y = 60; // Position near the top edge // Hide level up button (replaced by stats screen) levelUpBtn.visible = false; // Show stats button and optionally indicate available skill points statsBtn.visible = true; // Center inventory and stats buttons horizontally at the bottom var totalBtnWidth = statsBtn.width + inventoryBtn.width + 40; var centerX = 0; // LK.gui.bottom center is x=0 statsBtn.x = centerX + totalBtnWidth / 2 - statsBtn.width / 2; statsBtn.y = -40; // Position near the bottom edge inventoryBtn.x = centerX - totalBtnWidth / 2 + inventoryBtn.width / 2; inventoryBtn.y = -40; // Position near the bottom edge if (player.skillPoints > 0) { statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); // Animate if points available } else { statsBtn.alpha = 1; } } // Returns true if (row, col) is inside grid function inBounds(row, col) { return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS; } // Get all adjacent tile positions function getAdjacent(row, col) { var adj = []; for (var dr = -1; dr <= 1; dr++) { for (var dc = -1; dc <= 1; dc++) { if (dr === 0 && dc === 0) continue; var nr = row + dr, nc = col + dc; if (inBounds(nr, nc)) adj.push([nr, nc]); } } return adj; } // Reveal empty tiles recursively (flood fill) function revealEmptyTiles(row, col) { var tile = grid[row][col]; if (tile.revealed) return; tile.reveal(); revealedTiles++; if (tile.adjacentMonsterDamage === 0) { var adj = getAdjacent(row, col); for (var i = 0; i < adj.length; i++) { var _adj$i = _slicedToArray(adj[i], 2), nr = _adj$i[0], nc = _adj$i[1]; var t = grid[nr][nc]; if (t instanceof EmptyTile && !t.revealed) { revealEmptyTiles(nr, nc); } } } } // Reveal all monsters (on game over) function revealAllMonsters() { for (var i = 0; i < tileObjs.length; i++) { var t = tileObjs[i]; if (t instanceof MonsterTile && !t.revealed) { t.reveal(); } } } // --- Board Generation --- function generateBoard() { // Clear previous for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].destroy(); } grid = []; tileObjs = []; monstersLeft = LEVELS[currentLevel].monsters; revealedTiles = 0; totalSafeTiles = GRID_COLS * GRID_ROWS - LEVELS[currentLevel].monsters; gameOver = false; // Place monsters var monsterPositions = []; while (monsterPositions.length < LEVELS[currentLevel].monsters) { var r = Math.floor(Math.random() * GRID_ROWS); var c = Math.floor(Math.random() * GRID_COLS); var key = r + ',' + c; var found = false; for (var i = 0; i < monsterPositions.length; i++) { if (monsterPositions[i][0] === r && monsterPositions[i][1] === c) { found = true; break; } } if (!found) monsterPositions.push([r, c]); } // Build grid var stats = LEVELS[currentLevel].monsterStats; for (var row = 0; row < GRID_ROWS; row++) { grid[row] = []; for (var col = 0; col < GRID_COLS; col++) { var isMonster = false; for (var i = 0; i < monsterPositions.length; i++) { if (monsterPositions[i][0] === row && monsterPositions[i][1] === col) { isMonster = true; break; } } var tile; // Always place a chest at (0,0) for dev if (row === 0 && col === 0) { tile = new ChestTile(); } else if (isMonster) { tile = new MonsterTile(); // Level-based monster stats tile.maxHp = tile.hp = stats.minHp + Math.floor(Math.random() * (stats.maxHp - stats.minHp + 1)); tile.damage = stats.minDmg + Math.floor(Math.random() * (stats.maxDmg - stats.minDmg + 1)); tile.exp = stats.minExp + Math.floor(Math.random() * (stats.maxExp - stats.minExp + 1)); } else { tile = new EmptyTile(); } tile.x = GRID_OFFSET_X + col * TILE_SIZE + TILE_SIZE / 2; tile.y = GRID_OFFSET_Y + row * TILE_SIZE + TILE_SIZE / 2; game.addChild(tile); grid[row][col] = tile; tileObjs.push(tile); } } // Set adjacent monster counts for empty tiles for (var row = 0; row < GRID_ROWS; row++) { for (var col = 0; col < GRID_COLS; col++) { var tile = grid[row][col]; if (tile instanceof EmptyTile) { var adj = getAdjacent(row, col); var damageSum = 0; // Variable to store the sum of damage for (var i = 0; i < adj.length; i++) { var _adj$i2 = _slicedToArray(adj[i], 2), nr = _adj$i2[0], nc = _adj$i2[1]; if (grid[nr][nc] instanceof MonsterTile) { damageSum += grid[nr][nc].damage; // Add monster's damage to the sum } } tile.adjacentMonsterDamage = damageSum; // Store the damage sum } } } } // --- Game Logic --- function handleTileDown(x, y, obj) { if (gameOver) return; if (battleScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't allow tile interaction if battle screen, level end, or shop screen is up // Find which tile was pressed for (var i = 0; i < tileObjs.length; i++) { var tile = tileObjs[i]; if (tile.revealed) continue; if (tile.cover && tile.cover.alpha > 0.1) { // Check if (x, y) is inside tile var dx = x - tile.x; var dy = y - tile.y; if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) { // Reveal tile if (tile instanceof EmptyTile) { revealEmptyTiles(Math.floor((tile.y - GRID_OFFSET_Y) / TILE_SIZE), Math.floor((tile.x - GRID_OFFSET_X) / TILE_SIZE)); updateGUI(); // Win check // (Removed: revealing all safe tiles no longer triggers win. Level is won only when all monsters are defeated.) } else if (tile instanceof ChestTile) { // Reveal chest and show chest screen tile.reveal(); chestScreen.startChest(tile); } else if (tile instanceof MonsterTile) { // Reveal monster and start battle tile.reveal(); // Reveal the monster visually // Start the battle sequence battleScreen.startBattle(tile); } break; } } } } // --- Level Up UI --- // Old level up UI functions removed // --- Event Handlers --- game.down = function (x, y, obj) { if (mainScreen.visible) return; // Prevent interaction if main screen is visible // The click handling for levelUpBtn and levelUpChoiceBtns // has been moved to their own 'down' event handlers. // Check for tile interactions handleTileDown(x, y, obj); }; game.update = function () { if (mainScreen.visible || battleScreen.visible || playerStatsScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't update game elements when main, battle, stats, level end, or shop screen is up // Animate level up button (removed) // if (levelUpBtn.visible) { // levelUpBtn.x = 0; // Centered horizontally relative to LK.gui.top // levelUpBtn.y = 120; // levelUpBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); // } // Animate level up choices (removed) // for (var i = 0; i < levelUpChoiceBtns.length; i++) { // var btn = levelUpChoiceBtns[i]; // if (btn.visible) { // btn.x = (i - 1) * 400; // x relative to LK.gui.bottom // btn.y = -2732 * 0.15; // Position choices above the bottom boundary of the battle area // btn.alpha = 0.9 + 0.1 * Math.sin(LK.ticks / 15 + i); // } // } // Animate stats button if skill points are available if (statsBtn.visible && player.skillPoints > 0) { statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); } else if (statsBtn.visible) { statsBtn.alpha = 1; // Solid if no points } if (mainScreen.visible || battleScreen.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) { for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].visible = false; } } else { for (var i = 0; i < tileObjs.length; i++) { tileObjs[i].visible = true; } } }; // --- Start Game --- function startGame() { generateBoard(); levelText.setText('LVL: ' + (currentLevel + 1)); updateGUI(); } // Show main screen at start mainScreen.startMainScreen();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BattleScreen: Handles the turn-based combat
var BattleScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.8
});
// Monster display
self.monsterDisplay = self.attachAsset('monsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
// Position near top-right corner within the battle area
y: 2732 * 0.2 + 200,
// Position near top-right corner within the battle area
scaleX: 2,
scaleY: 2
});
// Monster stats text
self.monsterStatText = new Text2('', {
size: 60,
fill: "#fff"
});
self.monsterStatText.anchor.set(0.5, 0); // Anchor to top-center
self.monsterStatText.x = self.monsterDisplay.x;
self.monsterStatText.y = self.monsterDisplay.y + self.monsterDisplay.height / 2 + 60; // Move further below monster image
self.addChild(self.monsterStatText);
// Player stats text (in battle)
self.playerBattleStatText = new Text2('', {
size: 60,
fill: "#fff"
});
self.playerDisplay = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: 300,
// Position near bottom-left corner within the battle area
y: 2732 * 0.8 - 200,
// Position near bottom-left corner within the battle area
scaleX: 2,
scaleY: 2
});
self.playerBattleStatText.anchor.set(0.5, 0); // Anchor to top-center
// Set stat text position to where hero will appear
self.playerBattleStatText.x = 300;
self.playerBattleStatText.y = 2732 * 0.8 - 200 + 100 + 60; // 100 is half hero height at scale 2
self.addChild(self.playerBattleStatText);
// Action button (Attack)
self.attackBtn = new Text2('ATTACK', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.attackBtn.anchor.set(0.5, 0.5);
self.attackBtn.x = 2048 / 2;
self.attackBtn.y = 2732 - 300; // Position near bottom
self.addChild(self.attackBtn);
self.attackBtn.down = function () {
if (!self.visible) return;
self.playerTurn();
};
self.currentMonster = null; // Reference to the monster tile being fought
// Start battle
self.startBattle = function (monsterTile) {
self.currentMonster = monsterTile;
self.visible = true;
self.alpha = 0;
game.addChild(self); // Add battle screen to game
updateGUI(); // Ensure main GUI elements are hidden
// Hide hero/monster and attack button for intro animation
self.monsterDisplay.visible = false;
self.playerDisplay.visible = false;
self.monsterStatText.visible = false;
self.playerBattleStatText.visible = false;
self.attackBtn.visible = false;
// Fade in battle screen
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show "You encountered a monster!" message
var encounterText = new Text2('You encountered a monster!', {
size: 90,
fill: 0xFFE066
});
encounterText.anchor.set(0.5, 0.5);
encounterText.x = 2048 / 2;
encounterText.y = 2732 / 2 - 200;
self.addChild(encounterText);
tween(encounterText, {
alpha: 0
}, {
duration: 900,
delay: 900,
easing: tween.easeOut,
onFinish: function onFinish() {
encounterText.destroy();
// Prepare hero/monster offscreen for slide-in
// Save original positions
var heroTargetX = 300;
var heroTargetY = 2732 * 0.8 - 200;
var monsterTargetX = 2048 - 300;
var monsterTargetY = 2732 * 0.2 + 200;
self.playerDisplay.x = -self.playerDisplay.width; // Offscreen left
self.playerDisplay.y = heroTargetY;
self.monsterDisplay.x = 2048 + self.monsterDisplay.width; // Offscreen right
self.monsterDisplay.y = monsterTargetY;
self.playerDisplay.visible = true;
self.monsterDisplay.visible = true;
self.monsterStatText.visible = false;
self.playerBattleStatText.visible = false;
// Slide in hero and monster simultaneously
var slideInCount = 0;
function afterSlideIn() {
slideInCount++;
if (slideInCount === 2) {
// Show stats and decide who attacks first
self.monsterStatText.visible = true;
self.playerBattleStatText.visible = true;
self.updateBattleStats();
// Randomize who attacks first: 0 = player, 1 = monster
var firstAttacker = Math.random() < 0.5 ? "player" : "monster";
var firstText = firstAttacker === "monster" ? "Monster strikes first!" : "You start!";
var firstTextColor = firstAttacker === "monster" ? 0xff0000 : 0xFFE066;
var whoFirstText = new Text2(firstText, {
size: 90,
fill: firstTextColor
});
whoFirstText.anchor.set(0.5, 0.5);
whoFirstText.x = 2048 / 2;
whoFirstText.y = 2732 / 2;
self.addChild(whoFirstText);
tween(whoFirstText, {
alpha: 0
}, {
duration: 900,
delay: 900,
easing: tween.easeOut,
onFinish: function onFinish() {
whoFirstText.destroy();
// Show attack button and start the correct turn after a short delay
self.attackBtn.visible = true;
if (firstAttacker === "monster") {
LK.setTimeout(self.monsterTurn, 500);
}
// If player starts, do nothing: player must press attackBtn
}
});
}
}
tween(self.playerDisplay, {
x: heroTargetX
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: afterSlideIn
});
tween(self.monsterDisplay, {
x: monsterTargetX
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: afterSlideIn
});
}
});
}
});
};
// End battle
self.endBattle = function (win) {
self.visible = false;
self.alpha = 0; // Reset alpha
self.attackBtn.visible = false;
if (win) {
// expGained and player.gainExp(expGained) are now handled in playerTurn's onFinish monster death animation
self.currentMonster.defeat();
monstersLeft--;
updateGUI();
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
if (monstersLeft <= 0) {
// If not last level, advance to next level
if (currentLevel < LEVELS.length - 1) {
// Show level end screen
levelEndScreen.startLevelEndScreen();
} else {
LK.showYouWin();
gameOver = true;
}
} else {}
// Player lost, game over is handled in handleTileDown
}
self.currentMonster = null;
self.parent.removeChild(self); // Remove battle screen from game
};
// Update battle stats display
self.updateBattleStats = function () {
if (self.currentMonster) {
self.monsterStatText.setText('HP:' + self.currentMonster.hp + '/' + self.currentMonster.maxHp + ' DMG:' + self.currentMonster.damage);
self.playerBattleStatText.setText('HP:' + player.hp + '/' + player.maxHp + ' DMG:' + player.damage);
}
};
// Player's turn
self.playerTurn = function () {
if (!self.currentMonster) return;
// Animate player attack: slide playerDisplay forward, then back, then apply damage
var originalX = self.playerDisplay.x;
var attackX = originalX + 220;
self.attackBtn.visible = false; // Hide attack button during animation
// Show "Player Attacks!" text
var playerAttackText = new Text2('Player Attacks!', {
size: 80,
fill: 0xffe066
});
playerAttackText.anchor.set(0.5, 0.5);
playerAttackText.x = 2048 / 2;
playerAttackText.y = self.monsterStatText.y - 100;
self.addChild(playerAttackText);
function doAttack() {
// Damage and flash
self.currentMonster.hp -= player.damage;
LK.effects.flashObject(self.monsterDisplay, 0xffe066, 300);
// Show animated -X text above monster in battle
if (self.currentMonster && self.monsterDisplay) {
var monsterDmgText = new Text2('-' + player.damage, {
size: 90,
fill: 0xff0000
});
monsterDmgText.anchor.set(0.5, 1);
// Position above monster sprite
monsterDmgText.x = self.monsterDisplay.x;
monsterDmgText.y = self.monsterDisplay.y - self.monsterDisplay.height / 2 - 20;
self.addChild(monsterDmgText);
// Animate: pop up and fade out
tween(monsterDmgText, {
y: monsterDmgText.y - 80,
alpha: 0
}, {
duration: 700,
easing: tween.cubicOut,
onFinish: function onFinish() {
monsterDmgText.destroy();
}
});
}
self.updateBattleStats();
// Fade out attack text
tween(playerAttackText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
playerAttackText.destroy();
}
});
// If monster dies, animate monster death
if (self.currentMonster.hp <= 0) {
// Monster death animation: fade out and scale down
tween(self.monsterDisplay, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.cubicIn,
onFinish: function onFinish() {
var expGained = self.currentMonster.exp;
var lootMessages = [];
// Award EXP
player.gainExp(expGained);
// Coin Drop
var coinsDropped = Math.floor(self.currentMonster.exp * (2 + Math.random() * 4)); // 2-5 coins per EXP point
if (coinsDropped > 0) {
player.gold += coinsDropped;
lootMessages.push(coinsDropped + ' Coins');
}
// Gear Drop (Rare)
var gearDropChance = 0.1; // 10% chance for now, we can adjust this later!
if (Math.random() < gearDropChance) {
if (chestScreen && chestScreen._rewardTypes && chestScreen._rewardTypes.length > 2 && chestScreen._lootQualities && chestScreen._lootQualities.length > 0) {
var potentialGearRewards = chestScreen._rewardTypes.slice(2); // Exclude potion and coins
if (potentialGearRewards.length > 0) {
var gearReward = potentialGearRewards[Math.floor(Math.random() * potentialGearRewards.length)];
var gearQuality = chestScreen._lootQualities[Math.floor(Math.random() * chestScreen._lootQualities.length)];
player.equipGear(gearReward.type, gearQuality); // This also calls player.updateEquippedStats()
var gearLabel = gearQuality.label + ' ' + gearReward.label;
lootMessages.push(gearLabel);
// playerStatsScreen and playerInventoryScreen will be updated via updateGUI() when battle ends
}
}
}
// Construct Victory Message
var victoryMessage = 'Victory!\nGained ' + expGained + ' EXP!';
if (lootMessages.length > 0) {
victoryMessage += '\n\nFound:\n' + lootMessages.join('\n');
}
// Show "Victory!" message before closing battle screen
var victoryText = new Text2(victoryMessage, {
size: 100,
fill: 0xA3E635,
align: 'center' // Ensure multi-line text is centered
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = 2048 / 2;
victoryText.y = 2732 / 2;
self.addChild(victoryText);
victoryText.alpha = 0;
// Fade in, hold, then fade out
tween(victoryText, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(victoryText, {
alpha: 0
}, {
duration: 500,
delay: 1000,
// Increased delay to read loot
easing: tween.easeIn,
onFinish: function onFinish() {
victoryText.destroy();
self.endBattle(true); // Player wins
// Reset monsterDisplay for next battle
self.monsterDisplay.alpha = 1;
self.monsterDisplay.scaleX = 2;
self.monsterDisplay.scaleY = 2;
}
});
}
});
}
});
} else {
// Monster survives, monster's turn after a delay
LK.setTimeout(self.monsterTurn, 500);
self.attackBtn.visible = true;
}
}
// Animate player forward, then back, then do attack
tween(self.playerDisplay, {
x: attackX
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.playerDisplay, {
x: originalX
}, {
duration: 180,
easing: tween.cubicIn,
onFinish: doAttack
});
}
});
};
// Monster's turn
self.monsterTurn = function () {
if (!self.currentMonster) return;
// Animate monster attack: slide monsterDisplay forward, then back, then apply damage
var originalX = self.monsterDisplay.x;
var attackX = originalX - 220; // Slide left toward player
self.attackBtn.visible = false; // Hide attack button during animation
// Show "Monster Attacks!" text
var monsterAttackText = new Text2('Monster Attacks!', {
size: 80,
fill: 0xff0000
});
monsterAttackText.anchor.set(0.5, 0.5);
monsterAttackText.x = 2048 / 2;
monsterAttackText.y = self.playerBattleStatText.y + 100;
self.addChild(monsterAttackText);
function doMonsterAttack() {
player.takeDamage(self.currentMonster.damage);
// Flash monster and player
LK.effects.flashObject(self.monsterDisplay, 0xff0000, 300); // Flash monster indicating it attacked
LK.effects.flashObject(self.playerDisplay, 0xff0000, 300); // Flash player sprite
LK.effects.flashObject(self.playerBattleStatText, 0xff0000, 300); // Flash player stats indicating damage
// Show animated -X text above player in battle (damage taken) or show "Blocked!" text
if (player && self.playerDisplay) {
var damageTaken = Math.max(0, self.currentMonster.damage - player.defense);
var playerDmgText;
var textColor;
if (damageTaken > 0) {
playerDmgText = new Text2('-' + damageTaken, {
size: 90,
fill: 0xff0000 // Red for damage
});
textColor = 0xff0000;
} else {
playerDmgText = new Text2('Blocked!', {
size: 80,
fill: 0x7DD3FC // Light blue for blocked
});
textColor = 0x7DD3FC;
}
playerDmgText.anchor.set(0.5, 1);
// Position above player sprite
playerDmgText.x = self.playerDisplay.x;
playerDmgText.y = self.playerDisplay.y - self.playerDisplay.height / 2 - 20;
self.addChild(playerDmgText);
// Animate: pop up and fade out
tween(playerDmgText, {
y: playerDmgText.y - 80,
alpha: 0
}, {
duration: 700,
easing: tween.cubicOut,
onFinish: function onFinish() {
playerDmgText.destroy();
}
});
}
// Fade out attack text
tween(monsterAttackText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
monsterAttackText.destroy();
// Show attack button again if player is still alive and battle is ongoing
if (player.hp > 0 && self.currentMonster && !gameOver) {
self.attackBtn.visible = true;
}
}
});
self.updateBattleStats();
if (player.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
revealAllMonsters();
self.endBattle(false); // Player loses
}
}
// Animate monster forward, then back, then do attack
tween(self.monsterDisplay, {
x: attackX
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.monsterDisplay, {
x: originalX
}, {
duration: 180,
easing: tween.cubicIn,
onFinish: doMonsterAttack
});
}
});
};
return self;
});
// ChestScreen: Handles the chest found popup
var ChestScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.8
});
// Chest display (center) - use chest image asset
self.chestDisplay = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 100,
scaleX: 2,
scaleY: 2
});
// "You found a chest!" text
self.chestText = new Text2('You found a chest!', {
size: 90,
fill: 0xFFD700
});
self.chestText.anchor.set(0.5, 0.5);
self.chestText.x = 2048 / 2;
self.chestText.y = 2732 / 2 - 350;
self.addChild(self.chestText);
// Player display (bottom, like battle screen)
self.playerDisplay = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 * 0.8,
scaleX: 2,
scaleY: 2
});
// --- Double-tap to open chest logic ---
self._lastTapTime = 0;
self._tapCount = 0;
self._rewardPopup = null;
// Chest reward types
self._rewardTypes = [{
type: "potion",
label: "Life Potion",
color: 0xA3E635
}, {
type: "coins",
label: "Coins",
color: 0xFFD700
}, {
type: "shield",
label: "Shield",
color: 0x7DD3FC
}, {
type: "sword",
label: "Sword",
color: 0xF87171
}, {
type: "helmet",
label: "Helmet",
color: 0xFACC15
}, {
type: "armour",
label: "Armour",
color: 0xA78BFA
}];
// Loot qualities
self._lootQualities = [{
label: "Wooden",
modifier: 1,
color: 0x8B4513 // SaddleBrown
}, {
label: "Bronze",
modifier: 2,
color: 0xCD7F32 // Bronze
}, {
label: "Silver",
modifier: 3,
color: 0xC0C0C0 // Silver
}];
// Helper to show reward popup
self._showRewardPopup = function (reward) {
if (self._rewardPopup) {
self._rewardPopup.destroy();
self._rewardPopup = null;
}
var popup = new Container();
// Overlay
var overlay = LK.getAsset('emptyTileCover', {
width: 900,
height: 400,
alpha: 0.95,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
popup.addChild(overlay);
// Reward text
var rewardText = new Text2('You found: ' + reward.label + '!', {
size: 80,
fill: reward.color
});
rewardText.anchor.set(0.5, 0.5);
rewardText.x = 2048 / 2;
rewardText.y = 2732 / 2 - 40;
popup.addChild(rewardText);
// "OK" button
var okBtn = new Text2('OK', {
size: 70,
fill: "#fff",
font: "Impact"
});
okBtn.anchor.set(0.5, 0.5);
okBtn.x = 2048 / 2;
okBtn.y = 2732 / 2 + 80;
okBtn.down = function () {
popup.destroy();
self._rewardPopup = null;
self.endChest();
};
popup.addChild(okBtn);
self._rewardPopup = popup;
game.addChild(popup);
};
// Double-tap handler for chest
self.chestDisplay.down = function (x, y, obj) {
var now = Date.now();
if (now - self._lastTapTime < 400) {
self._tapCount++;
} else {
self._tapCount = 1;
}
self._lastTapTime = now;
if (self._tapCount === 2 && self.visible && !self._rewardPopup) {
// Open chest
if (self.currentChest && !self.currentChest.opened) {
self.currentChest.open();
// Pick a random reward
var reward = self._rewardTypes[Math.floor(Math.random() * self._rewardTypes.length)];
var quality = null;
if (reward.type !== "potion" && reward.type !== "coins") {
// For gear, pick a random quality
quality = self._lootQualities[Math.floor(Math.random() * self._lootQualities.length)];
}
// Apply reward effect (for now, just heal for potion, add coins, or do nothing for gear)
if (reward.type === "potion") {
player.inventory.potionCount++; // Add potion to inventory
self._showRewardPopup(reward);
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
} else if (reward.type === "coins") {
player.gold += 10 + Math.floor(Math.random() * 10); // Add 10-20 coins
self._showRewardPopup(reward);
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
} else if (quality) {
// Add gear to player inventory with quality modifier
player.equipGear(reward.type, quality);
var lootLabel = quality.label + ' ' + reward.label;
self._showRewardPopup({
label: lootLabel,
color: quality.color
});
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
}
}
self._tapCount = 0;
}
};
// Start chest screen
self.startChest = function (chestTile) {
self.currentChest = chestTile;
self.visible = true;
self.alpha = 0;
game.addChild(self);
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End chest screen
self.endChest = function () {
self.visible = false;
self.alpha = 0;
self.currentChest = null;
if (self._rewardPopup) {
self._rewardPopup.destroy();
self._rewardPopup = null;
}
if (self.parent) self.parent.removeChild(self);
updateGUI(); // Ensure main GUI is updated after closing chest screen
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
};
return self;
});
// ChestTile: Represents a chest hidden under a tile
var ChestTile = Container.expand(function () {
var self = Container.call(this);
self.revealed = false;
self.opened = false;
self.cover = self.attachAsset('emptyTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a chest image asset
self.chest = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
// Reveal the chest
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.chest.alpha = 1;
};
// Mark as opened
self.open = function () {
self.opened = true;
self.cover.alpha = 0.1; // Grey out cover
self.chest.alpha = 0.5; // Grey out chest image
// Optionally animate chest opening here
};
return self;
});
// EmptyTile: Represents a safe tile (no monster)
var EmptyTile = Container.expand(function () {
var self = Container.call(this);
self.revealed = false;
self.adjacentMonsterDamage = 0; // Change to store damage sum
self.cover = self.attachAsset('emptyTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
self.bg = self.attachAsset('emptyTileBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
self.adjText = new Text2('', {
size: 48,
fill: "#fff"
});
self.adjText.anchor.set(0.5, 0.5);
self.adjText.alpha = 0;
self.addChild(self.adjText);
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.bg.alpha = 1;
if (self.adjacentMonsterDamage > 0) {
// Check damage sum
self.adjText.setText(self.adjacentMonsterDamage + ''); // Display damage sum
self.adjText.alpha = 1;
}
};
return self;
});
// LevelEndScreen: Appears after clearing a level, offering Next Level and Shop options
var LevelEndScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1000,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Level Complete!', {
size: 100,
fill: 0xA3E635
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Next Level Button
self.nextLevelBtn = new Text2('NEXT LEVEL', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.nextLevelBtn.anchor.set(0.5, 0.5);
self.nextLevelBtn.x = 2048 / 2;
self.nextLevelBtn.y = self.panel.y + 100;
self.nextLevelBtn.down = function () {
if (!self.visible) return;
self.endLevelEndScreen();
currentLevel++;
MONSTER_COUNT = LEVELS[currentLevel].monsters;
generateBoard();
updateGUI();
levelText.setText('LVL: ' + (currentLevel + 1));
gameOver = false;
};
self.addChild(self.nextLevelBtn);
// Shop Button
self.shopBtn = new Text2('SHOP', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.shopBtn.anchor.set(0.5, 0.5);
self.shopBtn.x = 2048 / 2;
self.shopBtn.y = self.panel.y + 300;
self.shopBtn.down = function () {
if (!self.visible) return;
self.endLevelEndScreen();
shopScreen.startShopScreen();
};
self.addChild(self.shopBtn);
// Start the level end screen
self.startLevelEndScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the level end screen
self.endLevelEndScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
updateGUI(); // Ensure main GUI is updated when closing
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
};
return self;
});
var MainScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1000,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Dungeon Sweeper', {
size: 150,
fill: 0xFFE066 // Gold
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 150;
self.addChild(self.titleText);
// Start Game Button
self.startGameBtn = new Text2('START GAME', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.startGameBtn.anchor.set(0.5, 0.5);
self.startGameBtn.x = 2048 / 2;
self.startGameBtn.y = self.panel.y + 100;
self.startGameBtn.down = function () {
if (!self.visible) return;
self.endMainScreen();
// Start the game logic here (e.g., generateBoard)
startGame(); // Call a new function to start the game
};
self.addChild(self.startGameBtn);
// (Reset Progress Button removed)
// Start the main screen
self.startMainScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the main screen
self.endMainScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
};
return self;
});
// MonsterTile: Represents a monster hidden under a tile
var MonsterTile = Container.expand(function () {
var self = Container.call(this);
// Monster stats
self.hp = 1;
self.maxHp = 1;
self.damage = 1;
self.exp = 1;
self.revealed = false;
self.defeated = false;
// Visuals
self.cover = self.attachAsset('monsterTileCover', {
anchorX: 0.5,
anchorY: 0.5
});
self.monster = self.attachAsset('monsterTileMonster', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Hidden until revealed
});
// Show monster stats as text (hidden until revealed)
self.statText = new Text2('', {
size: 48,
fill: "#fff"
});
self.statText.anchor.set(0.5, 0.5);
self.statText.alpha = 0;
self.addChild(self.statText);
// Reveal the monster
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
self.cover.alpha = 0.2;
self.monster.alpha = 1;
self.statText.setText('');
self.statText.alpha = 0;
};
// Mark as defeated
self.defeat = function () {
self.defeated = true;
self.monster.alpha = 0.3;
self.cover.alpha = 0.1;
self.statText.setText('');
self.statText.alpha = 0;
// Update adjacent empty tiles' numbers since this monster is no longer generating damage
if (typeof grid !== "undefined") {
// Find this monster's position in the grid
for (var row = 0; row < grid.length; row++) {
for (var col = 0; col < grid[row].length; col++) {
if (grid[row][col] === self) {
var adj = getAdjacent(row, col);
for (var i = 0; i < adj.length; i++) {
var nr = adj[i][0],
nc = adj[i][1];
var t = grid[nr][nc];
if (t instanceof EmptyTile) {
// Recalculate adjacentMonsterDamage for this empty tile
var adj2 = getAdjacent(nr, nc);
var damageSum = 0;
for (var j = 0; j < adj2.length; j++) {
var ar = adj2[j][0],
ac = adj2[j][1];
var adjTile = grid[ar][ac];
if (adjTile instanceof MonsterTile && !adjTile.defeated) {
damageSum += adjTile.damage;
}
}
t.adjacentMonsterDamage = damageSum;
// If revealed, update the displayed number
if (t.revealed) {
if (damageSum > 0) {
t.adjText.setText(damageSum + '');
t.adjText.alpha = 1;
} else {
t.adjText.setText('');
t.adjText.alpha = 0;
}
}
}
}
// Only need to update once for the found monster
row = grid.length; // break outer loop
break;
}
}
}
}
};
return self;
});
// Player: Holds player stats and methods
var Player = Container.expand(function () {
var self = Container.call(this);
// Player stats
self.level = 1;
self.exp = 0;
self.expToLevel = 3; // Start with 3 exp needed for level up
self.maxHp = 10;
self.hp = self.maxHp;
self.damage = 1; // Start with 1 damage
self.defense = 0; // Initial defense is 0
self.skillPoints = 0; // Points to spend on upgrades
self.gold = 0; // Player's currency
self.inventory = {
// Player inventory
potionCount: 0,
sword: null,
// Equipped items
shield: null,
helmet: null,
armour: null
};
self.baseDamage = 1; // Base damage before gear
self.baseDefense = 0; // Base defense before gear
// Gain experience
self.gainExp = function (amount) {
self.exp += amount;
while (self.exp >= self.expToLevel) {
self.exp -= self.expToLevel;
self.levelUp();
}
};
// Level up player
self.levelUp = function () {
self.level++;
self.skillPoints++;
self.expToLevel = Math.floor(self.expToLevel * 1.2); // Increase EXP needed for next level
// Increase base stats on level up
// self.maxHp += 5; // Example stat increase
// self.damage += 1; // Example stat increase
self.hp = self.maxHp; // Heal to full HP on level up
self.updateEquippedStats(); // Update stats after base increase
};
// Take damage
self.takeDamage = function (amount) {
var damageTaken = Math.max(0, amount - self.defense); // Damage reduced by defense
self.hp -= damageTaken;
if (self.hp < 0) self.hp = 0;
};
// Spend skill points
self.spendSkillPoint = function (stat) {
if (self.skillPoints <= 0) return;
self.skillPoints--;
if (stat === "hp") {
self.maxHp += 10; // More HP per point
self.hp = self.maxHp; // Heal to full
} else if (stat === "damage") {
self.baseDamage += 2; // Increase base damage
} else if (stat === "defense") {
self.baseDefense += 1; // Increase base defense
}
self.updateEquippedStats(); // Update stats after spending points
};
// Equip gear
self.equipGear = function (type, quality) {
self.inventory[type] = {
type: type,
label: quality.label,
modifier: quality.modifier,
color: quality.color
};
self.updateEquippedStats(); // Update stats after equipping
};
// Update stats based on equipped gear
self.updateEquippedStats = function () {
self.damage = self.baseDamage;
self.defense = self.baseDefense;
if (self.inventory.sword) self.damage += self.inventory.sword.modifier;
if (self.inventory.shield) self.defense += self.inventory.shield.modifier;
if (self.inventory.helmet) self.defense += Math.floor(self.inventory.helmet.modifier / 2); // Helmet gives half defense
if (self.inventory.armour) self.defense += self.inventory.armour.modifier; // Armour gives full defense
if (self.inventory.armour && self.inventory.armour.modifier > 0) {
// Add an extra HP bonus for armour
self.maxHp += self.inventory.armour.modifier * 5; // Example HP bonus
}
// Ensure current HP doesn't exceed new max HP after stat changes
if (self.hp > self.maxHp) self.hp = self.maxHp;
};
return self;
});
// PlayerInventoryScreen: Displays player's inventory and allows using potions
var PlayerInventoryScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1600,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('INVENTORY', {
size: 100,
fill: 0x7DD3FC // Light Blue
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Gold display
self.goldText = new Text2('', {
size: 60,
fill: 0xFFD700 // Gold
});
self.goldText.anchor.set(0, 0.5);
self.goldText.x = self.panel.x - self.panel.width / 2 + 80;
self.goldText.y = self.panel.y - self.panel.height / 2 + 220;
self.addChild(self.goldText);
// Inventory items display
self.inventoryItemsText = new Text2('', {
size: 60,
fill: "#fff"
});
self.inventoryItemsText.anchor.set(0, 0); // Anchor to top-left
self.inventoryItemsText.x = self.panel.x - self.panel.width / 2 + 80;
self.inventoryItemsText.y = self.goldText.y + 80;
self.addChild(self.inventoryItemsText);
// Use Potion button
self.usePotionBtn = new Text2('USE POTION', {
size: 70,
fill: 0xA3E635,
// Green
font: "Impact"
});
self.usePotionBtn.anchor.set(0.5, 0.5);
self.usePotionBtn.x = 2048 / 2;
self.usePotionBtn.y = self.panel.y + self.panel.height / 2 - 250;
self.usePotionBtn.visible = false; // Initially hidden
self.usePotionBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0 || player.hp >= player.maxHp) return;
player.inventory.potionCount--;
player.hp += 20; // Heal amount
if (player.hp > player.maxHp) player.hp = player.maxHp;
self.updateInventoryDisplay();
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
updateGUI(); // Update main GUI HP display
};
self.addChild(self.usePotionBtn);
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact"
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endInventoryScreen();
};
self.addChild(self.closeBtn);
// Update the displayed inventory
self.updateInventoryDisplay = function () {
self.goldText.setText('Coins: ' + player.gold);
var inventoryText = '';
inventoryText += 'Life Potions: ' + player.inventory.potionCount + '\n\n';
var gearTypes = ['sword', 'shield', 'helmet', 'armour'];
for (var i = 0; i < gearTypes.length; i++) {
var type = gearTypes[i];
var item = player.inventory[type];
if (item) {
var attributeText = '';
if (type === 'sword') {
attributeText = '(+' + item.modifier + ' DMG)';
} else if (type === 'shield' || type === 'helmet' || type === 'armour') {
// Calculate defense bonus based on type (matching player.updateEquippedStats)
var defenseBonus = 0;
if (type === 'shield') defenseBonus = item.modifier;
if (type === 'helmet') defenseBonus = Math.floor(item.modifier / 2);
if (type === 'armour') defenseBonus = item.modifier;
attributeText = '(+' + defenseBonus + ' DEF)';
}
inventoryText += item.label + ' ' + type.charAt(0).toUpperCase() + type.slice(1) + ' ' + attributeText + '\n';
} else {
inventoryText += type.charAt(0).toUpperCase() + type.slice(1) + ': -\n';
}
}
self.inventoryItemsText.setText(inventoryText);
// Show/hide use potion button
if (player.inventory.potionCount > 0 && player.hp < player.maxHp) {
self.usePotionBtn.visible = true;
} else {
self.usePotionBtn.visible = false;
}
};
// Start the inventory screen
self.startInventoryScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
self.updateInventoryDisplay(); // Update inventory when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the inventory screen
self.endInventoryScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
updateGUI(); // Ensure main GUI is updated when closing
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
};
return self;
});
// PlayerStatsScreen: Displays and allows spending skill points
var PlayerStatsScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1200,
height: 1600,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Player Stats', {
size: 100,
fill: 0xFFE066
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Stat texts
self.hpStatText = new Text2('', {
size: 60,
fill: 0xA3E635
});
self.hpStatText.anchor.set(0, 0.5);
self.hpStatText.x = self.panel.x - self.panel.width / 2 + 80;
self.hpStatText.y = self.panel.y - 200;
self.addChild(self.hpStatText);
self.dmgStatText = new Text2('', {
size: 60,
fill: 0xF87171
});
self.dmgStatText.anchor.set(0, 0.5);
self.dmgStatText.x = self.panel.x - self.panel.width / 2 + 80;
self.dmgStatText.y = self.panel.y;
self.addChild(self.dmgStatText);
self.defStatText = new Text2('', {
size: 60,
fill: 0x7DD3FC
});
self.defStatText.anchor.set(0, 0.5);
self.defStatText.x = self.panel.x - self.panel.width / 2 + 80;
self.defStatText.y = self.panel.y + 200;
self.addChild(self.defStatText);
self.skillPointsText = new Text2('', {
size: 60,
fill: 0xFFE066
});
self.skillPointsText.anchor.set(0.5, 0.5);
self.skillPointsText.x = 2048 / 2;
self.skillPointsText.y = self.panel.y + self.panel.height / 2 - 200;
self.addChild(self.skillPointsText);
// Upgrade buttons
self.upgradeBtns = [];
var upgradeOptions = [{
label: "+HP & Heal",
stat: "hp",
color: 0xA3E635
}, {
label: "+DMG",
stat: "damage",
color: 0xF87171
}, {
label: "+DEF",
stat: "defense",
color: 0x7DD3FC
}];
for (var i = 0; i < upgradeOptions.length; i++) {
var option = upgradeOptions[i];
var btn = new Text2(option.label, {
size: 60,
fill: option.color,
font: "Impact"
});
btn.anchor.set(0.5, 0.5);
btn.x = self.panel.x + self.panel.width / 2 - 150;
btn.y = self.panel.y - 200 + i * 200;
btn.optionStat = option.stat; // Store the stat this button upgrades
btn.visible = false; // Initially hidden
btn.down = function () {
if (!self.visible || player.skillPoints <= 0) return;
player.spendSkillPoint(this.optionStat);
self.updateStatsDisplay();
updateGUI(); // Update main GUI as well
// Hide buttons if no skill points left
if (player.skillPoints <= 0) {
self.hideUpgradeButtons();
}
};
self.addChild(btn);
self.upgradeBtns.push(btn);
}
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact"
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endStatsScreen();
};
self.addChild(self.closeBtn);
// Update the displayed stats
self.updateStatsDisplay = function () {
self.hpStatText.setText('HP: ' + player.hp + '/' + player.maxHp);
self.dmgStatText.setText('DMG: ' + player.damage);
self.defStatText.setText('DEF: ' + player.defense);
self.skillPointsText.setText('Skill Points: ' + player.skillPoints);
if (player.skillPoints > 0) {
self.showUpgradeButtons();
} else {
self.hideUpgradeButtons();
}
};
// Show upgrade buttons
self.showUpgradeButtons = function () {
for (var i = 0; i < self.upgradeBtns.length; i++) {
self.upgradeBtns[i].visible = true;
}
};
// Hide upgrade buttons
self.hideUpgradeButtons = function () {
for (var i = 0; i < self.upgradeBtns.length; i++) {
self.upgradeBtns[i].visible = false;
}
};
// Start the stats screen
self.startStatsScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
self.updateStatsDisplay(); // Update stats when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the stats screen
self.endStatsScreen = function () {
self.visible = false;
self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
updateGUI(); // Ensure main GUI is updated when closing
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
};
return self;
});
// ShopScreen: Allows buying and selling items
var ShopScreen = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
self.alpha = 0;
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
width: 2048,
height: 2732,
alpha: 0.9
});
// Main panel background
self.panel = self.attachAsset('emptyTileBg', {
width: 1400,
height: 2000,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Title
self.titleText = new Text2('Shop', {
size: 100,
fill: 0xADD8E6 // LightBlue
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
self.titleText.y = self.panel.y - self.panel.height / 2 + 120;
self.addChild(self.titleText);
// Coins display
self.coinsText = new Text2('Coins: ' + player.gold, {
size: 60,
fill: 0xFFD700
});
self.coinsText.anchor.set(0, 0.5);
self.coinsText.x = self.panel.x - self.panel.width / 2 + 80;
self.coinsText.y = self.panel.y - self.panel.height / 2 + 220;
self.addChild(self.coinsText);
// Items for Sale (simplified for now: only potions)
self.itemsForSale = [{
type: "potion",
label: "Life Potion",
color: 0xA3E635,
cost: 15
}];
self.saleButtons = [];
var startY = self.coinsText.y + 100;
for (var i = 0; i < self.itemsForSale.length; i++) {
var item = self.itemsForSale[i];
var buyBtn = new Text2('BUY ' + item.label + ' (' + item.cost + ' Coins)', {
size: 60,
fill: item.color,
font: "Impact"
});
buyBtn.anchor.set(0, 0.5);
buyBtn.x = self.panel.x - self.panel.width / 2 + 80;
buyBtn.y = startY + i * 100;
buyBtn.item = item;
buyBtn.down = function () {
if (!self.visible) return;
var itemToBuy = this.item;
if (player.gold >= itemToBuy.cost) {
player.gold -= itemToBuy.cost;
if (itemToBuy.type === "potion") {
player.inventory.potionCount++;
}
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
// Show confirmation
self.showConfirmationText('Bought ' + itemToBuy.label + '!', itemToBuy.color);
} else {
// Show insufficient funds message
self.showConfirmationText('Not enough coins!', 0xFF0000);
}
};
self.addChild(buyBtn);
self.saleButtons.push(buyBtn);
}
// Items to Sell (simplified for now: gear and potions)
self.sellButtons = [];
var sellTitle = new Text2('Sell Items:', {
size: 70,
fill: 0xFFE066 // Yellow
});
sellTitle.anchor.set(0, 0.5);
sellTitle.x = self.panel.x - self.panel.width / 2 + 80;
sellTitle.y = startY + self.itemsForSale.length * 100 + 80;
self.addChild(sellTitle);
self.updateSellButtons = function () {
// Clear existing sell buttons
for (var i = 0; i < self.sellButtons.length; i++) {
self.sellButtons[i].destroy();
}
self.sellButtons = [];
var currentSellY = sellTitle.y + 80;
// Sell gear
var gearTypes = ['sword', 'shield', 'helmet', 'armour'];
for (var i = 0; i < gearTypes.length; i++) {
var gearType = gearTypes[i];
var item = player.inventory[gearType];
if (item) {
var sellPrice = item.modifier * 5; // Example sell price
var sellBtn = new Text2('SELL ' + item.label + ' ' + gearType.charAt(0).toUpperCase() + gearType.slice(1) + ' (' + sellPrice + ' Coins)', {
size: 50,
fill: item.color,
font: "Impact"
});
sellBtn.anchor.set(0, 0.5);
sellBtn.x = self.panel.x - self.panel.width / 2 + 80;
sellBtn.y = currentSellY;
sellBtn.itemType = gearType;
sellBtn.sellPrice = sellPrice;
sellBtn.down = function () {
if (!self.visible) return;
var type = this.itemType;
var price = this.sellPrice;
player.gold += price;
player.inventory[type] = null; // Remove item from inventory
player.updateEquippedStats(); // Update player stats
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
playerStatsScreen.updateStatsDisplay(); // Update stats screen if open
self.updateSellButtons(); // Refresh sell buttons
self.showConfirmationText('Sold ' + type.charAt(0).toUpperCase() + type.slice(1) + ' for ' + price + ' coins!', 0xFFD700);
};
self.addChild(sellBtn);
self.sellButtons.push(sellBtn);
currentSellY += 60;
}
}
// Sell potions
if (player.inventory.potionCount > 0) {
var potionSellPrice = 10; // Example sell price for potion
var sellPotionBtn = new Text2('SELL Life Potion (' + potionSellPrice + ' Coins)', {
size: 50,
fill: 0xA3E635,
font: "Impact"
});
sellPotionBtn.anchor.set(0, 0.5);
sellPotionBtn.x = self.panel.x - self.panel.width / 2 + 80;
sellPotionBtn.y = currentSellY;
sellPotionBtn.sellPrice = potionSellPrice;
sellPotionBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0) return;
var price = this.sellPrice;
player.gold += price;
player.inventory.potionCount--; // Remove potion
self.updateShopDisplay();
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
self.updateSellButtons(); // Refresh sell buttons
self.showConfirmationText('Sold a Life Potion for ' + price + ' coins!', 0xFFD700);
};
self.addChild(sellPotionBtn);
self.sellButtons.push(sellPotionBtn);
currentSellY += 60;
}
};
// Confirmation text display
self._confirmationText = null;
self.showConfirmationText = function (message, color) {
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
}
self._confirmationText = new Text2(message, {
size: 60,
fill: color
});
self._confirmationText.anchor.set(0.5, 0.5);
self._confirmationText.x = 2048 / 2;
self._confirmationText.y = self.panel.y + self.panel.height / 2 - 150;
self.addChild(self._confirmationText);
tween(self._confirmationText, {
alpha: 0
}, {
duration: 1000,
delay: 800,
onFinish: function onFinish() {
self._confirmationText.destroy();
self._confirmationText = null;
}
});
};
// Close button
self.closeBtn = new Text2('CLOSE', {
size: 70,
fill: "#fff",
font: "Impact"
});
self.closeBtn.anchor.set(0.5, 0.5);
self.closeBtn.x = 2048 / 2;
self.closeBtn.y = self.panel.y + self.panel.height / 2 - 80;
self.closeBtn.down = function () {
if (!self.visible) return;
self.endShopScreen();
};
self.addChild(self.closeBtn);
// Update the displayed shop info
self.updateShopDisplay = function () {
self.coinsText.setText('Coins: ' + player.gold);
self.updateSellButtons(); // Refresh sell buttons
};
// Start the shop screen
self.startShopScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
self.updateShopDisplay(); // Update shop info when opening
// Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
};
// End the shop screen
self.endShopScreen = function () {
self.visible = false;
self.alpha = 0;
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
self._confirmationText.destroy();
self._confirmationText = null;
}
if (self.parent) self.parent.removeChild(self);
if (monstersLeft <= 0 && currentLevel < LEVELS.length - 1) {
levelEndScreen.startLevelEndScreen();
} else {
updateGUI(); // Ensure main GUI is updated when closing
// Add transition for main GUI elements
tween(hpText, {
alpha: 1
}, {
duration: 300
});
tween(expText, {
alpha: 1
}, {
duration: 300
});
tween(levelText, {
alpha: 1
}, {
duration: 300
});
tween(statsBtn, {
alpha: 1
}, {
duration: 300
});
tween(inventoryBtn, {
alpha: 1
}, {
duration: 300
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22223a
});
/****
* Game Code
****/
// --- Game Constants ---
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
var GRID_COLS = 8;
var GRID_ROWS = 8;
var TILE_SIZE = 180;
var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * TILE_SIZE) / 2);
var GRID_OFFSET_Y = Math.floor(2732 * 0.2 + (2732 * 0.6 - GRID_ROWS * TILE_SIZE) / 2); // Center the grid vertically within the middle 60%
// --- Level System ---
var LEVELS = [{
monsters: 3,
monsterStats: {
minHp: 1,
maxHp: 2,
minDmg: 1,
maxDmg: 1,
minExp: 1,
maxExp: 1
}
}, {
monsters: 5,
monsterStats: {
minHp: 3,
maxHp: 5,
minDmg: 2,
maxDmg: 3,
minExp: 2,
maxExp: 3
}
}, {
monsters: 7,
monsterStats: {
minHp: 5,
maxHp: 8,
minDmg: 3,
maxDmg: 5,
minExp: 3,
maxExp: 4
}
}];
var currentLevel = 0;
var MONSTER_COUNT = LEVELS[currentLevel].monsters;
// --- Asset Initialization ---
// --- Game State ---
var player = new Player();
player.damage = 1;
player.baseDamage = 1;
player.exp = 0;
player.expToLevel = 3;
var grid = []; // 2D array [row][col]
var tileObjs = []; // Flat array of all tile objects for easy iteration
var monstersLeft = MONSTER_COUNT;
var revealedTiles = 0;
var totalSafeTiles = GRID_COLS * GRID_ROWS - MONSTER_COUNT;
var gameOver = false;
var mainScreen = new MainScreen(); // Initialize main screen
var battleScreen = new BattleScreen();
var chestScreen = new ChestScreen();
var playerStatsScreen = new PlayerStatsScreen(); // Initialize stats screen
var playerInventoryScreen = new PlayerInventoryScreen(); // Initialize inventory screen
var levelEndScreen = new LevelEndScreen(); // Initialize level end screen
var shopScreen = new ShopScreen(); // Initialize shop screen
// --- GUI Elements ---
var hpText = new Text2('', {
size: 40,
fill: 0xFF6666
});
hpText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(hpText);
var expText = new Text2('', {
size: 40,
fill: 0xFFE066
});
expText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(expText);
var levelText = new Text2('', {
size: 40,
fill: 0xA3E635
});
levelText.anchor.set(0, 0.5); // Anchor to left-middle
LK.gui.top.addChild(levelText);
// var monstersLeftText = new Text2('', {
// size: 40,
// fill: "#fff"
// });
// monstersLeftText.anchor.set(0, 0.5); // Anchor to left-middle
// LK.gui.top.addChild(monstersLeftText);
var statsBtn = new Text2('STATS', {
size: 70,
fill: "#fff",
font: "Impact"
});
statsBtn.anchor.set(1, 1); // Anchor to right-bottom
statsBtn.x = -100; // Position to the left of the right edge (adjusted for safe area)
statsBtn.y = -40; // Position near the bottom edge
statsBtn.visible = false; // Set initial visibility to false
statsBtn.down = function () {
if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active
playerStatsScreen.startStatsScreen();
};
LK.gui.bottom.addChild(statsBtn);
var inventoryBtn = new Text2('INVENTORY', {
size: 70,
fill: "#fff",
font: "Impact"
});
inventoryBtn.anchor.set(1, 1); // Anchor to right-bottom
inventoryBtn.x = -100 - statsBtn.width - 40; // Position to the left of the right edge (adjusted for safe area and other button)
inventoryBtn.y = -40; // Position near the bottom edge
inventoryBtn.visible = false; // Set initial visibility to false
inventoryBtn.down = function () {
if (gameOver || battleScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't open if another screen is active
playerInventoryScreen.startInventoryScreen();
};
LK.gui.bottom.addChild(inventoryBtn);
var levelUpBtn = new Text2('LEVEL UP', {
size: 70,
fill: "#fff",
font: "Impact"
});
levelUpBtn.anchor.set(0.5, 0.5);
levelUpBtn.alpha = 0.7;
levelUpBtn.visible = false;
levelUpBtn.down = function () {
if (!this.visible) return;
// Old level up choices removed
};
LK.gui.top.addChild(levelUpBtn);
// Old level up choice buttons and logic removed
// --- Helper Functions ---
function updateGUI() {
if (mainScreen.visible) {
hpText.visible = false;
expText.visible = false;
levelText.visible = false;
hpText.alpha = 0; // Set alpha to 0 for fade-in
expText.alpha = 0; // Set alpha to 0 for fade-in
levelText.alpha = 0; // Set alpha to 0 for fade-in
statsBtn.alpha = 0; // Set alpha to 0 for fade-in
inventoryBtn.alpha = 0; // Set alpha to 0 for fade-in
// monstersLeftText.visible = false;
levelUpBtn.visible = false;
statsBtn.visible = false; // Hide stats button
inventoryBtn.visible = false; // Hide inventory button
return;
}
if (battleScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) {
hpText.visible = false;
expText.visible = false;
levelText.visible = false;
hpText.alpha = 0; // Set alpha to 0 for fade-in
expText.alpha = 0; // Set alpha to 0 for fade-in
levelText.alpha = 0; // Set alpha to 0 for fade-in
statsBtn.alpha = 0; // Set alpha to 0 for fade-in
inventoryBtn.alpha = 0; // Set alpha to 0 for fade-in
// monstersLeftText.visible = false;
levelUpBtn.visible = false;
statsBtn.visible = false; // Hide stats button
inventoryBtn.visible = false; // Hide inventory button
if (battleScreen.visible) battleScreen.updateBattleStats(); // Update battle screen stats when visible
return;
} else {
hpText.visible = true;
expText.visible = true;
levelText.visible = true;
// monstersLeftText.visible = true;
statsBtn.visible = true; // Show stats button
inventoryBtn.visible = true; // Show inventory button
}
hpText.setText('HP: ' + player.hp + '/' + player.maxHp);
hpText.x = 450; // Position HP more to the right
hpText.y = 60; // Position near the top edge
// Show yellow + if enough EXP to level up
var canLevelUp = player.exp >= player.expToLevel;
var plusStr = canLevelUp ? " [color=#FFE066]+[/color]" : "";
expText.setText('EXP: ' + player.exp + '/' + player.expToLevel + plusStr);
expText.x = 450; // Position EXP more to the right
expText.y = 110; // Position below HP
// Show yellow + if enough EXP to level up next to LVL
var lvlPlusStr = canLevelUp ? " [color=#FFE066]+[/color]" : "";
levelText.setText('LVL: ' + player.level + lvlPlusStr);
levelText.x = 0; // Position LVL in the center of the top gui
levelText.y = 60; // Position near the top edge
levelText.anchor.set(0.5, 0.5);
// monstersLeftText.setText('Monsters: ' + monstersLeft);
// monstersLeftText.x = levelText.x + levelText.width + 40; // Position to the right of the level text
// monstersLeftText.y = 60; // Position near the top edge
// Hide level up button (replaced by stats screen)
levelUpBtn.visible = false;
// Show stats button and optionally indicate available skill points
statsBtn.visible = true;
// Center inventory and stats buttons horizontally at the bottom
var totalBtnWidth = statsBtn.width + inventoryBtn.width + 40;
var centerX = 0; // LK.gui.bottom center is x=0
statsBtn.x = centerX + totalBtnWidth / 2 - statsBtn.width / 2;
statsBtn.y = -40; // Position near the bottom edge
inventoryBtn.x = centerX - totalBtnWidth / 2 + inventoryBtn.width / 2;
inventoryBtn.y = -40; // Position near the bottom edge
if (player.skillPoints > 0) {
statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20); // Animate if points available
} else {
statsBtn.alpha = 1;
}
}
// Returns true if (row, col) is inside grid
function inBounds(row, col) {
return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS;
}
// Get all adjacent tile positions
function getAdjacent(row, col) {
var adj = [];
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var nr = row + dr,
nc = col + dc;
if (inBounds(nr, nc)) adj.push([nr, nc]);
}
}
return adj;
}
// Reveal empty tiles recursively (flood fill)
function revealEmptyTiles(row, col) {
var tile = grid[row][col];
if (tile.revealed) return;
tile.reveal();
revealedTiles++;
if (tile.adjacentMonsterDamage === 0) {
var adj = getAdjacent(row, col);
for (var i = 0; i < adj.length; i++) {
var _adj$i = _slicedToArray(adj[i], 2),
nr = _adj$i[0],
nc = _adj$i[1];
var t = grid[nr][nc];
if (t instanceof EmptyTile && !t.revealed) {
revealEmptyTiles(nr, nc);
}
}
}
}
// Reveal all monsters (on game over)
function revealAllMonsters() {
for (var i = 0; i < tileObjs.length; i++) {
var t = tileObjs[i];
if (t instanceof MonsterTile && !t.revealed) {
t.reveal();
}
}
}
// --- Board Generation ---
function generateBoard() {
// Clear previous
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].destroy();
}
grid = [];
tileObjs = [];
monstersLeft = LEVELS[currentLevel].monsters;
revealedTiles = 0;
totalSafeTiles = GRID_COLS * GRID_ROWS - LEVELS[currentLevel].monsters;
gameOver = false;
// Place monsters
var monsterPositions = [];
while (monsterPositions.length < LEVELS[currentLevel].monsters) {
var r = Math.floor(Math.random() * GRID_ROWS);
var c = Math.floor(Math.random() * GRID_COLS);
var key = r + ',' + c;
var found = false;
for (var i = 0; i < monsterPositions.length; i++) {
if (monsterPositions[i][0] === r && monsterPositions[i][1] === c) {
found = true;
break;
}
}
if (!found) monsterPositions.push([r, c]);
}
// Build grid
var stats = LEVELS[currentLevel].monsterStats;
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var isMonster = false;
for (var i = 0; i < monsterPositions.length; i++) {
if (monsterPositions[i][0] === row && monsterPositions[i][1] === col) {
isMonster = true;
break;
}
}
var tile;
// Always place a chest at (0,0) for dev
if (row === 0 && col === 0) {
tile = new ChestTile();
} else if (isMonster) {
tile = new MonsterTile();
// Level-based monster stats
tile.maxHp = tile.hp = stats.minHp + Math.floor(Math.random() * (stats.maxHp - stats.minHp + 1));
tile.damage = stats.minDmg + Math.floor(Math.random() * (stats.maxDmg - stats.minDmg + 1));
tile.exp = stats.minExp + Math.floor(Math.random() * (stats.maxExp - stats.minExp + 1));
} else {
tile = new EmptyTile();
}
tile.x = GRID_OFFSET_X + col * TILE_SIZE + TILE_SIZE / 2;
tile.y = GRID_OFFSET_Y + row * TILE_SIZE + TILE_SIZE / 2;
game.addChild(tile);
grid[row][col] = tile;
tileObjs.push(tile);
}
}
// Set adjacent monster counts for empty tiles
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var tile = grid[row][col];
if (tile instanceof EmptyTile) {
var adj = getAdjacent(row, col);
var damageSum = 0; // Variable to store the sum of damage
for (var i = 0; i < adj.length; i++) {
var _adj$i2 = _slicedToArray(adj[i], 2),
nr = _adj$i2[0],
nc = _adj$i2[1];
if (grid[nr][nc] instanceof MonsterTile) {
damageSum += grid[nr][nc].damage; // Add monster's damage to the sum
}
}
tile.adjacentMonsterDamage = damageSum; // Store the damage sum
}
}
}
}
// --- Game Logic ---
function handleTileDown(x, y, obj) {
if (gameOver) return;
if (battleScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't allow tile interaction if battle screen, level end, or shop screen is up
// Find which tile was pressed
for (var i = 0; i < tileObjs.length; i++) {
var tile = tileObjs[i];
if (tile.revealed) continue;
if (tile.cover && tile.cover.alpha > 0.1) {
// Check if (x, y) is inside tile
var dx = x - tile.x;
var dy = y - tile.y;
if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) {
// Reveal tile
if (tile instanceof EmptyTile) {
revealEmptyTiles(Math.floor((tile.y - GRID_OFFSET_Y) / TILE_SIZE), Math.floor((tile.x - GRID_OFFSET_X) / TILE_SIZE));
updateGUI();
// Win check
// (Removed: revealing all safe tiles no longer triggers win. Level is won only when all monsters are defeated.)
} else if (tile instanceof ChestTile) {
// Reveal chest and show chest screen
tile.reveal();
chestScreen.startChest(tile);
} else if (tile instanceof MonsterTile) {
// Reveal monster and start battle
tile.reveal(); // Reveal the monster visually
// Start the battle sequence
battleScreen.startBattle(tile);
}
break;
}
}
}
}
// --- Level Up UI ---
// Old level up UI functions removed
// --- Event Handlers ---
game.down = function (x, y, obj) {
if (mainScreen.visible) return; // Prevent interaction if main screen is visible
// The click handling for levelUpBtn and levelUpChoiceBtns
// has been moved to their own 'down' event handlers.
// Check for tile interactions
handleTileDown(x, y, obj);
};
game.update = function () {
if (mainScreen.visible || battleScreen.visible || playerStatsScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't update game elements when main, battle, stats, level end, or shop screen is up
// Animate level up button (removed)
// if (levelUpBtn.visible) {
// levelUpBtn.x = 0; // Centered horizontally relative to LK.gui.top
// levelUpBtn.y = 120;
// levelUpBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20);
// }
// Animate level up choices (removed)
// for (var i = 0; i < levelUpChoiceBtns.length; i++) {
// var btn = levelUpChoiceBtns[i];
// if (btn.visible) {
// btn.x = (i - 1) * 400; // x relative to LK.gui.bottom
// btn.y = -2732 * 0.15; // Position choices above the bottom boundary of the battle area
// btn.alpha = 0.9 + 0.1 * Math.sin(LK.ticks / 15 + i);
// }
// }
// Animate stats button if skill points are available
if (statsBtn.visible && player.skillPoints > 0) {
statsBtn.alpha = 0.8 + 0.2 * Math.sin(LK.ticks / 20);
} else if (statsBtn.visible) {
statsBtn.alpha = 1; // Solid if no points
}
if (mainScreen.visible || battleScreen.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) {
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].visible = false;
}
} else {
for (var i = 0; i < tileObjs.length; i++) {
tileObjs[i].visible = true;
}
}
};
// --- Start Game ---
function startGame() {
generateBoard();
levelText.setText('LVL: ' + (currentLevel + 1));
updateGUI();
}
// Show main screen at start
mainScreen.startMainScreen();