/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var Arrow = Container.expand(function () { var self = Container.call(this); var arrowGraphics = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); self.direction = "northeast"; // Starting direction self.speed = 10; // Movement speed will be used for object movement self.x = 2048 * (1 / 3); // Fixed position at 1/3 from left (2/3 left on screen) self.y = 2732 * (2 / 3); // Fixed position at 2/3 down on screen self.path = []; // Track arrow's path self.hitboxScale = 0.3; // Make hitbox 30% of the actual size // Override the intersects method to use a smaller hitbox self.originalIntersects = self.intersects; self.intersects = function (otherObject) { // Store original width and height var originalWidth = arrowGraphics.width; var originalHeight = arrowGraphics.height; // Temporarily scale down the width and height for collision detection arrowGraphics.width *= self.hitboxScale; arrowGraphics.height *= self.hitboxScale; // Check collision with smaller hitbox var result = self.originalIntersects.call(self, otherObject); // Restore original width and height arrowGraphics.width = originalWidth; arrowGraphics.height = originalHeight; return result; }; self.update = function () { // Direction changes based on touch state, but arrow doesn't move if (!game.isTouching) { // Not touching - arrow faces northeast self.direction = "northeast"; arrowGraphics.rotation = -Math.PI / 4; // 45 degrees up } else { // Touching - arrow faces southeast self.direction = "southeast"; arrowGraphics.rotation = Math.PI / 4; // 45 degrees down } // Adjust speed based on market cap // Base speed of 10 // Allow scaling up to 500% (speed of 50) var marketCapFactor = Math.max(0, marketCap) / 100; var speedMultiplier; if (marketCapFactor <= 20) { // Up to 300% speed (market cap of 2000) speedMultiplier = marketCapFactor * 0.5; // Original scaling } else { // More gradual scaling after 300% speedMultiplier = 10 + (marketCapFactor - 20) * 0.2; // Slower increase after 300% // Cap at 500% (total multiplier of 40) speedMultiplier = Math.min(speedMultiplier, 40); } self.speed = Math.max(10, Math.min(50, 10 + speedMultiplier)); // Record fixed path for consistency if (game.ticks % 5 === 0) { self.path.push({ x: self.x, y: self.y }); } }; // Direction is now controlled by touch state in update method return self; }); var BackgroundGrid = Container.expand(function () { var self = Container.call(this); var tileSize = 200; // Size of each grid square var gridTiles = []; // Store all grid tiles // Dark blue color for tiles, slightly lighter for outlines var tileColor = 0x003366; // Dark blue var outlineColor = 0x0066aa; // Lighter blue for outline // Create the grid to cover screen with some buffer self.createGrid = function () { // Calculate how many tiles we need to cover the entire screen with buffer var screenWidth = 2748; var screenHeight = 2732; // Add buffer to ensure full coverage when moving var bufferMultiplier = 2; var tilesX = Math.ceil(screenWidth * bufferMultiplier / tileSize) + 2; var tilesY = Math.ceil(screenHeight * bufferMultiplier / tileSize) + 2; // Center position to align with screen center var centerX = screenWidth / 2; var centerY = screenHeight / 2; // Calculate starting position (top-left of grid) var startX = centerX - tilesX * tileSize / 2; var startY = centerY - tilesY * tileSize / 2; // Create grid of square tiles for (var x = 0; x < tilesX; x++) { for (var y = 0; y < tilesY; y++) { // Create tile as a Container var tile = new Container(); // Create tile background var bg = LK.getAsset('background', { anchorX: 0, anchorY: 0, width: tileSize, height: tileSize, tint: tileColor }); // Create tile outline (slightly smaller to create border effect) var outline = LK.getAsset('background', { anchorX: 0, anchorY: 0, width: tileSize - 2, height: tileSize - 2, x: 1, y: 1, tint: outlineColor }); tile.addChild(bg); tile.addChild(outline); // Position tile tile.x = startX + x * tileSize; tile.y = startY + y * tileSize; // Initialize original position tile.origX = tile.x; tile.origY = tile.y; // Store tile gridTiles.push(tile); self.addChild(tile); } } }; // Move grid based on arrow direction and speed self.update = function () { if (!arrow || !arrow.direction) { return; } var moveAmountX = 0; var moveAmountY = 0; // Move opposite to the arrow direction if (arrow.direction === "northeast") { // Move background southwest moveAmountX = -arrow.speed; moveAmountY = arrow.speed; } else { // Move background northwest moveAmountX = -arrow.speed; moveAmountY = -arrow.speed; } // Move all tiles for (var i = 0; i < gridTiles.length; i++) { var tile = gridTiles[i]; tile.x += moveAmountX; tile.y += moveAmountY; // Wrap tiles around when they move too far if (tile.x < -tileSize * 2) { tile.x += gridTiles.length * tileSize / Math.sqrt(gridTiles.length); } else if (tile.x > 2048 + tileSize * 2) { tile.x -= gridTiles.length * tileSize / Math.sqrt(gridTiles.length); } if (tile.y < -tileSize * 2) { tile.y += gridTiles.length * tileSize / Math.sqrt(gridTiles.length); } else if (tile.y > 2732 + tileSize * 2) { tile.y -= gridTiles.length * tileSize / Math.sqrt(gridTiles.length); } } }; return self; }); var Bonus = Container.expand(function () { var self = Container.call(this); var bonusGraphics = self.attachAsset('bonus', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -8; self.collected = false; self.hitboxScale = 0.3; // Make hitbox 30% of the actual size // Override the intersects method to use a smaller hitbox self.originalIntersects = self.intersects; self.intersects = function (otherObject) { // Store original width and height var originalWidth = bonusGraphics.width; var originalHeight = bonusGraphics.height; // Temporarily scale down the width and height for collision detection bonusGraphics.width *= self.hitboxScale; bonusGraphics.height *= self.hitboxScale; // Check collision with smaller hitbox var result = self.originalIntersects.call(self, otherObject); // Restore original width and height bonusGraphics.width = originalWidth; bonusGraphics.height = originalHeight; return result; }; self.update = function () { // Bonuses now move towards the arrow (opposite direction) if (game.isTouching) { // Move opposite to southeast direction self.x -= arrow.speed; self.y -= arrow.speed; } else { // Move opposite to northeast direction self.x -= arrow.speed; self.y += arrow.speed; } // Spin the bonus //bonusGraphics.rotation += 0.05; }; self.collect = function () { self.collected = true; tween(bonusGraphics, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, easing: tween.easeOut }); }; return self; }); var DollarBill = Container.expand(function (direction) { var self = Container.call(this); var color = direction === "northeast" ? 0x00FF00 : 0xFF0000; var dollarGraphics = self.attachAsset('dollar', { anchorX: 0.5, anchorY: 0.5, tint: color }); //dollarGraphics.rotation = Math.random() * Math.PI * 2; // Random rotation dollarGraphics.scale.set(0.8, 0.5); // Make it more bill-shaped // No need for self.speed as we'll use arrow.speed directly self.direction = direction; // Store direction for movement self.update = function () { // Move in the opposite direction of what the arrow would move // Use arrow's current speed for consistent movement if (self.direction === "northeast") { self.x -= arrow.speed; self.y += arrow.speed; } else { self.x -= arrow.speed; self.y -= arrow.speed; } }; return self; }); var Moon = Container.expand(function () { var self = Container.call(this); // Create a white circular shape for the moon var moonGraphics = self.attachAsset('moon', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300, tint: 0xFFFFFF }); // Set initial position off-screen in the northeast corner self.x = 2548; // Just off-screen to the right self.y = -300; // Just off-screen above // Set slow movement speed self.speed = 2; self.update = function () { // Move slowly from northeast to southwest self.x -= self.speed; self.y += self.speed; // Remove when completely off-screen if (self.x < -400 || self.y > 3132) { self.readyForRemoval = true; } }; return self; }); var Obstacle = Container.expand(function () { var self = Container.call(this); var obstacleGraphics = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -10; self.hitboxScale = 0.2; // Make hitbox 20% of the actual size // Override the intersects method to use a smaller hitbox self.originalIntersects = self.intersects; self.intersects = function (otherObject) { // Store original width and height var originalWidth = obstacleGraphics.width; var originalHeight = obstacleGraphics.height; // Temporarily scale down the width and height for collision detection obstacleGraphics.width *= self.hitboxScale; obstacleGraphics.height *= self.hitboxScale; // Check collision with smaller hitbox var result = self.originalIntersects.call(self, otherObject); // Restore original width and height obstacleGraphics.width = originalWidth; obstacleGraphics.height = originalHeight; return result; }; self.update = function () { // Obstacles now move towards the arrow (opposite direction) // Always use arrow's current speed for consistent movement if (game.isTouching) { // Move opposite to southeast direction self.x -= arrow.speed; self.y -= arrow.speed; } else { // Move opposite to northeast direction self.x -= arrow.speed; self.y += arrow.speed; } }; return self; }); var StonksText = Container.expand(function (message, color) { var self = Container.call(this); var text = new Text2(message || "STONKS!", { size: 100, fill: color || "#00FF00" }); text.anchor.set(0.5, 0.5); self.addChild(text); self.animate = function () { self.alpha = 1; tween(self, { alpha: 0, y: self.y - 200 }, { duration: 1500, easing: tween.easeOut }); tween(self.scale, { x: 2, y: 2 }, { duration: 1500, easing: tween.easeOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0066ff }); /**** * Game Code ****/ // Track if the screen is being touched // Game variables game.isTouching = false; var gameStarted = false; // Track if the game has been started by the first touch // Camera functionality removed as the arrow now stays stationary // and other objects move around it var camera = { update: function update() {} // Empty function to prevent errors }; var arrow; var obstacles = []; var bonuses = []; var stonksTexts = []; var dollarBills = []; var moons = []; var lastObstacleTime = 0; var lastBonusTime = 0; var lastDollarTime = 0; var lastMoonAppearance = 0; var marketCap = 0; var marketCapFactor = 0; var gameActive = true; var moonAppearThreshold = 9000; // Moon appears every 9,000 points var lastMoonCheckPoint = 0; // Tracks the last point at which we checked for moon appearance // Create background grid var backgroundGrid = new BackgroundGrid(); backgroundGrid.createGrid(); game.addChild(backgroundGrid); // Add background reference for backward compatibility var background = { x: 2048 / 2, y: 2732 / 2 }; // Create GUI elements var marketCapText = new Text2("Stonk Market Cap: $0.00", { size: 60, fill: 0xFFFFFF }); marketCapText.anchor.set(0.5, 0); LK.gui.top.addChild(marketCapText); marketCapText.visible = false; // Hide initially // Create startup logo var logoText = new Text2("Stonks Go Up", { size: 150, fill: 0x00FF00 }); logoText.anchor.set(0.5, 1.2); logoText.x = 2048 / 2; logoText.y = 2732 / 2; game.addChild(logoText); // Add tap to start instruction var startInstructionText = new Text2("Tap to Start", { size: 80, fill: 0xFFFFFF }); startInstructionText.anchor.set(0.5, 0.5); startInstructionText.x = 2048 / 2; startInstructionText.y = 2732 / 2 + 200; game.addChild(startInstructionText); // Add high score display to the start screen var highScoreText = new Text2("High Score: $" + storage.highScore + ".00", { size: 60, fill: 0xFFFFFF }); highScoreText.anchor.set(0.5, 0); highScoreText.x = 2048 / 2; highScoreText.y = 2732 - 200; game.addChild(highScoreText); // Make the text pulse tween(startInstructionText.scale, { x: 1.1, y: 1.1 }, { duration: 800, easing: tween.easeInOut, loop: -1, yoyo: true }); // Add stonksguy mascot to lower-left corner var stonksGuy = game.addChild(LK.getAsset('stonksguy', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 2732 - 120 })); stonksGuy.visible = true; // Ensure stonksguy is visible // Create arrow in the center of the screen arrow = new Arrow(); // Arrow position is set in the class constructor game.addChild(arrow); // No camera setup needed since we have a fixed view // Background music will play on first touch, not at initialization // Handle touch events game.down = function (x, y, obj) { if (!gameStarted) { // First touch - start the game gameStarted = true; // Start playing background music now LK.playMusic('bgMusic'); // Hide the logo and instruction tween(logoText, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onComplete: function onComplete() { logoText.destroy(); } }); tween(startInstructionText, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onComplete: function onComplete() { startInstructionText.destroy(); } }); // Show the market cap text marketCapText.visible = true; // Set touch state game.isTouching = true; } else if (gameActive) { game.isTouching = true; } }; game.up = function (x, y, obj) { if (gameActive) { game.isTouching = false; } }; // Generate obstacles function createObstacle() { var obstacle = new Obstacle(); // Since arrow is fixed in center, use fixed screen coordinates var screenWidth = 2048; var screenHeight = 2732; // Randomly decide if this obstacle should be directly in the arrow's path var inPath = Math.random() < 0.4; // 40% chance to spawn in path var spawnRegion = Math.random(); if (inPath) { // Spawn directly in the path of the arrow based on current direction var distanceAhead = 1000 + Math.random() * 500; // Spawn far enough to give player time to react if (arrow.direction === "northeast") { // Position in the northeast path of the arrow obstacle.x = arrow.x + distanceAhead; obstacle.y = arrow.y - distanceAhead; } else { // Position in the southeast path of the arrow obstacle.x = arrow.x + distanceAhead; obstacle.y = arrow.y + distanceAhead; } } else if (spawnRegion < 0.7) { // Spawn directly to the right of the arrow (off-screen) obstacle.x = screenWidth + Math.random() * 200; // Just off-screen to the right obstacle.y = arrow.y; // Aligned with the arrow's y position } else { // Spawn above obstacle.x = screenWidth / 2 + Math.random() * 500; // Spread to the right of center obstacle.y = -200 - Math.random() * 200; // Just above the visible screen } obstacles.push(obstacle); game.addChild(obstacle); } // Generate bonuses function createBonus() { var bonus = new Bonus(); // Since arrow is fixed in center, use fixed screen coordinates var screenWidth = 2048; var screenHeight = 2732; // Spawn bonuses at the upper-right corner of visible screen var spawnRegion = Math.random(); if (spawnRegion < 0.6) { // Spawn to the right bonus.x = screenWidth + Math.random() * 300; // Just off-screen to the right bonus.y = screenHeight / 2 - Math.random() * 600; // Spread around middle height } else { // Spawn above bonus.x = screenWidth / 2 + Math.random() * 600; // Spread to the right of center bonus.y = -200 - Math.random() * 300; // Just above the visible screen } bonuses.push(bonus); game.addChild(bonus); } // Create dollar bill in appropriate direction function createDollarBill() { if (!arrow || !gameActive) { return; } var dollarBill = new DollarBill(arrow.direction); // Position the dollar bill in front of the arrow based on direction // Since the arrow is stationary, we position relative to the center var offsetDistance = 100; if (arrow.direction === "northeast") { dollarBill.x = arrow.x + offsetDistance; dollarBill.y = arrow.y - offsetDistance; dollarBill.rotation = -Math.PI / 4; // 45 degrees up } else { dollarBill.x = arrow.x + offsetDistance; dollarBill.y = arrow.y + offsetDistance; dollarBill.rotation = Math.PI / 4; // 45 degrees down } dollarBills.push(dollarBill); game.addChild(dollarBill); } // Create stonks text popup function createStonksText(isPositive, x, y) { var message = isPositive ? "STONKS!" : "NOT STONKS!"; var color = isPositive ? "#00FF00" : "#FF0000"; var stonksText = new StonksText(message, color); stonksText.x = x; stonksText.y = y; stonksText.animate(); stonksTexts.push(stonksText); game.addChild(stonksText); } // Game update loop game.update = function () { if (!gameStarted || !gameActive) { return; } // Check if we should create a moon (every 20,000 points) // Calculate the current "milestone" we've passed var currentMilestone = Math.floor(marketCap / moonAppearThreshold) * moonAppearThreshold; // If we've crossed a new 20,000 point threshold and we're not at a negative market cap if (currentMilestone > lastMoonCheckPoint && marketCap > 0) { // Create a new moon var moon = new Moon(); moons.push(moon); // Add to game at a lower z-index (behind everything except background) game.addChildAt(moon, game.getChildIndex(backgroundGrid) + 1); // Update the last checkpoint lastMoonCheckPoint = currentMilestone; } // Update the background grid backgroundGrid.update(); // Calculate spawn rate multipliers based on market cap var marketCapFactor = Math.max(0, marketCap) / 100; // Adjust scaling for rates to increase more gradually after 300% var scaleMultiplier; if (marketCapFactor <= 20) { // Up to 300% speed (market cap of 2000) scaleMultiplier = marketCapFactor; } else { // More gradual scaling after 300% scaleMultiplier = 20 + (marketCapFactor - 20) * 0.4; // Slower increase after 300% // Cap at 500% (market cap of 2000 + 5000 = 7000) scaleMultiplier = Math.min(scaleMultiplier, 40); // 40 is the cap for 500% } // Calculate rates with the new scaling var obstacleRate = Math.max(10, 60 - scaleMultiplier * 0.5); // Decrease from 60 down to 12 (500% of initial rate) var bonusRate = Math.max(36, 180 - scaleMultiplier * 1.0); // Decrease from 180 down to 36 (500% of initial rate) var dollarRate = Math.max(6, 30 - scaleMultiplier * 0.25); // Decrease from 30 down to 6 (500% of initial rate) // Scale stonksguy based on the current game speed - increases as the game speeds up // Calculate scale factor based on current speed (10 = normal speed, 50 = 500% speed) var speedScaleFactor = arrow.speed / 10; // 1.0 to 5.0 // Use a more moderate scaling that still shows growth but doesn't become too large var stonksGuyScale = 1.0 + (speedScaleFactor - 1) * 0.2; // Scale from 1.0 to 1.6 (at 500% speed) stonksGuy.scale.set(stonksGuyScale, stonksGuyScale); // Make stonksguy pulse (move up and down) every half second if (!stonksGuy.isPulsing) { var _pulseY = function pulseY() { tween(stonksGuy, { y: stonksGuy.originalY + 20 }, { duration: 483, easing: tween.easeInOut, onFinish: function onFinish() { tween(stonksGuy, { y: stonksGuy.originalY }, { duration: 483, easing: tween.easeInOut, onFinish: _pulseY }); } }); }; stonksGuy.isPulsing = true; stonksGuy.originalY = stonksGuy.y; _pulseY(); } // Create obstacles at dynamic intervals lastObstacleTime = lastObstacleTime + 1; if (lastObstacleTime > obstacleRate) { createObstacle(); lastObstacleTime = 0; } // Create bonuses at dynamic intervals lastBonusTime = lastBonusTime + 1; if (lastBonusTime > bonusRate) { createBonus(); lastBonusTime = 0; } // Create dollar bills at dynamic intervals lastDollarTime = lastDollarTime + 1; if (lastDollarTime > dollarRate) { createDollarBill(); lastDollarTime = 0; } // Update marketCap - increase when going northeast, decrease when going southeast var oldSpeed = arrow.speed; if (arrow.direction === "northeast") { marketCap += 1; } else if (arrow.direction === "southeast") { marketCap -= 1; } // Calculate current and previous spawn rates var prevMarketCapFactor = Math.max(0, marketCap - 1) / 100; var prevDollarRate = Math.max(10, 30 - prevMarketCapFactor * 5); var currentDollarRate = Math.max(10, 30 - marketCapFactor * 5); // Show visual feedback when speed increases significantly if (arrow.speed - oldSpeed > 0.5) { var speedText = new StonksText("SPEED UP!", "#FFFF00"); speedText.x = arrow.x; speedText.y = arrow.y - 100; speedText.animate(); stonksTexts.push(speedText); game.addChild(speedText); } // Show visual feedback when spawn rate increases significantly if (prevDollarRate - currentDollarRate > 0.5 && arrow.direction === "northeast") { var spawnText = new StonksText("SPAWN RATE UP!", "#00FFFF"); spawnText.x = arrow.x + 100; spawnText.y = arrow.y; spawnText.animate(); stonksTexts.push(spawnText); game.addChild(spawnText); } // Check if market cap has reached game-ending threshold of -10 if (marketCap <= -10 && gameActive) { // Game over gameActive = false; createStonksText(false, arrow.x, arrow.y); LK.getSound('crash').play(); // Flash screen red LK.effects.flashScreen(0xFF0000, 1000); // Show game over LK.setTimeout(function () { LK.setScore(marketCap); // Update high score if current market cap is higher if (marketCap > storage.highScore) { storage.highScore = marketCap; } LK.showGameOver(); }, 1500); } // Calculate rate percentages relative to initial rates for display // Cap display at 500% var speedPercentage = Math.min(500, Math.round(arrow.speed / 10 * 100)); var spawnRatePercentage = Math.min(500, Math.round(30 / dollarRate * 100)); marketCapText.setText("Stonk Market Cap: $" + marketCap + ".00"); // Check if obstacles are completely off-screen for (var i = obstacles.length - 1; i >= 0; i--) { // Remove obstacles that have moved off the left side of the screen if (obstacles[i].x < -200) { obstacles[i].destroy(); obstacles.splice(i, 1); } } // Check for bonuses that are off-screen for (var i = bonuses.length - 1; i >= 0; i--) { if (bonuses[i].x < -200 || bonuses[i].collected) { bonuses[i].destroy(); bonuses.splice(i, 1); } } // Remove faded stonks texts for (var i = stonksTexts.length - 1; i >= 0; i--) { if (stonksTexts[i].alpha <= 0) { stonksTexts[i].destroy(); stonksTexts.splice(i, 1); } } // Clean up dollar bills that are off-screen for (var i = dollarBills.length - 1; i >= 0; i--) { if (dollarBills[i].x < -200) { dollarBills[i].destroy(); dollarBills.splice(i, 1); } } // Clean up moons that are ready for removal for (var i = moons.length - 1; i >= 0; i--) { if (moons[i].readyForRemoval) { moons[i].destroy(); moons.splice(i, 1); } } // Check for collisions with obstacles for (var i = 0; i < obstacles.length; i++) { if (arrow.intersects(obstacles[i])) { // Game over gameActive = false; createStonksText(false, arrow.x, arrow.y); LK.getSound('crash').play(); // Stop background music immediately LK.stopMusic(); // Flash screen red LK.effects.flashScreen(0xFF0000, 1000); // Show game over LK.setTimeout(function () { LK.setScore(marketCap); // Update high score if current market cap is higher if (marketCap > storage.highScore) { storage.highScore = marketCap; } LK.showGameOver(); }, 1500); break; } } // Check for collisions with bonuses for (var i = bonuses.length - 1; i >= 0; i--) { if (!bonuses[i].collected && arrow.intersects(bonuses[i])) { // Collect bonus bonuses[i].collect(); LK.getSound('collect').play(); // Add bonus score var bonusValue = Math.floor(Math.random() * 1000) + 500; marketCap += bonusValue; // Show stonks text createStonksText(true, arrow.x, arrow.y); // Flash screen green LK.effects.flashScreen(0x00FF00, 300); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.direction = "northeast"; // Starting direction
self.speed = 10; // Movement speed will be used for object movement
self.x = 2048 * (1 / 3); // Fixed position at 1/3 from left (2/3 left on screen)
self.y = 2732 * (2 / 3); // Fixed position at 2/3 down on screen
self.path = []; // Track arrow's path
self.hitboxScale = 0.3; // Make hitbox 30% of the actual size
// Override the intersects method to use a smaller hitbox
self.originalIntersects = self.intersects;
self.intersects = function (otherObject) {
// Store original width and height
var originalWidth = arrowGraphics.width;
var originalHeight = arrowGraphics.height;
// Temporarily scale down the width and height for collision detection
arrowGraphics.width *= self.hitboxScale;
arrowGraphics.height *= self.hitboxScale;
// Check collision with smaller hitbox
var result = self.originalIntersects.call(self, otherObject);
// Restore original width and height
arrowGraphics.width = originalWidth;
arrowGraphics.height = originalHeight;
return result;
};
self.update = function () {
// Direction changes based on touch state, but arrow doesn't move
if (!game.isTouching) {
// Not touching - arrow faces northeast
self.direction = "northeast";
arrowGraphics.rotation = -Math.PI / 4; // 45 degrees up
} else {
// Touching - arrow faces southeast
self.direction = "southeast";
arrowGraphics.rotation = Math.PI / 4; // 45 degrees down
}
// Adjust speed based on market cap
// Base speed of 10
// Allow scaling up to 500% (speed of 50)
var marketCapFactor = Math.max(0, marketCap) / 100;
var speedMultiplier;
if (marketCapFactor <= 20) {
// Up to 300% speed (market cap of 2000)
speedMultiplier = marketCapFactor * 0.5; // Original scaling
} else {
// More gradual scaling after 300%
speedMultiplier = 10 + (marketCapFactor - 20) * 0.2; // Slower increase after 300%
// Cap at 500% (total multiplier of 40)
speedMultiplier = Math.min(speedMultiplier, 40);
}
self.speed = Math.max(10, Math.min(50, 10 + speedMultiplier));
// Record fixed path for consistency
if (game.ticks % 5 === 0) {
self.path.push({
x: self.x,
y: self.y
});
}
};
// Direction is now controlled by touch state in update method
return self;
});
var BackgroundGrid = Container.expand(function () {
var self = Container.call(this);
var tileSize = 200; // Size of each grid square
var gridTiles = []; // Store all grid tiles
// Dark blue color for tiles, slightly lighter for outlines
var tileColor = 0x003366; // Dark blue
var outlineColor = 0x0066aa; // Lighter blue for outline
// Create the grid to cover screen with some buffer
self.createGrid = function () {
// Calculate how many tiles we need to cover the entire screen with buffer
var screenWidth = 2748;
var screenHeight = 2732;
// Add buffer to ensure full coverage when moving
var bufferMultiplier = 2;
var tilesX = Math.ceil(screenWidth * bufferMultiplier / tileSize) + 2;
var tilesY = Math.ceil(screenHeight * bufferMultiplier / tileSize) + 2;
// Center position to align with screen center
var centerX = screenWidth / 2;
var centerY = screenHeight / 2;
// Calculate starting position (top-left of grid)
var startX = centerX - tilesX * tileSize / 2;
var startY = centerY - tilesY * tileSize / 2;
// Create grid of square tiles
for (var x = 0; x < tilesX; x++) {
for (var y = 0; y < tilesY; y++) {
// Create tile as a Container
var tile = new Container();
// Create tile background
var bg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
width: tileSize,
height: tileSize,
tint: tileColor
});
// Create tile outline (slightly smaller to create border effect)
var outline = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
width: tileSize - 2,
height: tileSize - 2,
x: 1,
y: 1,
tint: outlineColor
});
tile.addChild(bg);
tile.addChild(outline);
// Position tile
tile.x = startX + x * tileSize;
tile.y = startY + y * tileSize;
// Initialize original position
tile.origX = tile.x;
tile.origY = tile.y;
// Store tile
gridTiles.push(tile);
self.addChild(tile);
}
}
};
// Move grid based on arrow direction and speed
self.update = function () {
if (!arrow || !arrow.direction) {
return;
}
var moveAmountX = 0;
var moveAmountY = 0;
// Move opposite to the arrow direction
if (arrow.direction === "northeast") {
// Move background southwest
moveAmountX = -arrow.speed;
moveAmountY = arrow.speed;
} else {
// Move background northwest
moveAmountX = -arrow.speed;
moveAmountY = -arrow.speed;
}
// Move all tiles
for (var i = 0; i < gridTiles.length; i++) {
var tile = gridTiles[i];
tile.x += moveAmountX;
tile.y += moveAmountY;
// Wrap tiles around when they move too far
if (tile.x < -tileSize * 2) {
tile.x += gridTiles.length * tileSize / Math.sqrt(gridTiles.length);
} else if (tile.x > 2048 + tileSize * 2) {
tile.x -= gridTiles.length * tileSize / Math.sqrt(gridTiles.length);
}
if (tile.y < -tileSize * 2) {
tile.y += gridTiles.length * tileSize / Math.sqrt(gridTiles.length);
} else if (tile.y > 2732 + tileSize * 2) {
tile.y -= gridTiles.length * tileSize / Math.sqrt(gridTiles.length);
}
}
};
return self;
});
var Bonus = Container.expand(function () {
var self = Container.call(this);
var bonusGraphics = self.attachAsset('bonus', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -8;
self.collected = false;
self.hitboxScale = 0.3; // Make hitbox 30% of the actual size
// Override the intersects method to use a smaller hitbox
self.originalIntersects = self.intersects;
self.intersects = function (otherObject) {
// Store original width and height
var originalWidth = bonusGraphics.width;
var originalHeight = bonusGraphics.height;
// Temporarily scale down the width and height for collision detection
bonusGraphics.width *= self.hitboxScale;
bonusGraphics.height *= self.hitboxScale;
// Check collision with smaller hitbox
var result = self.originalIntersects.call(self, otherObject);
// Restore original width and height
bonusGraphics.width = originalWidth;
bonusGraphics.height = originalHeight;
return result;
};
self.update = function () {
// Bonuses now move towards the arrow (opposite direction)
if (game.isTouching) {
// Move opposite to southeast direction
self.x -= arrow.speed;
self.y -= arrow.speed;
} else {
// Move opposite to northeast direction
self.x -= arrow.speed;
self.y += arrow.speed;
}
// Spin the bonus
//bonusGraphics.rotation += 0.05;
};
self.collect = function () {
self.collected = true;
tween(bonusGraphics, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeOut
});
};
return self;
});
var DollarBill = Container.expand(function (direction) {
var self = Container.call(this);
var color = direction === "northeast" ? 0x00FF00 : 0xFF0000;
var dollarGraphics = self.attachAsset('dollar', {
anchorX: 0.5,
anchorY: 0.5,
tint: color
});
//dollarGraphics.rotation = Math.random() * Math.PI * 2; // Random rotation
dollarGraphics.scale.set(0.8, 0.5); // Make it more bill-shaped
// No need for self.speed as we'll use arrow.speed directly
self.direction = direction; // Store direction for movement
self.update = function () {
// Move in the opposite direction of what the arrow would move
// Use arrow's current speed for consistent movement
if (self.direction === "northeast") {
self.x -= arrow.speed;
self.y += arrow.speed;
} else {
self.x -= arrow.speed;
self.y -= arrow.speed;
}
};
return self;
});
var Moon = Container.expand(function () {
var self = Container.call(this);
// Create a white circular shape for the moon
var moonGraphics = self.attachAsset('moon', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
tint: 0xFFFFFF
});
// Set initial position off-screen in the northeast corner
self.x = 2548; // Just off-screen to the right
self.y = -300; // Just off-screen above
// Set slow movement speed
self.speed = 2;
self.update = function () {
// Move slowly from northeast to southwest
self.x -= self.speed;
self.y += self.speed;
// Remove when completely off-screen
if (self.x < -400 || self.y > 3132) {
self.readyForRemoval = true;
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -10;
self.hitboxScale = 0.2; // Make hitbox 20% of the actual size
// Override the intersects method to use a smaller hitbox
self.originalIntersects = self.intersects;
self.intersects = function (otherObject) {
// Store original width and height
var originalWidth = obstacleGraphics.width;
var originalHeight = obstacleGraphics.height;
// Temporarily scale down the width and height for collision detection
obstacleGraphics.width *= self.hitboxScale;
obstacleGraphics.height *= self.hitboxScale;
// Check collision with smaller hitbox
var result = self.originalIntersects.call(self, otherObject);
// Restore original width and height
obstacleGraphics.width = originalWidth;
obstacleGraphics.height = originalHeight;
return result;
};
self.update = function () {
// Obstacles now move towards the arrow (opposite direction)
// Always use arrow's current speed for consistent movement
if (game.isTouching) {
// Move opposite to southeast direction
self.x -= arrow.speed;
self.y -= arrow.speed;
} else {
// Move opposite to northeast direction
self.x -= arrow.speed;
self.y += arrow.speed;
}
};
return self;
});
var StonksText = Container.expand(function (message, color) {
var self = Container.call(this);
var text = new Text2(message || "STONKS!", {
size: 100,
fill: color || "#00FF00"
});
text.anchor.set(0.5, 0.5);
self.addChild(text);
self.animate = function () {
self.alpha = 1;
tween(self, {
alpha: 0,
y: self.y - 200
}, {
duration: 1500,
easing: tween.easeOut
});
tween(self.scale, {
x: 2,
y: 2
}, {
duration: 1500,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0066ff
});
/****
* Game Code
****/
// Track if the screen is being touched
// Game variables
game.isTouching = false;
var gameStarted = false; // Track if the game has been started by the first touch
// Camera functionality removed as the arrow now stays stationary
// and other objects move around it
var camera = {
update: function update() {} // Empty function to prevent errors
};
var arrow;
var obstacles = [];
var bonuses = [];
var stonksTexts = [];
var dollarBills = [];
var moons = [];
var lastObstacleTime = 0;
var lastBonusTime = 0;
var lastDollarTime = 0;
var lastMoonAppearance = 0;
var marketCap = 0;
var marketCapFactor = 0;
var gameActive = true;
var moonAppearThreshold = 9000; // Moon appears every 9,000 points
var lastMoonCheckPoint = 0; // Tracks the last point at which we checked for moon appearance
// Create background grid
var backgroundGrid = new BackgroundGrid();
backgroundGrid.createGrid();
game.addChild(backgroundGrid);
// Add background reference for backward compatibility
var background = {
x: 2048 / 2,
y: 2732 / 2
};
// Create GUI elements
var marketCapText = new Text2("Stonk Market Cap: $0.00", {
size: 60,
fill: 0xFFFFFF
});
marketCapText.anchor.set(0.5, 0);
LK.gui.top.addChild(marketCapText);
marketCapText.visible = false; // Hide initially
// Create startup logo
var logoText = new Text2("Stonks Go Up", {
size: 150,
fill: 0x00FF00
});
logoText.anchor.set(0.5, 1.2);
logoText.x = 2048 / 2;
logoText.y = 2732 / 2;
game.addChild(logoText);
// Add tap to start instruction
var startInstructionText = new Text2("Tap to Start", {
size: 80,
fill: 0xFFFFFF
});
startInstructionText.anchor.set(0.5, 0.5);
startInstructionText.x = 2048 / 2;
startInstructionText.y = 2732 / 2 + 200;
game.addChild(startInstructionText);
// Add high score display to the start screen
var highScoreText = new Text2("High Score: $" + storage.highScore + ".00", {
size: 60,
fill: 0xFFFFFF
});
highScoreText.anchor.set(0.5, 0);
highScoreText.x = 2048 / 2;
highScoreText.y = 2732 - 200;
game.addChild(highScoreText);
// Make the text pulse
tween(startInstructionText.scale, {
x: 1.1,
y: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
loop: -1,
yoyo: true
});
// Add stonksguy mascot to lower-left corner
var stonksGuy = game.addChild(LK.getAsset('stonksguy', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 2732 - 120
}));
stonksGuy.visible = true; // Ensure stonksguy is visible
// Create arrow in the center of the screen
arrow = new Arrow();
// Arrow position is set in the class constructor
game.addChild(arrow);
// No camera setup needed since we have a fixed view
// Background music will play on first touch, not at initialization
// Handle touch events
game.down = function (x, y, obj) {
if (!gameStarted) {
// First touch - start the game
gameStarted = true;
// Start playing background music now
LK.playMusic('bgMusic');
// Hide the logo and instruction
tween(logoText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onComplete: function onComplete() {
logoText.destroy();
}
});
tween(startInstructionText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onComplete: function onComplete() {
startInstructionText.destroy();
}
});
// Show the market cap text
marketCapText.visible = true;
// Set touch state
game.isTouching = true;
} else if (gameActive) {
game.isTouching = true;
}
};
game.up = function (x, y, obj) {
if (gameActive) {
game.isTouching = false;
}
};
// Generate obstacles
function createObstacle() {
var obstacle = new Obstacle();
// Since arrow is fixed in center, use fixed screen coordinates
var screenWidth = 2048;
var screenHeight = 2732;
// Randomly decide if this obstacle should be directly in the arrow's path
var inPath = Math.random() < 0.4; // 40% chance to spawn in path
var spawnRegion = Math.random();
if (inPath) {
// Spawn directly in the path of the arrow based on current direction
var distanceAhead = 1000 + Math.random() * 500; // Spawn far enough to give player time to react
if (arrow.direction === "northeast") {
// Position in the northeast path of the arrow
obstacle.x = arrow.x + distanceAhead;
obstacle.y = arrow.y - distanceAhead;
} else {
// Position in the southeast path of the arrow
obstacle.x = arrow.x + distanceAhead;
obstacle.y = arrow.y + distanceAhead;
}
} else if (spawnRegion < 0.7) {
// Spawn directly to the right of the arrow (off-screen)
obstacle.x = screenWidth + Math.random() * 200; // Just off-screen to the right
obstacle.y = arrow.y; // Aligned with the arrow's y position
} else {
// Spawn above
obstacle.x = screenWidth / 2 + Math.random() * 500; // Spread to the right of center
obstacle.y = -200 - Math.random() * 200; // Just above the visible screen
}
obstacles.push(obstacle);
game.addChild(obstacle);
}
// Generate bonuses
function createBonus() {
var bonus = new Bonus();
// Since arrow is fixed in center, use fixed screen coordinates
var screenWidth = 2048;
var screenHeight = 2732;
// Spawn bonuses at the upper-right corner of visible screen
var spawnRegion = Math.random();
if (spawnRegion < 0.6) {
// Spawn to the right
bonus.x = screenWidth + Math.random() * 300; // Just off-screen to the right
bonus.y = screenHeight / 2 - Math.random() * 600; // Spread around middle height
} else {
// Spawn above
bonus.x = screenWidth / 2 + Math.random() * 600; // Spread to the right of center
bonus.y = -200 - Math.random() * 300; // Just above the visible screen
}
bonuses.push(bonus);
game.addChild(bonus);
}
// Create dollar bill in appropriate direction
function createDollarBill() {
if (!arrow || !gameActive) {
return;
}
var dollarBill = new DollarBill(arrow.direction);
// Position the dollar bill in front of the arrow based on direction
// Since the arrow is stationary, we position relative to the center
var offsetDistance = 100;
if (arrow.direction === "northeast") {
dollarBill.x = arrow.x + offsetDistance;
dollarBill.y = arrow.y - offsetDistance;
dollarBill.rotation = -Math.PI / 4; // 45 degrees up
} else {
dollarBill.x = arrow.x + offsetDistance;
dollarBill.y = arrow.y + offsetDistance;
dollarBill.rotation = Math.PI / 4; // 45 degrees down
}
dollarBills.push(dollarBill);
game.addChild(dollarBill);
}
// Create stonks text popup
function createStonksText(isPositive, x, y) {
var message = isPositive ? "STONKS!" : "NOT STONKS!";
var color = isPositive ? "#00FF00" : "#FF0000";
var stonksText = new StonksText(message, color);
stonksText.x = x;
stonksText.y = y;
stonksText.animate();
stonksTexts.push(stonksText);
game.addChild(stonksText);
}
// Game update loop
game.update = function () {
if (!gameStarted || !gameActive) {
return;
}
// Check if we should create a moon (every 20,000 points)
// Calculate the current "milestone" we've passed
var currentMilestone = Math.floor(marketCap / moonAppearThreshold) * moonAppearThreshold;
// If we've crossed a new 20,000 point threshold and we're not at a negative market cap
if (currentMilestone > lastMoonCheckPoint && marketCap > 0) {
// Create a new moon
var moon = new Moon();
moons.push(moon);
// Add to game at a lower z-index (behind everything except background)
game.addChildAt(moon, game.getChildIndex(backgroundGrid) + 1);
// Update the last checkpoint
lastMoonCheckPoint = currentMilestone;
}
// Update the background grid
backgroundGrid.update();
// Calculate spawn rate multipliers based on market cap
var marketCapFactor = Math.max(0, marketCap) / 100;
// Adjust scaling for rates to increase more gradually after 300%
var scaleMultiplier;
if (marketCapFactor <= 20) {
// Up to 300% speed (market cap of 2000)
scaleMultiplier = marketCapFactor;
} else {
// More gradual scaling after 300%
scaleMultiplier = 20 + (marketCapFactor - 20) * 0.4; // Slower increase after 300%
// Cap at 500% (market cap of 2000 + 5000 = 7000)
scaleMultiplier = Math.min(scaleMultiplier, 40); // 40 is the cap for 500%
}
// Calculate rates with the new scaling
var obstacleRate = Math.max(10, 60 - scaleMultiplier * 0.5); // Decrease from 60 down to 12 (500% of initial rate)
var bonusRate = Math.max(36, 180 - scaleMultiplier * 1.0); // Decrease from 180 down to 36 (500% of initial rate)
var dollarRate = Math.max(6, 30 - scaleMultiplier * 0.25); // Decrease from 30 down to 6 (500% of initial rate)
// Scale stonksguy based on the current game speed - increases as the game speeds up
// Calculate scale factor based on current speed (10 = normal speed, 50 = 500% speed)
var speedScaleFactor = arrow.speed / 10; // 1.0 to 5.0
// Use a more moderate scaling that still shows growth but doesn't become too large
var stonksGuyScale = 1.0 + (speedScaleFactor - 1) * 0.2; // Scale from 1.0 to 1.6 (at 500% speed)
stonksGuy.scale.set(stonksGuyScale, stonksGuyScale);
// Make stonksguy pulse (move up and down) every half second
if (!stonksGuy.isPulsing) {
var _pulseY = function pulseY() {
tween(stonksGuy, {
y: stonksGuy.originalY + 20
}, {
duration: 483,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(stonksGuy, {
y: stonksGuy.originalY
}, {
duration: 483,
easing: tween.easeInOut,
onFinish: _pulseY
});
}
});
};
stonksGuy.isPulsing = true;
stonksGuy.originalY = stonksGuy.y;
_pulseY();
}
// Create obstacles at dynamic intervals
lastObstacleTime = lastObstacleTime + 1;
if (lastObstacleTime > obstacleRate) {
createObstacle();
lastObstacleTime = 0;
}
// Create bonuses at dynamic intervals
lastBonusTime = lastBonusTime + 1;
if (lastBonusTime > bonusRate) {
createBonus();
lastBonusTime = 0;
}
// Create dollar bills at dynamic intervals
lastDollarTime = lastDollarTime + 1;
if (lastDollarTime > dollarRate) {
createDollarBill();
lastDollarTime = 0;
}
// Update marketCap - increase when going northeast, decrease when going southeast
var oldSpeed = arrow.speed;
if (arrow.direction === "northeast") {
marketCap += 1;
} else if (arrow.direction === "southeast") {
marketCap -= 1;
}
// Calculate current and previous spawn rates
var prevMarketCapFactor = Math.max(0, marketCap - 1) / 100;
var prevDollarRate = Math.max(10, 30 - prevMarketCapFactor * 5);
var currentDollarRate = Math.max(10, 30 - marketCapFactor * 5);
// Show visual feedback when speed increases significantly
if (arrow.speed - oldSpeed > 0.5) {
var speedText = new StonksText("SPEED UP!", "#FFFF00");
speedText.x = arrow.x;
speedText.y = arrow.y - 100;
speedText.animate();
stonksTexts.push(speedText);
game.addChild(speedText);
}
// Show visual feedback when spawn rate increases significantly
if (prevDollarRate - currentDollarRate > 0.5 && arrow.direction === "northeast") {
var spawnText = new StonksText("SPAWN RATE UP!", "#00FFFF");
spawnText.x = arrow.x + 100;
spawnText.y = arrow.y;
spawnText.animate();
stonksTexts.push(spawnText);
game.addChild(spawnText);
}
// Check if market cap has reached game-ending threshold of -10
if (marketCap <= -10 && gameActive) {
// Game over
gameActive = false;
createStonksText(false, arrow.x, arrow.y);
LK.getSound('crash').play();
// Flash screen red
LK.effects.flashScreen(0xFF0000, 1000);
// Show game over
LK.setTimeout(function () {
LK.setScore(marketCap);
// Update high score if current market cap is higher
if (marketCap > storage.highScore) {
storage.highScore = marketCap;
}
LK.showGameOver();
}, 1500);
}
// Calculate rate percentages relative to initial rates for display
// Cap display at 500%
var speedPercentage = Math.min(500, Math.round(arrow.speed / 10 * 100));
var spawnRatePercentage = Math.min(500, Math.round(30 / dollarRate * 100));
marketCapText.setText("Stonk Market Cap: $" + marketCap + ".00");
// Check if obstacles are completely off-screen
for (var i = obstacles.length - 1; i >= 0; i--) {
// Remove obstacles that have moved off the left side of the screen
if (obstacles[i].x < -200) {
obstacles[i].destroy();
obstacles.splice(i, 1);
}
}
// Check for bonuses that are off-screen
for (var i = bonuses.length - 1; i >= 0; i--) {
if (bonuses[i].x < -200 || bonuses[i].collected) {
bonuses[i].destroy();
bonuses.splice(i, 1);
}
}
// Remove faded stonks texts
for (var i = stonksTexts.length - 1; i >= 0; i--) {
if (stonksTexts[i].alpha <= 0) {
stonksTexts[i].destroy();
stonksTexts.splice(i, 1);
}
}
// Clean up dollar bills that are off-screen
for (var i = dollarBills.length - 1; i >= 0; i--) {
if (dollarBills[i].x < -200) {
dollarBills[i].destroy();
dollarBills.splice(i, 1);
}
}
// Clean up moons that are ready for removal
for (var i = moons.length - 1; i >= 0; i--) {
if (moons[i].readyForRemoval) {
moons[i].destroy();
moons.splice(i, 1);
}
}
// Check for collisions with obstacles
for (var i = 0; i < obstacles.length; i++) {
if (arrow.intersects(obstacles[i])) {
// Game over
gameActive = false;
createStonksText(false, arrow.x, arrow.y);
LK.getSound('crash').play();
// Stop background music immediately
LK.stopMusic();
// Flash screen red
LK.effects.flashScreen(0xFF0000, 1000);
// Show game over
LK.setTimeout(function () {
LK.setScore(marketCap);
// Update high score if current market cap is higher
if (marketCap > storage.highScore) {
storage.highScore = marketCap;
}
LK.showGameOver();
}, 1500);
break;
}
}
// Check for collisions with bonuses
for (var i = bonuses.length - 1; i >= 0; i--) {
if (!bonuses[i].collected && arrow.intersects(bonuses[i])) {
// Collect bonus
bonuses[i].collect();
LK.getSound('collect').play();
// Add bonus score
var bonusValue = Math.floor(Math.random() * 1000) + 500;
marketCap += bonusValue;
// Show stonks text
createStonksText(true, arrow.x, arrow.y);
// Flash screen green
LK.effects.flashScreen(0x00FF00, 300);
}
}
};
arrow pointing to the right like on a stock market ticker. No shadows
cartoonish dollar bill. In-Game asset. 2d. High contrast. No shadows
green stock market chart candle. In-Game asset. 2d. High contrast. No shadows
red stock market chart candle. In-Game asset. 2d. High contrast. No shadows
screen-wrappable repeating blue grid on dark background. In-Game asset. 2d. High contrast. No shadows
white full moon. In-Game asset. 2d. High contrast. No shadows