User prompt
add more spaceing sideways between attack items and run in the actions
User prompt
re order player action in battle screen. Should be Attack Item Run
User prompt
Caan you make the title screen a little nicer, maybe add some animation, keep in mind its a dungeon RPG style game ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
order of battle actions should be Attack - Item - Run and addmore space between them.
User prompt
Add animation when player updates any stats, so that it is clear what it selected, and gives some satisfaction. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Shop should have a potion for sale for 10 coins
User prompt
Items action should be greyed out if player has no items
User prompt
when stats page or inventoyr pages are up, hide GUI elelemtns
User prompt
on level complete screen, also hide gui elements
User prompt
When the chest screen is up, hide the GUI elements
User prompt
Nice! Please make chest image size bigger, like you did with the player.
User prompt
LVL, EXP, HP, INVENTORY and STATS, should be easily turned on and off, as a whole. When an overlay or other screen is up, they should be hidden, and unhiddenwhen only the grid is displayed.
User prompt
When chest screen is up, hide the inventory and lvl gui
User prompt
hide guid when on chest screen too
User prompt
Image of player when a chest is found should be as big as when in the battle
User prompt
When player runs away, the cell shoudl still show there is a monster inside of it.
User prompt
add a black outline to all cells
User prompt
To resolve this, a thorough review of the `updateGUI` function and the logic that controls GUI visibility after running from battle would be necessary.
User prompt
Gui is not being displayed again after player runs away. It should. please fix it.
User prompt
When player runs away from battle, battle should end, and GUI should be displayed again
User prompt
when player runs away, make sure invenory and GUI is displayed again
User prompt
When player runs awawy from battle, then make sure the cell goesback to its original status,but also display the enemy tobe clicked on again
User prompt
if player runs from battle, make sure that cell is back to covered, but show the nemey in it
User prompt
if an enemy has not been defeated, player can click on it again even if it has been uncovered
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'self._confirmationText.destroy();' Line Number: 2035
/****
* 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,
// Position near top-right corner within the battle area, moved higher
scaleX: 4,
// Increased scale
scaleY: 4 // Increased scale
});
// Monster stats text
self.monsterStatText = new Text2('', {
size: 60,
fill: "#fff",
font: "Impact"
});
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 * self.monsterDisplay.scaleY / 2 + 60; // Positioned closer below monster image
self.addChild(self.monsterStatText);
// Player stats text (in battle)
self.playerBattleStatText = new Text2('', {
size: 60,
fill: "#fff",
font: "Impact"
});
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 - 350,
// Position near bottom-left corner within the battle area, moved higher
scaleX: 4,
// Increased scale
scaleY: 4 // Increased scale
});
self.playerBattleStatText.anchor.set(0.5, 0); // Anchor to top-center
// Set stat text position to where hero will appear
self.playerBattleStatText.x = self.playerDisplay.x; // Relative to player display X
self.playerBattleStatText.y = self.playerDisplay.y + self.playerDisplay.height * self.playerDisplay.scaleY / 2 + 60; // Positioned closer below player, accounting for new scale
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);
// Action button (Items) - Initial declaration for width calculation
// The actual self.itemsBtn will be (re)declared later. This corresponds to original first itemsBtn {J}
var tempItemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact"
});
// Action button (Run) - Initial declaration for width calculation
// The actual self.runBtn will be (re)declared later. This corresponds to original first runBtn {D}
var tempRunBtn = new Text2('RUN', {
size: 80,
fill: "#fff",
font: "Impact"
});
// Calculate total width using the three distinct buttons' initial declarations
var totalButtonWidth = self.attackBtn.width + tempItemsBtn.width + tempRunBtn.width + 80; // Widths + 2*40px spacing
var startX = (2048 - totalButtonWidth) / 2;
// --- Position buttons in order: Attack, Items, Run ---
var commonButtonY = 2732 - 300; // Common Y position for all buttons
// 1. ATTACK Button (leftmost)
// self.attackBtn is already defined and its anchor is set.
self.attackBtn.x = startX + self.attackBtn.width / 2;
self.attackBtn.y = commonButtonY;
self.addChild(self.attackBtn);
self.attackBtn.down = function () {
if (!self.visible) return;
self.playerTurn();
};
// 2. ITEMS Button (center)
// Action button (Items)//{Z} // This (re)declaration corresponds to original second itemsBtn {Z}
self.itemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.itemsBtn.anchor.set(0.5, 0.5);
self.itemsBtn.x = startX + self.attackBtn.width + 40 + self.itemsBtn.width / 2;
self.itemsBtn.y = commonButtonY;
self.addChild(self.itemsBtn);
self.itemsBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0) return; // Only allow if visible and player has potions
self.showBattleItemsMenu();
};
// 3. RUN Button (rightmost)
// Action button (Run)//{R} // This (re)declaration corresponds to original second runBtn {R}
self.runBtn = new Text2('RUN', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.runBtn.anchor.set(0.5, 0.5);
self.runBtn.x = startX + self.attackBtn.width + 40 + self.itemsBtn.width + 40 + self.runBtn.width / 2;
self.runBtn.y = commonButtonY;
self.addChild(self.runBtn);
self.runBtn.down = function () {
if (!self.visible) return;
self.tryToRun();
};
self.currentMonster = null; // Reference to the monster tile being fought
// Battle Items Menu
self.battleItemsMenu = new Container();
self.battleItemsMenu.visible = false;
self.battleItemsMenuOverlay = self.battleItemsMenu.attachAsset('emptyTileCover', {
width: 800,
height: 500,
alpha: 0.9,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
self.battleItemsMenu.titleText = new Text2('Use Item', {
size: 70,
fill: 0x7DD3FC,
// Light Blue
font: "Impact"
});
self.battleItemsMenu.titleText.anchor.set(0.5, 0.5);
self.battleItemsMenu.titleText.x = 2048 / 2;
self.battleItemsMenu.titleText.y = 2732 / 2 - 150;
self.battleItemsMenu.addChild(self.battleItemsMenu.titleText);
self.battleItemsMenu.potionCountText = new Text2('', {
size: 60,
fill: 0xA3E635,
// Green
font: "Impact"
});
self.battleItemsMenu.potionCountText.anchor.set(0.5, 0.5);
self.battleItemsMenu.potionCountText.x = 2048 / 2;
self.battleItemsMenu.potionCountText.y = 2732 / 2 - 50;
self.battleItemsMenu.addChild(self.battleItemsMenu.potionCountText);
self.battleItemsMenu.usePotionBtn = new Text2('Use Life Potion', {
size: 60,
fill: 0xA3E635,
// Green
font: "Impact"
});
self.battleItemsMenu.usePotionBtn.anchor.set(0.5, 0.5);
self.battleItemsMenu.usePotionBtn.x = 2048 / 2;
self.battleItemsMenu.usePotionBtn.y = 2732 / 2 + 50;
self.battleItemsMenu.addChild(self.battleItemsMenu.usePotionBtn);
self.battleItemsMenu.usePotionBtn.down = function () {
if (player.inventory.potionCount > 0 && player.hp < player.maxHp) {
player.inventory.potionCount--;
player.hp += 20; // Heal amount
if (player.hp > player.maxHp) player.hp = player.maxHp;
self.updateBattleStats(); // Update battle stats display
self.hideBattleItemsMenu();
self.showConfirmationText('Used a Life Potion!', 0xA3E635); // Show confirmation message
playerInventoryScreen.updateInventoryDisplay(); // Update inventory screen if open
showGameGUIWithTransition();
}
};
self.battleItemsMenu.closeBtn = new Text2('Close', {
size: 60,
fill: "#fff",
font: "Impact"
});
self.battleItemsMenu.closeBtn.anchor.set(0.5, 0.5);
self.battleItemsMenu.closeBtn.x = 2048 / 2;
self.battleItemsMenu.closeBtn.y = 2732 / 2 + 150;
self.battleItemsMenu.addChild(self.battleItemsMenu.closeBtn);
self.battleItemsMenu.closeBtn.down = function () {
self.hideBattleItemsMenu();
};
self.addChild(self.battleItemsMenu);
self._confirmationText = null; // Text for temporary messages (e.g., item used)
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,
font: "Impact"
});
self._confirmationText.anchor.set(0.5, 0.5);
self._confirmationText.x = 2048 / 2;
self._confirmationText.y = 2732 / 2 + 300; // Position below action buttons
self.addChild(self._confirmationText);
tween(self._confirmationText, {
alpha: 0
}, {
duration: 1000,
delay: 800,
onFinish: function onFinish() {
self._confirmationText.destroy();
self._confirmationText = null;
}
});
};
// 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;
self.runBtn.visible = false; // Initially hidden
self.itemsBtn.visible = false; // Hide items button
// 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,
font: "Impact"
});
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 - 350; // Adjusted Y position
var monsterTargetX = 2048 - 300;
var monsterTargetY = 2732 * 0.2; // Adjusted Y position
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,
font: "Impact"
});
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;
self.runBtn.visible = true; // Show run button after intro
self.itemsBtn.visible = true; // Show items button after intro
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;
self.runBtn.visible = false; // Hide run button
self.itemsBtn.visible = false; // Hide items button
self.hideBattleItemsMenu(); // Hide item menu
if (self._confirmationText) {
// Hide any confirmation text
self._confirmationText.destroy();
self._confirmationText = null;
}
if (win) {
// expGained and player.gainExp(expGained) are now handled in playerTurn's onFinish monster death animation
self.currentMonster.defeat();
monstersLeft--;
showGameGUIWithTransition();
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);
}
// Grey out and disable ITEMS button if player has no potions
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
};
// 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
self.runBtn.visible = false; // Hide run button during animation
self.itemsBtn.visible = false; // Hide items button during animation
// Show "Player Attacks!" text
var playerAttackText = new Text2('Player Attacks!', {
size: 80,
fill: 0xffe066,
font: "Impact"
});
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,
font: "Impact"
});
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\nLoot:\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
font: "Impact"
});
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 = 4; // Adjusted to new scale
self.monsterDisplay.scaleY = 4; // Adjusted to new scale
}
});
}
});
}
});
} else {
// Monster survives, monster's turn after a delay
LK.setTimeout(self.monsterTurn, 500);
self.attackBtn.visible = true;
self.runBtn.visible = true; // Show run button again
self.itemsBtn.visible = true; // Show items button again
}
}
// 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
self.runBtn.visible = false; // Hide run button during animation
self.itemsBtn.visible = false; // Hide items button during animation
// Show "Monster Attacks!" text
var monsterAttackText = new Text2('Monster Attacks!', {
size: 80,
fill: 0xff0000,
font: "Impact"
});
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
font: "Impact"
});
textColor = 0xff0000;
} else {
playerDmgText = new Text2('Blocked!', {
size: 80,
fill: 0x7DD3FC,
// Light blue for blocked
font: "Impact"
});
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.runBtn.visible = true; // Show run button again
self.itemsBtn.visible = true; // Show items button again
}
}
});
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
});
}
});
};
self.tryToRun = function () {
if (!self.currentMonster) return;
self.attackBtn.visible = false; // Hide attack and run buttons
self.runBtn.visible = false;
self.itemsBtn.visible = false; // Hide items button
self.itemsBtn.visible = false; // Hide items button
var runSuccess = true;
// Calculate chance to fail run based on monster level vs player level
if (self.currentMonster.exp > player.level) {
// Use monster EXP as a rough proxy for level/difficulty
var failChance = Math.min(0.8, (self.currentMonster.exp - player.level) * 0.1); // 10% fail chance per monster level difference, max 80%
if (Math.random() < failChance) {
runSuccess = false;
}
}
var runMessageText;
var runMessageColor;
if (runSuccess) {
runMessageText = new Text2('You successfully ran away!', {
size: 80,
fill: 0xA3E635,
// Green
font: "Impact"
});
} else {
runMessageText = new Text2('Failed to run!', {
size: 80,
fill: 0xFF0000,
// Red
font: "Impact"
});
}
runMessageText.anchor.set(0.5, 0.5);
runMessageText.x = 2048 / 2;
runMessageText.y = 2732 / 2;
self.addChild(runMessageText);
tween(runMessageText, {
alpha: 0
}, {
duration: 800,
delay: 600,
onFinish: function onFinish() {
runMessageText.destroy();
if (runSuccess) {
// Reset monster HP and mark as not defeated
self.currentMonster.hp = self.currentMonster.maxHp;
self.currentMonster.defeated = false; // Important: Reset defeated state
// Make sure monster remains visible but cover is restored
self.currentMonster.cover.alpha = 1; // Restore cover to original state
self.currentMonster.monster.alpha = 1; // Ensure monster image remains visible
self.currentMonster.revealed = false; // Set revealed to false
// End battle and return to grid
self.endBattle(false); // Pass false, as it's not a win. This sets battleScreen.visible to false.
showGameGUIWithTransition();
} else {
// Running failed, monster attacks
LK.setTimeout(self.monsterTurn, 300); // Monster attacks immediately after message
}
}
});
};
// Show the battle items menu
self.showBattleItemsMenu = function () {
self.battleItemsMenu.visible = true;
self.attackBtn.visible = false; // Hide action buttons
self.runBtn.visible = false;
self.itemsBtn.visible = false;
// Update potion count display
self.battleItemsMenu.potionCountText.setText('Potions: ' + player.inventory.potionCount);
// Disable use potion button if player is full HP or no potions
if (player.inventory.potionCount <= 0 || player.hp >= player.maxHp) {
self.battleItemsMenu.usePotionBtn.alpha = 0.5;
self.battleItemsMenu.usePotionBtn.interactive = false;
} else {
self.battleItemsMenu.usePotionBtn.alpha = 1;
self.battleItemsMenu.usePotionBtn.interactive = true;
}
// Also update ITEMS button state in battle screen
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
};
// Hide the battle items menu
self.hideBattleItemsMenu = function () {
self.battleItemsMenu.visible = false;
// Show action buttons again if player is alive and battle is ongoing
if (player.hp > 0 && self.currentMonster && !gameOver) {
self.attackBtn.visible = true;
self.runBtn.visible = true;
self.itemsBtn.visible = true;
// Update ITEMS button state
if (player.inventory.potionCount <= 0) {
self.itemsBtn.alpha = 0.5;
self.itemsBtn.interactive = false;
} else {
self.itemsBtn.alpha = 1;
self.itemsBtn.interactive = true;
}
}
};
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: 4,
// Increased scale
scaleY: 4 // Increased scale
});
// "You found a chest!" text
self.chestText = new Text2('You found a chest!', {
size: 90,
fill: 0xFFD700,
font: "Impact"
});
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: 4,
scaleY: 4
});
// --- 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,
font: "Impact"
});
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);
updateGUI(); // Hide main GUI
// 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);
showGameGUIWithTransition();
};
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.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
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.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
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",
font: "Impact"
});
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,
font: "Impact"
});
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);
updateGUI(); // Hide main GUI
// 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);
showGameGUIWithTransition();
};
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
font: "Impact"
});
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.outline = self.attachAsset('tileOutline', {
// Attach black outline
anchorX: 0.5,
anchorY: 0.5
});
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",
font: "Impact"
});
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;
});
// Add event listener to MonsterTile for clicking
// 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.ceil(self.expToLevel * 1.2); // Increase EXP needed for next level, ensuring it consistently grows
// 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
font: "Impact"
});
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
font: "Impact"
});
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",
font: "Impact"
});
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 () {
// Animate gold text if value changed
if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) {
tween(self.goldText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.goldText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.goldText.setText('Coins: ' + player.gold);
// Animate potion count if changed
if (typeof self._lastPotionCount !== "undefined" && self._lastPotionCount !== player.inventory.potionCount) {
tween(self.inventoryItemsText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.inventoryItemsText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
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);
// Store last values for next update
self._lastGold = player.gold;
self._lastPotionCount = player.inventory.potionCount;
// Show/hide use potion button
if (player.inventory.potionCount > 0 && player.hp < player.maxHp) {
self.usePotionBtn.visible = true;
self.usePotionBtn.alpha = 1;
self.usePotionBtn.interactive = true;
} else {
self.usePotionBtn.visible = true;
self.usePotionBtn.alpha = 0.5;
self.usePotionBtn.interactive = false;
}
};
// Start the inventory screen
self.startInventoryScreen = function () {
self.visible = true;
self.alpha = 0;
game.addChild(self);
updateGUI(); // Hide main GUI
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);
showGameGUIWithTransition();
};
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,
font: "Impact"
});
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,
font: "Impact"
});
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,
font: "Impact"
});
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,
font: "Impact"
});
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,
font: "Impact"
});
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 () {
// Animate stat text if value changed
if (typeof self._lastHp !== "undefined" && (self._lastHp !== player.hp || self._lastMaxHp !== player.maxHp)) {
tween(self.hpStatText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.hpStatText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
if (typeof self._lastDmg !== "undefined" && self._lastDmg !== player.damage) {
tween(self.dmgStatText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.dmgStatText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
if (typeof self._lastDef !== "undefined" && self._lastDef !== player.defense) {
tween(self.defStatText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.defStatText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
if (typeof self._lastSkillPoints !== "undefined" && self._lastSkillPoints !== player.skillPoints) {
tween(self.skillPointsText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.skillPointsText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
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);
// Store last values for next update
self._lastHp = player.hp;
self._lastMaxHp = player.maxHp;
self._lastDmg = player.damage;
self._lastDef = player.defense;
self._lastSkillPoints = 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);
updateGUI(); // Hide main GUI
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);
showGameGUIWithTransition();
};
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
font: "Impact"
});
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,
font: "Impact"
});
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: 10
}];
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
font: "Impact"
});
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,
font: "Impact"
});
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() {
if (self._confirmationText && typeof self._confirmationText.destroy === 'function') {
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 () {
// Animate coins text if value changed
if (typeof self._lastGold !== "undefined" && self._lastGold !== player.gold) {
tween(self.coinsText, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.coinsText, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
self.coinsText.setText('Coins: ' + player.gold);
self._lastGold = 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 {
showGameGUIWithTransition();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22223a
});
/****
* Game Code
****/
// New shape for black outline
// --- Game Constants ---
// Add event listener to MonsterTile for clicking
MonsterTile.prototype.down = function (x, y, obj) {
if (gameOver) return;
// Prevent interaction if any overlay screen is visible
if (mainScreen.visible || battleScreen.visible || battleScreen.battleItemsMenu.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return;
// If the monster is defeated, do nothing
if (this.defeated) return;
// If the monster is not defeated (either initially or after running away), a click should initiate a battle.
// Ensure it's revealed before starting the battle.
// The reveal() method has an internal check (if (self.revealed) return;)
// so it's safe to call even if already revealed.
this.reveal();
battleScreen.startBattle(this);
};
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;
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,
font: "Impact"
});
hpText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(hpText);
var expText = new Text2('', {
size: 40,
fill: 0xFFE066,
font: "Impact"
});
expText.anchor.set(0.5, 0); // Anchor to top-center
LK.gui.top.addChild(expText);
var levelText = new Text2('', {
size: 40,
fill: 0xA3E635,
font: "Impact"
});
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);
var managedGameGuiElements = [hpText, expText, levelText, statsBtn, inventoryBtn];
// Old level up choice buttons and logic removed
// --- Helper Functions ---
// Helper function to show main GUI elements with a fade-in transition
function showGameGUIWithTransition() {
updateGUI(); // Set visibility and content first
for (var i = 0; i < managedGameGuiElements.length; i++) {
var el = managedGameGuiElements[i];
// Tween alpha to 1. statsBtn's specific animation in game.update will take over if active.
tween(el, {
alpha: 1
}, {
duration: 300
});
}
}
function updateGUI() {
var showMainGui = !mainScreen.visible && !battleScreen.visible && !(chestScreen && chestScreen.visible) && !(playerStatsScreen && playerStatsScreen.visible) && !(playerInventoryScreen && playerInventoryScreen.visible) && !(levelEndScreen && levelEndScreen.visible) && !(shopScreen && shopScreen.visible);
for (var i = 0; i < managedGameGuiElements.length; i++) {
var el = managedGameGuiElements[i];
if (showMainGui) {
el.visible = true;
// Alpha is primarily handled by transitions or default to 1 after transition.
// Specific alpha animations (like statsBtn) are handled in game.update.
} else {
el.visible = false;
el.alpha = 0; // Ensure they are fully transparent when hidden
}
}
// Ensure levelUpBtn is also hidden if main GUI is hidden or if it's simply not used
levelUpBtn.visible = false;
// Specific content updates if GUI is shown
if (showMainGui) {
hpText.setText('HP: ' + player.hp + '/' + player.maxHp);
hpText.x = 450; // Position HP more to the right
hpText.y = 60; // Position near the top edge
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
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 logic remains commented out as in original
// monstersLeftText.setText('Monsters: ' + monstersLeft);
// monstersLeftText.x = levelText.x + levelText.width + 40;
// monstersLeftText.y = 60;
// Position and style statsBtn and inventoryBtn
// Their visibility is already handled by the loop above.
var totalBtnWidth = statsBtn.width + inventoryBtn.width + 40;
var centerX = 0; // LK.gui.bottom center is x=0, these are added to LK.gui.bottom
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.setText('STATS +');
if (statsBtn.style) statsBtn.style.fill = 0xFFE066; // Yellow color
if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFE066; // Set yellow tint
// Alpha for statsBtn (pulsing animation) is handled in game.update
} else {
statsBtn.setText('STATS');
if (statsBtn.style) statsBtn.style.fill = "#fff"; // Default white color
if (typeof statsBtn.tint !== "undefined") statsBtn.tint = 0xFFFFFF; // Reset to white tint
// Alpha for statsBtn (solid) is handled in game.update or by transition
}
} else {
// If GUI is hidden, and battle screen is the one visible, update its stats
if (battleScreen.visible) {
battleScreen.updateBattleStats();
}
}
}
// 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 || battleScreen.battleItemsMenu.visible || levelEndScreen.visible || shopScreen.visible || chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible) return; // Don't allow tile interaction if battle screen, battle item menu, level end, shop, chest, stats, or inventory 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 || battleScreen.battleItemsMenu.visible || chestScreen && chestScreen.visible || playerStatsScreen.visible || playerInventoryScreen.visible || levelEndScreen.visible || shopScreen.visible) return; // Don't update game elements when main, battle, battle item menu, stats, level end, shop, chest, stats, or inventory 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();
showGameGUIWithTransition(); // This will call updateGUI which handles levelText
}
// Show main screen at start
mainScreen.startMainScreen(); ===================================================================
--- original.js
+++ change.js
@@ -67,57 +67,66 @@
fill: "#fff",
font: "Impact"
});
self.attackBtn.anchor.set(0.5, 0.5);
- // Action button (Run)
- self.runBtn = new Text2('RUN', {
+ // Action button (Items) - Initial declaration for width calculation
+ // The actual self.itemsBtn will be (re)declared later. This corresponds to original first itemsBtn {J}
+ var tempItemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact"
});
- // Action button (Items)
- self.itemsBtn = new Text2('ITEMS', {
+ // Action button (Run) - Initial declaration for width calculation
+ // The actual self.runBtn will be (re)declared later. This corresponds to original first runBtn {D}
+ var tempRunBtn = new Text2('RUN', {
size: 80,
fill: "#fff",
font: "Impact"
});
- var totalButtonWidth = self.attackBtn.width + self.runBtn.width + self.itemsBtn.width + 80; // Widths + 2*40px spacing
+ // Calculate total width using the three distinct buttons' initial declarations
+ var totalButtonWidth = self.attackBtn.width + tempItemsBtn.width + tempRunBtn.width + 80; // Widths + 2*40px spacing
var startX = (2048 - totalButtonWidth) / 2;
- self.attackBtn.x = startX + self.runBtn.width + 40 + self.attackBtn.width / 2; // Center Attack button
- self.attackBtn.y = 2732 - 300; // Position near bottom
+ // --- Position buttons in order: Attack, Items, Run ---
+ var commonButtonY = 2732 - 300; // Common Y position for all buttons
+ // 1. ATTACK Button (leftmost)
+ // self.attackBtn is already defined and its anchor is set.
+ self.attackBtn.x = startX + self.attackBtn.width / 2;
+ self.attackBtn.y = commonButtonY;
self.addChild(self.attackBtn);
self.attackBtn.down = function () {
if (!self.visible) return;
self.playerTurn();
};
- // Action button (Run)
- self.runBtn = new Text2('RUN', {
- size: 80,
- fill: "#fff",
- font: "Impact"
- });
- self.runBtn.anchor.set(0.5, 0.5);
- self.runBtn.x = startX + self.runBtn.width / 2; // Position Run button to the left
- self.runBtn.y = self.attackBtn.y; // Align vertically with attack button
- self.addChild(self.runBtn);
- self.runBtn.down = function () {
- if (!self.visible) return;
- self.tryToRun();
- };
- // Action button (Items)
+ // 2. ITEMS Button (center)
+ // Action button (Items)//{Z} // This (re)declaration corresponds to original second itemsBtn {Z}
self.itemsBtn = new Text2('ITEMS', {
size: 80,
fill: "#fff",
font: "Impact"
});
self.itemsBtn.anchor.set(0.5, 0.5);
- self.itemsBtn.x = startX + self.runBtn.width + 40 + self.attackBtn.width + 40 + self.itemsBtn.width / 2; // Position Items button to the right
- self.itemsBtn.y = self.attackBtn.y; // Align vertically with attack button
+ self.itemsBtn.x = startX + self.attackBtn.width + 40 + self.itemsBtn.width / 2;
+ self.itemsBtn.y = commonButtonY;
self.addChild(self.itemsBtn);
self.itemsBtn.down = function () {
if (!self.visible || player.inventory.potionCount <= 0) return; // Only allow if visible and player has potions
self.showBattleItemsMenu();
};
+ // 3. RUN Button (rightmost)
+ // Action button (Run)//{R} // This (re)declaration corresponds to original second runBtn {R}
+ self.runBtn = new Text2('RUN', {
+ size: 80,
+ fill: "#fff",
+ font: "Impact"
+ });
+ self.runBtn.anchor.set(0.5, 0.5);
+ self.runBtn.x = startX + self.attackBtn.width + 40 + self.itemsBtn.width + 40 + self.runBtn.width / 2;
+ self.runBtn.y = commonButtonY;
+ self.addChild(self.runBtn);
+ self.runBtn.down = function () {
+ if (!self.visible) return;
+ self.tryToRun();
+ };
self.currentMonster = null; // Reference to the monster tile being fought
// Battle Items Menu
self.battleItemsMenu = new Container();
self.battleItemsMenu.visible = false;
@@ -1117,115 +1126,8 @@
return self;
});
var MainScreen = Container.expand(function () {
var self = Container.call(this);
- self._startPulseAnimation = function (element, targetPulseScale, pulseDuration) {
- if (!element || !element.parent) return;
- element._originalScaleX = element.scale.x;
- element._originalScaleY = element.scale.y;
- element._isPulsing = true;
- function animatePulse() {
- if (!element || !element.parent || !element.visible || !element._isPulsing) {
- tween.stop(element, {
- scaleX: true,
- scaleY: true
- });
- if (element && typeof element._originalScaleX !== 'undefined') {
- element.scale.set(element._originalScaleX, element._originalScaleY);
- }
- if (element) element._isPulsing = false;
- return;
- }
- tween(element, {
- scaleX: element._originalScaleX * targetPulseScale,
- scaleY: element._originalScaleY * targetPulseScale
- }, {
- duration: pulseDuration,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!element || !element.parent || !element.visible || !element._isPulsing) {
- animatePulse();
- return;
- }
- tween(element, {
- scaleX: element._originalScaleX,
- scaleY: element._originalScaleY
- }, {
- duration: pulseDuration,
- easing: tween.easeInOut,
- onFinish: animatePulse
- });
- }
- });
- }
- animatePulse();
- };
- self._stopPulseAnimation = function (element) {
- if (element) {
- element._isPulsing = false; // Signal to stop the loop
- // tween.stop will be called by the animation loop's exit condition
- }
- };
- self._startFloatAnimation = function (element, floatAmount, floatDuration) {
- if (!element || !element.parent) return;
- element._originalY = element.y; // Store original Y after entry animation
- element._isFloating = true;
- function floatCycleUp() {
- if (!element || !element.parent || !element.visible || !element._isFloating) {
- tween.stop(element, {
- y: true
- });
- if (element && typeof element._originalY !== 'undefined') element.y = element._originalY;
- if (element) element._isFloating = false;
- return;
- }
- tween(element, {
- y: element._originalY - floatAmount
- }, {
- // Float Up
- duration: floatDuration,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!element || !element.parent || !element.visible || !element._isFloating) {
- floatCycleUp();
- return;
- }
- floatCycleDown();
- }
- });
- }
- function floatCycleDown() {
- if (!element || !element.parent || !element.visible || !element._isFloating) {
- tween.stop(element, {
- y: true
- });
- if (element && typeof element._originalY !== 'undefined') element.y = element._originalY;
- if (element) element._isFloating = false;
- return;
- }
- tween(element, {
- y: element._originalY + floatAmount
- }, {
- // Float Down
- duration: floatDuration,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!element || !element.parent || !element.visible || !element._isFloating) {
- floatCycleDown();
- return;
- }
- floatCycleUp();
- }
- });
- }
- floatCycleUp(); // Start the cycle by floating up
- };
- self._stopFloatAnimation = function (element) {
- if (element) {
- element._isFloating = false; // Signal to stop the loop
- // tween.stop will be called by the animation loop's exit condition
- }
- };
self.visible = false; // Initially hidden
self.alpha = 0; // Set alpha to 0 for fade-in animation
// Background overlay
self.overlay = self.attachAsset('emptyTileCover', {
@@ -1250,9 +1152,9 @@
font: "Impact"
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.x = 2048 / 2;
- self.titleText.y = self.panel.y - self.panel.height / 2 + 150; // Final Y position
+ 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,
@@ -1268,77 +1170,26 @@
// 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);
- // Initial states for animation
- self.titleText.alpha = 0;
- self.titleText.y = self.panel.y - self.panel.height / 2 + 150 + 50; // Start lower
- self.titleText.scale.set(0.9);
- self.startGameBtn.alpha = 0;
- self.startGameBtn.scale.set(0.8);
- // Fade in main panel first
+ // Fade in
tween(self, {
alpha: 1
}, {
duration: 300,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- // Animate title in
- tween(self.titleText, {
- alpha: 1,
- y: self.panel.y - self.panel.height / 2 + 150,
- // Final Y
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 800,
- easing: tween.elasticOut,
- // A nice bouncy feel
- onFinish: function onFinish() {
- self._startFloatAnimation(self.titleText, 10, 1500); // Float +/- 10px, 1.5s per half-cycle
- }
- });
- // Animate start button in (delayed)
- LK.setTimeout(function () {
- tween(self.startGameBtn, {
- alpha: 1,
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 600,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- self._startPulseAnimation(self.startGameBtn, 1.03, 1000); // Pulse to 103% scale, 1s per half-cycle
- }
- });
- }, 300); // Delay start button animation
- }
+ easing: tween.easeOut
});
};
// End the main screen
self.endMainScreen = function () {
- // Stop continuous animations
- self._stopFloatAnimation(self.titleText);
- self._stopPulseAnimation(self.startGameBtn);
- // Stop any other ongoing tweens on these elements
- tween.stop(self.titleText);
- tween.stop(self.startGameBtn);
- // Reset visual properties if needed, though panel fade out might cover it
- if (self.titleText) {
- self.titleText.alpha = 0; // Ensure it's hidden for next time
- // Scale and Y will be reset by startMainScreen
- }
- if (self.startGameBtn) {
- self.startGameBtn.alpha = 0; // Ensure it's hidden
- // Scale will be reset by startMainScreen
- }
self.visible = false;
- self.alpha = 0; //{aA} // Panel will fade out if a tween is applied, or set directly
+ self.alpha = 0;
if (self.parent) self.parent.removeChild(self);
};
return self;
});