/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Button = Container.expand(function () {
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
self.enabled = true;
self.hitWidth = 280;
self.hitHeight = 60;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: 22,
fill: '#ffffff',
align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
self.setEnabled = function (enabled) {
self.enabled = enabled;
self.alpha = enabled ? 1 : 0.45;
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.press = function () {
if (!self.enabled) {
return;
}
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var DustParticle = Container.expand(function () {
var self = Container.call(this);
var dust = self.attachAsset('dustParticle', {
anchorX: 0.5,
anchorY: 0.5
});
dust.alpha = 0.8;
self.velocityX = (Math.random() - 0.5) * 5;
self.velocityY = (Math.random() - 0.5) * 5;
self.gravity = 0.08;
self.life = 0.45;
self.maxLife = 0.45;
self.update = function () {
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.life -= 1 / 60;
self.scale.set(1 + (1 - self.life / self.maxLife) * 0.5);
dust.alpha = 0.8 * (self.life / self.maxLife);
if (self.life <= 0) {
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
};
return self;
});
var ScratchTicket = Container.expand(function (ticketType) {
var self = Container.call(this);
self.ticketType = ticketType || 0;
self.symbols = [];
self.scratchAmount = 0;
self.resolved = false;
self.canScratch = true;
self.ticketWidth = 600;
self.ticketHeight = 800;
self.modifier = rollTicketModifier(ticketType);
var scratchSpeedBonus = gameState.upgrades[2] * 0.03;
var prestigeScratchBonus = gameState.prestigeChips * 0.003;
self.autoResolveThreshold = clamp(0.55 - scratchSpeedBonus - prestigeScratchBonus, 0.22, 0.55);
var bg = self.attachAsset('ticketBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.tint = getTicketTint(self.ticketType);
var overlay = self.attachAsset('scratchOverlay', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.alpha = 0.96;
var titleText = new Text2(getTicketName(self.ticketType), {
size: 28,
fill: '#ffffff',
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -350;
self.addChild(titleText);
var infoText = new Text2('Scratch to reveal 3 matching symbols', {
size: 16,
fill: '#ffd700',
align: 'center'
});
infoText.anchor.set(0.5, 0);
infoText.y = -310;
self.addChild(infoText);
var modifierLine = new Text2(self.modifier.label, {
size: 18,
fill: self.modifier.color,
align: 'center'
});
modifierLine.anchor.set(0.5, 0);
modifierLine.y = -280;
self.addChild(modifierLine);
var gridStartX = -140;
var gridStartY = -140;
var spacing = 160;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
var symbol = new _Symbol();
symbol.x = gridStartX + col * spacing;
symbol.y = gridStartY + row * spacing;
self.symbols.push(symbol);
self.addChild(symbol);
}
}
applyModifierToTicket(self);
self.containsGlobalPoint = function (x, y) {
return pointInRect(x, y, self.x, self.y, self.ticketWidth, self.ticketHeight);
};
self.updateScratch = function (amount) {
if (!self.canScratch || self.resolved) {
return;
}
self.scratchAmount = clamp(self.scratchAmount + amount, 0, 1);
overlay.alpha = clamp(0.96 - self.scratchAmount, 0, 0.96);
var revealTarget = Math.floor(self.scratchAmount * self.symbols.length);
var alreadyRevealed = 0;
for (var i = 0; i < self.symbols.length; i++) {
if (self.symbols[i].revealed) {
alreadyRevealed++;
}
}
while (alreadyRevealed < revealTarget) {
var hidden = [];
for (var j = 0; j < self.symbols.length; j++) {
if (!self.symbols[j].revealed) {
hidden.push(self.symbols[j]);
}
}
if (hidden.length <= 0) {
break;
}
var pick = hidden[Math.floor(Math.random() * hidden.length)];
pick.reveal();
alreadyRevealed++;
}
if (self.scratchAmount >= self.autoResolveThreshold) {
self.resolveTicket();
}
};
self.checkMatch = function () {
var counts = {};
for (var i = 0; i < self.symbols.length; i++) {
var type = self.symbols[i].symbolType;
counts[type] = (counts[type] || 0) + 1;
}
var bestType = null;
var bestCount = 0;
for (var key in counts) {
if (counts[key] > bestCount) {
bestType = key;
bestCount = counts[key];
}
}
return {
win: bestCount >= 3,
type: bestType,
count: bestCount
};
};
self.revealAll = function () {
for (var i = 0; i < self.symbols.length; i++) {
self.symbols[i].reveal();
}
overlay.alpha = 0;
};
self.resolveTicket = function () {
if (self.resolved) {
return;
}
self.resolved = true;
self.canScratch = false;
self.revealAll();
var result = self.checkMatch();
result.modifier = self.modifier;
result.ticketType = self.ticketType;
resolveCurrentTicket(result);
};
return self;
});
/****
* Modifiers
****/
var UpgradeCard = Container.expand(function () {
var self = Container.call(this);
self.upgradeId = 0;
self.hitWidth = 280;
self.hitHeight = 42;
self.onClick = function () {};
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.scaleY = 0.7;
var nameText = new Text2('', {
size: 16,
fill: '#ffffff',
align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -15;
self.addChild(nameText);
var costText = new Text2('', {
size: 14,
fill: '#ffd700',
align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 5;
self.addChild(costText);
self.setUpgrade = function (id, level, cost, affordable) {
self.upgradeId = id;
var names = ['Better Odds', 'Bigger Payouts', 'Faster Scratch', 'Combo Boost', 'Jackpot Gain', 'Coin Magnet'];
nameText.setText(names[id] + ' Lv' + (level + 1));
costText.setText('Cost: ' + formatMoney(cost));
self.alpha = affordable ? 1 : 0.55;
};
self.setHovered = function (hovered) {
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.containsGlobalPoint = function (x, y) {
var gx = rightPanel.x + self.x;
var gy = rightPanel.y + self.y;
return pointInRect(x, y, gx, gy, self.hitWidth, self.hitHeight);
};
self.press = function () {
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var _Symbol = Container.expand(function () {
var self = Container.call(this);
self.symbolType = 0;
self.revealed = false;
var bg = self.attachAsset('symbolBg', {
anchorX: 0.5,
anchorY: 0.5
});
var symbolText = new Text2('', {
size: 56,
fill: '#ffffff',
align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
symbolText.alpha = 0.25;
self.addChild(symbolText);
self.setSymbol = function (type) {
self.symbolType = type;
var symbols = ['🍀', '💎', '🎁', '⭐', '🔥', '🎰'];
symbolText.setText(symbols[type % symbols.length]);
};
self.highlight = function (color) {
bg.tint = color || 0x7a6a2a;
};
self.reveal = function () {
if (self.revealed) {
return;
}
self.revealed = true;
bg.tint = 0x4a4a6a;
symbolText.alpha = 1;
safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
/****
* Helpers
****/
/****
* Runtime State
****/
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function safePlaySound(name) {
try {
var s = LK.getSound(name);
if (s && s.play) {
s.play();
}
} catch (e) {}
}
function safePlayMusic(name, options) {
try {
LK.playMusic(name, options || {});
} catch (e) {}
}
function formatMoney(value) {
return '$' + Math.floor(value);
}
function pointInRect(px, py, cx, cy, width, height) {
return px >= cx - width * 0.5 && px <= cx + width * 0.5 && py >= cy - height * 0.5 && py <= cy + height * 0.5;
}
function getUpgradeCost(id, level) {
var baseCosts = [50, 75, 60, 80, 100, 70];
return Math.floor(baseCosts[id] * Math.pow(1.3, level));
}
function getTicketName(type) {
if (type === 1) {
return 'LUCKY TICKET';
}
if (type === 2) {
return 'GOLD TICKET';
}
return 'BASIC TICKET';
}
function getTicketTint(type) {
if (type === 1) {
return 0x1a3a2e;
}
if (type === 2) {
return 0x3a2e1a;
}
return 0x2a2a4e;
}
function getTicketCost(type) {
return [5, 15, 50][type] || 5;
}
function canBuyTicket(type) {
if (type === 1 && !gameState.unlockLuckyTicket) {
return false;
}
if (type === 2 && !gameState.unlockGoldTicket) {
return false;
}
return true;
}
function getPrestigeReward() {
return Math.floor(Math.sqrt(gameState.totalCoins / 500));
}
function getPrestigeMultiplier() {
return 1 + gameState.prestigeChips * 0.05;
}
function getCurrentAchievementsCount() {
var count = 0;
if (gameState.achievementFirstWin) {
count++;
}
if (gameState.achievementLuckyUnlocked) {
count++;
}
if (gameState.achievementGoldUnlocked) {
count++;
}
if (gameState.achievementFirstJackpot) {
count++;
}
if (gameState.achievementStreak5) {
count++;
}
if (gameState.achievementStreak10) {
count++;
}
if (gameState.achievementFirstPrestige) {
count++;
}
if (gameState.achievementTickets50) {
count++;
}
if (gameState.achievementTickets200) {
count++;
}
if (gameState.achievementBigWin100) {
count++;
}
if (gameState.achievementModifierWin) {
count++;
}
if (gameState.achievementWealthy1000) {
count++;
}
return count;
}
function throttleScratchSound() {
if (LK.ticks - gameState.lastScratchSoundTick > 3) {
gameState.lastScratchSoundTick = LK.ticks;
safePlaySound('scratchSound');
}
}
function showFlash(color, alpha) {
if (!flashOverlay) {
return;
}
flashOverlay.tint = color || 0xffffff;
flashOverlay.alpha = alpha || 0.35;
tween(flashOverlay, {
alpha: 0
}, {
duration: 250,
easing: tween.easeOut
});
}
/****
* Storage Defaults - Flat Keys Only
****/
if (storage.coins === undefined) {
storage.coins = 100;
}
if (storage.totalCoins === undefined) {
storage.totalCoins = 100;
}
if (storage.bestStreak === undefined) {
storage.bestStreak = 0;
}
if (storage.jackpotMeter === undefined) {
storage.jackpotMeter = 0;
}
if (storage.ticketsScratchedTotal === undefined) {
storage.ticketsScratchedTotal = 0;
}
if (storage.biggestWin === undefined) {
storage.biggestWin = 0;
}
if (storage.jackpotsHit === undefined) {
storage.jackpotsHit = 0;
}
if (storage.prestigeCount === undefined) {
storage.prestigeCount = 0;
}
if (storage.prestigeChips === undefined) {
storage.prestigeChips = 0;
}
if (storage.upgrade0 === undefined) {
storage.upgrade0 = 0;
}
if (storage.upgrade1 === undefined) {
storage.upgrade1 = 0;
}
if (storage.upgrade2 === undefined) {
storage.upgrade2 = 0;
}
if (storage.upgrade3 === undefined) {
storage.upgrade3 = 0;
}
if (storage.upgrade4 === undefined) {
storage.upgrade4 = 0;
}
if (storage.upgrade5 === undefined) {
storage.upgrade5 = 0;
}
if (storage.unlockLuckyTicket === undefined) {
storage.unlockLuckyTicket = false;
}
if (storage.unlockGoldTicket === undefined) {
storage.unlockGoldTicket = false;
}
if (storage.unlockJackpotSystem === undefined) {
storage.unlockJackpotSystem = false;
}
if (storage.unlockModifiers === undefined) {
storage.unlockModifiers = false;
}
if (storage.unlockPrestige === undefined) {
storage.unlockPrestige = false;
}
if (storage.statsTotalWins === undefined) {
storage.statsTotalWins = 0;
}
if (storage.statsTotalLosses === undefined) {
storage.statsTotalLosses = 0;
}
if (storage.statsNearMisses === undefined) {
storage.statsNearMisses = 0;
}
if (storage.statsCardsBasic === undefined) {
storage.statsCardsBasic = 0;
}
if (storage.statsCardsLucky === undefined) {
storage.statsCardsLucky = 0;
}
if (storage.statsCardsGold === undefined) {
storage.statsCardsGold = 0;
}
if (storage.statsModifierWins === undefined) {
storage.statsModifierWins = 0;
}
if (storage.statsPrestigeEarnedLifetime === undefined) {
storage.statsPrestigeEarnedLifetime = 0;
}
if (storage.achievementFirstWin === undefined) {
storage.achievementFirstWin = false;
}
if (storage.achievementLuckyUnlocked === undefined) {
storage.achievementLuckyUnlocked = false;
}
if (storage.achievementGoldUnlocked === undefined) {
storage.achievementGoldUnlocked = false;
}
if (storage.achievementFirstJackpot === undefined) {
storage.achievementFirstJackpot = false;
}
if (storage.achievementStreak5 === undefined) {
storage.achievementStreak5 = false;
}
if (storage.achievementStreak10 === undefined) {
storage.achievementStreak10 = false;
}
if (storage.achievementFirstPrestige === undefined) {
storage.achievementFirstPrestige = false;
}
if (storage.achievementTickets50 === undefined) {
storage.achievementTickets50 = false;
}
if (storage.achievementTickets200 === undefined) {
storage.achievementTickets200 = false;
}
if (storage.achievementBigWin100 === undefined) {
storage.achievementBigWin100 = false;
}
if (storage.achievementModifierWin === undefined) {
storage.achievementModifierWin = false;
}
if (storage.achievementWealthy1000 === undefined) {
storage.achievementWealthy1000 = false;
}
var gameState = {
coins: storage.coins,
totalCoins: storage.totalCoins,
streak: 0,
bestStreak: storage.bestStreak,
jackpotMeter: storage.jackpotMeter,
jackpotMax: 100,
ticketsScratchedTotal: storage.ticketsScratchedTotal,
biggestWin: storage.biggestWin,
jackpotsHit: storage.jackpotsHit,
prestigeCount: storage.prestigeCount,
prestigeChips: storage.prestigeChips,
upgrades: [storage.upgrade0, storage.upgrade1, storage.upgrade2, storage.upgrade3, storage.upgrade4, storage.upgrade5],
unlockLuckyTicket: storage.unlockLuckyTicket,
unlockGoldTicket: storage.unlockGoldTicket,
unlockJackpotSystem: storage.unlockJackpotSystem,
unlockModifiers: storage.unlockModifiers,
unlockPrestige: storage.unlockPrestige,
statsTotalWins: storage.statsTotalWins,
statsTotalLosses: storage.statsTotalLosses,
statsNearMisses: storage.statsNearMisses,
statsCardsBasic: storage.statsCardsBasic,
statsCardsLucky: storage.statsCardsLucky,
statsCardsGold: storage.statsCardsGold,
statsModifierWins: storage.statsModifierWins,
statsPrestigeEarnedLifetime: storage.statsPrestigeEarnedLifetime,
achievementFirstWin: storage.achievementFirstWin,
achievementLuckyUnlocked: storage.achievementLuckyUnlocked,
achievementGoldUnlocked: storage.achievementGoldUnlocked,
achievementFirstJackpot: storage.achievementFirstJackpot,
achievementStreak5: storage.achievementStreak5,
achievementStreak10: storage.achievementStreak10,
achievementFirstPrestige: storage.achievementFirstPrestige,
achievementTickets50: storage.achievementTickets50,
achievementTickets200: storage.achievementTickets200,
achievementBigWin100: storage.achievementBigWin100,
achievementModifierWin: storage.achievementModifierWin,
achievementWealthy1000: storage.achievementWealthy1000,
lastScratchSoundTick: -999,
isGameOver: false
};
var currentTicket = null;
var flashOverlay = null;
var gameOverOverlay = null;
var gameOverRestartButton = null;
var dragData = {
active: false,
lastX: 0,
lastY: 0
};
var dustParticles = [];
var popupTexts = [];
var leftPanel, rightPanel, bottomPanel, centerInfoPanel;
var coinText, streakText, jackpotBar, jackpotText, statsText, modifierText, unlockText, achievementText, prestigeText;
var ticketBuyButtons = [];
var upgradeCards = [];
var prestigeButton;
var restartRunButton;
var wipeSaveButton;
/****
* Modifiers
****/
function rollTicketModifier(ticketType) {
if (!gameState.unlockModifiers) {
return {
id: 'none',
label: 'Standard Ticket',
color: '#cccccc',
payoutMult: 1,
jackpotBonus: 0
};
}
var pool = [{
id: 'none',
label: 'Standard Ticket',
color: '#cccccc',
payoutMult: 1,
jackpotBonus: 0
}, {
id: 'double',
label: 'Double Payout',
color: '#00ff88',
payoutMult: 2,
jackpotBonus: 0
}, {
id: 'jackpot',
label: 'Double Jackpot Gain',
color: '#ffd700',
payoutMult: 1,
jackpotBonus: 2
}, {
id: 'wild',
label: 'Lucky Wild Card',
color: '#66ccff',
payoutMult: 1.35,
jackpotBonus: 0
}, {
id: 'hot',
label: 'Hot Streak Boost',
color: '#ff9966',
payoutMult: 1.5,
jackpotBonus: 0
}];
var oddsBoost = gameState.upgrades[0] * 0.02 + gameState.prestigeChips * 0.005;
var roll = Math.random();
if (roll < 0.35 - oddsBoost) {
return pool[0];
}
if (roll < 0.55) {
return pool[1];
}
if (roll < 0.72) {
return pool[2];
}
if (roll < 0.87) {
return pool[3];
}
return pool[4];
}
function applyModifierToTicket(ticket) {
if (!ticket || !ticket.modifier) {
return;
}
if (ticket.modifier.id === 'wild') {
var wildIndex = Math.floor(Math.random() * ticket.symbols.length);
var baseType = ticket.symbols[Math.floor(Math.random() * ticket.symbols.length)].symbolType;
ticket.symbols[wildIndex].setSymbol(baseType);
ticket.symbols[wildIndex].highlight(0x2a6a8a);
}
}
/****
* Achievements / Milestones
****/
function unlockAchievement(key, label) {
if (gameState[key]) {
return;
}
gameState[key] = true;
createPopupText(1024, 760, 'ACHIEVEMENT: ' + label, '#ffd700', 28);
showFlash(0xffd700, 0.2);
}
function checkMilestones() {
if (!gameState.unlockLuckyTicket && gameState.ticketsScratchedTotal >= 10) {
gameState.unlockLuckyTicket = true;
unlockAchievement('achievementLuckyUnlocked', 'Lucky Tickets Unlocked!');
createPopupText(1024, 500, 'LUCKY TICKETS UNLOCKED', '#00ff88', 38);
}
if (!gameState.unlockGoldTicket && gameState.totalCoins >= 1000) {
gameState.unlockGoldTicket = true;
unlockAchievement('achievementGoldUnlocked', 'Gold Tickets Unlocked!');
createPopupText(1024, 550, 'GOLD TICKETS UNLOCKED', '#ffd700', 38);
}
if (!gameState.unlockJackpotSystem && gameState.ticketsScratchedTotal >= 15) {
gameState.unlockJackpotSystem = true;
createPopupText(1024, 600, 'JACKPOT SYSTEM UNLOCKED', '#66ccff', 30);
}
if (!gameState.unlockModifiers && gameState.ticketsScratchedTotal >= 25) {
gameState.unlockModifiers = true;
createPopupText(1024, 650, 'TICKET MODIFIERS UNLOCKED', '#ff9966', 30);
}
if (!gameState.unlockPrestige && gameState.totalCoins >= 5000) {
gameState.unlockPrestige = true;
createPopupText(1024, 700, 'PRESTIGE UNLOCKED', '#ffffff', 30);
}
}
function evaluateAchievements() {
if (gameState.statsTotalWins >= 1) {
unlockAchievement('achievementFirstWin', 'First Win');
}
if (gameState.jackpotsHit >= 1) {
unlockAchievement('achievementFirstJackpot', 'First Jackpot');
}
if (gameState.bestStreak >= 5) {
unlockAchievement('achievementStreak5', '5 Win Streak');
}
if (gameState.bestStreak >= 10) {
unlockAchievement('achievementStreak10', '10 Win Streak');
}
if (gameState.ticketsScratchedTotal >= 50) {
unlockAchievement('achievementTickets50', '50 Tickets Scratched');
}
if (gameState.ticketsScratchedTotal >= 200) {
unlockAchievement('achievementTickets200', '200 Tickets Scratched');
}
if (gameState.biggestWin >= 100) {
unlockAchievement('achievementBigWin100', 'Big Win 100+');
}
if (gameState.statsModifierWins >= 1) {
unlockAchievement('achievementModifierWin', 'Modifier Master');
}
if (gameState.totalCoins >= 1000) {
unlockAchievement('achievementWealthy1000', 'Earned 1000 Coins');
}
if (gameState.prestigeCount >= 1) {
unlockAchievement('achievementFirstPrestige', 'First Prestige');
}
}
/****
* Reset / Save
****/
function restartRunOnly() {
gameState.coins = 100 + gameState.prestigeChips * 10;
gameState.streak = 0;
gameState.jackpotMeter = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 950, 'RUN RESTARTED', '#ffffff', 34);
updateUI();
saveProgress();
}
function hardRestartGame() {
gameState.coins = 100;
gameState.totalCoins = 100;
gameState.streak = 0;
gameState.bestStreak = 0;
gameState.jackpotMeter = 0;
gameState.ticketsScratchedTotal = 0;
gameState.biggestWin = 0;
gameState.jackpotsHit = 0;
gameState.prestigeCount = 0;
gameState.prestigeChips = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.unlockLuckyTicket = false;
gameState.unlockGoldTicket = false;
gameState.unlockJackpotSystem = false;
gameState.unlockModifiers = false;
gameState.unlockPrestige = false;
gameState.statsTotalWins = 0;
gameState.statsTotalLosses = 0;
gameState.statsNearMisses = 0;
gameState.statsCardsBasic = 0;
gameState.statsCardsLucky = 0;
gameState.statsCardsGold = 0;
gameState.statsModifierWins = 0;
gameState.statsPrestigeEarnedLifetime = 0;
gameState.achievementFirstWin = false;
gameState.achievementLuckyUnlocked = false;
gameState.achievementGoldUnlocked = false;
gameState.achievementFirstJackpot = false;
gameState.achievementStreak5 = false;
gameState.achievementStreak10 = false;
gameState.achievementFirstPrestige = false;
gameState.achievementTickets50 = false;
gameState.achievementTickets200 = false;
gameState.achievementBigWin100 = false;
gameState.achievementModifierWin = false;
gameState.achievementWealthy1000 = false;
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 950, 'SAVE WIPED', '#ff6b6b', 34);
updateUI();
saveProgress();
}
function resetRunForPrestige() {
var reward = getPrestigeReward();
if (!gameState.unlockPrestige || reward <= 0) {
return;
}
gameState.prestigeCount++;
gameState.prestigeChips += reward;
gameState.statsPrestigeEarnedLifetime += reward;
gameState.coins = 100 + reward * 10;
gameState.streak = 0;
gameState.jackpotMeter = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 900, 'PRESTIGE +' + reward + ' CHIPS', '#ffffff', 40);
showFlash(0xffffff, 0.35);
evaluateAchievements();
updateUI();
saveProgress();
}
function saveProgress() {
storage.coins = gameState.coins;
storage.totalCoins = gameState.totalCoins;
storage.bestStreak = gameState.bestStreak;
storage.jackpotMeter = gameState.jackpotMeter;
storage.ticketsScratchedTotal = gameState.ticketsScratchedTotal;
storage.biggestWin = gameState.biggestWin;
storage.jackpotsHit = gameState.jackpotsHit;
storage.prestigeCount = gameState.prestigeCount;
storage.prestigeChips = gameState.prestigeChips;
storage.upgrade0 = gameState.upgrades[0];
storage.upgrade1 = gameState.upgrades[1];
storage.upgrade2 = gameState.upgrades[2];
storage.upgrade3 = gameState.upgrades[3];
storage.upgrade4 = gameState.upgrades[4];
storage.upgrade5 = gameState.upgrades[5];
storage.unlockLuckyTicket = gameState.unlockLuckyTicket;
storage.unlockGoldTicket = gameState.unlockGoldTicket;
storage.unlockJackpotSystem = gameState.unlockJackpotSystem;
storage.unlockModifiers = gameState.unlockModifiers;
storage.unlockPrestige = gameState.unlockPrestige;
storage.statsTotalWins = gameState.statsTotalWins;
storage.statsTotalLosses = gameState.statsTotalLosses;
storage.statsNearMisses = gameState.statsNearMisses;
storage.statsCardsBasic = gameState.statsCardsBasic;
storage.statsCardsLucky = gameState.statsCardsLucky;
storage.statsCardsGold = gameState.statsCardsGold;
storage.statsModifierWins = gameState.statsModifierWins;
storage.statsPrestigeEarnedLifetime = gameState.statsPrestigeEarnedLifetime;
storage.achievementFirstWin = gameState.achievementFirstWin;
storage.achievementLuckyUnlocked = gameState.achievementLuckyUnlocked;
storage.achievementGoldUnlocked = gameState.achievementGoldUnlocked;
storage.achievementFirstJackpot = gameState.achievementFirstJackpot;
storage.achievementStreak5 = gameState.achievementStreak5;
storage.achievementStreak10 = gameState.achievementStreak10;
storage.achievementFirstPrestige = gameState.achievementFirstPrestige;
storage.achievementTickets50 = gameState.achievementTickets50;
storage.achievementTickets200 = gameState.achievementTickets200;
storage.achievementBigWin100 = gameState.achievementBigWin100;
storage.achievementModifierWin = gameState.achievementModifierWin;
storage.achievementWealthy1000 = gameState.achievementWealthy1000;
}
/****
* UI
****/
function initUI() {
leftPanel = new Container();
leftPanel.x = 100;
leftPanel.y = 100;
game.addChild(leftPanel);
var leftBg = LK.getAsset('panelBg', {
anchorX: 0,
anchorY: 0
});
leftBg.scaleY = 1.05;
leftPanel.addChild(leftBg);
var coinsLabelText = new Text2('COINS', {
size: 20,
fill: '#ffd700'
});
coinsLabelText.anchor.set(0.5, 0);
coinsLabelText.x = 150;
coinsLabelText.y = 20;
leftPanel.addChild(coinsLabelText);
coinText = new Text2('$100', {
size: 32,
fill: '#ffffff'
});
coinText.anchor.set(0.5, 0);
coinText.x = 150;
coinText.y = 60;
leftPanel.addChild(coinText);
var streakLabelText = new Text2('STREAK', {
size: 20,
fill: '#ff6b6b'
});
streakLabelText.anchor.set(0.5, 0);
streakLabelText.x = 150;
streakLabelText.y = 130;
leftPanel.addChild(streakLabelText);
streakText = new Text2('x0', {
size: 24,
fill: '#ffffff'
});
streakText.anchor.set(0.5, 0);
streakText.x = 150;
streakText.y = 160;
leftPanel.addChild(streakText);
var jackpotLabelText = new Text2('JACKPOT', {
size: 20,
fill: '#ffd700'
});
jackpotLabelText.anchor.set(0.5, 0);
jackpotLabelText.x = 150;
jackpotLabelText.y = 220;
leftPanel.addChild(jackpotLabelText);
var jackpotBgBar = LK.getAsset('meterBarEmpty', {
anchorX: 0.5,
anchorY: 0
});
jackpotBgBar.x = 150;
jackpotBgBar.y = 250;
leftPanel.addChild(jackpotBgBar);
jackpotBar = LK.getAsset('meterBarFull', {
anchorX: 0.5,
anchorY: 0
});
jackpotBar.x = 150;
jackpotBar.y = 250;
jackpotBar.scaleX = 0;
leftPanel.addChild(jackpotBar);
jackpotText = new Text2('0/100', {
size: 20,
fill: '#ffffff'
});
jackpotText.anchor.set(0.5, 0);
jackpotText.x = 150;
jackpotText.y = 280;
leftPanel.addChild(jackpotText);
prestigeText = new Text2('', {
size: 14,
fill: '#66ccff',
align: 'center'
});
prestigeText.anchor.set(0.5, 0);
prestigeText.x = 150;
prestigeText.y = 320;
leftPanel.addChild(prestigeText);
statsText = new Text2('', {
size: 20,
fill: '#bbbbbb',
align: 'center'
});
statsText.anchor.set(0.5, 0);
statsText.x = 150;
statsText.y = 370;
leftPanel.addChild(statsText);
unlockText = new Text2('', {
size: 20,
fill: '#00ff88',
align: 'center'
});
unlockText.anchor.set(0.5, 0);
unlockText.x = 150;
unlockText.y = 500;
leftPanel.addChild(unlockText);
achievementText = new Text2('', {
size: 20,
fill: '#ffd700',
align: 'center'
});
achievementText.anchor.set(0.5, 0);
achievementText.x = 150;
achievementText.y = 570;
leftPanel.addChild(achievementText);
rightPanel = new Container();
rightPanel.x = 1848;
rightPanel.y = 100;
game.addChild(rightPanel);
var rightBg = LK.getAsset('panelBg', {
anchorX: 1,
anchorY: 0
});
rightBg.scaleY = 1.25;
rightPanel.addChild(rightBg);
var shopLabelText = new Text2('UPGRADES', {
size: 20,
fill: '#00ff88'
});
shopLabelText.anchor.set(0.5, 0);
shopLabelText.x = -150;
shopLabelText.y = 20;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
card.y = 90 + i * 82;
card.x = -150;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
prestigeButton = new Button();
prestigeButton.x = -150;
prestigeButton.y = 610;
prestigeButton.setText('Prestige');
prestigeButton.onClick = function () {
resetRunForPrestige();
};
rightPanel.addChild(prestigeButton);
restartRunButton = new Button();
restartRunButton.x = -150;
restartRunButton.y = 690;
restartRunButton.setText('Restart Run');
restartRunButton.onClick = function () {
restartRunOnly();
};
rightPanel.addChild(restartRunButton);
wipeSaveButton = new Button();
wipeSaveButton.x = -150;
wipeSaveButton.y = 770;
wipeSaveButton.setText('Wipe Save');
wipeSaveButton.onClick = function () {
hardRestartGame();
};
rightPanel.addChild(wipeSaveButton);
bottomPanel = new Container();
bottomPanel.x = 1024;
bottomPanel.y = 2520;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'Basic\n$5',
cost: 5,
type: 0
}, {
name: 'Lucky\n$15',
cost: 15,
type: 1
}, {
name: 'Gold\n$50',
cost: 50,
type: 2
}];
for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
btn.x = -300 + t * 300;
btn.y = 0;
btn.setText(ticketTypes[t].name);
btn.cost = ticketTypes[t].cost;
btn.type = ticketTypes[t].type;
btn.onClick = function () {
onBuyTicket(this.type, this.cost);
}.bind(btn);
ticketBuyButtons.push(btn);
bottomPanel.addChild(btn);
}
centerInfoPanel = new Container();
centerInfoPanel.x = 1024;
centerInfoPanel.y = 180;
game.addChild(centerInfoPanel);
modifierText = new Text2('', {
size: 18,
fill: '#ffffff',
align: 'center'
});
modifierText.anchor.set(0.5, 0);
centerInfoPanel.addChild(modifierText);
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: 40,
fill: '#ffd700'
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 30;
game.addChild(titleText);
flashOverlay = LK.getAsset('flashBg', {
anchorX: 0.5,
anchorY: 0.5
});
flashOverlay.x = 1024;
flashOverlay.y = 1366;
flashOverlay.alpha = 0;
game.addChild(flashOverlay);
initGameOverUI();
}
function initGameOverUI() {
gameOverOverlay = new Container();
gameOverOverlay.visible = false;
game.addChild(gameOverOverlay);
var dark = LK.getAsset('overlayDark', {
anchorX: 0.5,
anchorY: 0.5
});
dark.x = 1024;
dark.y = 1366;
dark.alpha = 0.7;
gameOverOverlay.addChild(dark);
var title = new Text2('YOU RAN OUT OF MONEY', {
size: 42,
fill: '#ff6b6b',
align: 'center'
});
title.anchor.set(0.5, 0.5);
title.x = 1024;
title.y = 1050;
gameOverOverlay.addChild(title);
var sub = new Text2('Your run is over. Restart the run or wipe your save.', {
size: 22,
fill: '#ffffff',
align: 'center'
});
sub.anchor.set(0.5, 0.5);
sub.x = 1024;
sub.y = 1120;
gameOverOverlay.addChild(sub);
gameOverRestartButton = new Button();
gameOverRestartButton.x = 1024;
gameOverRestartButton.y = 1240;
gameOverRestartButton.setText('Restart Run');
gameOverRestartButton.onClick = function () {
restartRunOnly();
};
gameOverOverlay.addChild(gameOverRestartButton);
}
function showGameOver() {
gameState.isGameOver = true;
dragData.active = false;
if (gameOverOverlay) {
gameOverOverlay.visible = true;
}
showFlash(0xff0000, 0.15);
}
function hideGameOver() {
if (gameOverOverlay) {
gameOverOverlay.visible = false;
}
}
function updateUI() {
if (coinText) {
coinText.setText(formatMoney(gameState.coins));
}
if (streakText) {
streakText.setText('x' + gameState.streak);
}
if (jackpotBar) {
jackpotBar.scaleX = clamp(gameState.jackpotMeter / gameState.jackpotMax, 0, 1);
}
if (jackpotText) {
jackpotText.setText(gameState.jackpotMeter + '/' + gameState.jackpotMax);
}
if (prestigeText) {
prestigeText.setText('Prestige Chips: ' + gameState.prestigeChips + '\nNext Prestige: +' + getPrestigeReward() + '\nMultiplier: x' + getPrestigeMultiplier().toFixed(2));
}
if (statsText) {
statsText.setText('Best Streak: ' + gameState.bestStreak + '\nBiggest Win: ' + formatMoney(gameState.biggestWin) + '\nTickets: ' + gameState.ticketsScratchedTotal + '\nWins/Losses: ' + gameState.statsTotalWins + '/' + gameState.statsTotalLosses);
}
if (unlockText) {
unlockText.setText('Unlocks' + '\nLucky: ' + (gameState.unlockLuckyTicket ? 'Yes' : 'No') + '\nGold: ' + (gameState.unlockGoldTicket ? 'Yes' : 'No') + '\nModifiers: ' + (gameState.unlockModifiers ? 'Yes' : 'No') + '\nPrestige: ' + (gameState.unlockPrestige ? 'Yes' : 'No'));
}
if (achievementText) {
achievementText.setText('Achievements: ' + getCurrentAchievementsCount() + '\nNear Misses: ' + gameState.statsNearMisses + '\nModifier Wins: ' + gameState.statsModifierWins);
}
if (modifierText) {
modifierText.setText(currentTicket ? 'Current Modifier: ' + currentTicket.modifier.label : 'Buy a ticket to start');
}
for (var i = 0; i < upgradeCards.length; i++) {
var level = gameState.upgrades[i];
var cost = getUpgradeCost(i, level);
upgradeCards[i].setUpgrade(i, level, cost, gameState.coins >= cost && !gameState.isGameOver);
}
for (var j = 0; j < ticketBuyButtons.length; j++) {
var canAfford = gameState.coins >= ticketBuyButtons[j].cost;
var unlocked = canBuyTicket(ticketBuyButtons[j].type);
ticketBuyButtons[j].setEnabled(canAfford && unlocked && !gameState.isGameOver);
if (!unlocked) {
if (ticketBuyButtons[j].type === 1) {
ticketBuyButtons[j].setText('Lucky\nLocked');
}
if (ticketBuyButtons[j].type === 2) {
ticketBuyButtons[j].setText('Gold\nLocked');
}
} else {
if (ticketBuyButtons[j].type === 0) {
ticketBuyButtons[j].setText('Basic\n$5');
}
if (ticketBuyButtons[j].type === 1) {
ticketBuyButtons[j].setText('Lucky\n$15');
}
if (ticketBuyButtons[j].type === 2) {
ticketBuyButtons[j].setText('Gold\n$50');
}
}
}
if (prestigeButton) {
prestigeButton.setText('Prestige\n+' + getPrestigeReward());
prestigeButton.setEnabled(gameState.unlockPrestige && getPrestigeReward() > 0 && !gameState.isGameOver);
}
if (restartRunButton) {
restartRunButton.setEnabled(true);
}
if (wipeSaveButton) {
wipeSaveButton.setEnabled(true);
}
}
/****
* Gameplay
****/
function createNewTicket(ticketType) {
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
}
currentTicket = new ScratchTicket(ticketType);
currentTicket.x = 1024;
currentTicket.y = 1200;
game.addChild(currentTicket);
dragData.active = false;
updateUI();
}
function onBuyTicket(type, cost) {
if (gameState.isGameOver) {
return;
}
if (currentTicket && !currentTicket.resolved) {
return;
}
if (!canBuyTicket(type)) {
return;
}
if (gameState.coins < cost) {
return;
}
gameState.coins -= cost;
gameState.ticketsScratchedTotal++;
if (type === 0) {
gameState.statsCardsBasic++;
}
if (type === 1) {
gameState.statsCardsLucky++;
}
if (type === 2) {
gameState.statsCardsGold++;
}
createNewTicket(type);
checkMilestones();
evaluateAchievements();
updateUI();
saveProgress();
}
function onUpgradeClick(upgradeId) {
if (gameState.isGameOver) {
return;
}
var level = gameState.upgrades[upgradeId];
var cost = getUpgradeCost(upgradeId, level);
if (gameState.coins < cost) {
return;
}
gameState.coins -= cost;
gameState.upgrades[upgradeId]++;
safePlaySound('upgradeSound');
createPopupText(1700, 700, 'UPGRADE BOUGHT', '#00ff88', 24);
updateUI();
saveProgress();
checkForGameOver();
}
function createPopupText(x, y, textValue, color, size) {
var text = new Text2(textValue, {
size: size || 32,
fill: color || '#ffd700',
align: 'center'
});
text.anchor.set(0.5, 0.5);
text.x = x;
text.y = y;
game.addChild(text);
popupTexts.push(text);
tween(text, {
y: y - 90,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (text.parent) {
text.parent.removeChild(text);
}
var index = popupTexts.indexOf(text);
if (index >= 0) {
popupTexts.splice(index, 1);
}
}
});
}
function createDustParticles(x, y) {
for (var i = 0; i < 8; i++) {
var particle = new DustParticle();
particle.x = x + (Math.random() - 0.5) * 30;
particle.y = y + (Math.random() - 0.5) * 30;
game.addChild(particle);
dustParticles.push(particle);
}
}
function createCoinBurst(x, y, count) {
for (var i = 0; i < count; i++) {
createPopupText(x + (Math.random() - 0.5) * 120, y + (Math.random() - 0.5) * 50, '+', '#ffd700', 20);
}
}
function resolveCurrentTicket(result) {
if (!currentTicket) {
return;
}
var wasNearMiss = !result.win && result.count === 2;
if (wasNearMiss) {
gameState.statsNearMisses++;
createPopupText(currentTicket.x, currentTicket.y - 120, 'NEAR MISS!', '#ff9966', 24);
}
if (result.win) {
safePlaySound('winSound');
showFlash(0xffd700, 0.15);
var basePayouts = [10, 25, 50];
var matchBonus = 1 + (result.count - 3) * 0.35;
var payoutUpgrade = 1 + gameState.upgrades[1] * 0.2;
var comboBoost = 1 + gameState.streak * (0.1 + gameState.upgrades[3] * 0.02);
var prestigeBoost = getPrestigeMultiplier();
var modifierBoost = result.modifier ? result.modifier.payoutMult : 1;
if (result.modifier && result.modifier.id === 'hot') {
modifierBoost += gameState.streak * 0.05;
}
var finalPayout = Math.floor(basePayouts[result.ticketType] * matchBonus * payoutUpgrade * comboBoost * prestigeBoost * modifierBoost);
gameState.coins += finalPayout;
gameState.totalCoins += finalPayout;
gameState.streak++;
gameState.statsTotalWins++;
if (result.modifier && result.modifier.id !== 'none') {
gameState.statsModifierWins++;
}
if (gameState.streak > gameState.bestStreak) {
gameState.bestStreak = gameState.streak;
}
if (finalPayout > gameState.biggestWin) {
gameState.biggestWin = finalPayout;
}
if (gameState.unlockJackpotSystem) {
var jackpotGain = 1 + gameState.upgrades[4] + Math.floor(gameState.prestigeChips * 0.1);
if (result.modifier) {
jackpotGain += result.modifier.jackpotBonus || 0;
}
gameState.jackpotMeter += jackpotGain;
if (gameState.jackpotMeter >= gameState.jackpotMax) {
gameState.jackpotMeter = 0;
gameState.jackpotsHit++;
var jackpotReward = Math.floor((100 + gameState.jackpotsHit * 25) * getPrestigeMultiplier());
gameState.coins += jackpotReward;
gameState.totalCoins += jackpotReward;
createPopupText(currentTicket.x, currentTicket.y - 160, 'JACKPOT BONUS ' + formatMoney(jackpotReward), '#00ff88', 34);
createCoinBurst(currentTicket.x, currentTicket.y - 40, 12);
showFlash(0x00ff88, 0.28);
}
}
createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), '#ffd700', 34);
createCoinBurst(currentTicket.x, currentTicket.y + 40, 8);
} else {
gameState.streak = 0;
gameState.statsTotalLosses++;
createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', '#ff6b6b', 30);
}
checkMilestones();
evaluateAchievements();
updateUI();
saveProgress();
checkForGameOver();
}
function checkForGameOver() {
if (gameState.isGameOver) {
return;
}
var hasActiveTicket = currentTicket && !currentTicket.resolved;
if (gameState.coins <= 0 && !hasActiveTicket) {
showGameOver();
}
}
/****
* Boot
****/
initUI();
updateUI();
safePlayMusic('bgMusic', {
loop: true
});
/****
* Input
****/
game.down = function (x, y, obj) {
if (gameState.isGameOver) {
if (gameOverRestartButton) {
if (pointInRect(x, y, gameOverRestartButton.x, gameOverRestartButton.y, gameOverRestartButton.hitWidth, gameOverRestartButton.hitHeight)) {
gameOverRestartButton.press();
return;
}
}
return;
}
for (var i = 0; i < ticketBuyButtons.length; i++) {
var btnX = bottomPanel.x + ticketBuyButtons[i].x;
var btnY = bottomPanel.y + ticketBuyButtons[i].y;
if (pointInRect(x, y, btnX, btnY, ticketBuyButtons[i].hitWidth, ticketBuyButtons[i].hitHeight)) {
ticketBuyButtons[i].press();
return;
}
}
for (var j = 0; j < upgradeCards.length; j++) {
if (upgradeCards[j].containsGlobalPoint(x, y)) {
upgradeCards[j].press();
return;
}
}
if (prestigeButton) {
var pX = rightPanel.x + prestigeButton.x;
var pY = rightPanel.y + prestigeButton.y;
if (pointInRect(x, y, pX, pY, prestigeButton.hitWidth, prestigeButton.hitHeight)) {
prestigeButton.press();
return;
}
}
if (restartRunButton) {
var rrX = rightPanel.x + restartRunButton.x;
var rrY = rightPanel.y + restartRunButton.y;
if (pointInRect(x, y, rrX, rrY, restartRunButton.hitWidth, restartRunButton.hitHeight)) {
restartRunButton.press();
return;
}
}
if (wipeSaveButton) {
var wsX = rightPanel.x + wipeSaveButton.x;
var wsY = rightPanel.y + wipeSaveButton.y;
if (pointInRect(x, y, wsX, wsY, wipeSaveButton.hitWidth, wipeSaveButton.hitHeight)) {
wipeSaveButton.press();
return;
}
}
if (currentTicket && !currentTicket.resolved && currentTicket.containsGlobalPoint(x, y)) {
dragData.active = true;
dragData.lastX = x;
dragData.lastY = y;
}
};
game.move = function (x, y, obj) {
if (gameState.isGameOver) {
return;
}
for (var i = 0; i < ticketBuyButtons.length; i++) {
var btnGX = bottomPanel.x + ticketBuyButtons[i].x;
var btnGY = bottomPanel.y + ticketBuyButtons[i].y;
ticketBuyButtons[i].setHovered(pointInRect(x, y, btnGX, btnGY, ticketBuyButtons[i].hitWidth, ticketBuyButtons[i].hitHeight));
}
for (var j = 0; j < upgradeCards.length; j++) {
upgradeCards[j].setHovered(upgradeCards[j].containsGlobalPoint(x, y));
}
if (prestigeButton) {
var pX = rightPanel.x + prestigeButton.x;
var pY = rightPanel.y + prestigeButton.y;
prestigeButton.setHovered(pointInRect(x, y, pX, pY, prestigeButton.hitWidth, prestigeButton.hitHeight));
}
if (restartRunButton) {
var rrX = rightPanel.x + restartRunButton.x;
var rrY = rightPanel.y + restartRunButton.y;
restartRunButton.setHovered(pointInRect(x, y, rrX, rrY, restartRunButton.hitWidth, restartRunButton.hitHeight));
}
if (wipeSaveButton) {
var wsX = rightPanel.x + wipeSaveButton.x;
var wsY = rightPanel.y + wipeSaveButton.y;
wipeSaveButton.setHovered(pointInRect(x, y, wsX, wsY, wipeSaveButton.hitWidth, wipeSaveButton.hitHeight));
}
if (!dragData.active || !currentTicket || currentTicket.resolved) {
return;
}
if (!currentTicket.containsGlobalPoint(x, y)) {
return;
}
var dx = x - dragData.lastX;
var dy = y - dragData.lastY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 6) {
var scratchAmount = distance / 430;
currentTicket.updateScratch(scratchAmount);
createDustParticles(x, y);
throttleScratchSound();
dragData.lastX = x;
dragData.lastY = y;
updateUI();
}
};
game.up = function () {
dragData.active = false;
};
/****
* Update
****/
game.update = function () {
for (var i = dustParticles.length - 1; i >= 0; i--) {
if (!dustParticles[i] || !dustParticles[i].parent) {
dustParticles.splice(i, 1);
continue;
}
dustParticles[i].update();
}
if (LK.ticks % 300 === 0) {
saveProgress();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Button = Container.expand(function () {
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
self.enabled = true;
self.hitWidth = 280;
self.hitHeight = 60;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: 22,
fill: '#ffffff',
align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
self.setEnabled = function (enabled) {
self.enabled = enabled;
self.alpha = enabled ? 1 : 0.45;
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.press = function () {
if (!self.enabled) {
return;
}
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var DustParticle = Container.expand(function () {
var self = Container.call(this);
var dust = self.attachAsset('dustParticle', {
anchorX: 0.5,
anchorY: 0.5
});
dust.alpha = 0.8;
self.velocityX = (Math.random() - 0.5) * 5;
self.velocityY = (Math.random() - 0.5) * 5;
self.gravity = 0.08;
self.life = 0.45;
self.maxLife = 0.45;
self.update = function () {
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.life -= 1 / 60;
self.scale.set(1 + (1 - self.life / self.maxLife) * 0.5);
dust.alpha = 0.8 * (self.life / self.maxLife);
if (self.life <= 0) {
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
};
return self;
});
var ScratchTicket = Container.expand(function (ticketType) {
var self = Container.call(this);
self.ticketType = ticketType || 0;
self.symbols = [];
self.scratchAmount = 0;
self.resolved = false;
self.canScratch = true;
self.ticketWidth = 600;
self.ticketHeight = 800;
self.modifier = rollTicketModifier(ticketType);
var scratchSpeedBonus = gameState.upgrades[2] * 0.03;
var prestigeScratchBonus = gameState.prestigeChips * 0.003;
self.autoResolveThreshold = clamp(0.55 - scratchSpeedBonus - prestigeScratchBonus, 0.22, 0.55);
var bg = self.attachAsset('ticketBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.tint = getTicketTint(self.ticketType);
var overlay = self.attachAsset('scratchOverlay', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.alpha = 0.96;
var titleText = new Text2(getTicketName(self.ticketType), {
size: 28,
fill: '#ffffff',
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -350;
self.addChild(titleText);
var infoText = new Text2('Scratch to reveal 3 matching symbols', {
size: 16,
fill: '#ffd700',
align: 'center'
});
infoText.anchor.set(0.5, 0);
infoText.y = -310;
self.addChild(infoText);
var modifierLine = new Text2(self.modifier.label, {
size: 18,
fill: self.modifier.color,
align: 'center'
});
modifierLine.anchor.set(0.5, 0);
modifierLine.y = -280;
self.addChild(modifierLine);
var gridStartX = -140;
var gridStartY = -140;
var spacing = 160;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
var symbol = new _Symbol();
symbol.x = gridStartX + col * spacing;
symbol.y = gridStartY + row * spacing;
self.symbols.push(symbol);
self.addChild(symbol);
}
}
applyModifierToTicket(self);
self.containsGlobalPoint = function (x, y) {
return pointInRect(x, y, self.x, self.y, self.ticketWidth, self.ticketHeight);
};
self.updateScratch = function (amount) {
if (!self.canScratch || self.resolved) {
return;
}
self.scratchAmount = clamp(self.scratchAmount + amount, 0, 1);
overlay.alpha = clamp(0.96 - self.scratchAmount, 0, 0.96);
var revealTarget = Math.floor(self.scratchAmount * self.symbols.length);
var alreadyRevealed = 0;
for (var i = 0; i < self.symbols.length; i++) {
if (self.symbols[i].revealed) {
alreadyRevealed++;
}
}
while (alreadyRevealed < revealTarget) {
var hidden = [];
for (var j = 0; j < self.symbols.length; j++) {
if (!self.symbols[j].revealed) {
hidden.push(self.symbols[j]);
}
}
if (hidden.length <= 0) {
break;
}
var pick = hidden[Math.floor(Math.random() * hidden.length)];
pick.reveal();
alreadyRevealed++;
}
if (self.scratchAmount >= self.autoResolveThreshold) {
self.resolveTicket();
}
};
self.checkMatch = function () {
var counts = {};
for (var i = 0; i < self.symbols.length; i++) {
var type = self.symbols[i].symbolType;
counts[type] = (counts[type] || 0) + 1;
}
var bestType = null;
var bestCount = 0;
for (var key in counts) {
if (counts[key] > bestCount) {
bestType = key;
bestCount = counts[key];
}
}
return {
win: bestCount >= 3,
type: bestType,
count: bestCount
};
};
self.revealAll = function () {
for (var i = 0; i < self.symbols.length; i++) {
self.symbols[i].reveal();
}
overlay.alpha = 0;
};
self.resolveTicket = function () {
if (self.resolved) {
return;
}
self.resolved = true;
self.canScratch = false;
self.revealAll();
var result = self.checkMatch();
result.modifier = self.modifier;
result.ticketType = self.ticketType;
resolveCurrentTicket(result);
};
return self;
});
/****
* Modifiers
****/
var UpgradeCard = Container.expand(function () {
var self = Container.call(this);
self.upgradeId = 0;
self.hitWidth = 280;
self.hitHeight = 42;
self.onClick = function () {};
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.scaleY = 0.7;
var nameText = new Text2('', {
size: 16,
fill: '#ffffff',
align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -15;
self.addChild(nameText);
var costText = new Text2('', {
size: 14,
fill: '#ffd700',
align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 5;
self.addChild(costText);
self.setUpgrade = function (id, level, cost, affordable) {
self.upgradeId = id;
var names = ['Better Odds', 'Bigger Payouts', 'Faster Scratch', 'Combo Boost', 'Jackpot Gain', 'Coin Magnet'];
nameText.setText(names[id] + ' Lv' + (level + 1));
costText.setText('Cost: ' + formatMoney(cost));
self.alpha = affordable ? 1 : 0.55;
};
self.setHovered = function (hovered) {
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.containsGlobalPoint = function (x, y) {
var gx = rightPanel.x + self.x;
var gy = rightPanel.y + self.y;
return pointInRect(x, y, gx, gy, self.hitWidth, self.hitHeight);
};
self.press = function () {
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var _Symbol = Container.expand(function () {
var self = Container.call(this);
self.symbolType = 0;
self.revealed = false;
var bg = self.attachAsset('symbolBg', {
anchorX: 0.5,
anchorY: 0.5
});
var symbolText = new Text2('', {
size: 56,
fill: '#ffffff',
align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
symbolText.alpha = 0.25;
self.addChild(symbolText);
self.setSymbol = function (type) {
self.symbolType = type;
var symbols = ['🍀', '💎', '🎁', '⭐', '🔥', '🎰'];
symbolText.setText(symbols[type % symbols.length]);
};
self.highlight = function (color) {
bg.tint = color || 0x7a6a2a;
};
self.reveal = function () {
if (self.revealed) {
return;
}
self.revealed = true;
bg.tint = 0x4a4a6a;
symbolText.alpha = 1;
safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
/****
* Helpers
****/
/****
* Runtime State
****/
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function safePlaySound(name) {
try {
var s = LK.getSound(name);
if (s && s.play) {
s.play();
}
} catch (e) {}
}
function safePlayMusic(name, options) {
try {
LK.playMusic(name, options || {});
} catch (e) {}
}
function formatMoney(value) {
return '$' + Math.floor(value);
}
function pointInRect(px, py, cx, cy, width, height) {
return px >= cx - width * 0.5 && px <= cx + width * 0.5 && py >= cy - height * 0.5 && py <= cy + height * 0.5;
}
function getUpgradeCost(id, level) {
var baseCosts = [50, 75, 60, 80, 100, 70];
return Math.floor(baseCosts[id] * Math.pow(1.3, level));
}
function getTicketName(type) {
if (type === 1) {
return 'LUCKY TICKET';
}
if (type === 2) {
return 'GOLD TICKET';
}
return 'BASIC TICKET';
}
function getTicketTint(type) {
if (type === 1) {
return 0x1a3a2e;
}
if (type === 2) {
return 0x3a2e1a;
}
return 0x2a2a4e;
}
function getTicketCost(type) {
return [5, 15, 50][type] || 5;
}
function canBuyTicket(type) {
if (type === 1 && !gameState.unlockLuckyTicket) {
return false;
}
if (type === 2 && !gameState.unlockGoldTicket) {
return false;
}
return true;
}
function getPrestigeReward() {
return Math.floor(Math.sqrt(gameState.totalCoins / 500));
}
function getPrestigeMultiplier() {
return 1 + gameState.prestigeChips * 0.05;
}
function getCurrentAchievementsCount() {
var count = 0;
if (gameState.achievementFirstWin) {
count++;
}
if (gameState.achievementLuckyUnlocked) {
count++;
}
if (gameState.achievementGoldUnlocked) {
count++;
}
if (gameState.achievementFirstJackpot) {
count++;
}
if (gameState.achievementStreak5) {
count++;
}
if (gameState.achievementStreak10) {
count++;
}
if (gameState.achievementFirstPrestige) {
count++;
}
if (gameState.achievementTickets50) {
count++;
}
if (gameState.achievementTickets200) {
count++;
}
if (gameState.achievementBigWin100) {
count++;
}
if (gameState.achievementModifierWin) {
count++;
}
if (gameState.achievementWealthy1000) {
count++;
}
return count;
}
function throttleScratchSound() {
if (LK.ticks - gameState.lastScratchSoundTick > 3) {
gameState.lastScratchSoundTick = LK.ticks;
safePlaySound('scratchSound');
}
}
function showFlash(color, alpha) {
if (!flashOverlay) {
return;
}
flashOverlay.tint = color || 0xffffff;
flashOverlay.alpha = alpha || 0.35;
tween(flashOverlay, {
alpha: 0
}, {
duration: 250,
easing: tween.easeOut
});
}
/****
* Storage Defaults - Flat Keys Only
****/
if (storage.coins === undefined) {
storage.coins = 100;
}
if (storage.totalCoins === undefined) {
storage.totalCoins = 100;
}
if (storage.bestStreak === undefined) {
storage.bestStreak = 0;
}
if (storage.jackpotMeter === undefined) {
storage.jackpotMeter = 0;
}
if (storage.ticketsScratchedTotal === undefined) {
storage.ticketsScratchedTotal = 0;
}
if (storage.biggestWin === undefined) {
storage.biggestWin = 0;
}
if (storage.jackpotsHit === undefined) {
storage.jackpotsHit = 0;
}
if (storage.prestigeCount === undefined) {
storage.prestigeCount = 0;
}
if (storage.prestigeChips === undefined) {
storage.prestigeChips = 0;
}
if (storage.upgrade0 === undefined) {
storage.upgrade0 = 0;
}
if (storage.upgrade1 === undefined) {
storage.upgrade1 = 0;
}
if (storage.upgrade2 === undefined) {
storage.upgrade2 = 0;
}
if (storage.upgrade3 === undefined) {
storage.upgrade3 = 0;
}
if (storage.upgrade4 === undefined) {
storage.upgrade4 = 0;
}
if (storage.upgrade5 === undefined) {
storage.upgrade5 = 0;
}
if (storage.unlockLuckyTicket === undefined) {
storage.unlockLuckyTicket = false;
}
if (storage.unlockGoldTicket === undefined) {
storage.unlockGoldTicket = false;
}
if (storage.unlockJackpotSystem === undefined) {
storage.unlockJackpotSystem = false;
}
if (storage.unlockModifiers === undefined) {
storage.unlockModifiers = false;
}
if (storage.unlockPrestige === undefined) {
storage.unlockPrestige = false;
}
if (storage.statsTotalWins === undefined) {
storage.statsTotalWins = 0;
}
if (storage.statsTotalLosses === undefined) {
storage.statsTotalLosses = 0;
}
if (storage.statsNearMisses === undefined) {
storage.statsNearMisses = 0;
}
if (storage.statsCardsBasic === undefined) {
storage.statsCardsBasic = 0;
}
if (storage.statsCardsLucky === undefined) {
storage.statsCardsLucky = 0;
}
if (storage.statsCardsGold === undefined) {
storage.statsCardsGold = 0;
}
if (storage.statsModifierWins === undefined) {
storage.statsModifierWins = 0;
}
if (storage.statsPrestigeEarnedLifetime === undefined) {
storage.statsPrestigeEarnedLifetime = 0;
}
if (storage.achievementFirstWin === undefined) {
storage.achievementFirstWin = false;
}
if (storage.achievementLuckyUnlocked === undefined) {
storage.achievementLuckyUnlocked = false;
}
if (storage.achievementGoldUnlocked === undefined) {
storage.achievementGoldUnlocked = false;
}
if (storage.achievementFirstJackpot === undefined) {
storage.achievementFirstJackpot = false;
}
if (storage.achievementStreak5 === undefined) {
storage.achievementStreak5 = false;
}
if (storage.achievementStreak10 === undefined) {
storage.achievementStreak10 = false;
}
if (storage.achievementFirstPrestige === undefined) {
storage.achievementFirstPrestige = false;
}
if (storage.achievementTickets50 === undefined) {
storage.achievementTickets50 = false;
}
if (storage.achievementTickets200 === undefined) {
storage.achievementTickets200 = false;
}
if (storage.achievementBigWin100 === undefined) {
storage.achievementBigWin100 = false;
}
if (storage.achievementModifierWin === undefined) {
storage.achievementModifierWin = false;
}
if (storage.achievementWealthy1000 === undefined) {
storage.achievementWealthy1000 = false;
}
var gameState = {
coins: storage.coins,
totalCoins: storage.totalCoins,
streak: 0,
bestStreak: storage.bestStreak,
jackpotMeter: storage.jackpotMeter,
jackpotMax: 100,
ticketsScratchedTotal: storage.ticketsScratchedTotal,
biggestWin: storage.biggestWin,
jackpotsHit: storage.jackpotsHit,
prestigeCount: storage.prestigeCount,
prestigeChips: storage.prestigeChips,
upgrades: [storage.upgrade0, storage.upgrade1, storage.upgrade2, storage.upgrade3, storage.upgrade4, storage.upgrade5],
unlockLuckyTicket: storage.unlockLuckyTicket,
unlockGoldTicket: storage.unlockGoldTicket,
unlockJackpotSystem: storage.unlockJackpotSystem,
unlockModifiers: storage.unlockModifiers,
unlockPrestige: storage.unlockPrestige,
statsTotalWins: storage.statsTotalWins,
statsTotalLosses: storage.statsTotalLosses,
statsNearMisses: storage.statsNearMisses,
statsCardsBasic: storage.statsCardsBasic,
statsCardsLucky: storage.statsCardsLucky,
statsCardsGold: storage.statsCardsGold,
statsModifierWins: storage.statsModifierWins,
statsPrestigeEarnedLifetime: storage.statsPrestigeEarnedLifetime,
achievementFirstWin: storage.achievementFirstWin,
achievementLuckyUnlocked: storage.achievementLuckyUnlocked,
achievementGoldUnlocked: storage.achievementGoldUnlocked,
achievementFirstJackpot: storage.achievementFirstJackpot,
achievementStreak5: storage.achievementStreak5,
achievementStreak10: storage.achievementStreak10,
achievementFirstPrestige: storage.achievementFirstPrestige,
achievementTickets50: storage.achievementTickets50,
achievementTickets200: storage.achievementTickets200,
achievementBigWin100: storage.achievementBigWin100,
achievementModifierWin: storage.achievementModifierWin,
achievementWealthy1000: storage.achievementWealthy1000,
lastScratchSoundTick: -999,
isGameOver: false
};
var currentTicket = null;
var flashOverlay = null;
var gameOverOverlay = null;
var gameOverRestartButton = null;
var dragData = {
active: false,
lastX: 0,
lastY: 0
};
var dustParticles = [];
var popupTexts = [];
var leftPanel, rightPanel, bottomPanel, centerInfoPanel;
var coinText, streakText, jackpotBar, jackpotText, statsText, modifierText, unlockText, achievementText, prestigeText;
var ticketBuyButtons = [];
var upgradeCards = [];
var prestigeButton;
var restartRunButton;
var wipeSaveButton;
/****
* Modifiers
****/
function rollTicketModifier(ticketType) {
if (!gameState.unlockModifiers) {
return {
id: 'none',
label: 'Standard Ticket',
color: '#cccccc',
payoutMult: 1,
jackpotBonus: 0
};
}
var pool = [{
id: 'none',
label: 'Standard Ticket',
color: '#cccccc',
payoutMult: 1,
jackpotBonus: 0
}, {
id: 'double',
label: 'Double Payout',
color: '#00ff88',
payoutMult: 2,
jackpotBonus: 0
}, {
id: 'jackpot',
label: 'Double Jackpot Gain',
color: '#ffd700',
payoutMult: 1,
jackpotBonus: 2
}, {
id: 'wild',
label: 'Lucky Wild Card',
color: '#66ccff',
payoutMult: 1.35,
jackpotBonus: 0
}, {
id: 'hot',
label: 'Hot Streak Boost',
color: '#ff9966',
payoutMult: 1.5,
jackpotBonus: 0
}];
var oddsBoost = gameState.upgrades[0] * 0.02 + gameState.prestigeChips * 0.005;
var roll = Math.random();
if (roll < 0.35 - oddsBoost) {
return pool[0];
}
if (roll < 0.55) {
return pool[1];
}
if (roll < 0.72) {
return pool[2];
}
if (roll < 0.87) {
return pool[3];
}
return pool[4];
}
function applyModifierToTicket(ticket) {
if (!ticket || !ticket.modifier) {
return;
}
if (ticket.modifier.id === 'wild') {
var wildIndex = Math.floor(Math.random() * ticket.symbols.length);
var baseType = ticket.symbols[Math.floor(Math.random() * ticket.symbols.length)].symbolType;
ticket.symbols[wildIndex].setSymbol(baseType);
ticket.symbols[wildIndex].highlight(0x2a6a8a);
}
}
/****
* Achievements / Milestones
****/
function unlockAchievement(key, label) {
if (gameState[key]) {
return;
}
gameState[key] = true;
createPopupText(1024, 760, 'ACHIEVEMENT: ' + label, '#ffd700', 28);
showFlash(0xffd700, 0.2);
}
function checkMilestones() {
if (!gameState.unlockLuckyTicket && gameState.ticketsScratchedTotal >= 10) {
gameState.unlockLuckyTicket = true;
unlockAchievement('achievementLuckyUnlocked', 'Lucky Tickets Unlocked!');
createPopupText(1024, 500, 'LUCKY TICKETS UNLOCKED', '#00ff88', 38);
}
if (!gameState.unlockGoldTicket && gameState.totalCoins >= 1000) {
gameState.unlockGoldTicket = true;
unlockAchievement('achievementGoldUnlocked', 'Gold Tickets Unlocked!');
createPopupText(1024, 550, 'GOLD TICKETS UNLOCKED', '#ffd700', 38);
}
if (!gameState.unlockJackpotSystem && gameState.ticketsScratchedTotal >= 15) {
gameState.unlockJackpotSystem = true;
createPopupText(1024, 600, 'JACKPOT SYSTEM UNLOCKED', '#66ccff', 30);
}
if (!gameState.unlockModifiers && gameState.ticketsScratchedTotal >= 25) {
gameState.unlockModifiers = true;
createPopupText(1024, 650, 'TICKET MODIFIERS UNLOCKED', '#ff9966', 30);
}
if (!gameState.unlockPrestige && gameState.totalCoins >= 5000) {
gameState.unlockPrestige = true;
createPopupText(1024, 700, 'PRESTIGE UNLOCKED', '#ffffff', 30);
}
}
function evaluateAchievements() {
if (gameState.statsTotalWins >= 1) {
unlockAchievement('achievementFirstWin', 'First Win');
}
if (gameState.jackpotsHit >= 1) {
unlockAchievement('achievementFirstJackpot', 'First Jackpot');
}
if (gameState.bestStreak >= 5) {
unlockAchievement('achievementStreak5', '5 Win Streak');
}
if (gameState.bestStreak >= 10) {
unlockAchievement('achievementStreak10', '10 Win Streak');
}
if (gameState.ticketsScratchedTotal >= 50) {
unlockAchievement('achievementTickets50', '50 Tickets Scratched');
}
if (gameState.ticketsScratchedTotal >= 200) {
unlockAchievement('achievementTickets200', '200 Tickets Scratched');
}
if (gameState.biggestWin >= 100) {
unlockAchievement('achievementBigWin100', 'Big Win 100+');
}
if (gameState.statsModifierWins >= 1) {
unlockAchievement('achievementModifierWin', 'Modifier Master');
}
if (gameState.totalCoins >= 1000) {
unlockAchievement('achievementWealthy1000', 'Earned 1000 Coins');
}
if (gameState.prestigeCount >= 1) {
unlockAchievement('achievementFirstPrestige', 'First Prestige');
}
}
/****
* Reset / Save
****/
function restartRunOnly() {
gameState.coins = 100 + gameState.prestigeChips * 10;
gameState.streak = 0;
gameState.jackpotMeter = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 950, 'RUN RESTARTED', '#ffffff', 34);
updateUI();
saveProgress();
}
function hardRestartGame() {
gameState.coins = 100;
gameState.totalCoins = 100;
gameState.streak = 0;
gameState.bestStreak = 0;
gameState.jackpotMeter = 0;
gameState.ticketsScratchedTotal = 0;
gameState.biggestWin = 0;
gameState.jackpotsHit = 0;
gameState.prestigeCount = 0;
gameState.prestigeChips = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.unlockLuckyTicket = false;
gameState.unlockGoldTicket = false;
gameState.unlockJackpotSystem = false;
gameState.unlockModifiers = false;
gameState.unlockPrestige = false;
gameState.statsTotalWins = 0;
gameState.statsTotalLosses = 0;
gameState.statsNearMisses = 0;
gameState.statsCardsBasic = 0;
gameState.statsCardsLucky = 0;
gameState.statsCardsGold = 0;
gameState.statsModifierWins = 0;
gameState.statsPrestigeEarnedLifetime = 0;
gameState.achievementFirstWin = false;
gameState.achievementLuckyUnlocked = false;
gameState.achievementGoldUnlocked = false;
gameState.achievementFirstJackpot = false;
gameState.achievementStreak5 = false;
gameState.achievementStreak10 = false;
gameState.achievementFirstPrestige = false;
gameState.achievementTickets50 = false;
gameState.achievementTickets200 = false;
gameState.achievementBigWin100 = false;
gameState.achievementModifierWin = false;
gameState.achievementWealthy1000 = false;
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 950, 'SAVE WIPED', '#ff6b6b', 34);
updateUI();
saveProgress();
}
function resetRunForPrestige() {
var reward = getPrestigeReward();
if (!gameState.unlockPrestige || reward <= 0) {
return;
}
gameState.prestigeCount++;
gameState.prestigeChips += reward;
gameState.statsPrestigeEarnedLifetime += reward;
gameState.coins = 100 + reward * 10;
gameState.streak = 0;
gameState.jackpotMeter = 0;
gameState.upgrades = [0, 0, 0, 0, 0, 0];
gameState.isGameOver = false;
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
currentTicket = null;
}
hideGameOver();
createPopupText(1024, 900, 'PRESTIGE +' + reward + ' CHIPS', '#ffffff', 40);
showFlash(0xffffff, 0.35);
evaluateAchievements();
updateUI();
saveProgress();
}
function saveProgress() {
storage.coins = gameState.coins;
storage.totalCoins = gameState.totalCoins;
storage.bestStreak = gameState.bestStreak;
storage.jackpotMeter = gameState.jackpotMeter;
storage.ticketsScratchedTotal = gameState.ticketsScratchedTotal;
storage.biggestWin = gameState.biggestWin;
storage.jackpotsHit = gameState.jackpotsHit;
storage.prestigeCount = gameState.prestigeCount;
storage.prestigeChips = gameState.prestigeChips;
storage.upgrade0 = gameState.upgrades[0];
storage.upgrade1 = gameState.upgrades[1];
storage.upgrade2 = gameState.upgrades[2];
storage.upgrade3 = gameState.upgrades[3];
storage.upgrade4 = gameState.upgrades[4];
storage.upgrade5 = gameState.upgrades[5];
storage.unlockLuckyTicket = gameState.unlockLuckyTicket;
storage.unlockGoldTicket = gameState.unlockGoldTicket;
storage.unlockJackpotSystem = gameState.unlockJackpotSystem;
storage.unlockModifiers = gameState.unlockModifiers;
storage.unlockPrestige = gameState.unlockPrestige;
storage.statsTotalWins = gameState.statsTotalWins;
storage.statsTotalLosses = gameState.statsTotalLosses;
storage.statsNearMisses = gameState.statsNearMisses;
storage.statsCardsBasic = gameState.statsCardsBasic;
storage.statsCardsLucky = gameState.statsCardsLucky;
storage.statsCardsGold = gameState.statsCardsGold;
storage.statsModifierWins = gameState.statsModifierWins;
storage.statsPrestigeEarnedLifetime = gameState.statsPrestigeEarnedLifetime;
storage.achievementFirstWin = gameState.achievementFirstWin;
storage.achievementLuckyUnlocked = gameState.achievementLuckyUnlocked;
storage.achievementGoldUnlocked = gameState.achievementGoldUnlocked;
storage.achievementFirstJackpot = gameState.achievementFirstJackpot;
storage.achievementStreak5 = gameState.achievementStreak5;
storage.achievementStreak10 = gameState.achievementStreak10;
storage.achievementFirstPrestige = gameState.achievementFirstPrestige;
storage.achievementTickets50 = gameState.achievementTickets50;
storage.achievementTickets200 = gameState.achievementTickets200;
storage.achievementBigWin100 = gameState.achievementBigWin100;
storage.achievementModifierWin = gameState.achievementModifierWin;
storage.achievementWealthy1000 = gameState.achievementWealthy1000;
}
/****
* UI
****/
function initUI() {
leftPanel = new Container();
leftPanel.x = 100;
leftPanel.y = 100;
game.addChild(leftPanel);
var leftBg = LK.getAsset('panelBg', {
anchorX: 0,
anchorY: 0
});
leftBg.scaleY = 1.05;
leftPanel.addChild(leftBg);
var coinsLabelText = new Text2('COINS', {
size: 20,
fill: '#ffd700'
});
coinsLabelText.anchor.set(0.5, 0);
coinsLabelText.x = 150;
coinsLabelText.y = 20;
leftPanel.addChild(coinsLabelText);
coinText = new Text2('$100', {
size: 32,
fill: '#ffffff'
});
coinText.anchor.set(0.5, 0);
coinText.x = 150;
coinText.y = 60;
leftPanel.addChild(coinText);
var streakLabelText = new Text2('STREAK', {
size: 20,
fill: '#ff6b6b'
});
streakLabelText.anchor.set(0.5, 0);
streakLabelText.x = 150;
streakLabelText.y = 130;
leftPanel.addChild(streakLabelText);
streakText = new Text2('x0', {
size: 24,
fill: '#ffffff'
});
streakText.anchor.set(0.5, 0);
streakText.x = 150;
streakText.y = 160;
leftPanel.addChild(streakText);
var jackpotLabelText = new Text2('JACKPOT', {
size: 20,
fill: '#ffd700'
});
jackpotLabelText.anchor.set(0.5, 0);
jackpotLabelText.x = 150;
jackpotLabelText.y = 220;
leftPanel.addChild(jackpotLabelText);
var jackpotBgBar = LK.getAsset('meterBarEmpty', {
anchorX: 0.5,
anchorY: 0
});
jackpotBgBar.x = 150;
jackpotBgBar.y = 250;
leftPanel.addChild(jackpotBgBar);
jackpotBar = LK.getAsset('meterBarFull', {
anchorX: 0.5,
anchorY: 0
});
jackpotBar.x = 150;
jackpotBar.y = 250;
jackpotBar.scaleX = 0;
leftPanel.addChild(jackpotBar);
jackpotText = new Text2('0/100', {
size: 20,
fill: '#ffffff'
});
jackpotText.anchor.set(0.5, 0);
jackpotText.x = 150;
jackpotText.y = 280;
leftPanel.addChild(jackpotText);
prestigeText = new Text2('', {
size: 14,
fill: '#66ccff',
align: 'center'
});
prestigeText.anchor.set(0.5, 0);
prestigeText.x = 150;
prestigeText.y = 320;
leftPanel.addChild(prestigeText);
statsText = new Text2('', {
size: 20,
fill: '#bbbbbb',
align: 'center'
});
statsText.anchor.set(0.5, 0);
statsText.x = 150;
statsText.y = 370;
leftPanel.addChild(statsText);
unlockText = new Text2('', {
size: 20,
fill: '#00ff88',
align: 'center'
});
unlockText.anchor.set(0.5, 0);
unlockText.x = 150;
unlockText.y = 500;
leftPanel.addChild(unlockText);
achievementText = new Text2('', {
size: 20,
fill: '#ffd700',
align: 'center'
});
achievementText.anchor.set(0.5, 0);
achievementText.x = 150;
achievementText.y = 570;
leftPanel.addChild(achievementText);
rightPanel = new Container();
rightPanel.x = 1848;
rightPanel.y = 100;
game.addChild(rightPanel);
var rightBg = LK.getAsset('panelBg', {
anchorX: 1,
anchorY: 0
});
rightBg.scaleY = 1.25;
rightPanel.addChild(rightBg);
var shopLabelText = new Text2('UPGRADES', {
size: 20,
fill: '#00ff88'
});
shopLabelText.anchor.set(0.5, 0);
shopLabelText.x = -150;
shopLabelText.y = 20;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
card.y = 90 + i * 82;
card.x = -150;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
prestigeButton = new Button();
prestigeButton.x = -150;
prestigeButton.y = 610;
prestigeButton.setText('Prestige');
prestigeButton.onClick = function () {
resetRunForPrestige();
};
rightPanel.addChild(prestigeButton);
restartRunButton = new Button();
restartRunButton.x = -150;
restartRunButton.y = 690;
restartRunButton.setText('Restart Run');
restartRunButton.onClick = function () {
restartRunOnly();
};
rightPanel.addChild(restartRunButton);
wipeSaveButton = new Button();
wipeSaveButton.x = -150;
wipeSaveButton.y = 770;
wipeSaveButton.setText('Wipe Save');
wipeSaveButton.onClick = function () {
hardRestartGame();
};
rightPanel.addChild(wipeSaveButton);
bottomPanel = new Container();
bottomPanel.x = 1024;
bottomPanel.y = 2520;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'Basic\n$5',
cost: 5,
type: 0
}, {
name: 'Lucky\n$15',
cost: 15,
type: 1
}, {
name: 'Gold\n$50',
cost: 50,
type: 2
}];
for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
btn.x = -300 + t * 300;
btn.y = 0;
btn.setText(ticketTypes[t].name);
btn.cost = ticketTypes[t].cost;
btn.type = ticketTypes[t].type;
btn.onClick = function () {
onBuyTicket(this.type, this.cost);
}.bind(btn);
ticketBuyButtons.push(btn);
bottomPanel.addChild(btn);
}
centerInfoPanel = new Container();
centerInfoPanel.x = 1024;
centerInfoPanel.y = 180;
game.addChild(centerInfoPanel);
modifierText = new Text2('', {
size: 18,
fill: '#ffffff',
align: 'center'
});
modifierText.anchor.set(0.5, 0);
centerInfoPanel.addChild(modifierText);
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: 40,
fill: '#ffd700'
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 30;
game.addChild(titleText);
flashOverlay = LK.getAsset('flashBg', {
anchorX: 0.5,
anchorY: 0.5
});
flashOverlay.x = 1024;
flashOverlay.y = 1366;
flashOverlay.alpha = 0;
game.addChild(flashOverlay);
initGameOverUI();
}
function initGameOverUI() {
gameOverOverlay = new Container();
gameOverOverlay.visible = false;
game.addChild(gameOverOverlay);
var dark = LK.getAsset('overlayDark', {
anchorX: 0.5,
anchorY: 0.5
});
dark.x = 1024;
dark.y = 1366;
dark.alpha = 0.7;
gameOverOverlay.addChild(dark);
var title = new Text2('YOU RAN OUT OF MONEY', {
size: 42,
fill: '#ff6b6b',
align: 'center'
});
title.anchor.set(0.5, 0.5);
title.x = 1024;
title.y = 1050;
gameOverOverlay.addChild(title);
var sub = new Text2('Your run is over. Restart the run or wipe your save.', {
size: 22,
fill: '#ffffff',
align: 'center'
});
sub.anchor.set(0.5, 0.5);
sub.x = 1024;
sub.y = 1120;
gameOverOverlay.addChild(sub);
gameOverRestartButton = new Button();
gameOverRestartButton.x = 1024;
gameOverRestartButton.y = 1240;
gameOverRestartButton.setText('Restart Run');
gameOverRestartButton.onClick = function () {
restartRunOnly();
};
gameOverOverlay.addChild(gameOverRestartButton);
}
function showGameOver() {
gameState.isGameOver = true;
dragData.active = false;
if (gameOverOverlay) {
gameOverOverlay.visible = true;
}
showFlash(0xff0000, 0.15);
}
function hideGameOver() {
if (gameOverOverlay) {
gameOverOverlay.visible = false;
}
}
function updateUI() {
if (coinText) {
coinText.setText(formatMoney(gameState.coins));
}
if (streakText) {
streakText.setText('x' + gameState.streak);
}
if (jackpotBar) {
jackpotBar.scaleX = clamp(gameState.jackpotMeter / gameState.jackpotMax, 0, 1);
}
if (jackpotText) {
jackpotText.setText(gameState.jackpotMeter + '/' + gameState.jackpotMax);
}
if (prestigeText) {
prestigeText.setText('Prestige Chips: ' + gameState.prestigeChips + '\nNext Prestige: +' + getPrestigeReward() + '\nMultiplier: x' + getPrestigeMultiplier().toFixed(2));
}
if (statsText) {
statsText.setText('Best Streak: ' + gameState.bestStreak + '\nBiggest Win: ' + formatMoney(gameState.biggestWin) + '\nTickets: ' + gameState.ticketsScratchedTotal + '\nWins/Losses: ' + gameState.statsTotalWins + '/' + gameState.statsTotalLosses);
}
if (unlockText) {
unlockText.setText('Unlocks' + '\nLucky: ' + (gameState.unlockLuckyTicket ? 'Yes' : 'No') + '\nGold: ' + (gameState.unlockGoldTicket ? 'Yes' : 'No') + '\nModifiers: ' + (gameState.unlockModifiers ? 'Yes' : 'No') + '\nPrestige: ' + (gameState.unlockPrestige ? 'Yes' : 'No'));
}
if (achievementText) {
achievementText.setText('Achievements: ' + getCurrentAchievementsCount() + '\nNear Misses: ' + gameState.statsNearMisses + '\nModifier Wins: ' + gameState.statsModifierWins);
}
if (modifierText) {
modifierText.setText(currentTicket ? 'Current Modifier: ' + currentTicket.modifier.label : 'Buy a ticket to start');
}
for (var i = 0; i < upgradeCards.length; i++) {
var level = gameState.upgrades[i];
var cost = getUpgradeCost(i, level);
upgradeCards[i].setUpgrade(i, level, cost, gameState.coins >= cost && !gameState.isGameOver);
}
for (var j = 0; j < ticketBuyButtons.length; j++) {
var canAfford = gameState.coins >= ticketBuyButtons[j].cost;
var unlocked = canBuyTicket(ticketBuyButtons[j].type);
ticketBuyButtons[j].setEnabled(canAfford && unlocked && !gameState.isGameOver);
if (!unlocked) {
if (ticketBuyButtons[j].type === 1) {
ticketBuyButtons[j].setText('Lucky\nLocked');
}
if (ticketBuyButtons[j].type === 2) {
ticketBuyButtons[j].setText('Gold\nLocked');
}
} else {
if (ticketBuyButtons[j].type === 0) {
ticketBuyButtons[j].setText('Basic\n$5');
}
if (ticketBuyButtons[j].type === 1) {
ticketBuyButtons[j].setText('Lucky\n$15');
}
if (ticketBuyButtons[j].type === 2) {
ticketBuyButtons[j].setText('Gold\n$50');
}
}
}
if (prestigeButton) {
prestigeButton.setText('Prestige\n+' + getPrestigeReward());
prestigeButton.setEnabled(gameState.unlockPrestige && getPrestigeReward() > 0 && !gameState.isGameOver);
}
if (restartRunButton) {
restartRunButton.setEnabled(true);
}
if (wipeSaveButton) {
wipeSaveButton.setEnabled(true);
}
}
/****
* Gameplay
****/
function createNewTicket(ticketType) {
if (currentTicket) {
if (currentTicket.parent) {
currentTicket.parent.removeChild(currentTicket);
}
currentTicket.destroy();
}
currentTicket = new ScratchTicket(ticketType);
currentTicket.x = 1024;
currentTicket.y = 1200;
game.addChild(currentTicket);
dragData.active = false;
updateUI();
}
function onBuyTicket(type, cost) {
if (gameState.isGameOver) {
return;
}
if (currentTicket && !currentTicket.resolved) {
return;
}
if (!canBuyTicket(type)) {
return;
}
if (gameState.coins < cost) {
return;
}
gameState.coins -= cost;
gameState.ticketsScratchedTotal++;
if (type === 0) {
gameState.statsCardsBasic++;
}
if (type === 1) {
gameState.statsCardsLucky++;
}
if (type === 2) {
gameState.statsCardsGold++;
}
createNewTicket(type);
checkMilestones();
evaluateAchievements();
updateUI();
saveProgress();
}
function onUpgradeClick(upgradeId) {
if (gameState.isGameOver) {
return;
}
var level = gameState.upgrades[upgradeId];
var cost = getUpgradeCost(upgradeId, level);
if (gameState.coins < cost) {
return;
}
gameState.coins -= cost;
gameState.upgrades[upgradeId]++;
safePlaySound('upgradeSound');
createPopupText(1700, 700, 'UPGRADE BOUGHT', '#00ff88', 24);
updateUI();
saveProgress();
checkForGameOver();
}
function createPopupText(x, y, textValue, color, size) {
var text = new Text2(textValue, {
size: size || 32,
fill: color || '#ffd700',
align: 'center'
});
text.anchor.set(0.5, 0.5);
text.x = x;
text.y = y;
game.addChild(text);
popupTexts.push(text);
tween(text, {
y: y - 90,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (text.parent) {
text.parent.removeChild(text);
}
var index = popupTexts.indexOf(text);
if (index >= 0) {
popupTexts.splice(index, 1);
}
}
});
}
function createDustParticles(x, y) {
for (var i = 0; i < 8; i++) {
var particle = new DustParticle();
particle.x = x + (Math.random() - 0.5) * 30;
particle.y = y + (Math.random() - 0.5) * 30;
game.addChild(particle);
dustParticles.push(particle);
}
}
function createCoinBurst(x, y, count) {
for (var i = 0; i < count; i++) {
createPopupText(x + (Math.random() - 0.5) * 120, y + (Math.random() - 0.5) * 50, '+', '#ffd700', 20);
}
}
function resolveCurrentTicket(result) {
if (!currentTicket) {
return;
}
var wasNearMiss = !result.win && result.count === 2;
if (wasNearMiss) {
gameState.statsNearMisses++;
createPopupText(currentTicket.x, currentTicket.y - 120, 'NEAR MISS!', '#ff9966', 24);
}
if (result.win) {
safePlaySound('winSound');
showFlash(0xffd700, 0.15);
var basePayouts = [10, 25, 50];
var matchBonus = 1 + (result.count - 3) * 0.35;
var payoutUpgrade = 1 + gameState.upgrades[1] * 0.2;
var comboBoost = 1 + gameState.streak * (0.1 + gameState.upgrades[3] * 0.02);
var prestigeBoost = getPrestigeMultiplier();
var modifierBoost = result.modifier ? result.modifier.payoutMult : 1;
if (result.modifier && result.modifier.id === 'hot') {
modifierBoost += gameState.streak * 0.05;
}
var finalPayout = Math.floor(basePayouts[result.ticketType] * matchBonus * payoutUpgrade * comboBoost * prestigeBoost * modifierBoost);
gameState.coins += finalPayout;
gameState.totalCoins += finalPayout;
gameState.streak++;
gameState.statsTotalWins++;
if (result.modifier && result.modifier.id !== 'none') {
gameState.statsModifierWins++;
}
if (gameState.streak > gameState.bestStreak) {
gameState.bestStreak = gameState.streak;
}
if (finalPayout > gameState.biggestWin) {
gameState.biggestWin = finalPayout;
}
if (gameState.unlockJackpotSystem) {
var jackpotGain = 1 + gameState.upgrades[4] + Math.floor(gameState.prestigeChips * 0.1);
if (result.modifier) {
jackpotGain += result.modifier.jackpotBonus || 0;
}
gameState.jackpotMeter += jackpotGain;
if (gameState.jackpotMeter >= gameState.jackpotMax) {
gameState.jackpotMeter = 0;
gameState.jackpotsHit++;
var jackpotReward = Math.floor((100 + gameState.jackpotsHit * 25) * getPrestigeMultiplier());
gameState.coins += jackpotReward;
gameState.totalCoins += jackpotReward;
createPopupText(currentTicket.x, currentTicket.y - 160, 'JACKPOT BONUS ' + formatMoney(jackpotReward), '#00ff88', 34);
createCoinBurst(currentTicket.x, currentTicket.y - 40, 12);
showFlash(0x00ff88, 0.28);
}
}
createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), '#ffd700', 34);
createCoinBurst(currentTicket.x, currentTicket.y + 40, 8);
} else {
gameState.streak = 0;
gameState.statsTotalLosses++;
createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', '#ff6b6b', 30);
}
checkMilestones();
evaluateAchievements();
updateUI();
saveProgress();
checkForGameOver();
}
function checkForGameOver() {
if (gameState.isGameOver) {
return;
}
var hasActiveTicket = currentTicket && !currentTicket.resolved;
if (gameState.coins <= 0 && !hasActiveTicket) {
showGameOver();
}
}
/****
* Boot
****/
initUI();
updateUI();
safePlayMusic('bgMusic', {
loop: true
});
/****
* Input
****/
game.down = function (x, y, obj) {
if (gameState.isGameOver) {
if (gameOverRestartButton) {
if (pointInRect(x, y, gameOverRestartButton.x, gameOverRestartButton.y, gameOverRestartButton.hitWidth, gameOverRestartButton.hitHeight)) {
gameOverRestartButton.press();
return;
}
}
return;
}
for (var i = 0; i < ticketBuyButtons.length; i++) {
var btnX = bottomPanel.x + ticketBuyButtons[i].x;
var btnY = bottomPanel.y + ticketBuyButtons[i].y;
if (pointInRect(x, y, btnX, btnY, ticketBuyButtons[i].hitWidth, ticketBuyButtons[i].hitHeight)) {
ticketBuyButtons[i].press();
return;
}
}
for (var j = 0; j < upgradeCards.length; j++) {
if (upgradeCards[j].containsGlobalPoint(x, y)) {
upgradeCards[j].press();
return;
}
}
if (prestigeButton) {
var pX = rightPanel.x + prestigeButton.x;
var pY = rightPanel.y + prestigeButton.y;
if (pointInRect(x, y, pX, pY, prestigeButton.hitWidth, prestigeButton.hitHeight)) {
prestigeButton.press();
return;
}
}
if (restartRunButton) {
var rrX = rightPanel.x + restartRunButton.x;
var rrY = rightPanel.y + restartRunButton.y;
if (pointInRect(x, y, rrX, rrY, restartRunButton.hitWidth, restartRunButton.hitHeight)) {
restartRunButton.press();
return;
}
}
if (wipeSaveButton) {
var wsX = rightPanel.x + wipeSaveButton.x;
var wsY = rightPanel.y + wipeSaveButton.y;
if (pointInRect(x, y, wsX, wsY, wipeSaveButton.hitWidth, wipeSaveButton.hitHeight)) {
wipeSaveButton.press();
return;
}
}
if (currentTicket && !currentTicket.resolved && currentTicket.containsGlobalPoint(x, y)) {
dragData.active = true;
dragData.lastX = x;
dragData.lastY = y;
}
};
game.move = function (x, y, obj) {
if (gameState.isGameOver) {
return;
}
for (var i = 0; i < ticketBuyButtons.length; i++) {
var btnGX = bottomPanel.x + ticketBuyButtons[i].x;
var btnGY = bottomPanel.y + ticketBuyButtons[i].y;
ticketBuyButtons[i].setHovered(pointInRect(x, y, btnGX, btnGY, ticketBuyButtons[i].hitWidth, ticketBuyButtons[i].hitHeight));
}
for (var j = 0; j < upgradeCards.length; j++) {
upgradeCards[j].setHovered(upgradeCards[j].containsGlobalPoint(x, y));
}
if (prestigeButton) {
var pX = rightPanel.x + prestigeButton.x;
var pY = rightPanel.y + prestigeButton.y;
prestigeButton.setHovered(pointInRect(x, y, pX, pY, prestigeButton.hitWidth, prestigeButton.hitHeight));
}
if (restartRunButton) {
var rrX = rightPanel.x + restartRunButton.x;
var rrY = rightPanel.y + restartRunButton.y;
restartRunButton.setHovered(pointInRect(x, y, rrX, rrY, restartRunButton.hitWidth, restartRunButton.hitHeight));
}
if (wipeSaveButton) {
var wsX = rightPanel.x + wipeSaveButton.x;
var wsY = rightPanel.y + wipeSaveButton.y;
wipeSaveButton.setHovered(pointInRect(x, y, wsX, wsY, wipeSaveButton.hitWidth, wipeSaveButton.hitHeight));
}
if (!dragData.active || !currentTicket || currentTicket.resolved) {
return;
}
if (!currentTicket.containsGlobalPoint(x, y)) {
return;
}
var dx = x - dragData.lastX;
var dy = y - dragData.lastY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 6) {
var scratchAmount = distance / 430;
currentTicket.updateScratch(scratchAmount);
createDustParticles(x, y);
throttleScratchSound();
dragData.lastX = x;
dragData.lastY = y;
updateUI();
}
};
game.up = function () {
dragData.active = false;
};
/****
* Update
****/
game.update = function () {
for (var i = dustParticles.length - 1; i >= 0; i--) {
if (!dustParticles[i] || !dustParticles[i].parent) {
dustParticles.splice(i, 1);
continue;
}
dustParticles[i].update();
}
if (LK.ticks % 300 === 0) {
saveProgress();
}
};