Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'var global = obj.parent.toGlobal(obj.position);' Line Number: 55
Code edit (1 edits merged)
Please save this source code
User prompt
Lucky Scratch Tycoon
Initial prompt
Build a complete 2D browser game called Gas Station Gold (working title: Lucky Scratch Tycoon), designed for itch.io as a fast, addictive, mouse-driven incremental scratch-ticket game. The game should feel satisfying, replayable, juicy, and easy to understand within 5 seconds. It must be a fake gambling-themed game with no real money, no real-world payouts, and no online features. The goal is to let the player buy scratch tickets, scratch them with the mouse, reveal symbols, win coins, and spend coins on upgrades that make future tickets more rewarding and more exciting. The design should focus on short sessions, strong anticipation, rapid progression, and polished feedback, which aligns well with popular browser incremental and mouse-only game patterns on itch.io. Make the entire game playable with mouse only. The player starts with a small number of coins and can buy a basic scratch ticket. Each ticket appears in the center of the screen as a rectangular card with a scratchable silver overlay. Under that overlay is a grid of hidden symbols, such as cherries, stars, sevens, coins, clovers, and diamonds. The player scratches by holding the mouse and dragging across the ticket. Scratching should remove the overlay in a smooth, satisfying way and reveal symbols underneath. The scratch system should feel tactile and juicy, with small particle dust, subtle sound effects, and a reveal shimmer when a symbol becomes visible. The player should be able to clearly see progress as they scratch, and the ticket should auto-resolve once enough of it is revealed. Use a simple but addictive core rule: matching 3 identical symbols anywhere on the card gives a payout. Add extra excitement by allowing rare bonus symbols like a multiplier icon, jackpot icon, chain icon, or instant coin icon. A multiplier icon increases the payout of the ticket. A jackpot icon fills a jackpot meter. A chain icon reveals one additional hidden tile automatically. An instant coin icon gives a small immediate reward. Tickets should resolve quickly so a full early-game card takes about 5 to 10 seconds to buy, scratch, and cash out. The player must always feel like one more ticket is worth trying. Create a clean 2D UI layout that is attractive and easy to read. Place the scratch ticket in the center. Put the player’s coin total, jackpot meter, and streak/combo meter on the left side. Put the upgrade shop on the right side. At the bottom, place buttons to buy ticket types. At the top, place the game title, settings button, and a prestige/reset button that is locked until later. Make the style bold, polished, readable, and premium, with a dark background and gold/yellow highlight accents. The UI should feel modern and game-like, not like a real casino website. Avoid clutter. Everything should be large, readable, and satisfying to click. Strong juice and good UI feedback matter more than complicated art. Implement at least three ticket types: Basic Ticket: cheap, small payout potential, beginner-friendly. Lucky Ticket: medium cost, slightly better odds for rare symbols. Gold Ticket: expensive, higher chance of multipliers and jackpot gain. Each ticket type should feel meaningfully different through cost, visuals, odds, and payout potential. Unlock better tickets through progression. The player should start with only the Basic Ticket and unlock the others by earning enough total coins or buying certain upgrades. Implement a simple but satisfying upgrade shop with permanent upgrades purchased using coins. Include at least these upgrades: Better Odds: slightly increases rare symbol chances. Bigger Payouts: increases total reward from winning cards. Faster Scratch: reduces how much the player must scratch before the ticket auto-resolves. Combo Boost: increases payout while a win streak is active. Jackpot Gain: jackpot icons fill the jackpot meter faster. Coin Magnet: makes earned coins fly to the wallet faster and feel more satisfying. Upgrades should start cheap and get more expensive each level. The player should be able to buy upgrades frequently in the first few minutes so progression feels fast and exciting. Add a combo/streak system. If the player wins on multiple tickets in a row, a streak meter increases. Each streak level gives a bonus payout multiplier. Losing a ticket reduces or resets the streak. This creates tension and makes even small wins feel important. Add clear visual and audio feedback whenever the streak increases. Add a jackpot meter. Certain rare symbols fill the jackpot meter. When the meter fills, the player earns a special jackpot ticket with much higher reward potential and extra flashy presentation. The jackpot moment should feel exciting, with stronger particles, brighter colors, screen shake, animated text, and a satisfying sound stinger. Jackpot tickets are one of the main medium-term goals that keep players engaged. Include a lightweight prestige system called something like “Lucky License” or “VIP Scratch Club.” After the player reaches a certain milestone, such as earning a target amount of total coins, they can reset most progress in exchange for a permanent currency like Lucky Chips. Lucky Chips can be spent on a small set of permanent bonuses such as starting with more coins, improved rare symbol chance, stronger jackpot rewards, or cheaper early upgrades. This prestige loop should be simple, clearly explained, and designed to encourage replayability rather than confusion. The game must feel polished. Add satisfying juice throughout: Scratch dust particles while dragging on the ticket. Coin popups and floating text for rewards. Small screen shake on big wins. Rolling number animations for coin total changes. Distinct sounds for scratching, revealing, winning, jackpot fills, upgrade purchases, and prestige. Color-coded rarity feedback for rare symbols. Smooth button hover and click feedback. A short, subtle animation when a ticket is purchased and when it is cashed out. Balance the pacing so the game is fun immediately: First ticket within 2 seconds. First win within 10 seconds. First upgrade within 30 seconds. First rare symbol within 1 to 2 minutes. First jackpot within 5 to 10 minutes. First prestige opportunity within about 15 to 25 minutes. The gameplay should never stall early. It should constantly offer a nearby reward, unlock, or goal. Include a save system so the player’s progress is preserved between sessions if possible in the target environment. If permanent saving is not supported by the platform, simulate session progression cleanly and structure the code so save support can be added later. Include a stats screen tracking total tickets scratched, total coins earned, biggest win, best streak, jackpots triggered, and total prestige resets. Keep the art scope intentionally low. Use simple 2D UI panels, icons, particles, bold typography, and clean symbol art. Do not spend time on character models, story scenes, or complex worlds. This project should be mostly UI, tuning, particles, and sound. Prioritize fast development and addictive feel over content quantity. The finished result should feel like a small premium browser toy that is immediately fun and hard to stop playing. Structure the code clearly and modularly. Separate systems for ticket generation, scratch detection, payout evaluation, upgrade data, combo logic, jackpot logic, prestige logic, UI updates, audio feedback, and save data. Use easy-to-edit variables for symbol odds, payout amounts, upgrade scaling, jackpot requirements, and progression milestones so the game can be tuned quickly. Important design rules: Mouse-only controls. No multiplayer. No real-money references beyond playful theme dressing. No ads, no fake mobile monetization, no energy timers. Fast load time. Strong readable UI. Designed for short sessions but capable of longer idle/replay sessions. Browser-friendly performance. The moment-to-moment feel should be satisfying enough that scratching one more ticket is always tempting. At the end, generate the full playable prototype with all core systems working, placeholder art where needed, and enough polish that it already feels fun. Focus on the main loop first: buy ticket, scratch ticket, evaluate result, reward coins, buy upgrades, repeat. Then add combo meter, jackpot meter, and prestige. Make sure the game is complete, understandable, and addictive even with simple assets.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Button = Container.expand(function (styleType) {
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
self.enabled = true;
self.hitWidth = UI.buttonWidth;
self.hitHeight = styleType === 'small' ? UI.smallButtonHeight : UI.buttonHeight;
self.styleType = styleType || 'normal';
var assetName = self.styleType === 'small' ? 'smallButtonBg' : 'buttonBg';
var bg = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: self.styleType === 'small' ? 24 : 28,
fill: COLORS.white,
align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
self.setTint = function (color) {
bg.tint = color;
self.baseTint = color;
};
self.setEnabled = function (enabled) {
self.enabled = enabled;
self.alpha = enabled ? 1 : 0.45;
if (!enabled) {
bg.tint = COLORS.buttonDisabled;
} else {
bg.tint = self.isHovered ? COLORS.buttonHover : self.baseTint || COLORS.button;
}
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
if (!self.enabled) {
bg.tint = COLORS.buttonDisabled;
return;
}
bg.tint = hovered ? COLORS.buttonHover : self.baseTint || COLORS.button;
};
self.press = function () {
if (!self.enabled) {
return;
}
safePlaySound('buttonClickSound');
self.scale.set(0.98);
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
self.onClick();
};
self.setTint(COLORS.button);
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.82;
self.velocityX = (Math.random() - 0.5) * 6;
self.velocityY = (Math.random() - 0.5) * 6;
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.6);
dust.alpha = 0.82 * (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 = UI.ticketWidth;
self.ticketHeight = UI.ticketHeight;
self.modifier = rollTicketModifier(ticketType);
var scratchSpeedBonus = gameState.upgrades[2] * 0.03;
var prestigeScratchBonus = gameState.prestigeChips * 0.003;
self.autoResolveThreshold = clamp(0.56 - scratchSpeedBonus - prestigeScratchBonus, 0.22, 0.56);
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.95;
var titleText = new Text2(getTicketName(self.ticketType), {
size: 38,
fill: COLORS.white,
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -500;
self.addChild(titleText);
var infoText = new Text2('Scratch to reveal 3 matching symbols', {
size: 24,
fill: COLORS.primary,
align: 'center'
});
infoText.anchor.set(0.5, 0);
infoText.y = -450;
self.addChild(infoText);
var modifierLine = new Text2(self.modifier.label, {
size: 26,
fill: self.modifier.color,
align: 'center'
});
modifierLine.anchor.set(0.5, 0);
modifierLine.y = -405;
self.addChild(modifierLine);
var gridStartX = -UI.symbolSpacing;
var gridStartY = -UI.symbolSpacing;
var spacing = UI.symbolSpacing;
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 + 70;
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.95 - self.scratchAmount, 0, 0.95);
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 = UI.buttonWidth;
self.hitHeight = UI.upgradeCardHeight;
self.onClick = function () {};
var bg = self.attachAsset('upgradeCardBg', {
anchorX: 0.5,
anchorY: 0.5
});
var nameText = new Text2('', {
size: 24,
fill: COLORS.white,
align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -28;
self.addChild(nameText);
var descText = new Text2('', {
size: 16,
fill: COLORS.muted,
align: 'center'
});
descText.anchor.set(0.5, 0);
descText.y = 2;
self.addChild(descText);
var costText = new Text2('', {
size: 20,
fill: COLORS.gold,
align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 34;
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'];
var descs = ['More good modifier rolls', 'Higher match payouts', 'Resolve cards faster', 'Bigger streak scaling', 'Fill jackpot quicker', 'Extra economy support'];
nameText.setText(names[id] + ' Lv.' + (level + 1));
descText.setText(descs[id]);
costText.setText('BUY ' + formatMoney(cost));
self.alpha = affordable ? 1 : 0.52;
};
self.setHovered = function (hovered) {
bg.tint = hovered ? 0x2c5c8f : 0x1f3b60;
};
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.scale.set(0.985);
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
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: 74,
fill: COLORS.white,
align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
symbolText.alpha = 0.22;
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 || 0x8b7a31;
};
self.reveal = function () {
if (self.revealed) {
return;
}
self.revealed = true;
bg.tint = 0x4c5f82;
symbolText.alpha = 1;
safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x081018
});
/****
* Game Code
****/
/****
* UI SCALE + LAYOUT
****/
/****
* LUCKY SCRATCH TYCOON
* Larger, cleaner, more polished Upit version
****/
/****
* Runtime State
****/
var SCREEN_W = 2048;
var SCREEN_H = 2732;
var CENTER_X = 1024;
var CENTER_Y = 1366;
var UI = {
panelWidth: 430,
panelHeight: 1040,
panelCornerOffset: 90,
buttonWidth: 560,
buttonHeight: 92,
smallButtonHeight: 84,
upgradeCardHeight: 108,
meterWidth: 320,
meterHeight: 26,
ticketWidth: 860,
ticketHeight: 1120,
symbolSize: 180,
symbolSpacing: 220,
leftPanelX: 70,
leftPanelY: 180,
rightPanelX: 1978,
rightPanelY: 180,
bottomPanelY: 2520,
titleY: 54,
modifierY: 170,
ticketY: 1320,
titleSize: 56,
sectionLabelSize: 28,
bigValueSize: 54,
mediumValueSize: 34,
bodySize: 24,
smallSize: 20,
tinySize: 17,
popupSize: 42
};
/****
* COLORS
****/
var COLORS = {
bg: 0x081018,
panel: 0x162235,
panelAlt: 0x1b2a40,
button: 0x255c99,
buttonHover: 0x3a78bf,
buttonDisabled: 0x3a4558,
primary: '#ffd76a',
white: '#ffffff',
soft: '#c9d3e3',
muted: '#94a3b8',
success: '#44e39a',
warning: '#ffb25f',
danger: '#ff6f7d',
cyan: '#69d2ff',
gold: '#ffe07a',
purple: '#b48cff'
};
/****
* Helpers
****/
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.32, 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 0x183b31;
}
if (type === 2) {
return 0x4a3818;
}
return 0x28344e;
}
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.3;
tween(flashOverlay, {
alpha: 0
}, {
duration: 260,
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: COLORS.soft,
payoutMult: 1,
jackpotBonus: 0
};
}
var pool = [{
id: 'none',
label: 'Standard Ticket',
color: COLORS.soft,
payoutMult: 1,
jackpotBonus: 0
}, {
id: 'double',
label: 'Double Payout',
color: COLORS.success,
payoutMult: 2,
jackpotBonus: 0
}, {
id: 'jackpot',
label: 'Double Jackpot Gain',
color: COLORS.gold,
payoutMult: 1,
jackpotBonus: 2
}, {
id: 'wild',
label: 'Lucky Wild Card',
color: COLORS.cyan,
payoutMult: 1.35,
jackpotBonus: 0
}, {
id: 'hot',
label: 'Hot Streak Boost',
color: COLORS.warning,
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(0x2d7697);
}
}
/****
* Achievements / Milestones
****/
function unlockAchievement(key, label) {
if (gameState[key]) {
return;
}
gameState[key] = true;
createPopupText(CENTER_X, 920, 'ACHIEVEMENT: ' + label, COLORS.primary, 30);
showFlash(0xffd700, 0.18);
}
function checkMilestones() {
if (!gameState.unlockLuckyTicket && gameState.ticketsScratchedTotal >= 10) {
gameState.unlockLuckyTicket = true;
unlockAchievement('achievementLuckyUnlocked', 'Lucky Tickets Unlocked!');
createPopupText(CENTER_X, 680, 'LUCKY TICKETS UNLOCKED', COLORS.success, 44);
}
if (!gameState.unlockGoldTicket && gameState.totalCoins >= 1000) {
gameState.unlockGoldTicket = true;
unlockAchievement('achievementGoldUnlocked', 'Gold Tickets Unlocked!');
createPopupText(CENTER_X, 740, 'GOLD TICKETS UNLOCKED', COLORS.primary, 44);
}
if (!gameState.unlockJackpotSystem && gameState.ticketsScratchedTotal >= 15) {
gameState.unlockJackpotSystem = true;
createPopupText(CENTER_X, 800, 'JACKPOT SYSTEM UNLOCKED', COLORS.cyan, 34);
}
if (!gameState.unlockModifiers && gameState.ticketsScratchedTotal >= 25) {
gameState.unlockModifiers = true;
createPopupText(CENTER_X, 860, 'TICKET MODIFIERS UNLOCKED', COLORS.warning, 34);
}
if (!gameState.unlockPrestige && gameState.totalCoins >= 5000) {
gameState.unlockPrestige = true;
createPopupText(CENTER_X, 920, 'PRESTIGE UNLOCKED', COLORS.white, 34);
}
}
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(CENTER_X, 1040, 'RUN RESTARTED', COLORS.white, 42);
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(CENTER_X, 1040, 'SAVE WIPED', COLORS.danger, 42);
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(CENTER_X, 980, 'PRESTIGE +' + reward + ' CHIPS', COLORS.white, 46);
showFlash(0xffffff, 0.32);
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 = UI.leftPanelX;
leftPanel.y = UI.leftPanelY;
game.addChild(leftPanel);
var leftBg = LK.getAsset('panelBg', {
anchorX: 0,
anchorY: 0
});
leftPanel.addChild(leftBg);
var leftHeader = new Text2('PLAYER STATS', {
size: UI.sectionLabelSize,
fill: COLORS.primary
});
leftHeader.anchor.set(0.5, 0);
leftHeader.x = UI.panelWidth * 0.5;
leftHeader.y = 26;
leftPanel.addChild(leftHeader);
var coinsLabelText = new Text2('COINS', {
size: UI.smallSize,
fill: COLORS.primary
});
coinsLabelText.anchor.set(0.5, 0);
coinsLabelText.x = UI.panelWidth * 0.5;
coinsLabelText.y = 92;
leftPanel.addChild(coinsLabelText);
coinText = new Text2('$100', {
size: UI.bigValueSize,
fill: COLORS.white
});
coinText.anchor.set(0.5, 0);
coinText.x = UI.panelWidth * 0.5;
coinText.y = 126;
leftPanel.addChild(coinText);
var streakLabelText = new Text2('STREAK', {
size: UI.smallSize,
fill: COLORS.danger
});
streakLabelText.anchor.set(0.5, 0);
streakLabelText.x = UI.panelWidth * 0.5;
streakLabelText.y = 212;
leftPanel.addChild(streakLabelText);
streakText = new Text2('x0', {
size: UI.mediumValueSize,
fill: COLORS.white
});
streakText.anchor.set(0.5, 0);
streakText.x = UI.panelWidth * 0.5;
streakText.y = 246;
leftPanel.addChild(streakText);
var jackpotLabelText = new Text2('JACKPOT METER', {
size: UI.smallSize,
fill: COLORS.primary
});
jackpotLabelText.anchor.set(0.5, 0);
jackpotLabelText.x = UI.panelWidth * 0.5;
jackpotLabelText.y = 328;
leftPanel.addChild(jackpotLabelText);
var jackpotBgBar = LK.getAsset('meterBarEmpty', {
anchorX: 0.5,
anchorY: 0
});
jackpotBgBar.x = UI.panelWidth * 0.5;
jackpotBgBar.y = 366;
leftPanel.addChild(jackpotBgBar);
jackpotBar = LK.getAsset('meterBarFull', {
anchorX: 0.5,
anchorY: 0
});
jackpotBar.x = UI.panelWidth * 0.5;
jackpotBar.y = 366;
jackpotBar.scaleX = 0;
leftPanel.addChild(jackpotBar);
jackpotText = new Text2('0 / 100', {
size: UI.smallSize,
fill: COLORS.white
});
jackpotText.anchor.set(0.5, 0);
jackpotText.x = UI.panelWidth * 0.5;
jackpotText.y = 402;
leftPanel.addChild(jackpotText);
prestigeText = new Text2('', {
size: UI.bodySize,
fill: COLORS.cyan,
align: 'center'
});
prestigeText.anchor.set(0.5, 0);
prestigeText.x = UI.panelWidth * 0.5;
prestigeText.y = 468;
leftPanel.addChild(prestigeText);
statsText = new Text2('', {
size: UI.smallSize,
fill: COLORS.soft,
align: 'center'
});
statsText.anchor.set(0.5, 0);
statsText.x = UI.panelWidth * 0.5;
statsText.y = 622;
leftPanel.addChild(statsText);
unlockText = new Text2('', {
size: UI.smallSize,
fill: COLORS.success,
align: 'center'
});
unlockText.anchor.set(0.5, 0);
unlockText.x = UI.panelWidth * 0.5;
unlockText.y = 780;
leftPanel.addChild(unlockText);
achievementText = new Text2('', {
size: UI.smallSize,
fill: COLORS.gold,
align: 'center'
});
achievementText.anchor.set(0.5, 0);
achievementText.x = UI.panelWidth * 0.5;
achievementText.y = 910;
leftPanel.addChild(achievementText);
rightPanel = new Container();
rightPanel.x = UI.rightPanelX;
rightPanel.y = UI.rightPanelY;
game.addChild(rightPanel);
var rightBg = LK.getAsset('panelBgAlt', {
anchorX: 1,
anchorY: 0
});
rightPanel.addChild(rightBg);
var shopLabelText = new Text2('UPGRADES', {
size: UI.sectionLabelSize,
fill: COLORS.success
});
shopLabelText.anchor.set(0.5, 0);
shopLabelText.x = -UI.panelWidth * 0.5;
shopLabelText.y = 26;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
card.y = 118 + i * 126;
card.x = -UI.panelWidth * 0.5;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
prestigeButton = new Button();
prestigeButton.x = -UI.panelWidth * 0.5;
prestigeButton.y = 878;
prestigeButton.setText('PRESTIGE');
prestigeButton.setTint(0x5b46a3);
prestigeButton.onClick = function () {
resetRunForPrestige();
};
rightPanel.addChild(prestigeButton);
restartRunButton = new Button('small');
restartRunButton.x = -UI.panelWidth * 0.5;
restartRunButton.y = 958;
restartRunButton.setText('RESTART RUN');
restartRunButton.setTint(0x2a6e56);
restartRunButton.onClick = function () {
restartRunOnly();
};
rightPanel.addChild(restartRunButton);
wipeSaveButton = new Button('small');
wipeSaveButton.x = -UI.panelWidth * 0.5;
wipeSaveButton.y = 1026;
wipeSaveButton.setText('WIPE SAVE');
wipeSaveButton.setTint(0x8a3340);
wipeSaveButton.onClick = function () {
hardRestartGame();
};
rightPanel.addChild(wipeSaveButton);
bottomPanel = new Container();
bottomPanel.x = CENTER_X;
bottomPanel.y = UI.bottomPanelY;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'BASIC\n$5',
cost: 5,
type: 0,
tint: 0x255c99
}, {
name: 'LUCKY\n$15',
cost: 15,
type: 1,
tint: 0x29765c
}, {
name: 'GOLD\n$50',
cost: 50,
type: 2,
tint: 0x977336
}];
for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
btn.x = -420 + t * 420;
btn.y = 0;
btn.setText(ticketTypes[t].name);
btn.setTint(ticketTypes[t].tint);
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 = CENTER_X;
centerInfoPanel.y = UI.modifierY;
game.addChild(centerInfoPanel);
modifierText = new Text2('', {
size: 28,
fill: COLORS.white,
align: 'center'
});
modifierText.anchor.set(0.5, 0);
centerInfoPanel.addChild(modifierText);
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: UI.titleSize,
fill: COLORS.primary
});
titleText.anchor.set(0.5, 0);
titleText.x = CENTER_X;
titleText.y = UI.titleY;
game.addChild(titleText);
flashOverlay = LK.getAsset('flashBg', {
anchorX: 0.5,
anchorY: 0.5
});
flashOverlay.x = CENTER_X;
flashOverlay.y = CENTER_Y;
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 = CENTER_X;
dark.y = CENTER_Y;
dark.alpha = 0.72;
gameOverOverlay.addChild(dark);
var title = new Text2('YOU RAN OUT OF MONEY', {
size: 60,
fill: COLORS.danger,
align: 'center'
});
title.anchor.set(0.5, 0.5);
title.x = CENTER_X;
title.y = 1050;
gameOverOverlay.addChild(title);
var sub = new Text2('Your run is over. Restart the run or wipe your save.', {
size: 28,
fill: COLORS.white,
align: 'center'
});
sub.anchor.set(0.5, 0.5);
sub.x = CENTER_X;
sub.y = 1130;
gameOverOverlay.addChild(sub);
gameOverRestartButton = new Button();
gameOverRestartButton.x = CENTER_X;
gameOverRestartButton.y = 1260;
gameOverRestartButton.setText('RESTART RUN');
gameOverRestartButton.setTint(0x2a6e56);
gameOverRestartButton.onClick = function () {
restartRunOnly();
};
gameOverOverlay.addChild(gameOverRestartButton);
}
function showGameOver() {
gameState.isGameOver = true;
dragData.active = false;
if (gameOverOverlay) {
gameOverOverlay.visible = true;
}
showFlash(0xff0000, 0.14);
}
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 Scratched: ' + gameState.ticketsScratchedTotal + '\nWins / Losses: ' + gameState.statsTotalWins + ' / ' + gameState.statsTotalLosses);
}
if (unlockText) {
unlockText.setText('Unlocks' + '\nLucky Ticket: ' + (gameState.unlockLuckyTicket ? 'Yes' : 'No') + '\nGold Ticket: ' + (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 = CENTER_X;
currentTicket.y = UI.ticketY;
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(1720, 960, 'UPGRADE BOUGHT', COLORS.success, 28);
updateUI();
saveProgress();
checkForGameOver();
}
function createPopupText(x, y, textValue, color, size) {
var text = new Text2(textValue, {
size: size || UI.popupSize,
fill: color || COLORS.primary,
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 - 110,
alpha: 0
}, {
duration: 850,
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 < 10; i++) {
var particle = new DustParticle();
particle.x = x + (Math.random() - 0.5) * 40;
particle.y = y + (Math.random() - 0.5) * 40;
game.addChild(particle);
dustParticles.push(particle);
}
}
function createCoinBurst(x, y, count) {
for (var i = 0; i < count; i++) {
createPopupText(x + (Math.random() - 0.5) * 140, y + (Math.random() - 0.5) * 60, '+', COLORS.primary, 24);
}
}
function resolveCurrentTicket(result) {
if (!currentTicket) {
return;
}
var wasNearMiss = !result.win && result.count === 2;
if (wasNearMiss) {
gameState.statsNearMisses++;
createPopupText(currentTicket.x, currentTicket.y - 170, 'NEAR MISS!', COLORS.warning, 30);
}
if (result.win) {
safePlaySound('winSound');
showFlash(0xffd700, 0.13);
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 - 220, 'JACKPOT BONUS ' + formatMoney(jackpotReward), COLORS.success, 40);
createCoinBurst(currentTicket.x, currentTicket.y - 50, 14);
showFlash(0x00ff88, 0.24);
}
}
createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), COLORS.primary, 44);
createCoinBurst(currentTicket.x, currentTicket.y + 55, 10);
} else {
gameState.streak = 0;
gameState.statsTotalLosses++;
createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', COLORS.danger, 34);
}
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 / 520;
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 (styleType) {
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
self.enabled = true;
self.hitWidth = UI.buttonWidth;
self.hitHeight = styleType === 'small' ? UI.smallButtonHeight : UI.buttonHeight;
self.styleType = styleType || 'normal';
var assetName = self.styleType === 'small' ? 'smallButtonBg' : 'buttonBg';
var bg = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: self.styleType === 'small' ? 24 : 28,
fill: COLORS.white,
align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
self.setTint = function (color) {
bg.tint = color;
self.baseTint = color;
};
self.setEnabled = function (enabled) {
self.enabled = enabled;
self.alpha = enabled ? 1 : 0.45;
if (!enabled) {
bg.tint = COLORS.buttonDisabled;
} else {
bg.tint = self.isHovered ? COLORS.buttonHover : self.baseTint || COLORS.button;
}
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
if (!self.enabled) {
bg.tint = COLORS.buttonDisabled;
return;
}
bg.tint = hovered ? COLORS.buttonHover : self.baseTint || COLORS.button;
};
self.press = function () {
if (!self.enabled) {
return;
}
safePlaySound('buttonClickSound');
self.scale.set(0.98);
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
self.onClick();
};
self.setTint(COLORS.button);
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.82;
self.velocityX = (Math.random() - 0.5) * 6;
self.velocityY = (Math.random() - 0.5) * 6;
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.6);
dust.alpha = 0.82 * (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 = UI.ticketWidth;
self.ticketHeight = UI.ticketHeight;
self.modifier = rollTicketModifier(ticketType);
var scratchSpeedBonus = gameState.upgrades[2] * 0.03;
var prestigeScratchBonus = gameState.prestigeChips * 0.003;
self.autoResolveThreshold = clamp(0.56 - scratchSpeedBonus - prestigeScratchBonus, 0.22, 0.56);
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.95;
var titleText = new Text2(getTicketName(self.ticketType), {
size: 38,
fill: COLORS.white,
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -500;
self.addChild(titleText);
var infoText = new Text2('Scratch to reveal 3 matching symbols', {
size: 24,
fill: COLORS.primary,
align: 'center'
});
infoText.anchor.set(0.5, 0);
infoText.y = -450;
self.addChild(infoText);
var modifierLine = new Text2(self.modifier.label, {
size: 26,
fill: self.modifier.color,
align: 'center'
});
modifierLine.anchor.set(0.5, 0);
modifierLine.y = -405;
self.addChild(modifierLine);
var gridStartX = -UI.symbolSpacing;
var gridStartY = -UI.symbolSpacing;
var spacing = UI.symbolSpacing;
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 + 70;
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.95 - self.scratchAmount, 0, 0.95);
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 = UI.buttonWidth;
self.hitHeight = UI.upgradeCardHeight;
self.onClick = function () {};
var bg = self.attachAsset('upgradeCardBg', {
anchorX: 0.5,
anchorY: 0.5
});
var nameText = new Text2('', {
size: 24,
fill: COLORS.white,
align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -28;
self.addChild(nameText);
var descText = new Text2('', {
size: 16,
fill: COLORS.muted,
align: 'center'
});
descText.anchor.set(0.5, 0);
descText.y = 2;
self.addChild(descText);
var costText = new Text2('', {
size: 20,
fill: COLORS.gold,
align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 34;
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'];
var descs = ['More good modifier rolls', 'Higher match payouts', 'Resolve cards faster', 'Bigger streak scaling', 'Fill jackpot quicker', 'Extra economy support'];
nameText.setText(names[id] + ' Lv.' + (level + 1));
descText.setText(descs[id]);
costText.setText('BUY ' + formatMoney(cost));
self.alpha = affordable ? 1 : 0.52;
};
self.setHovered = function (hovered) {
bg.tint = hovered ? 0x2c5c8f : 0x1f3b60;
};
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.scale.set(0.985);
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
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: 74,
fill: COLORS.white,
align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
symbolText.alpha = 0.22;
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 || 0x8b7a31;
};
self.reveal = function () {
if (self.revealed) {
return;
}
self.revealed = true;
bg.tint = 0x4c5f82;
symbolText.alpha = 1;
safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x081018
});
/****
* Game Code
****/
/****
* UI SCALE + LAYOUT
****/
/****
* LUCKY SCRATCH TYCOON
* Larger, cleaner, more polished Upit version
****/
/****
* Runtime State
****/
var SCREEN_W = 2048;
var SCREEN_H = 2732;
var CENTER_X = 1024;
var CENTER_Y = 1366;
var UI = {
panelWidth: 430,
panelHeight: 1040,
panelCornerOffset: 90,
buttonWidth: 560,
buttonHeight: 92,
smallButtonHeight: 84,
upgradeCardHeight: 108,
meterWidth: 320,
meterHeight: 26,
ticketWidth: 860,
ticketHeight: 1120,
symbolSize: 180,
symbolSpacing: 220,
leftPanelX: 70,
leftPanelY: 180,
rightPanelX: 1978,
rightPanelY: 180,
bottomPanelY: 2520,
titleY: 54,
modifierY: 170,
ticketY: 1320,
titleSize: 56,
sectionLabelSize: 28,
bigValueSize: 54,
mediumValueSize: 34,
bodySize: 24,
smallSize: 20,
tinySize: 17,
popupSize: 42
};
/****
* COLORS
****/
var COLORS = {
bg: 0x081018,
panel: 0x162235,
panelAlt: 0x1b2a40,
button: 0x255c99,
buttonHover: 0x3a78bf,
buttonDisabled: 0x3a4558,
primary: '#ffd76a',
white: '#ffffff',
soft: '#c9d3e3',
muted: '#94a3b8',
success: '#44e39a',
warning: '#ffb25f',
danger: '#ff6f7d',
cyan: '#69d2ff',
gold: '#ffe07a',
purple: '#b48cff'
};
/****
* Helpers
****/
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.32, 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 0x183b31;
}
if (type === 2) {
return 0x4a3818;
}
return 0x28344e;
}
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.3;
tween(flashOverlay, {
alpha: 0
}, {
duration: 260,
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: COLORS.soft,
payoutMult: 1,
jackpotBonus: 0
};
}
var pool = [{
id: 'none',
label: 'Standard Ticket',
color: COLORS.soft,
payoutMult: 1,
jackpotBonus: 0
}, {
id: 'double',
label: 'Double Payout',
color: COLORS.success,
payoutMult: 2,
jackpotBonus: 0
}, {
id: 'jackpot',
label: 'Double Jackpot Gain',
color: COLORS.gold,
payoutMult: 1,
jackpotBonus: 2
}, {
id: 'wild',
label: 'Lucky Wild Card',
color: COLORS.cyan,
payoutMult: 1.35,
jackpotBonus: 0
}, {
id: 'hot',
label: 'Hot Streak Boost',
color: COLORS.warning,
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(0x2d7697);
}
}
/****
* Achievements / Milestones
****/
function unlockAchievement(key, label) {
if (gameState[key]) {
return;
}
gameState[key] = true;
createPopupText(CENTER_X, 920, 'ACHIEVEMENT: ' + label, COLORS.primary, 30);
showFlash(0xffd700, 0.18);
}
function checkMilestones() {
if (!gameState.unlockLuckyTicket && gameState.ticketsScratchedTotal >= 10) {
gameState.unlockLuckyTicket = true;
unlockAchievement('achievementLuckyUnlocked', 'Lucky Tickets Unlocked!');
createPopupText(CENTER_X, 680, 'LUCKY TICKETS UNLOCKED', COLORS.success, 44);
}
if (!gameState.unlockGoldTicket && gameState.totalCoins >= 1000) {
gameState.unlockGoldTicket = true;
unlockAchievement('achievementGoldUnlocked', 'Gold Tickets Unlocked!');
createPopupText(CENTER_X, 740, 'GOLD TICKETS UNLOCKED', COLORS.primary, 44);
}
if (!gameState.unlockJackpotSystem && gameState.ticketsScratchedTotal >= 15) {
gameState.unlockJackpotSystem = true;
createPopupText(CENTER_X, 800, 'JACKPOT SYSTEM UNLOCKED', COLORS.cyan, 34);
}
if (!gameState.unlockModifiers && gameState.ticketsScratchedTotal >= 25) {
gameState.unlockModifiers = true;
createPopupText(CENTER_X, 860, 'TICKET MODIFIERS UNLOCKED', COLORS.warning, 34);
}
if (!gameState.unlockPrestige && gameState.totalCoins >= 5000) {
gameState.unlockPrestige = true;
createPopupText(CENTER_X, 920, 'PRESTIGE UNLOCKED', COLORS.white, 34);
}
}
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(CENTER_X, 1040, 'RUN RESTARTED', COLORS.white, 42);
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(CENTER_X, 1040, 'SAVE WIPED', COLORS.danger, 42);
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(CENTER_X, 980, 'PRESTIGE +' + reward + ' CHIPS', COLORS.white, 46);
showFlash(0xffffff, 0.32);
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 = UI.leftPanelX;
leftPanel.y = UI.leftPanelY;
game.addChild(leftPanel);
var leftBg = LK.getAsset('panelBg', {
anchorX: 0,
anchorY: 0
});
leftPanel.addChild(leftBg);
var leftHeader = new Text2('PLAYER STATS', {
size: UI.sectionLabelSize,
fill: COLORS.primary
});
leftHeader.anchor.set(0.5, 0);
leftHeader.x = UI.panelWidth * 0.5;
leftHeader.y = 26;
leftPanel.addChild(leftHeader);
var coinsLabelText = new Text2('COINS', {
size: UI.smallSize,
fill: COLORS.primary
});
coinsLabelText.anchor.set(0.5, 0);
coinsLabelText.x = UI.panelWidth * 0.5;
coinsLabelText.y = 92;
leftPanel.addChild(coinsLabelText);
coinText = new Text2('$100', {
size: UI.bigValueSize,
fill: COLORS.white
});
coinText.anchor.set(0.5, 0);
coinText.x = UI.panelWidth * 0.5;
coinText.y = 126;
leftPanel.addChild(coinText);
var streakLabelText = new Text2('STREAK', {
size: UI.smallSize,
fill: COLORS.danger
});
streakLabelText.anchor.set(0.5, 0);
streakLabelText.x = UI.panelWidth * 0.5;
streakLabelText.y = 212;
leftPanel.addChild(streakLabelText);
streakText = new Text2('x0', {
size: UI.mediumValueSize,
fill: COLORS.white
});
streakText.anchor.set(0.5, 0);
streakText.x = UI.panelWidth * 0.5;
streakText.y = 246;
leftPanel.addChild(streakText);
var jackpotLabelText = new Text2('JACKPOT METER', {
size: UI.smallSize,
fill: COLORS.primary
});
jackpotLabelText.anchor.set(0.5, 0);
jackpotLabelText.x = UI.panelWidth * 0.5;
jackpotLabelText.y = 328;
leftPanel.addChild(jackpotLabelText);
var jackpotBgBar = LK.getAsset('meterBarEmpty', {
anchorX: 0.5,
anchorY: 0
});
jackpotBgBar.x = UI.panelWidth * 0.5;
jackpotBgBar.y = 366;
leftPanel.addChild(jackpotBgBar);
jackpotBar = LK.getAsset('meterBarFull', {
anchorX: 0.5,
anchorY: 0
});
jackpotBar.x = UI.panelWidth * 0.5;
jackpotBar.y = 366;
jackpotBar.scaleX = 0;
leftPanel.addChild(jackpotBar);
jackpotText = new Text2('0 / 100', {
size: UI.smallSize,
fill: COLORS.white
});
jackpotText.anchor.set(0.5, 0);
jackpotText.x = UI.panelWidth * 0.5;
jackpotText.y = 402;
leftPanel.addChild(jackpotText);
prestigeText = new Text2('', {
size: UI.bodySize,
fill: COLORS.cyan,
align: 'center'
});
prestigeText.anchor.set(0.5, 0);
prestigeText.x = UI.panelWidth * 0.5;
prestigeText.y = 468;
leftPanel.addChild(prestigeText);
statsText = new Text2('', {
size: UI.smallSize,
fill: COLORS.soft,
align: 'center'
});
statsText.anchor.set(0.5, 0);
statsText.x = UI.panelWidth * 0.5;
statsText.y = 622;
leftPanel.addChild(statsText);
unlockText = new Text2('', {
size: UI.smallSize,
fill: COLORS.success,
align: 'center'
});
unlockText.anchor.set(0.5, 0);
unlockText.x = UI.panelWidth * 0.5;
unlockText.y = 780;
leftPanel.addChild(unlockText);
achievementText = new Text2('', {
size: UI.smallSize,
fill: COLORS.gold,
align: 'center'
});
achievementText.anchor.set(0.5, 0);
achievementText.x = UI.panelWidth * 0.5;
achievementText.y = 910;
leftPanel.addChild(achievementText);
rightPanel = new Container();
rightPanel.x = UI.rightPanelX;
rightPanel.y = UI.rightPanelY;
game.addChild(rightPanel);
var rightBg = LK.getAsset('panelBgAlt', {
anchorX: 1,
anchorY: 0
});
rightPanel.addChild(rightBg);
var shopLabelText = new Text2('UPGRADES', {
size: UI.sectionLabelSize,
fill: COLORS.success
});
shopLabelText.anchor.set(0.5, 0);
shopLabelText.x = -UI.panelWidth * 0.5;
shopLabelText.y = 26;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
card.y = 118 + i * 126;
card.x = -UI.panelWidth * 0.5;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
prestigeButton = new Button();
prestigeButton.x = -UI.panelWidth * 0.5;
prestigeButton.y = 878;
prestigeButton.setText('PRESTIGE');
prestigeButton.setTint(0x5b46a3);
prestigeButton.onClick = function () {
resetRunForPrestige();
};
rightPanel.addChild(prestigeButton);
restartRunButton = new Button('small');
restartRunButton.x = -UI.panelWidth * 0.5;
restartRunButton.y = 958;
restartRunButton.setText('RESTART RUN');
restartRunButton.setTint(0x2a6e56);
restartRunButton.onClick = function () {
restartRunOnly();
};
rightPanel.addChild(restartRunButton);
wipeSaveButton = new Button('small');
wipeSaveButton.x = -UI.panelWidth * 0.5;
wipeSaveButton.y = 1026;
wipeSaveButton.setText('WIPE SAVE');
wipeSaveButton.setTint(0x8a3340);
wipeSaveButton.onClick = function () {
hardRestartGame();
};
rightPanel.addChild(wipeSaveButton);
bottomPanel = new Container();
bottomPanel.x = CENTER_X;
bottomPanel.y = UI.bottomPanelY;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'BASIC\n$5',
cost: 5,
type: 0,
tint: 0x255c99
}, {
name: 'LUCKY\n$15',
cost: 15,
type: 1,
tint: 0x29765c
}, {
name: 'GOLD\n$50',
cost: 50,
type: 2,
tint: 0x977336
}];
for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
btn.x = -420 + t * 420;
btn.y = 0;
btn.setText(ticketTypes[t].name);
btn.setTint(ticketTypes[t].tint);
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 = CENTER_X;
centerInfoPanel.y = UI.modifierY;
game.addChild(centerInfoPanel);
modifierText = new Text2('', {
size: 28,
fill: COLORS.white,
align: 'center'
});
modifierText.anchor.set(0.5, 0);
centerInfoPanel.addChild(modifierText);
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: UI.titleSize,
fill: COLORS.primary
});
titleText.anchor.set(0.5, 0);
titleText.x = CENTER_X;
titleText.y = UI.titleY;
game.addChild(titleText);
flashOverlay = LK.getAsset('flashBg', {
anchorX: 0.5,
anchorY: 0.5
});
flashOverlay.x = CENTER_X;
flashOverlay.y = CENTER_Y;
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 = CENTER_X;
dark.y = CENTER_Y;
dark.alpha = 0.72;
gameOverOverlay.addChild(dark);
var title = new Text2('YOU RAN OUT OF MONEY', {
size: 60,
fill: COLORS.danger,
align: 'center'
});
title.anchor.set(0.5, 0.5);
title.x = CENTER_X;
title.y = 1050;
gameOverOverlay.addChild(title);
var sub = new Text2('Your run is over. Restart the run or wipe your save.', {
size: 28,
fill: COLORS.white,
align: 'center'
});
sub.anchor.set(0.5, 0.5);
sub.x = CENTER_X;
sub.y = 1130;
gameOverOverlay.addChild(sub);
gameOverRestartButton = new Button();
gameOverRestartButton.x = CENTER_X;
gameOverRestartButton.y = 1260;
gameOverRestartButton.setText('RESTART RUN');
gameOverRestartButton.setTint(0x2a6e56);
gameOverRestartButton.onClick = function () {
restartRunOnly();
};
gameOverOverlay.addChild(gameOverRestartButton);
}
function showGameOver() {
gameState.isGameOver = true;
dragData.active = false;
if (gameOverOverlay) {
gameOverOverlay.visible = true;
}
showFlash(0xff0000, 0.14);
}
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 Scratched: ' + gameState.ticketsScratchedTotal + '\nWins / Losses: ' + gameState.statsTotalWins + ' / ' + gameState.statsTotalLosses);
}
if (unlockText) {
unlockText.setText('Unlocks' + '\nLucky Ticket: ' + (gameState.unlockLuckyTicket ? 'Yes' : 'No') + '\nGold Ticket: ' + (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 = CENTER_X;
currentTicket.y = UI.ticketY;
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(1720, 960, 'UPGRADE BOUGHT', COLORS.success, 28);
updateUI();
saveProgress();
checkForGameOver();
}
function createPopupText(x, y, textValue, color, size) {
var text = new Text2(textValue, {
size: size || UI.popupSize,
fill: color || COLORS.primary,
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 - 110,
alpha: 0
}, {
duration: 850,
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 < 10; i++) {
var particle = new DustParticle();
particle.x = x + (Math.random() - 0.5) * 40;
particle.y = y + (Math.random() - 0.5) * 40;
game.addChild(particle);
dustParticles.push(particle);
}
}
function createCoinBurst(x, y, count) {
for (var i = 0; i < count; i++) {
createPopupText(x + (Math.random() - 0.5) * 140, y + (Math.random() - 0.5) * 60, '+', COLORS.primary, 24);
}
}
function resolveCurrentTicket(result) {
if (!currentTicket) {
return;
}
var wasNearMiss = !result.win && result.count === 2;
if (wasNearMiss) {
gameState.statsNearMisses++;
createPopupText(currentTicket.x, currentTicket.y - 170, 'NEAR MISS!', COLORS.warning, 30);
}
if (result.win) {
safePlaySound('winSound');
showFlash(0xffd700, 0.13);
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 - 220, 'JACKPOT BONUS ' + formatMoney(jackpotReward), COLORS.success, 40);
createCoinBurst(currentTicket.x, currentTicket.y - 50, 14);
showFlash(0x00ff88, 0.24);
}
}
createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), COLORS.primary, 44);
createCoinBurst(currentTicket.x, currentTicket.y + 55, 10);
} else {
gameState.streak = 0;
gameState.statsTotalLosses++;
createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', COLORS.danger, 34);
}
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 / 520;
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();
}
};