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 () {
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
self.hitWidth = 280;
self.hitHeight = 60;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
size: 22,
fill: '#ffffff',
align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
self.setEnabled = function (enabled) {
self.enabled = enabled;
self.alpha = enabled ? 1 : 0.45;
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.containsPoint = function (x, y) {
return pointInRect(x, y, self.x, self.y, self.hitWidth, self.hitHeight);
};
self.press = function () {
if (self.enabled === false) return;
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var DustParticle = Container.expand(function () {
var self = Container.call(this);
var dust = self.attachAsset('dustParticle', {
anchorX: 0.5,
anchorY: 0.5
});
dust.alpha = 0.8;
self.velocityX = (Math.random() - 0.5) * 4;
self.velocityY = (Math.random() - 0.5) * 4;
self.gravity = 0.08;
self.life = 0.4;
self.maxLife = 0.4;
self.update = function () {
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.life -= 1 / 60;
dust.alpha = 0.8 * (self.life / self.maxLife);
if (self.life <= 0) {
if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
var ScratchTicket = Container.expand(function (ticketType) {
var self = Container.call(this);
self.ticketType = ticketType || 0;
self.symbols = [];
self.scratchAmount = 0;
self.resolved = false;
self.canScratch = true;
self.autoResolveThreshold = 0.55 - gameState.upgrades[2] * 0.03;
self.autoResolveThreshold = clamp(self.autoResolveThreshold, 0.25, 0.55);
self.ticketWidth = 600;
self.ticketHeight = 800;
var bg = self.attachAsset('ticketBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.tint = getTicketTint(self.ticketType);
var overlay = self.attachAsset('scratchOverlay', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.alpha = 0.96;
var titleText = new Text2(getTicketName(self.ticketType), {
size: 28,
fill: '#ffffff',
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -350;
self.addChild(titleText);
var infoText = new Text2('Scratch to reveal 3 matching symbols', {
size: 16,
fill: '#ffd700',
align: 'center'
});
infoText.anchor.set(0.5, 0);
infoText.y = -310;
self.addChild(infoText);
var gridStartX = -140;
var gridStartY = -140;
var spacing = 160;
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 3; col++) {
var symbol = new _Symbol();
symbol.x = gridStartX + col * spacing;
symbol.y = gridStartY + row * spacing;
self.symbols.push(symbol);
self.addChild(symbol);
}
}
self.containsGlobalPoint = function (x, y) {
return pointInRect(x, y, self.x, self.y, self.ticketWidth, self.ticketHeight);
};
self.updateScratch = function (amount) {
if (!self.canScratch || self.resolved) return;
self.scratchAmount = clamp(self.scratchAmount + amount, 0, 1);
overlay.alpha = clamp(0.96 - self.scratchAmount, 0, 0.96);
var revealTarget = Math.floor(self.scratchAmount * self.symbols.length);
var alreadyRevealed = 0;
for (var i = 0; i < self.symbols.length; i++) {
if (self.symbols[i].revealed) alreadyRevealed++;
}
while (alreadyRevealed < revealTarget) {
var hidden = [];
for (var j = 0; j < self.symbols.length; j++) {
if (!self.symbols[j].revealed) hidden.push(self.symbols[j]);
}
if (hidden.length <= 0) break;
var pick = hidden[Math.floor(Math.random() * hidden.length)];
pick.reveal();
alreadyRevealed++;
}
if (self.scratchAmount >= self.autoResolveThreshold) {
self.resolveTicket();
}
};
self.checkMatch = function () {
var counts = {};
for (var i = 0; i < self.symbols.length; i++) {
var type = self.symbols[i].symbolType;
counts[type] = (counts[type] || 0) + 1;
}
var bestType = null;
var bestCount = 0;
for (var key in counts) {
if (counts[key] > bestCount) {
bestType = key;
bestCount = counts[key];
}
}
if (bestCount >= 3) {
return {
win: true,
type: bestType,
count: bestCount
};
}
return {
win: false,
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();
resolveCurrentTicket(result);
};
return self;
});
var UpgradeCard = Container.expand(function () {
var self = Container.call(this);
self.upgradeId = 0;
self.level = 0;
self.cost = 0;
self.onClick = function () {};
self.isHovered = false;
self.hitWidth = 280;
self.hitHeight = 42;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.scaleY = 0.7;
var nameText = new Text2('', {
size: 16,
fill: '#ffffff',
align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -15;
self.addChild(nameText);
var costText = new Text2('', {
size: 14,
fill: '#ffd700',
align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 5;
self.addChild(costText);
self.setUpgrade = function (id, level, cost, affordable) {
self.upgradeId = id;
self.level = level;
self.cost = cost;
var names = ['Better Odds', 'Bigger Payouts', 'Faster Scratch', 'Combo Boost', 'Jackpot Gain', 'Coin Magnet'];
nameText.setText(names[id] + ' Lv' + (level + 1));
costText.setText('Cost: ' + formatMoney(cost));
self.alpha = affordable ? 1 : 0.55;
};
self.setHovered = function (hovered) {
self.isHovered = hovered;
bg.tint = hovered ? 0x15537a : 0x0f3460;
};
self.containsGlobalPoint = function (x, y) {
var gx = rightPanel.x + self.x;
var gy = rightPanel.y + self.y;
return pointInRect(x, y, gx, gy, self.hitWidth, self.hitHeight);
};
self.press = function () {
safePlaySound('buttonClickSound');
self.onClick();
};
return self;
});
var _Symbol = Container.expand(function () {
var self = Container.call(this);
self.symbolType = 0;
self.revealed = false;
var bg = self.attachAsset('symbolBg', {
anchorX: 0.5,
anchorY: 0.5
});
var symbolText = new Text2('', {
size: 56,
fill: '#ffffff',
align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
symbolText.alpha = 0.25;
self.addChild(symbolText);
self.setSymbol = function (type) {
self.symbolType = type;
var symbols = ['🍀', '💎', '🎁', '⭐', '🔥', '🎰'];
symbolText.setText(symbols[type % symbols.length]);
};
self.reveal = function () {
if (self.revealed) return;
self.revealed = true;
bg.tint = 0x4a4a6a;
symbolText.alpha = 1;
safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
/****
* Helpers
****/
/****
* Game State
****/
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function safePlaySound(name) {
try {
var s = LK.getSound(name);
if (s && s.play) s.play();
} catch (e) {}
}
function safePlayMusic(name, options) {
try {
LK.playMusic(name, options || {});
} catch (e) {}
}
function formatMoney(value) {
return '$' + Math.floor(value);
}
function pointInRect(px, py, cx, cy, width, height) {
return px >= cx - width * 0.5 && px <= cx + width * 0.5 && py >= cy - height * 0.5 && py <= cy + height * 0.5;
}
function getTicketCost(type) {
var costs = [5, 15, 50];
return costs[type] || 5;
}
function getUpgradeCost(id, level) {
var baseCosts = [50, 75, 60, 80, 100, 70];
return Math.floor(baseCosts[id] * Math.pow(1.3, level));
}
function getTicketName(type) {
if (type === 1) return 'LUCKY TICKET';
if (type === 2) return 'GOLD TICKET';
return 'BASIC TICKET';
}
function getTicketTint(type) {
if (type === 1) return 0x1a3a2e;
if (type === 2) return 0x3a2e1a;
return 0x2a2a4e;
}
if (!storage.coins && storage.coins !== 0) storage.coins = 100;
if (!storage.totalCoins && storage.totalCoins !== 0) storage.totalCoins = 100;
if (!storage.bestStreak && storage.bestStreak !== 0) storage.bestStreak = 0;
if (!storage.jackpotMeter && storage.jackpotMeter !== 0) storage.jackpotMeter = 0;
if (!storage.ticketsScratchedTotal && storage.ticketsScratchedTotal !== 0) storage.ticketsScratchedTotal = 0;
if (!storage.biggestWin && storage.biggestWin !== 0) storage.biggestWin = 0;
if (!storage.jackpotsHit && storage.jackpotsHit !== 0) storage.jackpotsHit = 0;
if (!storage.prestigeCount && storage.prestigeCount !== 0) storage.prestigeCount = 0;
if (!storage.prestigeChips && storage.prestigeChips !== 0) storage.prestigeChips = 0;
if (!storage.upgrades) storage.upgrades = [0, 0, 0, 0, 0, 0];
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.upgrades
};
var currentTicket = null;
var dragData = {
active: false,
lastX: 0,
lastY: 0
};
var dustParticles = [];
var popupTexts = [];
var leftPanel, rightPanel, bottomPanel;
var coinText, streakText, jackpotBar, jackpotText, statsText;
var ticketBuyButtons = [];
var upgradeCards = [];
/****
* UI
****/
function initUI() {
leftPanel = new Container();
leftPanel.x = 100;
leftPanel.y = 100;
game.addChild(leftPanel);
var leftBg = LK.getAsset('panelBg', {
anchorX: 0,
anchorY: 0
});
leftBg.scaleY = 0.8;
leftPanel.addChild(leftBg);
var coinsLabelText = new Text2('COINS', {
size: 20,
fill: '#ffd700'
});
coinsLabelText.anchor.set(0.5, 0);
coinsLabelText.x = 150;
coinsLabelText.y = 20;
leftPanel.addChild(coinsLabelText);
coinText = new Text2('$100', {
size: 32,
fill: '#ffffff'
});
coinText.anchor.set(0.5, 0);
coinText.x = 150;
coinText.y = 60;
leftPanel.addChild(coinText);
var streakLabelText = new Text2('STREAK', {
size: 16,
fill: '#ff6b6b'
});
streakLabelText.anchor.set(0.5, 0);
streakLabelText.x = 150;
streakLabelText.y = 140;
leftPanel.addChild(streakLabelText);
streakText = new Text2('x0', {
size: 24,
fill: '#ffffff'
});
streakText.anchor.set(0.5, 0);
streakText.x = 150;
streakText.y = 170;
leftPanel.addChild(streakText);
var jackpotLabelText = new Text2('JACKPOT', {
size: 16,
fill: '#ffd700'
});
jackpotLabelText.anchor.set(0.5, 0);
jackpotLabelText.x = 150;
jackpotLabelText.y = 240;
leftPanel.addChild(jackpotLabelText);
var jackpotBgBar = LK.getAsset('meterBarEmpty', {
anchorX: 0.5,
anchorY: 0
});
jackpotBgBar.x = 150;
jackpotBgBar.y = 270;
leftPanel.addChild(jackpotBgBar);
jackpotBar = LK.getAsset('meterBarFull', {
anchorX: 0.5,
anchorY: 0
});
jackpotBar.x = 150;
jackpotBar.y = 270;
jackpotBar.scaleX = 0;
leftPanel.addChild(jackpotBar);
jackpotText = new Text2('0/100', {
size: 12,
fill: '#ffffff'
});
jackpotText.anchor.set(0.5, 0);
jackpotText.x = 150;
jackpotText.y = 300;
leftPanel.addChild(jackpotText);
statsText = new Text2('', {
size: 12,
fill: '#bbbbbb',
align: 'center'
});
statsText.anchor.set(0.5, 0);
statsText.x = 150;
statsText.y = 350;
leftPanel.addChild(statsText);
rightPanel = new Container();
rightPanel.x = 1848;
rightPanel.y = 100;
game.addChild(rightPanel);
var rightBg = LK.getAsset('panelBg', {
anchorX: 1,
anchorY: 0
});
rightBg.scaleY = 0.8;
rightPanel.addChild(rightBg);
var shopLabelText = new Text2('UPGRADES', {
size: 20,
fill: '#00ff88'
});
shopLabelText.anchor.set(0.5, 0);
shopLabelText.x = -150;
shopLabelText.y = 20;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
card.y = 90 + i * 82;
card.x = -150;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
bottomPanel = new Container();
bottomPanel.x = 1024;
bottomPanel.y = 2520;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'Basic\n$5',
cost: 5,
type: 0
}, {
name: 'Lucky\n$15',
cost: 15,
type: 1
}, {
name: 'Gold\n$50',
cost: 50,
type: 2
}];
for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
btn.x = -300 + t * 300;
btn.y = 0;
btn.setText(ticketTypes[t].name);
btn.cost = ticketTypes[t].cost;
btn.type = ticketTypes[t].type;
btn.onClick = function () {
onBuyTicket(this.type, this.cost);
}.bind(btn);
ticketBuyButtons.push(btn);
bottomPanel.addChild(btn);
}
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: 40,
fill: '#ffd700'
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 30;
game.addChild(titleText);
}
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 (statsText) {
statsText.setText('Best Streak: ' + gameState.bestStreak + '\nBiggest Win: ' + formatMoney(gameState.biggestWin) + '\nTickets: ' + gameState.ticketsScratchedTotal);
}
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);
}
for (var j = 0; j < ticketBuyButtons.length; j++) {
ticketBuyButtons[j].setEnabled(gameState.coins >= ticketBuyButtons[j].cost);
}
}
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.upgrades = gameState.upgrades;
}
/****
* Gameplay
****/
function createNewTicket(ticketType) {
if (currentTicket) {
if (currentTicket.parent) currentTicket.parent.removeChild(currentTicket);
currentTicket.destroy();
}
currentTicket = new ScratchTicket(ticketType);
currentTicket.x = 1024;
currentTicket.y = 1200;
game.addChild(currentTicket);
dragData.active = false;
}
function onBuyTicket(type, cost) {
if (currentTicket && !currentTicket.resolved) return;
if (gameState.coins < cost) return;
gameState.coins -= cost;
gameState.ticketsScratchedTotal++;
createNewTicket(type);
updateUI();
saveProgress();
}
function onUpgradeClick(upgradeId) {
var level = gameState.upgrades[upgradeId];
var cost = getUpgradeCost(upgradeId, level);
if (gameState.coins < cost) return;
gameState.coins -= cost;
gameState.upgrades[upgradeId]++;
safePlaySound('upgradeSound');
updateUI();
saveProgress();
}
function createPopupText(x, y, textValue, color) {
var text = new Text2(textValue, {
size: 32,
fill: color || '#ffd700',
align: 'center'
});
text.anchor.set(0.5, 0.5);
text.x = x;
text.y = y;
game.addChild(text);
popupTexts.push(text);
tween(text, {
y: y - 90,
alpha: 0
}, {
duration: 700,
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 < 6; i++) {
var particle = new DustParticle();
particle.x = x + (Math.random() - 0.5) * 24;
particle.y = y + (Math.random() - 0.5) * 24;
game.addChild(particle);
dustParticles.push(particle);
}
}
function resolveCurrentTicket(result) {
if (!currentTicket) return;
if (result.win) {
safePlaySound('winSound');
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 finalPayout = Math.floor(basePayouts[currentTicket.ticketType] * matchBonus * payoutUpgrade * comboBoost);
gameState.coins += finalPayout;
gameState.totalCoins += finalPayout;
gameState.streak++;
if (gameState.streak > gameState.bestStreak) {
gameState.bestStreak = gameState.streak;
}
if (finalPayout > gameState.biggestWin) {
gameState.biggestWin = finalPayout;
}
var jackpotGain = 1 + gameState.upgrades[4];
gameState.jackpotMeter += jackpotGain;
if (gameState.jackpotMeter >= gameState.jackpotMax) {
gameState.jackpotMeter = 0;
gameState.jackpotsHit++;
var jackpotReward = 100 + gameState.jackpotsHit * 25;
gameState.coins += jackpotReward;
gameState.totalCoins += jackpotReward;
createPopupText(currentTicket.x, currentTicket.y - 120, 'JACKPOT BONUS ' + formatMoney(jackpotReward), '#00ff88');
}
createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), '#ffd700');
} else {
gameState.streak = 0;
createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', '#ff6b6b');
}
updateUI();
saveProgress();
}
/****
* Boot
****/
initUI();
updateUI();
safePlayMusic('bgMusic', {
loop: true
});
/****
* Input
****/
game.down = function (x, y, obj) {
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 (currentTicket && !currentTicket.resolved && currentTicket.containsGlobalPoint(x, y)) {
dragData.active = true;
dragData.lastX = x;
dragData.lastY = y;
}
};
game.move = function (x, y, obj) {
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 (!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 / 450;
currentTicket.updateScratch(scratchAmount);
createDustParticles(x, y);
safePlaySound('scratchSound');
dragData.lastX = x;
dragData.lastY = y;
updateUI();
}
};
game.up = function (x, y, obj) {
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();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -11,37 +11,41 @@
var self = Container.call(this);
self.buttonText = '';
self.onClick = function () {};
self.isHovered = false;
+ self.hitWidth = 280;
+ self.hitHeight = 60;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
var text = new Text2('', {
- size: 24,
- fill: '#ffffff'
+ size: 22,
+ fill: '#ffffff',
+ align: 'center'
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.setText = function (str) {
self.buttonText = str;
text.setText(str);
};
- self.down = function () {
- LK.getSound('buttonClickSound').play();
- self.onClick();
+ self.setEnabled = function (enabled) {
+ self.enabled = enabled;
+ self.alpha = enabled ? 1 : 0.45;
};
- self.move = function (x, y, obj) {
- var globalPos = obj.parent.toGlobal(obj.position);
- var isInside = globalPos.x > -140 && globalPos.x < 140 && globalPos.y > -30 && globalPos.y < 30;
- if (isInside && !self.isHovered) {
- self.isHovered = true;
- bg.tint = 0x15537a;
- } else if (!isInside && self.isHovered) {
- self.isHovered = false;
- bg.tint = 0x0f3460;
- }
+ self.setHovered = function (hovered) {
+ self.isHovered = hovered;
+ bg.tint = hovered ? 0x15537a : 0x0f3460;
};
+ self.containsPoint = function (x, y) {
+ return pointInRect(x, y, self.x, self.y, self.hitWidth, self.hitHeight);
+ };
+ self.press = function () {
+ if (self.enabled === false) return;
+ safePlaySound('buttonClickSound');
+ self.onClick();
+ };
return self;
});
var DustParticle = Container.expand(function () {
var self = Container.call(this);
@@ -51,51 +55,61 @@
});
dust.alpha = 0.8;
self.velocityX = (Math.random() - 0.5) * 4;
self.velocityY = (Math.random() - 0.5) * 4;
- self.gravity = 0.1;
- self.life = 0.5;
- self.maxLife = 0.5;
+ self.gravity = 0.08;
+ self.life = 0.4;
+ self.maxLife = 0.4;
self.update = function () {
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.life -= 1 / 60;
dust.alpha = 0.8 * (self.life / self.maxLife);
if (self.life <= 0) {
+ if (self.parent) self.parent.removeChild(self);
self.destroy();
}
};
return self;
});
-var ScratchTicket = Container.expand(function () {
+var ScratchTicket = Container.expand(function (ticketType) {
var self = Container.call(this);
- self.ticketType = 0; // 0=Basic, 1=Lucky, 2=Gold
+ self.ticketType = ticketType || 0;
self.symbols = [];
- self.scratched = false;
self.scratchAmount = 0;
- self.revealThreshold = 0.5;
- self.matchResult = null;
+ self.resolved = false;
+ self.canScratch = true;
+ self.autoResolveThreshold = 0.55 - gameState.upgrades[2] * 0.03;
+ self.autoResolveThreshold = clamp(self.autoResolveThreshold, 0.25, 0.55);
+ self.ticketWidth = 600;
+ self.ticketHeight = 800;
var bg = self.attachAsset('ticketBg', {
anchorX: 0.5,
anchorY: 0.5
});
- bg.tint = 0x2a2a4e;
- // Set ticket color based on type
- if (self.ticketType === 1) bg.tint = 0x1a3a2e;else if (self.ticketType === 2) bg.tint = 0x3a2e1a;
+ bg.tint = getTicketTint(self.ticketType);
var overlay = self.attachAsset('scratchOverlay', {
anchorX: 0.5,
anchorY: 0.5
});
- overlay.alpha = 0.95;
- var titleText = new Text2('SCRATCH TICKET', {
- size: 30,
- fill: '#ffffff'
+ overlay.alpha = 0.96;
+ var titleText = new Text2(getTicketName(self.ticketType), {
+ size: 28,
+ fill: '#ffffff',
+ align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.y = -350;
self.addChild(titleText);
- // Create 3x3 grid of symbols
+ var infoText = new Text2('Scratch to reveal 3 matching symbols', {
+ size: 16,
+ fill: '#ffd700',
+ align: 'center'
+ });
+ infoText.anchor.set(0.5, 0);
+ infoText.y = -310;
+ self.addChild(infoText);
var gridStartX = -140;
var gridStartY = -140;
var spacing = 160;
for (var row = 0; row < 3; row++) {
@@ -106,41 +120,75 @@
self.symbols.push(symbol);
self.addChild(symbol);
}
}
+ self.containsGlobalPoint = function (x, y) {
+ return pointInRect(x, y, self.x, self.y, self.ticketWidth, self.ticketHeight);
+ };
self.updateScratch = function (amount) {
- self.scratchAmount += amount;
- if (self.scratchAmount > 1) self.scratchAmount = 1;
- overlay.alpha = Math.max(0, 1 - self.scratchAmount);
- // Reveal symbols as scratch increases
+ if (!self.canScratch || self.resolved) return;
+ self.scratchAmount = clamp(self.scratchAmount + amount, 0, 1);
+ overlay.alpha = clamp(0.96 - self.scratchAmount, 0, 0.96);
+ var revealTarget = Math.floor(self.scratchAmount * self.symbols.length);
+ var alreadyRevealed = 0;
for (var i = 0; i < self.symbols.length; i++) {
- if (Math.random() < self.scratchAmount * 0.3) {
- self.symbols[i].reveal();
+ 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] >= 3) {
- self.matchResult = {
- type: key,
- count: counts[key]
- };
- return true;
+ if (counts[key] > bestCount) {
+ bestType = key;
+ bestCount = counts[key];
}
}
- return false;
+ if (bestCount >= 3) {
+ return {
+ win: true,
+ type: bestType,
+ count: bestCount
+ };
+ }
+ return {
+ win: false,
+ type: bestType,
+ count: bestCount
+ };
};
- self.update = function () {
- if (self.scratchAmount >= self.revealThreshold && !self.scratched) {
- self.scratched = true;
+ 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();
+ resolveCurrentTicket(result);
+ };
return self;
});
var UpgradeCard = Container.expand(function () {
var self = Container.call(this);
@@ -148,50 +196,53 @@
self.level = 0;
self.cost = 0;
self.onClick = function () {};
self.isHovered = false;
+ self.hitWidth = 280;
+ self.hitHeight = 42;
var bg = self.attachAsset('buttonBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.scaleY = 0.7;
var nameText = new Text2('', {
size: 16,
- fill: '#ffffff'
+ fill: '#ffffff',
+ align: 'center'
});
nameText.anchor.set(0.5, 0);
nameText.y = -15;
self.addChild(nameText);
var costText = new Text2('', {
size: 14,
- fill: '#ffd700'
+ fill: '#ffd700',
+ align: 'center'
});
costText.anchor.set(0.5, 0);
costText.y = 5;
self.addChild(costText);
- self.setUpgrade = function (id, level, cost) {
+ self.setUpgrade = function (id, level, cost, affordable) {
self.upgradeId = id;
self.level = level;
self.cost = cost;
var names = ['Better Odds', 'Bigger Payouts', 'Faster Scratch', 'Combo Boost', 'Jackpot Gain', 'Coin Magnet'];
nameText.setText(names[id] + ' Lv' + (level + 1));
- costText.setText('Cost: $' + cost);
+ costText.setText('Cost: ' + formatMoney(cost));
+ self.alpha = affordable ? 1 : 0.55;
};
- self.down = function () {
- LK.getSound('buttonClickSound').play();
- self.onClick();
+ self.setHovered = function (hovered) {
+ self.isHovered = hovered;
+ bg.tint = hovered ? 0x15537a : 0x0f3460;
};
- self.move = function (x, y, obj) {
- var globalPos = obj.parent.toGlobal(obj.position);
- var isInside = globalPos.x > -140 && globalPos.x < 140 && globalPos.y > -21 && globalPos.y < 21;
- if (isInside && !self.isHovered) {
- self.isHovered = true;
- bg.tint = 0x15537a;
- } else if (!isInside && self.isHovered) {
- self.isHovered = false;
- bg.tint = 0x0f3460;
- }
+ self.containsGlobalPoint = function (x, y) {
+ var gx = rightPanel.x + self.x;
+ var gy = rightPanel.y + self.y;
+ return pointInRect(x, y, gx, gy, self.hitWidth, self.hitHeight);
};
+ self.press = function () {
+ safePlaySound('buttonClickSound');
+ self.onClick();
+ };
return self;
});
var _Symbol = Container.expand(function () {
var self = Container.call(this);
@@ -201,24 +252,26 @@
anchorX: 0.5,
anchorY: 0.5
});
var symbolText = new Text2('', {
- size: 60,
- fill: '#ffffff'
+ size: 56,
+ fill: '#ffffff',
+ align: 'center'
});
symbolText.anchor.set(0.5, 0.5);
+ symbolText.alpha = 0.25;
self.addChild(symbolText);
self.setSymbol = function (type) {
self.symbolType = type;
var symbols = ['🍀', '💎', '🎁', '⭐', '🔥', '🎰'];
symbolText.setText(symbols[type % symbols.length]);
};
self.reveal = function () {
- if (!self.revealed) {
- self.revealed = true;
- bg.tint = 0x4a4a6a;
- LK.getSound('revealSound').play();
- }
+ if (self.revealed) return;
+ self.revealed = true;
+ bg.tint = 0x4a4a6a;
+ symbolText.alpha = 1;
+ safePlaySound('revealSound');
};
self.setSymbol(Math.floor(Math.random() * 6));
return self;
});
@@ -232,41 +285,92 @@
/****
* Game Code
****/
-// Music
-// Sounds for feedback
-// Shapes for UI panels and game elements
-// Game state
+/****
+* Helpers
+****/
+/****
+* Game State
+****/
+function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+function safePlaySound(name) {
+ try {
+ var s = LK.getSound(name);
+ if (s && s.play) s.play();
+ } catch (e) {}
+}
+function safePlayMusic(name, options) {
+ try {
+ LK.playMusic(name, options || {});
+ } catch (e) {}
+}
+function formatMoney(value) {
+ return '$' + Math.floor(value);
+}
+function pointInRect(px, py, cx, cy, width, height) {
+ return px >= cx - width * 0.5 && px <= cx + width * 0.5 && py >= cy - height * 0.5 && py <= cy + height * 0.5;
+}
+function getTicketCost(type) {
+ var costs = [5, 15, 50];
+ return costs[type] || 5;
+}
+function getUpgradeCost(id, level) {
+ var baseCosts = [50, 75, 60, 80, 100, 70];
+ return Math.floor(baseCosts[id] * Math.pow(1.3, level));
+}
+function getTicketName(type) {
+ if (type === 1) return 'LUCKY TICKET';
+ if (type === 2) return 'GOLD TICKET';
+ return 'BASIC TICKET';
+}
+function getTicketTint(type) {
+ if (type === 1) return 0x1a3a2e;
+ if (type === 2) return 0x3a2e1a;
+ return 0x2a2a4e;
+}
+if (!storage.coins && storage.coins !== 0) storage.coins = 100;
+if (!storage.totalCoins && storage.totalCoins !== 0) storage.totalCoins = 100;
+if (!storage.bestStreak && storage.bestStreak !== 0) storage.bestStreak = 0;
+if (!storage.jackpotMeter && storage.jackpotMeter !== 0) storage.jackpotMeter = 0;
+if (!storage.ticketsScratchedTotal && storage.ticketsScratchedTotal !== 0) storage.ticketsScratchedTotal = 0;
+if (!storage.biggestWin && storage.biggestWin !== 0) storage.biggestWin = 0;
+if (!storage.jackpotsHit && storage.jackpotsHit !== 0) storage.jackpotsHit = 0;
+if (!storage.prestigeCount && storage.prestigeCount !== 0) storage.prestigeCount = 0;
+if (!storage.prestigeChips && storage.prestigeChips !== 0) storage.prestigeChips = 0;
+if (!storage.upgrades) storage.upgrades = [0, 0, 0, 0, 0, 0];
var gameState = {
- coins: storage.coins || 100,
- totalCoins: storage.totalCoins || 100,
+ coins: storage.coins,
+ totalCoins: storage.totalCoins,
streak: 0,
- bestStreak: storage.bestStreak || 0,
- jackpotMeter: storage.jackpotMeter || 0,
+ bestStreak: storage.bestStreak,
+ jackpotMeter: storage.jackpotMeter,
jackpotMax: 100,
- ticketsScratchedTotal: storage.ticketsScratchedTotal || 0,
- biggestWin: storage.biggestWin || 0,
- jackpotsHit: storage.jackpotsHit || 0,
- prestigeCount: storage.prestigeCount || 0,
- prestigeChips: storage.prestigeChips || 0,
- upgrades: storage.upgrades || [0, 0, 0, 0, 0, 0]
+ ticketsScratchedTotal: storage.ticketsScratchedTotal,
+ biggestWin: storage.biggestWin,
+ jackpotsHit: storage.jackpotsHit,
+ prestigeCount: storage.prestigeCount,
+ prestigeChips: storage.prestigeChips,
+ upgrades: storage.upgrades
};
var currentTicket = null;
var dragData = {
active: false,
- startX: 0,
- startY: 0
+ lastX: 0,
+ lastY: 0
};
var dustParticles = [];
-var coins = [];
-// UI Elements
+var popupTexts = [];
var leftPanel, rightPanel, bottomPanel;
-var coinText, streakText, jackpotBar, jackpotText;
+var coinText, streakText, jackpotBar, jackpotText, statsText;
var ticketBuyButtons = [];
var upgradeCards = [];
+/****
+* UI
+****/
function initUI() {
- // Left sidebar
leftPanel = new Container();
leftPanel.x = 100;
leftPanel.y = 100;
game.addChild(leftPanel);
@@ -338,9 +442,17 @@
jackpotText.anchor.set(0.5, 0);
jackpotText.x = 150;
jackpotText.y = 300;
leftPanel.addChild(jackpotText);
- // Right sidebar - upgrades
+ statsText = new Text2('', {
+ size: 12,
+ fill: '#bbbbbb',
+ align: 'center'
+ });
+ statsText.anchor.set(0.5, 0);
+ statsText.x = 150;
+ statsText.y = 350;
+ leftPanel.addChild(statsText);
rightPanel = new Container();
rightPanel.x = 1848;
rightPanel.y = 100;
game.addChild(rightPanel);
@@ -359,20 +471,19 @@
shopLabelText.y = 20;
rightPanel.addChild(shopLabelText);
for (var i = 0; i < 6; i++) {
var card = new UpgradeCard();
- card.y = 80 + i * 90;
+ card.y = 90 + i * 82;
card.x = -150;
card.onClick = function () {
onUpgradeClick(this.upgradeId);
}.bind(card);
upgradeCards.push(card);
rightPanel.addChild(card);
}
- // Bottom panel - ticket buy buttons
bottomPanel = new Container();
bottomPanel.x = 1024;
- bottomPanel.y = 2620;
+ bottomPanel.y = 2520;
game.addChild(bottomPanel);
var ticketTypes = [{
name: 'Basic\n$5',
cost: 5,
@@ -385,22 +496,21 @@
name: 'Gold\n$50',
cost: 50,
type: 2
}];
- for (var i = 0; i < 3; i++) {
+ for (var t = 0; t < ticketTypes.length; t++) {
var btn = new Button();
- btn.x = -300 + i * 300;
+ btn.x = -300 + t * 300;
btn.y = 0;
- btn.setText(ticketTypes[i].name);
- btn.cost = ticketTypes[i].cost;
- btn.type = ticketTypes[i].type;
+ btn.setText(ticketTypes[t].name);
+ btn.cost = ticketTypes[t].cost;
+ btn.type = ticketTypes[t].type;
btn.onClick = function () {
onBuyTicket(this.type, this.cost);
}.bind(btn);
ticketBuyButtons.push(btn);
bottomPanel.addChild(btn);
}
- // Top title
var titleText = new Text2('LUCKY SCRATCH TYCOON', {
size: 40,
fill: '#ffd700'
});
@@ -408,177 +518,213 @@
titleText.x = 1024;
titleText.y = 30;
game.addChild(titleText);
}
+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 (statsText) {
+ statsText.setText('Best Streak: ' + gameState.bestStreak + '\nBiggest Win: ' + formatMoney(gameState.biggestWin) + '\nTickets: ' + gameState.ticketsScratchedTotal);
+ }
+ 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);
+ }
+ for (var j = 0; j < ticketBuyButtons.length; j++) {
+ ticketBuyButtons[j].setEnabled(gameState.coins >= ticketBuyButtons[j].cost);
+ }
+}
+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.upgrades = gameState.upgrades;
+}
+/****
+* Gameplay
+****/
function createNewTicket(ticketType) {
if (currentTicket) {
- game.removeChild(currentTicket);
+ if (currentTicket.parent) currentTicket.parent.removeChild(currentTicket);
currentTicket.destroy();
}
- currentTicket = new ScratchTicket();
- currentTicket.ticketType = ticketType;
+ currentTicket = new ScratchTicket(ticketType);
currentTicket.x = 1024;
currentTicket.y = 1200;
game.addChild(currentTicket);
dragData.active = false;
}
function onBuyTicket(type, cost) {
- if (gameState.coins >= cost) {
- gameState.coins -= cost;
- createNewTicket(type);
- gameState.ticketsScratchedTotal++;
- }
+ if (currentTicket && !currentTicket.resolved) return;
+ if (gameState.coins < cost) return;
+ gameState.coins -= cost;
+ gameState.ticketsScratchedTotal++;
+ createNewTicket(type);
+ updateUI();
+ saveProgress();
}
function onUpgradeClick(upgradeId) {
- var baseCosts = [50, 75, 60, 80, 100, 70];
var level = gameState.upgrades[upgradeId];
- var cost = Math.floor(baseCosts[upgradeId] * Math.pow(1.3, level));
- if (gameState.coins >= cost) {
- gameState.coins -= cost;
- gameState.upgrades[upgradeId]++;
- LK.getSound('upgradeSound').play();
- updateUI();
- }
+ var cost = getUpgradeCost(upgradeId, level);
+ if (gameState.coins < cost) return;
+ gameState.coins -= cost;
+ gameState.upgrades[upgradeId]++;
+ safePlaySound('upgradeSound');
+ updateUI();
+ saveProgress();
}
-function createCoinPopup(x, y, amount) {
- var text = new Text2('+$' + amount, {
+function createPopupText(x, y, textValue, color) {
+ var text = new Text2(textValue, {
size: 32,
- fill: '#ffd700'
+ fill: color || '#ffd700',
+ align: 'center'
});
text.anchor.set(0.5, 0.5);
text.x = x;
text.y = y;
game.addChild(text);
- coins.push(text);
+ popupTexts.push(text);
tween(text, {
- y: y - 100,
+ y: y - 90,
alpha: 0
}, {
- duration: 800,
+ duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
- game.removeChild(text);
- coins.splice(coins.indexOf(text), 1);
+ if (text.parent) text.parent.removeChild(text);
+ var index = popupTexts.indexOf(text);
+ if (index >= 0) popupTexts.splice(index, 1);
}
});
}
function createDustParticles(x, y) {
- for (var i = 0; i < 8; i++) {
+ for (var i = 0; i < 6; i++) {
var particle = new DustParticle();
- particle.x = x + (Math.random() - 0.5) * 40;
- particle.y = y + (Math.random() - 0.5) * 40;
+ particle.x = x + (Math.random() - 0.5) * 24;
+ particle.y = y + (Math.random() - 0.5) * 24;
game.addChild(particle);
dustParticles.push(particle);
}
}
-function updateUI() {
- coinText.setText('$' + gameState.coins);
- streakText.setText('x' + gameState.streak);
- jackpotBar.scaleX = gameState.jackpotMeter / gameState.jackpotMax;
- jackpotText.setText(gameState.jackpotMeter + '/' + gameState.jackpotMax);
- for (var i = 0; i < upgradeCards.length; i++) {
- var baseCosts = [50, 75, 60, 80, 100, 70];
- var level = gameState.upgrades[i];
- var cost = Math.floor(baseCosts[i] * Math.pow(1.3, level));
- upgradeCards[i].setUpgrade(i, level, cost);
+function resolveCurrentTicket(result) {
+ if (!currentTicket) return;
+ if (result.win) {
+ safePlaySound('winSound');
+ 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 finalPayout = Math.floor(basePayouts[currentTicket.ticketType] * matchBonus * payoutUpgrade * comboBoost);
+ gameState.coins += finalPayout;
+ gameState.totalCoins += finalPayout;
+ gameState.streak++;
+ if (gameState.streak > gameState.bestStreak) {
+ gameState.bestStreak = gameState.streak;
+ }
+ if (finalPayout > gameState.biggestWin) {
+ gameState.biggestWin = finalPayout;
+ }
+ var jackpotGain = 1 + gameState.upgrades[4];
+ gameState.jackpotMeter += jackpotGain;
+ if (gameState.jackpotMeter >= gameState.jackpotMax) {
+ gameState.jackpotMeter = 0;
+ gameState.jackpotsHit++;
+ var jackpotReward = 100 + gameState.jackpotsHit * 25;
+ gameState.coins += jackpotReward;
+ gameState.totalCoins += jackpotReward;
+ createPopupText(currentTicket.x, currentTicket.y - 120, 'JACKPOT BONUS ' + formatMoney(jackpotReward), '#00ff88');
+ }
+ createPopupText(currentTicket.x, currentTicket.y, '+' + formatMoney(finalPayout), '#ffd700');
+ } else {
+ gameState.streak = 0;
+ createPopupText(currentTicket.x, currentTicket.y, 'NO MATCH', '#ff6b6b');
}
+ 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.upgrades = gameState.upgrades;
-}
-// Initialize UI and create first ticket
+/****
+* Boot
+****/
initUI();
-createNewTicket(0);
updateUI();
-// Play background music
-LK.playMusic('bgMusic', {
+safePlayMusic('bgMusic', {
loop: true
});
-// Game update loop
+/****
+* Input
+****/
game.down = function (x, y, obj) {
- if (currentTicket && Math.abs(currentTicket.x - x) < 350 && Math.abs(currentTicket.y - y) < 450) {
+ 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 (currentTicket && !currentTicket.resolved && currentTicket.containsGlobalPoint(x, y)) {
dragData.active = true;
- dragData.startX = x;
- dragData.startY = y;
+ dragData.lastX = x;
+ dragData.lastY = y;
}
};
game.move = function (x, y, obj) {
- if (dragData.active && currentTicket) {
- var dx = x - dragData.startX;
- var dy = y - dragData.startY;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance > 10) {
- var scratchAmount = distance / 200;
- currentTicket.updateScratch(scratchAmount);
- createDustParticles(x, y);
- LK.getSound('scratchSound').play();
- dragData.startX = x;
- dragData.startY = y;
- }
+ 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 (!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 / 450;
+ currentTicket.updateScratch(scratchAmount);
+ createDustParticles(x, y);
+ safePlaySound('scratchSound');
+ dragData.lastX = x;
+ dragData.lastY = y;
+ updateUI();
+ }
};
game.up = function (x, y, obj) {
dragData.active = false;
};
+/****
+* Update
+****/
game.update = function () {
- // Update current ticket
- if (currentTicket) {
- currentTicket.update();
- // Check if ticket is fully scratched
- if (currentTicket.scratched && !currentTicket.resolved) {
- currentTicket.resolved = true;
- if (currentTicket.checkMatch()) {
- // Win!
- LK.getSound('winSound').play();
- var basePayouts = [10, 25, 50];
- var payout = basePayouts[currentTicket.ticketType] * (1 + gameState.upgrades[1] * 0.2);
- var streakMultiplier = 1 + gameState.streak * 0.1;
- var finalPayout = Math.floor(payout * streakMultiplier);
- gameState.coins += finalPayout;
- gameState.totalCoins += finalPayout;
- gameState.streak++;
- if (gameState.streak > gameState.bestStreak) {
- gameState.bestStreak = gameState.streak;
- }
- if (finalPayout > gameState.biggestWin) {
- gameState.biggestWin = finalPayout;
- }
- gameState.jackpotMeter += Math.floor(Math.random() * 3) + 1;
- if (gameState.jackpotMeter >= gameState.jackpotMax) {
- gameState.jackpotMeter = gameState.jackpotMax;
- }
- createCoinPopup(currentTicket.x, currentTicket.y, finalPayout);
- // Auto create new ticket
- LK.setTimeout(function () {
- createNewTicket(Math.floor(Math.random() * 2));
- }, 500);
- } else {
- // Loss
- gameState.streak = 0;
- LK.setTimeout(function () {
- createNewTicket(0);
- }, 500);
- }
- }
- }
- // Update dust particles
for (var i = dustParticles.length - 1; i >= 0; i--) {
- dustParticles[i].update();
- if (!dustParticles[i].parent) {
+ if (!dustParticles[i] || !dustParticles[i].parent) {
dustParticles.splice(i, 1);
+ continue;
}
+ dustParticles[i].update();
}
- // Periodic save
if (LK.ticks % 300 === 0) {
saveProgress();
- updateUI();
}
};
\ No newline at end of file