User prompt
Stop screen flashing in rhythm mode
User prompt
Make sure best scores are being saved in rhythm mode ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Ensure ball goes through hoop with perfect tap ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Increase tap sensitivity
User prompt
In rhythm mode when tapped correctly make it the same as getting a greenzone perfect shot on swish mode ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
If a rhythm circles passes the tapzones without being tapped it counts as a miss
User prompt
In rhythm mode if you miss a rhythm circle 3 times it is game over.
User prompt
In rhythm mode if you miss a rhythm circles it is game over.
User prompt
In rhythm mode when you tap correctly make the players in that row shoot perfect swishes ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'ReferenceError: rhythmBestScore is not defined' in or related to this line: 'if (currentScore > rhythmBestScore) {' Line Number: 1902 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Seperate best scores for swish mode from best scores in rhythm mode ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Make tapzones taller
User prompt
Make tapzones taller
User prompt
In rhythm mode replace shotbars with a horizontal tap zone that if player taps the falling rhythm circles in the tap zone they score a perfect swish ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In rhythm mode Remove shotbar basketballs and score basket when you tap a rhythm circle in line with the greenzone ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Remove shotbar basketballs and score basket when you tap a rhythm circle in line with the greenzone
User prompt
Make the rhythm circles drop at 124 bpm ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Why isn't this system working for the rhythm game
User prompt
Score rhythm points if you tap the ball in the greenzone and the rhythm circles at the same time ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make timing rhythm circles fall from top of the screen in line with the greenzone balls and they need to be tapped as they pass the greenzone balls ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In rhythm mode Make rhythm circles fall to time tapping green ball ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'rhythmCircleActive')' in or related to this line: 'if (!firstBar.rhythmCircleActive) {' Line Number: 1831
User prompt
In rhythm mode Change the greenzones in score bars into rhythm circles that player must press every .5 seconds . If they press them correctly starting from left to right the balls will swish perfectly ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change the greenzones in score bars into rhythm circles that player must press every .5 seconds . If they press them correctly starting from left to right the balls will swish perfectly ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Change rhythm mode so that player must tap on of the green zone every .5 seconds to hit a basket and stay on beat.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { money: 0, player2Unlocked: false, player3Unlocked: false }); /**** * Classes ****/ var BallReflection = Container.expand(function () { var self = Container.call(this); var reflectionGraphics = self.attachAsset('basketball', { anchorX: 0.5, anchorY: 0.5 }); // Make reflection more transparent and darker reflectionGraphics.alpha = 0.3; reflectionGraphics.tint = 0x666666; // Flip reflection vertically reflectionGraphics.scaleY = -0.8; // Flipped and slightly smaller reflectionGraphics.scaleX = 0.8; // Set z-index to render on floor reflectionGraphics.zIndex = -3; self.parentBall = null; self.floorY = 1832; // Move floor up 900 pixels (700 + 200) self.update = function () { if (!self.parentBall || !self.parentBall.isActive) { self.destroy(); return; } // Position reflection below the ball self.x = self.parentBall.x; self.y = self.floorY - (self.floorY - self.parentBall.y) * 0.2; // Reflection distance from floor // Match parent ball rotation self.rotation = self.parentBall.rotation; // Fade reflection based on ball height var heightFactor = Math.max(0, (self.floorY - self.parentBall.y) / 1000); reflectionGraphics.alpha = 0.3 * heightFactor; }; return self; }); var Basketball = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('basketball', { anchorX: 0.5, anchorY: 0.5 }); // Set z-index to render in front of player1 ballGraphics.zIndex = 2; self.velocityX = 0; self.velocityY = 0; self.gravity = 0.5; self.bounceDecay = 0.7; self.hasScored = false; self.isActive = true; self.guaranteedSwish = false; self.update = function () { if (!self.isActive) return; self.velocityY += self.gravity; self.x += self.velocityX; self.y += self.velocityY; // Check hoop collision for all hoops var hoopsToCheck = [hoop]; if (typeof leftHoop !== 'undefined') { hoopsToCheck.push(leftHoop); } if (typeof rightHoop !== 'undefined') { hoopsToCheck.push(rightHoop); } for (var hoopIndex = 0; hoopIndex < hoopsToCheck.length; hoopIndex++) { var currentHoop = hoopsToCheck[hoopIndex]; if (currentHoop) { var hoopX = currentHoop.x; var hoopY = currentHoop.y - 350; // Adjust for hoop position within container var relativeX = self.x - hoopX; var relativeY = self.y - hoopY; // Check for scoring through hoop first (ball going through net area) // For guaranteed swish shots, use more lenient detection var hoopDetectionWidth = self.guaranteedSwish ? 80 : 60; var hoopDetectionTop = self.guaranteedSwish ? -20 : -10; var hoopDetectionBottom = self.guaranteedSwish ? 60 : 50; if (Math.abs(relativeX) <= hoopDetectionWidth && relativeY >= hoopDetectionTop && relativeY <= hoopDetectionBottom && self.velocityY > 0 && !self.hasScored) { // Ball is going through hoop - animate net and score self.hasScored = true; self.isActive = false; // Set ball to render behind net and hoop ballGraphics.zIndex = -2; // Animate ball going through and down tween(self, { y: self.y + 100, x: hoopX + (Math.random() - 0.5) * 20 }, { duration: 500, easing: tween.easeIn }); // Animate net pulsate tween(currentHoop.net, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(currentHoop.net, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeIn }); } }); // Score the basket currentStreak++; // Check for streak rewards var moneyEarned = 0; if (currentStreak === 10 && streakRewardsAwarded.indexOf(10) === -1) { moneyEarned = 20.00; streakRewardsAwarded.push(10); } else if (currentStreak === 20 && streakRewardsAwarded.indexOf(20) === -1) { moneyEarned = 50.00; streakRewardsAwarded.push(20); } else if (currentStreak === 30 && streakRewardsAwarded.indexOf(30) === -1) { moneyEarned = 75.00; streakRewardsAwarded.push(30); } else if (currentStreak === 50 && streakRewardsAwarded.indexOf(50) === -1) { moneyEarned = 100.00; streakRewardsAwarded.push(50); } // Apply money multiplier if active if (moneyEarned > 0) { if (moneyMultiplierBaskets > 0) { moneyEarned *= 2; moneyMultiplierBaskets--; } currentMoney += moneyEarned; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); LK.effects.flashScreen(0x00FF00, 600); // Green flash for money } var points = 2; if (self.isPerfectShot) { // Use cycling color for perfect shot screen flash var flashColor = basketColors[currentColorIndex]; LK.effects.flashScreen(flashColor, 400); } // Apply 2x multiplier if all backboards are same color if (allSameColorMultiplier) { points *= 2; } // Apply rhythm multiplier if in rhythm mode if (typeof rhythmMultiplier !== 'undefined' && rhythmMultiplier > 1) { points = Math.floor(points * rhythmMultiplier); } LK.setScore(LK.getScore() + points); // Check if score reached multiple of 50 points to reset timer var currentScore = LK.getScore(); var previousScore = currentScore - points; var currentFifties = Math.floor(currentScore / 50); var previousFifties = Math.floor(previousScore / 50); if (currentFifties > previousFifties) { gameTimer = 60 * 60; // Reset to 60 seconds at 60 FPS gameTimeLeft = 60; timerTxt.setText('01:00'); } // Play different sound based on which hoop scored if (currentHoop === leftHoop) { LK.getSound('1').play(); } else if (currentHoop === rightHoop) { LK.getSound('3').play(); } else { LK.getSound('2').play(); } scoreTxt.setText('Score: ' + LK.getScore()); streakTxt.setText('Streak: ' + currentStreak); LK.effects.flashObject(currentHoop, self.isPerfectShot ? 0xFFD700 : 0x00FF00, 300); // Animate backboard color change var backboard = currentHoop.children[0]; // Get the backboard (first child) if (self.isPerfectShot) { // For perfect shots, use cycling color directly var flashColor = basketColors[currentColorIndex]; // Cycle to next color for next basket currentColorIndex = (currentColorIndex + 1) % basketColors.length; // Track which backboard was scored on and update color tracking var hoopIndex = 0; // Default to center hoop if (currentHoop === leftHoop) hoopIndex = 1; if (currentHoop === rightHoop) hoopIndex = 2; backboardColors[hoopIndex] = flashColor; tween(backboard, { tint: flashColor }, { duration: 300, easing: tween.easeOut }); // Check if all backboards now have same color checkAllBackboardsSameColor(); // Play different sound based on which hoop scored when backboard changes color if (currentHoop === leftHoop) { LK.getSound('1').play(); } else if (currentHoop === rightHoop) { LK.getSound('3').play(); } else { LK.getSound('2').play(); } } else { // For regular shots, use cycling color directly var flashColor = basketColors[currentColorIndex]; // Cycle to next color for next basket currentColorIndex = (currentColorIndex + 1) % basketColors.length; // Track which backboard was scored on and update color tracking var hoopIndex = 0; // Default to center hoop if (currentHoop === leftHoop) hoopIndex = 1; if (currentHoop === rightHoop) hoopIndex = 2; backboardColors[hoopIndex] = flashColor; tween(backboard, { tint: flashColor }, { duration: 300, easing: tween.easeOut }); // Check if all backboards now have same color checkAllBackboardsSameColor(); // Play different sound based on which hoop scored when backboard changes color if (currentHoop === leftHoop) { LK.getSound('1').play(); } else if (currentHoop === rightHoop) { LK.getSound('3').play(); } else { LK.getSound('2').play(); } } return; } // Check if ball is hitting the hoop rim if (relativeX >= -125 && relativeX <= 125 && relativeY >= -25 && relativeY <= 25) { // Determine which side of the hoop was hit if (Math.abs(relativeX) > 90) { // Hit the sides of the rim self.velocityX = -self.velocityX * self.bounceDecay; if (relativeX < 0) { self.x = hoopX - 125; } else { self.x = hoopX + 125; } LK.getSound('bounce').play(); } else if (relativeY >= -25 && relativeY <= 0 && self.velocityY > 0) { // Hit the top of the rim (bouncing up) self.velocityY = -Math.abs(self.velocityY) * self.bounceDecay; self.y = hoopY - 25; self.velocityX *= 0.8; // Reduce horizontal velocity slightly LK.getSound('bounce').play(); } } // Check if ball is resting on the rim (low velocity and on top) if (Math.abs(relativeX) <= 90 && relativeY >= -30 && relativeY <= -20 && Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 3) { // Ball is resting on rim, make it roll into the hoop self.isActive = false; // Stop normal physics self.hasScored = true; // Mark as scored // Set ball to render behind net and hoop ballGraphics.zIndex = -2; // Animate rolling into hoop tween(self, { x: hoopX, y: hoopY + 40, rotation: self.rotation + Math.PI * 2 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { // Animate net pulsate tween(currentHoop.net, { scaleX: 1.8, scaleY: 1.8 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(currentHoop.net, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeIn }); } }); // Score the basket currentStreak++; // Check for streak rewards if (currentStreak === 10 && streakRewardsAwarded.indexOf(10) === -1) { currentMoney += 20.00; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); streakRewardsAwarded.push(10); LK.effects.flashScreen(0x00FF00, 600); // Green flash for money } else if (currentStreak === 20 && streakRewardsAwarded.indexOf(20) === -1) { currentMoney += 50.00; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); streakRewardsAwarded.push(20); LK.effects.flashScreen(0x00FF00, 600); // Green flash for money } else if (currentStreak === 30 && streakRewardsAwarded.indexOf(30) === -1) { currentMoney += 75.00; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); streakRewardsAwarded.push(30); LK.effects.flashScreen(0x00FF00, 600); // Green flash for money } else if (currentStreak === 50 && streakRewardsAwarded.indexOf(50) === -1) { currentMoney += 100.00; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); streakRewardsAwarded.push(50); LK.effects.flashScreen(0x00FF00, 600); // Green flash for money } var points = 2; // Apply 2x multiplier if all backboards are same color if (allSameColorMultiplier) { points *= 2; } LK.setScore(LK.getScore() + points); // Check if score reached multiple of 50 points to reset timer var currentScore = LK.getScore(); var previousScore = currentScore - points; var currentFifties = Math.floor(currentScore / 50); var previousFifties = Math.floor(previousScore / 50); if (currentFifties > previousFifties) { gameTimer = 60 * 60; // Reset to 60 seconds at 60 FPS gameTimeLeft = 60; timerTxt.setText('01:00'); } // Play different sound based on which hoop scored if (currentHoop === leftHoop) { LK.getSound('1').play(); } else if (currentHoop === rightHoop) { LK.getSound('3').play(); } else { LK.getSound('2').play(); } // Update displays scoreTxt.setText('Score: ' + LK.getScore()); streakTxt.setText('Streak: ' + currentStreak); // Visual feedback LK.effects.flashObject(currentHoop, 0x00FF00, 300); // Animate backboard color change var backboard = currentHoop.children[0]; // Get the backboard (first child) var flashColor = basketColors[currentColorIndex]; // Cycle to next color for next basket currentColorIndex = (currentColorIndex + 1) % basketColors.length; // Track which backboard was scored on and update color tracking var hoopIndex = 0; // Default to center hoop if (currentHoop === leftHoop) hoopIndex = 1; if (currentHoop === rightHoop) hoopIndex = 2; backboardColors[hoopIndex] = flashColor; tween(backboard, { tint: flashColor }, { duration: 300, easing: tween.easeOut }); // Check if all backboards now have same color checkAllBackboardsSameColor(); // Play different sound based on which hoop scored when backboard changes color if (currentHoop === leftHoop) { LK.getSound('1').play(); } else if (currentHoop === rightHoop) { LK.getSound('3').play(); } else { LK.getSound('2').play(); } } }); } } } // Ground collision removed - ball can fall through // Remove if off screen - ball is just removed, no streak penalty if (self.x < -100 || self.x > 2148 || self.y > 2800) { self.isActive = false; } }; return self; }); var Hoop = Container.expand(function () { var self = Container.call(this); var backboard = self.attachAsset('backboard', { anchorX: 0.5, anchorY: 1 }); backboard.y = -20; var hoop = self.attachAsset('hoop', { anchorX: 0.5, anchorY: 0.5 }); hoop.y = -350; // Set z-index to render in front of basketball hoop.zIndex = 1; var net = self.attachAsset('net', { anchorX: 0.5, anchorY: 0, scaleX: 1.5, scaleY: 1.5 }); net.y = -325; net.alpha = 0.8; // Set z-index to render in front of basketball net.zIndex = 2; // Store net reference for animation access self.net = net; self.hoopBounds = { left: -90, right: 90, top: -20, bottom: 20 }; // Enable z-index sorting for proper rendering order self.sortableChildren = true; return self; }); var Shop = Container.expand(function () { var self = Container.call(this); // Shop background var shopBg = self.attachAsset('Player4', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 25 }); shopBg.tint = 0x333333; shopBg.alpha = 0.9; self.isVisible = false; self.visible = false; // Shop title var shopTitle = new Text2('SHOP', { size: 120, fill: 0xFFD700 }); shopTitle.anchor.set(0.5, 0.5); shopTitle.y = -800; self.addChild(shopTitle); // Shop items array self.shopItems = [{ name: 'Speed Boost', description: 'Increase shot speed by 25%', cost: 50.00, purchased: false, type: 'speed' }, { name: 'Perfect Shot', description: 'Next 3 shots guaranteed perfect', cost: 25.00, purchased: false, type: 'perfect' }, { name: 'Money Multiplier', description: 'Double money for 10 baskets', cost: 75.00, purchased: false, type: 'money' }, { name: 'Time Extension', description: 'Add 30 seconds to timer', cost: 100.00, purchased: false, type: 'time' }]; // Create shop item buttons self.itemButtons = []; for (var i = 0; i < self.shopItems.length; i++) { var item = self.shopItems[i]; var yOffset = -400 + i * 200; // Item background var itemBg = self.attachAsset('Player4', { anchorX: 0.5, anchorY: 0.5, scaleX: 15, scaleY: 3 }); itemBg.y = yOffset; itemBg.tint = 0x555555; itemBg.alpha = 0.8; // Item name text var nameText = new Text2(item.name, { size: 60, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.y = yOffset - 30; self.addChild(nameText); // Item description text var descText = new Text2(item.description, { size: 40, fill: 0xCCCCCC }); descText.anchor.set(0.5, 0.5); descText.y = yOffset + 10; self.addChild(descText); // Item cost text var costText = new Text2('$' + item.cost.toFixed(2), { size: 50, fill: 0x00FF00 }); costText.anchor.set(0.5, 0.5); costText.y = yOffset + 50; self.addChild(costText); // Store button info for click detection self.itemButtons.push({ index: i, bounds: { x: -400, y: yOffset - 75, width: 800, height: 150 }, costText: costText, nameText: nameText }); } // Close button var closeBtn = new Text2('CLOSE', { size: 80, fill: 0xFF0000 }); closeBtn.anchor.set(0.5, 0.5); closeBtn.y = 600; self.addChild(closeBtn); self.closeBounds = { x: -150, y: 550, width: 300, height: 100 }; self.show = function () { // Pause the game LK.pauseGame(); // Show shop background var shopBackground = LK.getAsset('ShopBackground', { anchorX: 0, anchorY: 0 }); shopBackground.x = 0; shopBackground.y = 0; shopBackground.width = 2048; shopBackground.height = 2732; shopBackground.zIndex = 99; self.shopBg = shopBackground; game.addChild(shopBackground); self.isVisible = true; self.visible = true; self.x = 1024; self.y = 1366; self.zIndex = 100; // Update item availability based on money for (var i = 0; i < self.itemButtons.length; i++) { var button = self.itemButtons[i]; var item = self.shopItems[button.index]; if (currentMoney >= item.cost && !item.purchased) { button.costText.fill = 0x00FF00; button.nameText.fill = 0xFFFFFF; } else if (item.purchased) { button.costText.setText('OWNED'); button.costText.fill = 0xFFD700; button.nameText.fill = 0xFFD700; } else { button.costText.fill = 0xFF0000; button.nameText.fill = 0x888888; } } }; self.hide = function () { // Resume the game LK.resumeGame(); // Remove shop background if (self.shopBg) { self.shopBg.destroy(); self.shopBg = null; } self.isVisible = false; self.visible = false; }; self.handleClick = function (localX, localY) { // Check close button if (localX >= self.closeBounds.x && localX <= self.closeBounds.x + self.closeBounds.width && localY >= self.closeBounds.y && localY <= self.closeBounds.y + self.closeBounds.height) { self.hide(); return true; } // Check item buttons for (var i = 0; i < self.itemButtons.length; i++) { var button = self.itemButtons[i]; var bounds = button.bounds; if (localX >= bounds.x && localX <= bounds.x + bounds.width && localY >= bounds.y && localY <= bounds.y + bounds.height) { var item = self.shopItems[button.index]; // Check if can purchase if (currentMoney >= item.cost && !item.purchased) { self.purchaseItem(button.index); return true; } } } return false; }; self.purchaseItem = function (itemIndex) { var item = self.shopItems[itemIndex]; // Deduct money currentMoney -= item.cost; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); // Apply item effect switch (item.type) { case 'speed': // Increase shot bar speed for all bars shotBar.moveSpeed *= 1.25; shotBar2.moveSpeed *= 1.25; shotBar3.moveSpeed *= 1.25; break; case 'perfect': // Grant 3 guaranteed perfect shots perfectShotsRemaining = 3; break; case 'money': // Double money for next 10 baskets moneyMultiplierBaskets = 10; break; case 'time': // Add 30 seconds to timer gameTimer += 30 * 60; // 30 seconds at 60 FPS gameTimeLeft += 30; var minutes = Math.floor(gameTimeLeft / 60); var seconds = gameTimeLeft % 60; var formattedTime = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds; timerTxt.setText(formattedTime); break; } // Mark as purchased (except consumables) if (item.type !== 'perfect' && item.type !== 'money' && item.type !== 'time') { item.purchased = true; } // Visual feedback LK.effects.flashScreen(0x00FF00, 300); // Update display self.show(); }; return self; }); var ShotBar = Container.expand(function () { var self = Container.call(this); // Background bar - rotated to be vertical var bgBar = self.attachAsset('shotBarBg', { anchorX: 0.5, anchorY: 0.5 }); bgBar.rotation = Math.PI / 2 + Math.PI; // Rotate 90 degrees + 180 degrees to make vertical and flip // Green zone - rotated to be vertical var greenZone = self.attachAsset('shotBarGreen', { anchorX: 0.5, anchorY: 0.5 }); greenZone.rotation = Math.PI / 2 + Math.PI; // Rotate 90 degrees + 180 degrees to make vertical and flip greenZone.y = 0; // Start at center, will move vertically // Moving basketball indicator var indicator = self.attachAsset('basketball', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); self.isActive = false; self.isMoving = false; self.moveDirection = 1; // 1 for right, -1 for left self.moveSpeed = 8; self.maxDistance = 240; // Half of bar width minus indicator size self.perfectShot = false; self.startMoving = function () { self.isActive = true; self.isMoving = true; self.perfectShot = false; // Position green zone randomly vertically greenZone.y = (Math.random() - 0.5) * 360; // Random position within vertical bar // Reset indicator position vertically - start at top indicator.y = -self.maxDistance; self.moveDirection = 1; // Start moving downward self.visible = true; }; self.stopMoving = function () { self.isMoving = false; // Check if indicator is within green zone vertically var greenTop = greenZone.y - 60; var greenBottom = greenZone.y + 60; self.perfectShot = indicator.y >= greenTop && indicator.y <= greenBottom; // Use perfect shot bonus if available if (perfectShotsRemaining > 0 && !self.perfectShot) { self.perfectShot = true; perfectShotsRemaining--; } // Reset streak if player misses the green zone if (!self.perfectShot) { currentStreak = 0; streakTxt.setText('Streak: ' + currentStreak); streakRewardsAwarded = []; // Reset streak rewards } // Keep shot bar visible and restart movement after a brief pause var self_ref = self; LK.setTimeout(function () { self_ref.startMoving(); }, 200); return self.perfectShot; }; self.update = function () { if (!self.isMoving) return; // Increase speed every 50 points scored var currentSpeed = self.moveSpeed; var scoreMultiplier = Math.floor(LK.getScore() / 50); if (scoreMultiplier > 0) { currentSpeed = self.moveSpeed * (1 + scoreMultiplier * 0.5); // 50% speed increase per 50 points } // Move indicator back and forth vertically indicator.y += currentSpeed * self.moveDirection; // Bounce off edges if (indicator.y >= self.maxDistance) { indicator.y = self.maxDistance; self.moveDirection = -1; } else if (indicator.y <= -self.maxDistance) { indicator.y = -self.maxDistance; self.moveDirection = 1; } }; self.visible = true; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ // Game state management var gameState = 'title'; // 'title', 'playing', 'modeSelect' var gameMode = 'swish'; // 'swish' or 'rhythm' var modeSelectBackground = null; var swishButton = null; var rhythmButton = null; var titleBackground = null; var titleText = null; var startButton = null; var basketballs = []; var ballReflections = []; var hoop = null; var leftHoop = null; var rightHoop = null; var ground = null; var scoreTxt = null; var streakTxt = null; var timerTxt = null; var player1 = null; var player1down = null; var player2 = null; var player2down = null; var player3 = null; var player3down = null; var currentMoney = 0; var moneyTxt = null; var multiplierTxt = null; var bestScore = 0; var bestScoreTxt = null; var currentStreak = 0; var streakRewardsAwarded = []; // Track which streak rewards have been given var swipeStartX = 0; var swipeStartY = 0; var swipeStartTime = 0; var isSwipeStarted = false; var lastShotTime = 0; var gameTimer = 60 * 60; // 60 seconds at 60 FPS var gameTimeLeft = 60; var gameEnded = false; var musicNotes = ['note1', 'note2', 'note3', 'note4', 'note5']; var currentNoteIndex = 0; var shotBar = null; var shotBar2 = null; var shotBar3 = null; var isChargingShot = false; // Color cycling system for backboard changes var basketColors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFA500, 0x800080, 0xFFB6C1, 0x90EE90, 0x87CEEB, 0xDDA0DD]; var currentColorIndex = 0; // Track backboard colors for multiplier system var backboardColors = [0xFFFFFF, 0xFFFFFF, 0xFFFFFF]; // [hoop, leftHoop, rightHoop] - start with white var allSameColorMultiplier = false; var moneyAwarded = false; // Track if money has been awarded for current color match var player2Unlocked = storage.player2Unlocked || false; var player3Unlocked = storage.player3Unlocked || false; var unlockCost = 100.00; // Shop variables var shop = null; var perfectShotsRemaining = 0; var moneyMultiplierBaskets = 0; // Rhythm mode variables var beatInterval = 30; // 30 frames = 0.5 seconds at 60 FPS var lastBeatTime = 0; var beatTolerance = 10; // frames of tolerance for rhythm timing var rhythmStreak = 0; var beatIndicator = null; var rhythmMultiplier = 1; var greenZone = null; var lastTapTime = 0; var mustTapGreenZone = false; // Initialize title screen initializeTitleScreen(); function createBasketball(startX, startY, velocityX, velocityY) { var ball = new Basketball(); ball.x = startX; ball.y = startY; ball.velocityX = velocityX; ball.velocityY = velocityY; ball.isPerfectShot = false; basketballs.push(ball); game.addChild(ball); // Create reflection for this ball var reflection = new BallReflection(); reflection.parentBall = ball; ballReflections.push(reflection); game.addChild(reflection); return ball; } function checkBasketScore(ball) { // Scoring is now handled in Basketball class update method // This function is kept for compatibility but does nothing return; } function initializeTitleScreen() { // Create title background titleBackground = game.addChild(LK.getAsset('TitleBackground', { anchorX: 0, anchorY: 0 })); titleBackground.x = 0; titleBackground.y = 0; titleBackground.width = 2048; titleBackground.height = 2732; titleBackground.zIndex = -5; // Create game title using asset titleText = game.addChild(LK.getAsset('Title', { anchorX: 0.5, anchorY: 0.5 })); titleText.x = 1024; titleText.y = 900; titleText.zIndex = 10; // Create start button using tap to start asset startButton = game.addChild(LK.getAsset('Taptostart', { anchorX: 0.5, anchorY: 0.5 })); startButton.x = 1274; startButton.y = 1050; startButton.zIndex = 10; // Play make a wish sound when title screen loads LK.getSound('Makeaswish').play(); // Play title music when title screen loads LK.playMusic('Title'); // Animate start button pulsate continuously function pulsateStartButton() { if (!startButton || gameState !== 'title') return; // Safety check tween(startButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (!startButton || gameState !== 'title') return; // Safety check // Create continuous pulsating loop tween(startButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (!startButton || gameState !== 'title') return; // Safety check // Restart the pulsate cycle pulsateStartButton(); } }); } }); } pulsateStartButton(); } function initializeModeSelection() { // Hide title screen elements if (titleBackground) { titleBackground.destroy(); titleBackground = null; } if (titleText) { titleText.destroy(); titleText = null; } if (startButton) { tween.stop(startButton); startButton.destroy(); startButton = null; } // Create mode selection background modeSelectBackground = game.addChild(LK.getAsset('TitleBackground', { anchorX: 0, anchorY: 0 })); modeSelectBackground.x = 0; modeSelectBackground.y = 0; modeSelectBackground.width = 2048; modeSelectBackground.height = 2732; modeSelectBackground.zIndex = -5; // Create mode selection title var modeTitle = new Text2('SELECT MODE', { size: 120, fill: 0xFFD700 }); modeTitle.anchor.set(0.5, 0.5); modeTitle.x = 1024; modeTitle.y = 800; modeTitle.zIndex = 10; game.addChild(modeTitle); // Create Swish mode button swishButton = game.addChild(LK.getAsset('Swish', { anchorX: 0.5, anchorY: 0.5 })); swishButton.x = 1024; swishButton.y = 1200; swishButton.zIndex = 10; // Create Rhythm mode button rhythmButton = new Text2('RHYTHM', { size: 100, fill: 0xFF6B6B }); rhythmButton.anchor.set(0.5, 0.5); rhythmButton.x = 1024; rhythmButton.y = 1600; rhythmButton.zIndex = 10; game.addChild(rhythmButton); // Change game state to mode selection gameState = 'modeSelect'; // Animate buttons function animateModeButtons() { if (gameState !== 'modeSelect') return; tween(swishButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (gameState !== 'modeSelect') return; tween(swishButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 600, easing: tween.easeInOut, onFinish: animateModeButtons }); } }); } animateModeButtons(); } function initializeGameplay() { // Hide title screen elements if (titleBackground) { titleBackground.destroy(); titleBackground = null; } if (titleText) { titleText.destroy(); titleText = null; } if (startButton) { // Stop any active tweens on the start button before destroying tween.stop(startButton); startButton.destroy(); startButton = null; } // Hide mode selection elements if (modeSelectBackground) { modeSelectBackground.destroy(); modeSelectBackground = null; } if (swishButton) { tween.stop(swishButton); swishButton.destroy(); swishButton = null; } if (rhythmButton) { rhythmButton.destroy(); rhythmButton = null; } // Initialize all game elements (moved from main game code) // Create shop instance shop = game.addChild(new Shop()); // Create multiplier indicator text multiplierTxt = new Text2('', { size: 50, fill: 0xFFD700 }); multiplierTxt.anchor.set(0.5, 0); multiplierTxt.y = 180; LK.gui.top.addChild(multiplierTxt); // Enable z-index sorting for proper rendering order game.sortableChildren = true; // Add background with high definition scaling var background = game.addChild(LK.getAsset('Background', { anchorX: 0, anchorY: 0 })); background.x = 0; background.y = 0; background.width = 2048; background.height = 2732; background.zIndex = -10; // Create hoops hoop = game.addChild(new Hoop()); hoop.x = 1024; hoop.y = 1300; leftHoop = game.addChild(new Hoop()); leftHoop.x = 1024 - 375 - 350; leftHoop.y = 1300; rightHoop = game.addChild(new Hoop()); rightHoop.x = 1024 + 375 + 350; rightHoop.y = 1300; // Create UI displays scoreTxt = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); streakTxt = new Text2('Streak: 0', { size: 60, fill: 0xFFD700 }); streakTxt.anchor.set(0.5, 0); streakTxt.y = 100; LK.gui.top.addChild(streakTxt); timerTxt = new Text2('01:00', { size: 80, fill: 0xFF0000, font: "'Courier New', 'Monaco', 'Lucida Console', monospace" }); timerTxt.anchor.set(0.5, 0); timerTxt.y = 160; LK.gui.top.addChild(timerTxt); bestScore = storage.bestScore || 0; bestScoreTxt = new Text2('Best: ' + bestScore, { size: 60, fill: 0xFFD700 }); bestScoreTxt.anchor.set(1, 0); bestScoreTxt.x = -100; LK.gui.topRight.addChild(bestScoreTxt); currentMoney = storage.money || 0; moneyTxt = new Text2('$' + currentMoney.toFixed(2), { size: 50, fill: 0x00FF00 }); moneyTxt.anchor.set(0, 0); moneyTxt.x = 120; moneyTxt.y = 10; LK.gui.topLeft.addChild(moneyTxt); // Create shot bars shotBar = game.addChild(new ShotBar()); shotBar.x = 1024; shotBar.y = 1600; shotBar2 = game.addChild(new ShotBar()); shotBar2.x = 300; shotBar2.y = 1600; shotBar3 = game.addChild(new ShotBar()); shotBar3.x = rightHoop.x; shotBar3.y = 1600; // Add players player1 = game.addChild(LK.getAsset('Player1', { anchorX: 0.5, anchorY: 1 })); player1.x = 1024; player1.y = 2732; player1.zIndex = 1; player1.visible = false; player1down = game.addChild(LK.getAsset('Player1down', { anchorX: 0.5, anchorY: 1 })); player1down.x = 1024; player1down.y = 2732; player1down.zIndex = 1; player1down.visible = true; player2 = game.addChild(LK.getAsset('Player2', { anchorX: 0.5, anchorY: 1 })); player2.x = 300; player2.y = 2732; player2.zIndex = 1; player2.visible = false; player2down = game.addChild(LK.getAsset('Player2down', { anchorX: 0.5, anchorY: 1 })); player2down.x = 300; player2down.y = 2732; player2down.zIndex = 1; player2down.visible = true; player3 = game.addChild(LK.getAsset('Player3', { anchorX: 0.5, anchorY: 1 })); player3.x = 1748; player3.y = 2732; player3.zIndex = 1; player3.visible = false; player3down = game.addChild(LK.getAsset('Player3down', { anchorX: 0.5, anchorY: 1 })); player3down.x = 1748; player3down.y = 2732; player3down.zIndex = 1; player3down.visible = true; // Create lock overlays var player2Lock = null; var player2UnlockBtn = null; var player2LockImg = null; if (!player2Unlocked) { player2Lock = game.addChild(LK.getAsset('Player4', { anchorX: 0.5, anchorY: 1, scaleX: 6.5, scaleY: 10 })); player2Lock.x = 280; player2Lock.y = 2732; player2Lock.zIndex = 3; player2Lock.alpha = 1.0; player2Lock.tint = 0x000000; player2LockImg = game.addChild(LK.getAsset('Locked', { anchorX: 0.5, anchorY: 0.5 })); player2LockImg.x = 300; player2LockImg.y = 1700; player2LockImg.zIndex = 5; player2UnlockBtn = new Text2('UNLOCK\n$100', { size: 40, fill: 0xFFFFFF }); player2UnlockBtn.anchor.set(0.5, 0.5); player2UnlockBtn.x = 300; player2UnlockBtn.y = 2400; player2UnlockBtn.zIndex = 4; game.addChild(player2UnlockBtn); } var player3Lock = null; var player3UnlockBtn = null; var player3LockImg = null; if (!player3Unlocked) { player3Lock = game.addChild(LK.getAsset('Player4', { anchorX: 0.5, anchorY: 1, scaleX: 6.5, scaleY: 10 })); player3Lock.x = 1728; player3Lock.y = 2732; player3Lock.zIndex = 3; player3Lock.alpha = 1.0; player3Lock.tint = 0x000000; player3LockImg = game.addChild(LK.getAsset('Locked', { anchorX: 0.5, anchorY: 0.5 })); player3LockImg.x = 1748; player3LockImg.y = 1700; player3LockImg.zIndex = 5; player3UnlockBtn = new Text2('UNLOCK\n$100', { size: 40, fill: 0xFFFFFF }); player3UnlockBtn.anchor.set(0.5, 0.5); player3UnlockBtn.x = 1748; player3UnlockBtn.y = 2400; player3UnlockBtn.zIndex = 4; game.addChild(player3UnlockBtn); } // Start shot bars shotBar.startMoving(); shotBar2.startMoving(); shotBar3.startMoving(); // Initialize rhythm mode elements if in rhythm mode if (gameMode === 'rhythm') { // Create beat indicator beatIndicator = new Text2('♪', { size: 200, fill: 0xFFD700 }); beatIndicator.anchor.set(0.5, 0.5); beatIndicator.x = 1024; beatIndicator.y = 400; beatIndicator.zIndex = 20; game.addChild(beatIndicator); // Create green zone that player must tap greenZone = game.addChild(LK.getAsset('greenBall', { anchorX: 0.5, anchorY: 0.5 })); greenZone.x = 1024; greenZone.y = 600; greenZone.zIndex = 20; greenZone.alpha = 0.8; // Create rhythm streak display var rhythmStreakTxt = new Text2('Rhythm: 0', { size: 60, fill: 0xFF6B6B }); rhythmStreakTxt.anchor.set(0.5, 0); rhythmStreakTxt.y = 240; LK.gui.top.addChild(rhythmStreakTxt); // Start beat timing lastBeatTime = LK.ticks; mustTapGreenZone = true; } // Change game state to playing gameState = 'playing'; } function checkAllBackboardsSameColor() { // Check if all three backboards have the same color var allSame = backboardColors[0] === backboardColors[1] && backboardColors[1] === backboardColors[2]; // Debug logging to see what's happening console.log('Checking colors:', backboardColors, 'All same:', allSame, 'Current multiplier:', allSameColorMultiplier); // Only activate multiplier if colors changed to same (not if already same) // Also ensure we're not comparing the initial white colors if (allSame && !allSameColorMultiplier && backboardColors[0] !== 0xFFFFFF) { allSameColorMultiplier = true; console.log('Activating 2x multiplier!'); multiplierTxt.setText('2X MULTIPLIER ACTIVE!'); // Visual feedback for achieving same colors LK.effects.flashScreen(0xFFD700, 500); // Gold flash // Award $100.00 if not already awarded for this color match if (!moneyAwarded) { currentMoney += 100.00; storage.money = currentMoney; moneyTxt.setText('$' + currentMoney.toFixed(2)); moneyAwarded = true; // Additional visual feedback for money award LK.effects.flashScreen(0x00FF00, 800); // Green flash for money console.log('Awarded $100.00! Total money:', currentMoney); } } else if (!allSame && allSameColorMultiplier) { allSameColorMultiplier = false; moneyAwarded = false; // Reset money award flag when colors no longer match console.log('Deactivating 2x multiplier'); multiplierTxt.setText(''); } } game.down = function (x, y, obj) { // Handle title screen clicks if (gameState === 'title') { // Check if clicked on start button area (tap to start button) var buttonLeft = startButton.x - 250; // Half of button width var buttonRight = startButton.x + 250; var buttonTop = startButton.y - 250; // Half of button height var buttonBottom = startButton.y + 250; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { initializeModeSelection(); } return; } // Handle mode selection clicks if (gameState === 'modeSelect') { // Check Swish button var swishLeft = swishButton.x - 200; var swishRight = swishButton.x + 200; var swishTop = swishButton.y - 150; var swishBottom = swishButton.y + 150; if (x >= swishLeft && x <= swishRight && y >= swishTop && y <= swishBottom) { gameMode = 'swish'; initializeGameplay(); return; } // Check Rhythm button var rhythmLeft = rhythmButton.x - 200; var rhythmRight = rhythmButton.x + 200; var rhythmTop = rhythmButton.y - 50; var rhythmBottom = rhythmButton.y + 50; if (x >= rhythmLeft && x <= rhythmRight && y >= rhythmTop && y <= rhythmBottom) { gameMode = 'rhythm'; initializeGameplay(); return; } return; } if (LK.ticks - lastShotTime < 30) return; // Reduce cooldown for better responsiveness // Handle shop clicks if shop is visible if (shop && shop.isVisible) { var shopPos = shop.toLocal({ x: x, y: y }); if (shop.handleClick(shopPos.x, shopPos.y)) { return; } } // Check for unlock button clicks first - expanded touch area if (!player2Unlocked && player2UnlockBtn && x >= 150 && x <= 450 && y >= 2200 && y <= 2600) { if (currentMoney >= unlockCost) { // Unlock Player2 currentMoney -= unlockCost; storage.money = currentMoney; player2Unlocked = true; storage.player2Unlocked = true; moneyTxt.setText('$' + currentMoney.toFixed(2)); // Remove lock overlay if (player2Lock) { player2Lock.destroy(); player2Lock = null; } if (player2LockImg) { player2LockImg.destroy(); player2LockImg = null; } if (player2UnlockBtn) { player2UnlockBtn.destroy(); player2UnlockBtn = null; } LK.effects.flashScreen(0x00FF00, 500); } return; } if (!player3Unlocked && player3UnlockBtn && x >= 1598 && x <= 1898 && y >= 2200 && y <= 2600) { if (currentMoney >= unlockCost) { // Unlock Player3 currentMoney -= unlockCost; storage.money = currentMoney; player3Unlocked = true; storage.player3Unlocked = true; moneyTxt.setText('$' + currentMoney.toFixed(2)); // Remove lock overlay if (player3Lock) { player3Lock.destroy(); player3Lock = null; } if (player3LockImg) { player3LockImg.destroy(); player3LockImg = null; } if (player3UnlockBtn) { player3UnlockBtn.destroy(); player3UnlockBtn = null; } LK.effects.flashScreen(0x00FF00, 500); } return; } // Check if tap is below the top of the backboard (backboards are at y=1300 and extend up ~726 pixels) // Greatly increased tap sensitivity by expanding the detection area further var backboardTopY = 1300 - 1100; // Expanded tap area by 374 pixels above backboard (200) if (y <= backboardTopY) { return; // Don't allow shooting if tapping above the backboard } // Determine which player/hoop to target based on tap position var targetHoop = hoop; var currentShotBar = shotBar; var activePlayer = 1; // Track which player is shooting if (x < 683) { // Left third of screen - Player2's area if (!player2Unlocked) return; // Block shooting if locked targetHoop = leftHoop; currentShotBar = shotBar2; activePlayer = 2; } else if (x > 1365) { // Right third of screen - Player3's area if (!player3Unlocked) return; // Block shooting if locked targetHoop = rightHoop; currentShotBar = shotBar3; activePlayer = 3; } if (currentShotBar.isActive) { // Stop the shot bar and check for perfect shot var isPerfect = currentShotBar.stopMoving(); isChargingShot = false; // Check if tapping green zone first in rhythm mode var onBeat = false; if (gameMode === 'rhythm') { // Check if player is tapping on green zone var greenZoneLeft = greenZone.x - 75; var greenZoneRight = greenZone.x + 75; var greenZoneTop = greenZone.y - 75; var greenZoneBottom = greenZone.y + 75; if (x >= greenZoneLeft && x <= greenZoneRight && y >= greenZoneTop && y <= greenZoneBottom) { // Player tapped green zone var timeSinceLastBeat = LK.ticks - lastBeatTime; var timeToNextBeat = beatInterval - timeSinceLastBeat; // Check if tap is within beat tolerance if (timeSinceLastBeat <= beatTolerance || timeToNextBeat <= beatTolerance) { onBeat = true; rhythmStreak++; rhythmMultiplier = Math.min(3, 1 + Math.floor(rhythmStreak / 5) * 0.5); // Max 3x multiplier LK.effects.flashScreen(0xFF6B6B, 200); // Pink flash for on-beat tap lastTapTime = LK.ticks; mustTapGreenZone = false; // Allow shooting now } else { rhythmStreak = 0; rhythmMultiplier = 1; mustTapGreenZone = true; // Must wait for next beat } // Update rhythm streak display var rhythmStreakTxt = LK.gui.top.children.find(function (child) { return child.text && child.text.indexOf('Rhythm:') !== -1; }); if (rhythmStreakTxt) { rhythmStreakTxt.setText('Rhythm: ' + rhythmStreak + ' (' + rhythmMultiplier.toFixed(1) + 'x)'); } return; // Don't proceed with shooting, this was just a green zone tap } else if (mustTapGreenZone) { // Player tried to shoot without tapping green zone first return; // Block shooting } } // Switch sprites for the appropriate player if (activePlayer === 1) { // Player1 shooting player1.visible = true; player1down.visible = false; // Switch back after 1 second LK.setTimeout(function () { player1.visible = false; player1down.visible = true; }, 1000); } else if (activePlayer === 2) { // Player2 shooting player2.visible = true; player2down.visible = false; // Switch back after 1 second LK.setTimeout(function () { player2.visible = false; player2down.visible = true; }, 1000); } else if (activePlayer === 3) { // Player3 shooting player3.visible = true; player3down.visible = false; // Switch back after 1 second LK.setTimeout(function () { player3.visible = false; player3down.visible = true; }, 1000); } // Calculate shooting parameters based on active player var startX, startY; if (activePlayer === 2) { // Launch from Player2's position (left player) startX = 300; // Player2's x position startY = 2000; } else if (activePlayer === 3) { // Launch from Player3's position (right player) startX = 1748; // Player3's x position startY = 2000; } else { // Launch from Player1's position (center) for main hoop startX = 1024; // Player1's x position startY = 2000; } var velocityX = 0; var velocityY = -20; // Base upward velocity if (isPerfect) { // Perfect shot - guaranteed swish trajectory var targetX = targetHoop.x; var targetY = targetHoop.y - 350; // Aim directly at hoop center for clean entry var deltaX = targetX - startX; var deltaY = targetY - startY; // Calculate perfect parabolic trajectory for guaranteed swish var time = 50; // Slightly more time for smoother arc var gravity = 0.5; // Match basketball gravity // Calculate initial velocities for perfect arc velocityX = deltaX / time; velocityY = deltaY / time - 0.5 * gravity * time; // Fine-tune for guaranteed swish - ensure ball enters hoop cleanly var horizontalAdjustment = 0; // No horizontal drift for perfect shot var verticalAdjustment = -2; // Slight downward bias for clean entry velocityX += horizontalAdjustment; velocityY += verticalAdjustment; // Create perfect shot basketball var ball = createBasketball(startX, startY, velocityX, velocityY); ball.isPerfectShot = true; // Ensure ball will score by setting special trajectory flag ball.guaranteedSwish = true; // Visual feedback for perfect shot LK.effects.flashScreen(0x00FF00, 200); // Ball color tinting removed to prevent color changes // Add vertical rotation animation tween(ball, { rotation: ball.rotation + Math.PI * 4 }, { duration: 1500, easing: tween.easeOut }); } else { // Regular shot with some randomness velocityX = (Math.random() - 0.5) * 8; velocityY = -16 - Math.random() * 8; var ball = createBasketball(startX, startY, velocityX, velocityY); // Add vertical rotation animation for regular shot tween(ball, { rotation: ball.rotation + Math.PI * 3 }, { duration: 1200, easing: tween.easeOut }); } lastShotTime = LK.ticks; } else { // Start charging shot isChargingShot = true; currentShotBar.startMoving(); swipeStartX = x; swipeStartY = y; swipeStartTime = LK.ticks; } }; game.up = function (x, y, obj) { // Shot bar system handles shooting now, remove swipe-based shooting }; game.update = function () { // Only run gameplay logic when in playing state if (gameState !== 'playing') { return; } // Update basketballs for (var i = basketballs.length - 1; i >= 0; i--) { var ball = basketballs[i]; if (!ball.isActive) { ball.destroy(); basketballs.splice(i, 1); continue; } } // Clean up orphaned reflections for (var i = ballReflections.length - 1; i >= 0; i--) { var reflection = ballReflections[i]; if (!reflection.parentBall || !reflection.parentBall.isActive) { reflection.destroy(); ballReflections.splice(i, 1); } // Check for scoring checkBasketScore(ball); // Check collision with other basketballs for (var j = i + 1; j < basketballs.length; j++) { var otherBall = basketballs[j]; if (!otherBall.isActive) continue; var dx = ball.x - otherBall.x; var dy = ball.y - otherBall.y; var distance = Math.sqrt(dx * dx + dy * dy); var minDistance = 120; // Combined radius of two basketballs if (distance < minDistance && distance > 0) { // Calculate collision normal var normalX = dx / distance; var normalY = dy / distance; // Separate balls to prevent overlap var overlap = minDistance - distance; var separationX = normalX * overlap * 0.5; var separationY = normalY * overlap * 0.5; ball.x += separationX; ball.y += separationY; otherBall.x -= separationX; otherBall.y -= separationY; // Calculate relative velocity var relativeVelX = ball.velocityX - otherBall.velocityX; var relativeVelY = ball.velocityY - otherBall.velocityY; // Calculate relative velocity along collision normal var relativeSpeed = relativeVelX * normalX + relativeVelY * normalY; // Only resolve if objects are moving towards each other if (relativeSpeed > 0) continue; // Calculate impulse scalar (assuming equal mass) var impulse = -2 * relativeSpeed / 2; var impulseX = impulse * normalX; var impulseY = impulse * normalY; // Apply impulse with bounce decay var bounceDecay = 0.8; ball.velocityX += impulseX * bounceDecay; ball.velocityY += impulseY * bounceDecay; otherBall.velocityX -= impulseX * bounceDecay; otherBall.velocityY -= impulseY * bounceDecay; // Play bounce sound LK.getSound('bounce').play(); } } } // Streak is only reset when player misses a shot (handled in Basketball class) // No automatic reset when no balls are active // Update timer if (!gameEnded) { gameTimer--; var newTimeLeft = Math.ceil(gameTimer / 60); if (newTimeLeft !== gameTimeLeft) { gameTimeLeft = newTimeLeft; // Format timer as MM:SS var minutes = Math.floor(gameTimeLeft / 60); var seconds = gameTimeLeft % 60; var formattedTime = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds; timerTxt.setText(formattedTime); } // Check if time is up if (gameTimer <= 0 && !gameEnded) { gameEnded = true; timerTxt.setText('00:00'); // Check and update best score var currentScore = LK.getScore(); if (currentScore > bestScore) { bestScore = currentScore; storage.bestScore = bestScore; bestScoreTxt.setText('Best: ' + bestScore); } LK.showGameOver(); } } // Rhythm mode beat system if (gameMode === 'rhythm' && beatIndicator) { // Check for beat timing every 0.5 seconds if (LK.ticks - lastBeatTime >= beatInterval) { lastBeatTime = LK.ticks; mustTapGreenZone = true; // Reset requirement to tap green zone // Animate beat indicator tween(beatIndicator, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(beatIndicator, { scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeIn }); } }); // Animate green zone to pulse if (greenZone) { tween(greenZone, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(greenZone, { scaleX: 1.0, scaleY: 1.0 }, { duration: 150, easing: tween.easeIn }); } }); } // Play beat sound LK.getSound('1').play(); } } // Win condition removed - no score target }; // Title screen is now initialized - gameplay will be initialized when start is pressed
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
money: 0,
player2Unlocked: false,
player3Unlocked: false
});
/****
* Classes
****/
var BallReflection = Container.expand(function () {
var self = Container.call(this);
var reflectionGraphics = self.attachAsset('basketball', {
anchorX: 0.5,
anchorY: 0.5
});
// Make reflection more transparent and darker
reflectionGraphics.alpha = 0.3;
reflectionGraphics.tint = 0x666666;
// Flip reflection vertically
reflectionGraphics.scaleY = -0.8; // Flipped and slightly smaller
reflectionGraphics.scaleX = 0.8;
// Set z-index to render on floor
reflectionGraphics.zIndex = -3;
self.parentBall = null;
self.floorY = 1832; // Move floor up 900 pixels (700 + 200)
self.update = function () {
if (!self.parentBall || !self.parentBall.isActive) {
self.destroy();
return;
}
// Position reflection below the ball
self.x = self.parentBall.x;
self.y = self.floorY - (self.floorY - self.parentBall.y) * 0.2; // Reflection distance from floor
// Match parent ball rotation
self.rotation = self.parentBall.rotation;
// Fade reflection based on ball height
var heightFactor = Math.max(0, (self.floorY - self.parentBall.y) / 1000);
reflectionGraphics.alpha = 0.3 * heightFactor;
};
return self;
});
var Basketball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('basketball', {
anchorX: 0.5,
anchorY: 0.5
});
// Set z-index to render in front of player1
ballGraphics.zIndex = 2;
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.5;
self.bounceDecay = 0.7;
self.hasScored = false;
self.isActive = true;
self.guaranteedSwish = false;
self.update = function () {
if (!self.isActive) return;
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
// Check hoop collision for all hoops
var hoopsToCheck = [hoop];
if (typeof leftHoop !== 'undefined') {
hoopsToCheck.push(leftHoop);
}
if (typeof rightHoop !== 'undefined') {
hoopsToCheck.push(rightHoop);
}
for (var hoopIndex = 0; hoopIndex < hoopsToCheck.length; hoopIndex++) {
var currentHoop = hoopsToCheck[hoopIndex];
if (currentHoop) {
var hoopX = currentHoop.x;
var hoopY = currentHoop.y - 350; // Adjust for hoop position within container
var relativeX = self.x - hoopX;
var relativeY = self.y - hoopY;
// Check for scoring through hoop first (ball going through net area)
// For guaranteed swish shots, use more lenient detection
var hoopDetectionWidth = self.guaranteedSwish ? 80 : 60;
var hoopDetectionTop = self.guaranteedSwish ? -20 : -10;
var hoopDetectionBottom = self.guaranteedSwish ? 60 : 50;
if (Math.abs(relativeX) <= hoopDetectionWidth && relativeY >= hoopDetectionTop && relativeY <= hoopDetectionBottom && self.velocityY > 0 && !self.hasScored) {
// Ball is going through hoop - animate net and score
self.hasScored = true;
self.isActive = false;
// Set ball to render behind net and hoop
ballGraphics.zIndex = -2;
// Animate ball going through and down
tween(self, {
y: self.y + 100,
x: hoopX + (Math.random() - 0.5) * 20
}, {
duration: 500,
easing: tween.easeIn
});
// Animate net pulsate
tween(currentHoop.net, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentHoop.net, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Score the basket
currentStreak++;
// Check for streak rewards
var moneyEarned = 0;
if (currentStreak === 10 && streakRewardsAwarded.indexOf(10) === -1) {
moneyEarned = 20.00;
streakRewardsAwarded.push(10);
} else if (currentStreak === 20 && streakRewardsAwarded.indexOf(20) === -1) {
moneyEarned = 50.00;
streakRewardsAwarded.push(20);
} else if (currentStreak === 30 && streakRewardsAwarded.indexOf(30) === -1) {
moneyEarned = 75.00;
streakRewardsAwarded.push(30);
} else if (currentStreak === 50 && streakRewardsAwarded.indexOf(50) === -1) {
moneyEarned = 100.00;
streakRewardsAwarded.push(50);
}
// Apply money multiplier if active
if (moneyEarned > 0) {
if (moneyMultiplierBaskets > 0) {
moneyEarned *= 2;
moneyMultiplierBaskets--;
}
currentMoney += moneyEarned;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
LK.effects.flashScreen(0x00FF00, 600); // Green flash for money
}
var points = 2;
if (self.isPerfectShot) {
// Use cycling color for perfect shot screen flash
var flashColor = basketColors[currentColorIndex];
LK.effects.flashScreen(flashColor, 400);
}
// Apply 2x multiplier if all backboards are same color
if (allSameColorMultiplier) {
points *= 2;
}
// Apply rhythm multiplier if in rhythm mode
if (typeof rhythmMultiplier !== 'undefined' && rhythmMultiplier > 1) {
points = Math.floor(points * rhythmMultiplier);
}
LK.setScore(LK.getScore() + points);
// Check if score reached multiple of 50 points to reset timer
var currentScore = LK.getScore();
var previousScore = currentScore - points;
var currentFifties = Math.floor(currentScore / 50);
var previousFifties = Math.floor(previousScore / 50);
if (currentFifties > previousFifties) {
gameTimer = 60 * 60; // Reset to 60 seconds at 60 FPS
gameTimeLeft = 60;
timerTxt.setText('01:00');
}
// Play different sound based on which hoop scored
if (currentHoop === leftHoop) {
LK.getSound('1').play();
} else if (currentHoop === rightHoop) {
LK.getSound('3').play();
} else {
LK.getSound('2').play();
}
scoreTxt.setText('Score: ' + LK.getScore());
streakTxt.setText('Streak: ' + currentStreak);
LK.effects.flashObject(currentHoop, self.isPerfectShot ? 0xFFD700 : 0x00FF00, 300);
// Animate backboard color change
var backboard = currentHoop.children[0]; // Get the backboard (first child)
if (self.isPerfectShot) {
// For perfect shots, use cycling color directly
var flashColor = basketColors[currentColorIndex];
// Cycle to next color for next basket
currentColorIndex = (currentColorIndex + 1) % basketColors.length;
// Track which backboard was scored on and update color tracking
var hoopIndex = 0; // Default to center hoop
if (currentHoop === leftHoop) hoopIndex = 1;
if (currentHoop === rightHoop) hoopIndex = 2;
backboardColors[hoopIndex] = flashColor;
tween(backboard, {
tint: flashColor
}, {
duration: 300,
easing: tween.easeOut
});
// Check if all backboards now have same color
checkAllBackboardsSameColor();
// Play different sound based on which hoop scored when backboard changes color
if (currentHoop === leftHoop) {
LK.getSound('1').play();
} else if (currentHoop === rightHoop) {
LK.getSound('3').play();
} else {
LK.getSound('2').play();
}
} else {
// For regular shots, use cycling color directly
var flashColor = basketColors[currentColorIndex];
// Cycle to next color for next basket
currentColorIndex = (currentColorIndex + 1) % basketColors.length;
// Track which backboard was scored on and update color tracking
var hoopIndex = 0; // Default to center hoop
if (currentHoop === leftHoop) hoopIndex = 1;
if (currentHoop === rightHoop) hoopIndex = 2;
backboardColors[hoopIndex] = flashColor;
tween(backboard, {
tint: flashColor
}, {
duration: 300,
easing: tween.easeOut
});
// Check if all backboards now have same color
checkAllBackboardsSameColor();
// Play different sound based on which hoop scored when backboard changes color
if (currentHoop === leftHoop) {
LK.getSound('1').play();
} else if (currentHoop === rightHoop) {
LK.getSound('3').play();
} else {
LK.getSound('2').play();
}
}
return;
}
// Check if ball is hitting the hoop rim
if (relativeX >= -125 && relativeX <= 125 && relativeY >= -25 && relativeY <= 25) {
// Determine which side of the hoop was hit
if (Math.abs(relativeX) > 90) {
// Hit the sides of the rim
self.velocityX = -self.velocityX * self.bounceDecay;
if (relativeX < 0) {
self.x = hoopX - 125;
} else {
self.x = hoopX + 125;
}
LK.getSound('bounce').play();
} else if (relativeY >= -25 && relativeY <= 0 && self.velocityY > 0) {
// Hit the top of the rim (bouncing up)
self.velocityY = -Math.abs(self.velocityY) * self.bounceDecay;
self.y = hoopY - 25;
self.velocityX *= 0.8; // Reduce horizontal velocity slightly
LK.getSound('bounce').play();
}
}
// Check if ball is resting on the rim (low velocity and on top)
if (Math.abs(relativeX) <= 90 && relativeY >= -30 && relativeY <= -20 && Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 3) {
// Ball is resting on rim, make it roll into the hoop
self.isActive = false; // Stop normal physics
self.hasScored = true; // Mark as scored
// Set ball to render behind net and hoop
ballGraphics.zIndex = -2;
// Animate rolling into hoop
tween(self, {
x: hoopX,
y: hoopY + 40,
rotation: self.rotation + Math.PI * 2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Animate net pulsate
tween(currentHoop.net, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentHoop.net, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Score the basket
currentStreak++;
// Check for streak rewards
if (currentStreak === 10 && streakRewardsAwarded.indexOf(10) === -1) {
currentMoney += 20.00;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
streakRewardsAwarded.push(10);
LK.effects.flashScreen(0x00FF00, 600); // Green flash for money
} else if (currentStreak === 20 && streakRewardsAwarded.indexOf(20) === -1) {
currentMoney += 50.00;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
streakRewardsAwarded.push(20);
LK.effects.flashScreen(0x00FF00, 600); // Green flash for money
} else if (currentStreak === 30 && streakRewardsAwarded.indexOf(30) === -1) {
currentMoney += 75.00;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
streakRewardsAwarded.push(30);
LK.effects.flashScreen(0x00FF00, 600); // Green flash for money
} else if (currentStreak === 50 && streakRewardsAwarded.indexOf(50) === -1) {
currentMoney += 100.00;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
streakRewardsAwarded.push(50);
LK.effects.flashScreen(0x00FF00, 600); // Green flash for money
}
var points = 2;
// Apply 2x multiplier if all backboards are same color
if (allSameColorMultiplier) {
points *= 2;
}
LK.setScore(LK.getScore() + points);
// Check if score reached multiple of 50 points to reset timer
var currentScore = LK.getScore();
var previousScore = currentScore - points;
var currentFifties = Math.floor(currentScore / 50);
var previousFifties = Math.floor(previousScore / 50);
if (currentFifties > previousFifties) {
gameTimer = 60 * 60; // Reset to 60 seconds at 60 FPS
gameTimeLeft = 60;
timerTxt.setText('01:00');
}
// Play different sound based on which hoop scored
if (currentHoop === leftHoop) {
LK.getSound('1').play();
} else if (currentHoop === rightHoop) {
LK.getSound('3').play();
} else {
LK.getSound('2').play();
}
// Update displays
scoreTxt.setText('Score: ' + LK.getScore());
streakTxt.setText('Streak: ' + currentStreak);
// Visual feedback
LK.effects.flashObject(currentHoop, 0x00FF00, 300);
// Animate backboard color change
var backboard = currentHoop.children[0]; // Get the backboard (first child)
var flashColor = basketColors[currentColorIndex];
// Cycle to next color for next basket
currentColorIndex = (currentColorIndex + 1) % basketColors.length;
// Track which backboard was scored on and update color tracking
var hoopIndex = 0; // Default to center hoop
if (currentHoop === leftHoop) hoopIndex = 1;
if (currentHoop === rightHoop) hoopIndex = 2;
backboardColors[hoopIndex] = flashColor;
tween(backboard, {
tint: flashColor
}, {
duration: 300,
easing: tween.easeOut
});
// Check if all backboards now have same color
checkAllBackboardsSameColor();
// Play different sound based on which hoop scored when backboard changes color
if (currentHoop === leftHoop) {
LK.getSound('1').play();
} else if (currentHoop === rightHoop) {
LK.getSound('3').play();
} else {
LK.getSound('2').play();
}
}
});
}
}
}
// Ground collision removed - ball can fall through
// Remove if off screen - ball is just removed, no streak penalty
if (self.x < -100 || self.x > 2148 || self.y > 2800) {
self.isActive = false;
}
};
return self;
});
var Hoop = Container.expand(function () {
var self = Container.call(this);
var backboard = self.attachAsset('backboard', {
anchorX: 0.5,
anchorY: 1
});
backboard.y = -20;
var hoop = self.attachAsset('hoop', {
anchorX: 0.5,
anchorY: 0.5
});
hoop.y = -350;
// Set z-index to render in front of basketball
hoop.zIndex = 1;
var net = self.attachAsset('net', {
anchorX: 0.5,
anchorY: 0,
scaleX: 1.5,
scaleY: 1.5
});
net.y = -325;
net.alpha = 0.8;
// Set z-index to render in front of basketball
net.zIndex = 2;
// Store net reference for animation access
self.net = net;
self.hoopBounds = {
left: -90,
right: 90,
top: -20,
bottom: 20
};
// Enable z-index sorting for proper rendering order
self.sortableChildren = true;
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
// Shop background
var shopBg = self.attachAsset('Player4', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 20,
scaleY: 25
});
shopBg.tint = 0x333333;
shopBg.alpha = 0.9;
self.isVisible = false;
self.visible = false;
// Shop title
var shopTitle = new Text2('SHOP', {
size: 120,
fill: 0xFFD700
});
shopTitle.anchor.set(0.5, 0.5);
shopTitle.y = -800;
self.addChild(shopTitle);
// Shop items array
self.shopItems = [{
name: 'Speed Boost',
description: 'Increase shot speed by 25%',
cost: 50.00,
purchased: false,
type: 'speed'
}, {
name: 'Perfect Shot',
description: 'Next 3 shots guaranteed perfect',
cost: 25.00,
purchased: false,
type: 'perfect'
}, {
name: 'Money Multiplier',
description: 'Double money for 10 baskets',
cost: 75.00,
purchased: false,
type: 'money'
}, {
name: 'Time Extension',
description: 'Add 30 seconds to timer',
cost: 100.00,
purchased: false,
type: 'time'
}];
// Create shop item buttons
self.itemButtons = [];
for (var i = 0; i < self.shopItems.length; i++) {
var item = self.shopItems[i];
var yOffset = -400 + i * 200;
// Item background
var itemBg = self.attachAsset('Player4', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 15,
scaleY: 3
});
itemBg.y = yOffset;
itemBg.tint = 0x555555;
itemBg.alpha = 0.8;
// Item name text
var nameText = new Text2(item.name, {
size: 60,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.y = yOffset - 30;
self.addChild(nameText);
// Item description text
var descText = new Text2(item.description, {
size: 40,
fill: 0xCCCCCC
});
descText.anchor.set(0.5, 0.5);
descText.y = yOffset + 10;
self.addChild(descText);
// Item cost text
var costText = new Text2('$' + item.cost.toFixed(2), {
size: 50,
fill: 0x00FF00
});
costText.anchor.set(0.5, 0.5);
costText.y = yOffset + 50;
self.addChild(costText);
// Store button info for click detection
self.itemButtons.push({
index: i,
bounds: {
x: -400,
y: yOffset - 75,
width: 800,
height: 150
},
costText: costText,
nameText: nameText
});
}
// Close button
var closeBtn = new Text2('CLOSE', {
size: 80,
fill: 0xFF0000
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.y = 600;
self.addChild(closeBtn);
self.closeBounds = {
x: -150,
y: 550,
width: 300,
height: 100
};
self.show = function () {
// Pause the game
LK.pauseGame();
// Show shop background
var shopBackground = LK.getAsset('ShopBackground', {
anchorX: 0,
anchorY: 0
});
shopBackground.x = 0;
shopBackground.y = 0;
shopBackground.width = 2048;
shopBackground.height = 2732;
shopBackground.zIndex = 99;
self.shopBg = shopBackground;
game.addChild(shopBackground);
self.isVisible = true;
self.visible = true;
self.x = 1024;
self.y = 1366;
self.zIndex = 100;
// Update item availability based on money
for (var i = 0; i < self.itemButtons.length; i++) {
var button = self.itemButtons[i];
var item = self.shopItems[button.index];
if (currentMoney >= item.cost && !item.purchased) {
button.costText.fill = 0x00FF00;
button.nameText.fill = 0xFFFFFF;
} else if (item.purchased) {
button.costText.setText('OWNED');
button.costText.fill = 0xFFD700;
button.nameText.fill = 0xFFD700;
} else {
button.costText.fill = 0xFF0000;
button.nameText.fill = 0x888888;
}
}
};
self.hide = function () {
// Resume the game
LK.resumeGame();
// Remove shop background
if (self.shopBg) {
self.shopBg.destroy();
self.shopBg = null;
}
self.isVisible = false;
self.visible = false;
};
self.handleClick = function (localX, localY) {
// Check close button
if (localX >= self.closeBounds.x && localX <= self.closeBounds.x + self.closeBounds.width && localY >= self.closeBounds.y && localY <= self.closeBounds.y + self.closeBounds.height) {
self.hide();
return true;
}
// Check item buttons
for (var i = 0; i < self.itemButtons.length; i++) {
var button = self.itemButtons[i];
var bounds = button.bounds;
if (localX >= bounds.x && localX <= bounds.x + bounds.width && localY >= bounds.y && localY <= bounds.y + bounds.height) {
var item = self.shopItems[button.index];
// Check if can purchase
if (currentMoney >= item.cost && !item.purchased) {
self.purchaseItem(button.index);
return true;
}
}
}
return false;
};
self.purchaseItem = function (itemIndex) {
var item = self.shopItems[itemIndex];
// Deduct money
currentMoney -= item.cost;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
// Apply item effect
switch (item.type) {
case 'speed':
// Increase shot bar speed for all bars
shotBar.moveSpeed *= 1.25;
shotBar2.moveSpeed *= 1.25;
shotBar3.moveSpeed *= 1.25;
break;
case 'perfect':
// Grant 3 guaranteed perfect shots
perfectShotsRemaining = 3;
break;
case 'money':
// Double money for next 10 baskets
moneyMultiplierBaskets = 10;
break;
case 'time':
// Add 30 seconds to timer
gameTimer += 30 * 60; // 30 seconds at 60 FPS
gameTimeLeft += 30;
var minutes = Math.floor(gameTimeLeft / 60);
var seconds = gameTimeLeft % 60;
var formattedTime = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(formattedTime);
break;
}
// Mark as purchased (except consumables)
if (item.type !== 'perfect' && item.type !== 'money' && item.type !== 'time') {
item.purchased = true;
}
// Visual feedback
LK.effects.flashScreen(0x00FF00, 300);
// Update display
self.show();
};
return self;
});
var ShotBar = Container.expand(function () {
var self = Container.call(this);
// Background bar - rotated to be vertical
var bgBar = self.attachAsset('shotBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
bgBar.rotation = Math.PI / 2 + Math.PI; // Rotate 90 degrees + 180 degrees to make vertical and flip
// Green zone - rotated to be vertical
var greenZone = self.attachAsset('shotBarGreen', {
anchorX: 0.5,
anchorY: 0.5
});
greenZone.rotation = Math.PI / 2 + Math.PI; // Rotate 90 degrees + 180 degrees to make vertical and flip
greenZone.y = 0; // Start at center, will move vertically
// Moving basketball indicator
var indicator = self.attachAsset('basketball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
self.isActive = false;
self.isMoving = false;
self.moveDirection = 1; // 1 for right, -1 for left
self.moveSpeed = 8;
self.maxDistance = 240; // Half of bar width minus indicator size
self.perfectShot = false;
self.startMoving = function () {
self.isActive = true;
self.isMoving = true;
self.perfectShot = false;
// Position green zone randomly vertically
greenZone.y = (Math.random() - 0.5) * 360; // Random position within vertical bar
// Reset indicator position vertically - start at top
indicator.y = -self.maxDistance;
self.moveDirection = 1; // Start moving downward
self.visible = true;
};
self.stopMoving = function () {
self.isMoving = false;
// Check if indicator is within green zone vertically
var greenTop = greenZone.y - 60;
var greenBottom = greenZone.y + 60;
self.perfectShot = indicator.y >= greenTop && indicator.y <= greenBottom;
// Use perfect shot bonus if available
if (perfectShotsRemaining > 0 && !self.perfectShot) {
self.perfectShot = true;
perfectShotsRemaining--;
}
// Reset streak if player misses the green zone
if (!self.perfectShot) {
currentStreak = 0;
streakTxt.setText('Streak: ' + currentStreak);
streakRewardsAwarded = []; // Reset streak rewards
}
// Keep shot bar visible and restart movement after a brief pause
var self_ref = self;
LK.setTimeout(function () {
self_ref.startMoving();
}, 200);
return self.perfectShot;
};
self.update = function () {
if (!self.isMoving) return;
// Increase speed every 50 points scored
var currentSpeed = self.moveSpeed;
var scoreMultiplier = Math.floor(LK.getScore() / 50);
if (scoreMultiplier > 0) {
currentSpeed = self.moveSpeed * (1 + scoreMultiplier * 0.5); // 50% speed increase per 50 points
}
// Move indicator back and forth vertically
indicator.y += currentSpeed * self.moveDirection;
// Bounce off edges
if (indicator.y >= self.maxDistance) {
indicator.y = self.maxDistance;
self.moveDirection = -1;
} else if (indicator.y <= -self.maxDistance) {
indicator.y = -self.maxDistance;
self.moveDirection = 1;
}
};
self.visible = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game state management
var gameState = 'title'; // 'title', 'playing', 'modeSelect'
var gameMode = 'swish'; // 'swish' or 'rhythm'
var modeSelectBackground = null;
var swishButton = null;
var rhythmButton = null;
var titleBackground = null;
var titleText = null;
var startButton = null;
var basketballs = [];
var ballReflections = [];
var hoop = null;
var leftHoop = null;
var rightHoop = null;
var ground = null;
var scoreTxt = null;
var streakTxt = null;
var timerTxt = null;
var player1 = null;
var player1down = null;
var player2 = null;
var player2down = null;
var player3 = null;
var player3down = null;
var currentMoney = 0;
var moneyTxt = null;
var multiplierTxt = null;
var bestScore = 0;
var bestScoreTxt = null;
var currentStreak = 0;
var streakRewardsAwarded = []; // Track which streak rewards have been given
var swipeStartX = 0;
var swipeStartY = 0;
var swipeStartTime = 0;
var isSwipeStarted = false;
var lastShotTime = 0;
var gameTimer = 60 * 60; // 60 seconds at 60 FPS
var gameTimeLeft = 60;
var gameEnded = false;
var musicNotes = ['note1', 'note2', 'note3', 'note4', 'note5'];
var currentNoteIndex = 0;
var shotBar = null;
var shotBar2 = null;
var shotBar3 = null;
var isChargingShot = false;
// Color cycling system for backboard changes
var basketColors = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFA500, 0x800080, 0xFFB6C1, 0x90EE90, 0x87CEEB, 0xDDA0DD];
var currentColorIndex = 0;
// Track backboard colors for multiplier system
var backboardColors = [0xFFFFFF, 0xFFFFFF, 0xFFFFFF]; // [hoop, leftHoop, rightHoop] - start with white
var allSameColorMultiplier = false;
var moneyAwarded = false; // Track if money has been awarded for current color match
var player2Unlocked = storage.player2Unlocked || false;
var player3Unlocked = storage.player3Unlocked || false;
var unlockCost = 100.00;
// Shop variables
var shop = null;
var perfectShotsRemaining = 0;
var moneyMultiplierBaskets = 0;
// Rhythm mode variables
var beatInterval = 30; // 30 frames = 0.5 seconds at 60 FPS
var lastBeatTime = 0;
var beatTolerance = 10; // frames of tolerance for rhythm timing
var rhythmStreak = 0;
var beatIndicator = null;
var rhythmMultiplier = 1;
var greenZone = null;
var lastTapTime = 0;
var mustTapGreenZone = false;
// Initialize title screen
initializeTitleScreen();
function createBasketball(startX, startY, velocityX, velocityY) {
var ball = new Basketball();
ball.x = startX;
ball.y = startY;
ball.velocityX = velocityX;
ball.velocityY = velocityY;
ball.isPerfectShot = false;
basketballs.push(ball);
game.addChild(ball);
// Create reflection for this ball
var reflection = new BallReflection();
reflection.parentBall = ball;
ballReflections.push(reflection);
game.addChild(reflection);
return ball;
}
function checkBasketScore(ball) {
// Scoring is now handled in Basketball class update method
// This function is kept for compatibility but does nothing
return;
}
function initializeTitleScreen() {
// Create title background
titleBackground = game.addChild(LK.getAsset('TitleBackground', {
anchorX: 0,
anchorY: 0
}));
titleBackground.x = 0;
titleBackground.y = 0;
titleBackground.width = 2048;
titleBackground.height = 2732;
titleBackground.zIndex = -5;
// Create game title using asset
titleText = game.addChild(LK.getAsset('Title', {
anchorX: 0.5,
anchorY: 0.5
}));
titleText.x = 1024;
titleText.y = 900;
titleText.zIndex = 10;
// Create start button using tap to start asset
startButton = game.addChild(LK.getAsset('Taptostart', {
anchorX: 0.5,
anchorY: 0.5
}));
startButton.x = 1274;
startButton.y = 1050;
startButton.zIndex = 10;
// Play make a wish sound when title screen loads
LK.getSound('Makeaswish').play();
// Play title music when title screen loads
LK.playMusic('Title');
// Animate start button pulsate continuously
function pulsateStartButton() {
if (!startButton || gameState !== 'title') return; // Safety check
tween(startButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!startButton || gameState !== 'title') return; // Safety check
// Create continuous pulsating loop
tween(startButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!startButton || gameState !== 'title') return; // Safety check
// Restart the pulsate cycle
pulsateStartButton();
}
});
}
});
}
pulsateStartButton();
}
function initializeModeSelection() {
// Hide title screen elements
if (titleBackground) {
titleBackground.destroy();
titleBackground = null;
}
if (titleText) {
titleText.destroy();
titleText = null;
}
if (startButton) {
tween.stop(startButton);
startButton.destroy();
startButton = null;
}
// Create mode selection background
modeSelectBackground = game.addChild(LK.getAsset('TitleBackground', {
anchorX: 0,
anchorY: 0
}));
modeSelectBackground.x = 0;
modeSelectBackground.y = 0;
modeSelectBackground.width = 2048;
modeSelectBackground.height = 2732;
modeSelectBackground.zIndex = -5;
// Create mode selection title
var modeTitle = new Text2('SELECT MODE', {
size: 120,
fill: 0xFFD700
});
modeTitle.anchor.set(0.5, 0.5);
modeTitle.x = 1024;
modeTitle.y = 800;
modeTitle.zIndex = 10;
game.addChild(modeTitle);
// Create Swish mode button
swishButton = game.addChild(LK.getAsset('Swish', {
anchorX: 0.5,
anchorY: 0.5
}));
swishButton.x = 1024;
swishButton.y = 1200;
swishButton.zIndex = 10;
// Create Rhythm mode button
rhythmButton = new Text2('RHYTHM', {
size: 100,
fill: 0xFF6B6B
});
rhythmButton.anchor.set(0.5, 0.5);
rhythmButton.x = 1024;
rhythmButton.y = 1600;
rhythmButton.zIndex = 10;
game.addChild(rhythmButton);
// Change game state to mode selection
gameState = 'modeSelect';
// Animate buttons
function animateModeButtons() {
if (gameState !== 'modeSelect') return;
tween(swishButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (gameState !== 'modeSelect') return;
tween(swishButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: animateModeButtons
});
}
});
}
animateModeButtons();
}
function initializeGameplay() {
// Hide title screen elements
if (titleBackground) {
titleBackground.destroy();
titleBackground = null;
}
if (titleText) {
titleText.destroy();
titleText = null;
}
if (startButton) {
// Stop any active tweens on the start button before destroying
tween.stop(startButton);
startButton.destroy();
startButton = null;
}
// Hide mode selection elements
if (modeSelectBackground) {
modeSelectBackground.destroy();
modeSelectBackground = null;
}
if (swishButton) {
tween.stop(swishButton);
swishButton.destroy();
swishButton = null;
}
if (rhythmButton) {
rhythmButton.destroy();
rhythmButton = null;
}
// Initialize all game elements (moved from main game code)
// Create shop instance
shop = game.addChild(new Shop());
// Create multiplier indicator text
multiplierTxt = new Text2('', {
size: 50,
fill: 0xFFD700
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.y = 180;
LK.gui.top.addChild(multiplierTxt);
// Enable z-index sorting for proper rendering order
game.sortableChildren = true;
// Add background with high definition scaling
var background = game.addChild(LK.getAsset('Background', {
anchorX: 0,
anchorY: 0
}));
background.x = 0;
background.y = 0;
background.width = 2048;
background.height = 2732;
background.zIndex = -10;
// Create hoops
hoop = game.addChild(new Hoop());
hoop.x = 1024;
hoop.y = 1300;
leftHoop = game.addChild(new Hoop());
leftHoop.x = 1024 - 375 - 350;
leftHoop.y = 1300;
rightHoop = game.addChild(new Hoop());
rightHoop.x = 1024 + 375 + 350;
rightHoop.y = 1300;
// Create UI displays
scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
streakTxt = new Text2('Streak: 0', {
size: 60,
fill: 0xFFD700
});
streakTxt.anchor.set(0.5, 0);
streakTxt.y = 100;
LK.gui.top.addChild(streakTxt);
timerTxt = new Text2('01:00', {
size: 80,
fill: 0xFF0000,
font: "'Courier New', 'Monaco', 'Lucida Console', monospace"
});
timerTxt.anchor.set(0.5, 0);
timerTxt.y = 160;
LK.gui.top.addChild(timerTxt);
bestScore = storage.bestScore || 0;
bestScoreTxt = new Text2('Best: ' + bestScore, {
size: 60,
fill: 0xFFD700
});
bestScoreTxt.anchor.set(1, 0);
bestScoreTxt.x = -100;
LK.gui.topRight.addChild(bestScoreTxt);
currentMoney = storage.money || 0;
moneyTxt = new Text2('$' + currentMoney.toFixed(2), {
size: 50,
fill: 0x00FF00
});
moneyTxt.anchor.set(0, 0);
moneyTxt.x = 120;
moneyTxt.y = 10;
LK.gui.topLeft.addChild(moneyTxt);
// Create shot bars
shotBar = game.addChild(new ShotBar());
shotBar.x = 1024;
shotBar.y = 1600;
shotBar2 = game.addChild(new ShotBar());
shotBar2.x = 300;
shotBar2.y = 1600;
shotBar3 = game.addChild(new ShotBar());
shotBar3.x = rightHoop.x;
shotBar3.y = 1600;
// Add players
player1 = game.addChild(LK.getAsset('Player1', {
anchorX: 0.5,
anchorY: 1
}));
player1.x = 1024;
player1.y = 2732;
player1.zIndex = 1;
player1.visible = false;
player1down = game.addChild(LK.getAsset('Player1down', {
anchorX: 0.5,
anchorY: 1
}));
player1down.x = 1024;
player1down.y = 2732;
player1down.zIndex = 1;
player1down.visible = true;
player2 = game.addChild(LK.getAsset('Player2', {
anchorX: 0.5,
anchorY: 1
}));
player2.x = 300;
player2.y = 2732;
player2.zIndex = 1;
player2.visible = false;
player2down = game.addChild(LK.getAsset('Player2down', {
anchorX: 0.5,
anchorY: 1
}));
player2down.x = 300;
player2down.y = 2732;
player2down.zIndex = 1;
player2down.visible = true;
player3 = game.addChild(LK.getAsset('Player3', {
anchorX: 0.5,
anchorY: 1
}));
player3.x = 1748;
player3.y = 2732;
player3.zIndex = 1;
player3.visible = false;
player3down = game.addChild(LK.getAsset('Player3down', {
anchorX: 0.5,
anchorY: 1
}));
player3down.x = 1748;
player3down.y = 2732;
player3down.zIndex = 1;
player3down.visible = true;
// Create lock overlays
var player2Lock = null;
var player2UnlockBtn = null;
var player2LockImg = null;
if (!player2Unlocked) {
player2Lock = game.addChild(LK.getAsset('Player4', {
anchorX: 0.5,
anchorY: 1,
scaleX: 6.5,
scaleY: 10
}));
player2Lock.x = 280;
player2Lock.y = 2732;
player2Lock.zIndex = 3;
player2Lock.alpha = 1.0;
player2Lock.tint = 0x000000;
player2LockImg = game.addChild(LK.getAsset('Locked', {
anchorX: 0.5,
anchorY: 0.5
}));
player2LockImg.x = 300;
player2LockImg.y = 1700;
player2LockImg.zIndex = 5;
player2UnlockBtn = new Text2('UNLOCK\n$100', {
size: 40,
fill: 0xFFFFFF
});
player2UnlockBtn.anchor.set(0.5, 0.5);
player2UnlockBtn.x = 300;
player2UnlockBtn.y = 2400;
player2UnlockBtn.zIndex = 4;
game.addChild(player2UnlockBtn);
}
var player3Lock = null;
var player3UnlockBtn = null;
var player3LockImg = null;
if (!player3Unlocked) {
player3Lock = game.addChild(LK.getAsset('Player4', {
anchorX: 0.5,
anchorY: 1,
scaleX: 6.5,
scaleY: 10
}));
player3Lock.x = 1728;
player3Lock.y = 2732;
player3Lock.zIndex = 3;
player3Lock.alpha = 1.0;
player3Lock.tint = 0x000000;
player3LockImg = game.addChild(LK.getAsset('Locked', {
anchorX: 0.5,
anchorY: 0.5
}));
player3LockImg.x = 1748;
player3LockImg.y = 1700;
player3LockImg.zIndex = 5;
player3UnlockBtn = new Text2('UNLOCK\n$100', {
size: 40,
fill: 0xFFFFFF
});
player3UnlockBtn.anchor.set(0.5, 0.5);
player3UnlockBtn.x = 1748;
player3UnlockBtn.y = 2400;
player3UnlockBtn.zIndex = 4;
game.addChild(player3UnlockBtn);
}
// Start shot bars
shotBar.startMoving();
shotBar2.startMoving();
shotBar3.startMoving();
// Initialize rhythm mode elements if in rhythm mode
if (gameMode === 'rhythm') {
// Create beat indicator
beatIndicator = new Text2('♪', {
size: 200,
fill: 0xFFD700
});
beatIndicator.anchor.set(0.5, 0.5);
beatIndicator.x = 1024;
beatIndicator.y = 400;
beatIndicator.zIndex = 20;
game.addChild(beatIndicator);
// Create green zone that player must tap
greenZone = game.addChild(LK.getAsset('greenBall', {
anchorX: 0.5,
anchorY: 0.5
}));
greenZone.x = 1024;
greenZone.y = 600;
greenZone.zIndex = 20;
greenZone.alpha = 0.8;
// Create rhythm streak display
var rhythmStreakTxt = new Text2('Rhythm: 0', {
size: 60,
fill: 0xFF6B6B
});
rhythmStreakTxt.anchor.set(0.5, 0);
rhythmStreakTxt.y = 240;
LK.gui.top.addChild(rhythmStreakTxt);
// Start beat timing
lastBeatTime = LK.ticks;
mustTapGreenZone = true;
}
// Change game state to playing
gameState = 'playing';
}
function checkAllBackboardsSameColor() {
// Check if all three backboards have the same color
var allSame = backboardColors[0] === backboardColors[1] && backboardColors[1] === backboardColors[2];
// Debug logging to see what's happening
console.log('Checking colors:', backboardColors, 'All same:', allSame, 'Current multiplier:', allSameColorMultiplier);
// Only activate multiplier if colors changed to same (not if already same)
// Also ensure we're not comparing the initial white colors
if (allSame && !allSameColorMultiplier && backboardColors[0] !== 0xFFFFFF) {
allSameColorMultiplier = true;
console.log('Activating 2x multiplier!');
multiplierTxt.setText('2X MULTIPLIER ACTIVE!');
// Visual feedback for achieving same colors
LK.effects.flashScreen(0xFFD700, 500); // Gold flash
// Award $100.00 if not already awarded for this color match
if (!moneyAwarded) {
currentMoney += 100.00;
storage.money = currentMoney;
moneyTxt.setText('$' + currentMoney.toFixed(2));
moneyAwarded = true;
// Additional visual feedback for money award
LK.effects.flashScreen(0x00FF00, 800); // Green flash for money
console.log('Awarded $100.00! Total money:', currentMoney);
}
} else if (!allSame && allSameColorMultiplier) {
allSameColorMultiplier = false;
moneyAwarded = false; // Reset money award flag when colors no longer match
console.log('Deactivating 2x multiplier');
multiplierTxt.setText('');
}
}
game.down = function (x, y, obj) {
// Handle title screen clicks
if (gameState === 'title') {
// Check if clicked on start button area (tap to start button)
var buttonLeft = startButton.x - 250; // Half of button width
var buttonRight = startButton.x + 250;
var buttonTop = startButton.y - 250; // Half of button height
var buttonBottom = startButton.y + 250;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
initializeModeSelection();
}
return;
}
// Handle mode selection clicks
if (gameState === 'modeSelect') {
// Check Swish button
var swishLeft = swishButton.x - 200;
var swishRight = swishButton.x + 200;
var swishTop = swishButton.y - 150;
var swishBottom = swishButton.y + 150;
if (x >= swishLeft && x <= swishRight && y >= swishTop && y <= swishBottom) {
gameMode = 'swish';
initializeGameplay();
return;
}
// Check Rhythm button
var rhythmLeft = rhythmButton.x - 200;
var rhythmRight = rhythmButton.x + 200;
var rhythmTop = rhythmButton.y - 50;
var rhythmBottom = rhythmButton.y + 50;
if (x >= rhythmLeft && x <= rhythmRight && y >= rhythmTop && y <= rhythmBottom) {
gameMode = 'rhythm';
initializeGameplay();
return;
}
return;
}
if (LK.ticks - lastShotTime < 30) return; // Reduce cooldown for better responsiveness
// Handle shop clicks if shop is visible
if (shop && shop.isVisible) {
var shopPos = shop.toLocal({
x: x,
y: y
});
if (shop.handleClick(shopPos.x, shopPos.y)) {
return;
}
}
// Check for unlock button clicks first - expanded touch area
if (!player2Unlocked && player2UnlockBtn && x >= 150 && x <= 450 && y >= 2200 && y <= 2600) {
if (currentMoney >= unlockCost) {
// Unlock Player2
currentMoney -= unlockCost;
storage.money = currentMoney;
player2Unlocked = true;
storage.player2Unlocked = true;
moneyTxt.setText('$' + currentMoney.toFixed(2));
// Remove lock overlay
if (player2Lock) {
player2Lock.destroy();
player2Lock = null;
}
if (player2LockImg) {
player2LockImg.destroy();
player2LockImg = null;
}
if (player2UnlockBtn) {
player2UnlockBtn.destroy();
player2UnlockBtn = null;
}
LK.effects.flashScreen(0x00FF00, 500);
}
return;
}
if (!player3Unlocked && player3UnlockBtn && x >= 1598 && x <= 1898 && y >= 2200 && y <= 2600) {
if (currentMoney >= unlockCost) {
// Unlock Player3
currentMoney -= unlockCost;
storage.money = currentMoney;
player3Unlocked = true;
storage.player3Unlocked = true;
moneyTxt.setText('$' + currentMoney.toFixed(2));
// Remove lock overlay
if (player3Lock) {
player3Lock.destroy();
player3Lock = null;
}
if (player3LockImg) {
player3LockImg.destroy();
player3LockImg = null;
}
if (player3UnlockBtn) {
player3UnlockBtn.destroy();
player3UnlockBtn = null;
}
LK.effects.flashScreen(0x00FF00, 500);
}
return;
}
// Check if tap is below the top of the backboard (backboards are at y=1300 and extend up ~726 pixels)
// Greatly increased tap sensitivity by expanding the detection area further
var backboardTopY = 1300 - 1100; // Expanded tap area by 374 pixels above backboard (200)
if (y <= backboardTopY) {
return; // Don't allow shooting if tapping above the backboard
}
// Determine which player/hoop to target based on tap position
var targetHoop = hoop;
var currentShotBar = shotBar;
var activePlayer = 1; // Track which player is shooting
if (x < 683) {
// Left third of screen - Player2's area
if (!player2Unlocked) return; // Block shooting if locked
targetHoop = leftHoop;
currentShotBar = shotBar2;
activePlayer = 2;
} else if (x > 1365) {
// Right third of screen - Player3's area
if (!player3Unlocked) return; // Block shooting if locked
targetHoop = rightHoop;
currentShotBar = shotBar3;
activePlayer = 3;
}
if (currentShotBar.isActive) {
// Stop the shot bar and check for perfect shot
var isPerfect = currentShotBar.stopMoving();
isChargingShot = false;
// Check if tapping green zone first in rhythm mode
var onBeat = false;
if (gameMode === 'rhythm') {
// Check if player is tapping on green zone
var greenZoneLeft = greenZone.x - 75;
var greenZoneRight = greenZone.x + 75;
var greenZoneTop = greenZone.y - 75;
var greenZoneBottom = greenZone.y + 75;
if (x >= greenZoneLeft && x <= greenZoneRight && y >= greenZoneTop && y <= greenZoneBottom) {
// Player tapped green zone
var timeSinceLastBeat = LK.ticks - lastBeatTime;
var timeToNextBeat = beatInterval - timeSinceLastBeat;
// Check if tap is within beat tolerance
if (timeSinceLastBeat <= beatTolerance || timeToNextBeat <= beatTolerance) {
onBeat = true;
rhythmStreak++;
rhythmMultiplier = Math.min(3, 1 + Math.floor(rhythmStreak / 5) * 0.5); // Max 3x multiplier
LK.effects.flashScreen(0xFF6B6B, 200); // Pink flash for on-beat tap
lastTapTime = LK.ticks;
mustTapGreenZone = false; // Allow shooting now
} else {
rhythmStreak = 0;
rhythmMultiplier = 1;
mustTapGreenZone = true; // Must wait for next beat
}
// Update rhythm streak display
var rhythmStreakTxt = LK.gui.top.children.find(function (child) {
return child.text && child.text.indexOf('Rhythm:') !== -1;
});
if (rhythmStreakTxt) {
rhythmStreakTxt.setText('Rhythm: ' + rhythmStreak + ' (' + rhythmMultiplier.toFixed(1) + 'x)');
}
return; // Don't proceed with shooting, this was just a green zone tap
} else if (mustTapGreenZone) {
// Player tried to shoot without tapping green zone first
return; // Block shooting
}
}
// Switch sprites for the appropriate player
if (activePlayer === 1) {
// Player1 shooting
player1.visible = true;
player1down.visible = false;
// Switch back after 1 second
LK.setTimeout(function () {
player1.visible = false;
player1down.visible = true;
}, 1000);
} else if (activePlayer === 2) {
// Player2 shooting
player2.visible = true;
player2down.visible = false;
// Switch back after 1 second
LK.setTimeout(function () {
player2.visible = false;
player2down.visible = true;
}, 1000);
} else if (activePlayer === 3) {
// Player3 shooting
player3.visible = true;
player3down.visible = false;
// Switch back after 1 second
LK.setTimeout(function () {
player3.visible = false;
player3down.visible = true;
}, 1000);
}
// Calculate shooting parameters based on active player
var startX, startY;
if (activePlayer === 2) {
// Launch from Player2's position (left player)
startX = 300; // Player2's x position
startY = 2000;
} else if (activePlayer === 3) {
// Launch from Player3's position (right player)
startX = 1748; // Player3's x position
startY = 2000;
} else {
// Launch from Player1's position (center) for main hoop
startX = 1024; // Player1's x position
startY = 2000;
}
var velocityX = 0;
var velocityY = -20; // Base upward velocity
if (isPerfect) {
// Perfect shot - guaranteed swish trajectory
var targetX = targetHoop.x;
var targetY = targetHoop.y - 350; // Aim directly at hoop center for clean entry
var deltaX = targetX - startX;
var deltaY = targetY - startY;
// Calculate perfect parabolic trajectory for guaranteed swish
var time = 50; // Slightly more time for smoother arc
var gravity = 0.5; // Match basketball gravity
// Calculate initial velocities for perfect arc
velocityX = deltaX / time;
velocityY = deltaY / time - 0.5 * gravity * time;
// Fine-tune for guaranteed swish - ensure ball enters hoop cleanly
var horizontalAdjustment = 0; // No horizontal drift for perfect shot
var verticalAdjustment = -2; // Slight downward bias for clean entry
velocityX += horizontalAdjustment;
velocityY += verticalAdjustment;
// Create perfect shot basketball
var ball = createBasketball(startX, startY, velocityX, velocityY);
ball.isPerfectShot = true;
// Ensure ball will score by setting special trajectory flag
ball.guaranteedSwish = true;
// Visual feedback for perfect shot
LK.effects.flashScreen(0x00FF00, 200);
// Ball color tinting removed to prevent color changes
// Add vertical rotation animation
tween(ball, {
rotation: ball.rotation + Math.PI * 4
}, {
duration: 1500,
easing: tween.easeOut
});
} else {
// Regular shot with some randomness
velocityX = (Math.random() - 0.5) * 8;
velocityY = -16 - Math.random() * 8;
var ball = createBasketball(startX, startY, velocityX, velocityY);
// Add vertical rotation animation for regular shot
tween(ball, {
rotation: ball.rotation + Math.PI * 3
}, {
duration: 1200,
easing: tween.easeOut
});
}
lastShotTime = LK.ticks;
} else {
// Start charging shot
isChargingShot = true;
currentShotBar.startMoving();
swipeStartX = x;
swipeStartY = y;
swipeStartTime = LK.ticks;
}
};
game.up = function (x, y, obj) {
// Shot bar system handles shooting now, remove swipe-based shooting
};
game.update = function () {
// Only run gameplay logic when in playing state
if (gameState !== 'playing') {
return;
}
// Update basketballs
for (var i = basketballs.length - 1; i >= 0; i--) {
var ball = basketballs[i];
if (!ball.isActive) {
ball.destroy();
basketballs.splice(i, 1);
continue;
}
}
// Clean up orphaned reflections
for (var i = ballReflections.length - 1; i >= 0; i--) {
var reflection = ballReflections[i];
if (!reflection.parentBall || !reflection.parentBall.isActive) {
reflection.destroy();
ballReflections.splice(i, 1);
}
// Check for scoring
checkBasketScore(ball);
// Check collision with other basketballs
for (var j = i + 1; j < basketballs.length; j++) {
var otherBall = basketballs[j];
if (!otherBall.isActive) continue;
var dx = ball.x - otherBall.x;
var dy = ball.y - otherBall.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = 120; // Combined radius of two basketballs
if (distance < minDistance && distance > 0) {
// Calculate collision normal
var normalX = dx / distance;
var normalY = dy / distance;
// Separate balls to prevent overlap
var overlap = minDistance - distance;
var separationX = normalX * overlap * 0.5;
var separationY = normalY * overlap * 0.5;
ball.x += separationX;
ball.y += separationY;
otherBall.x -= separationX;
otherBall.y -= separationY;
// Calculate relative velocity
var relativeVelX = ball.velocityX - otherBall.velocityX;
var relativeVelY = ball.velocityY - otherBall.velocityY;
// Calculate relative velocity along collision normal
var relativeSpeed = relativeVelX * normalX + relativeVelY * normalY;
// Only resolve if objects are moving towards each other
if (relativeSpeed > 0) continue;
// Calculate impulse scalar (assuming equal mass)
var impulse = -2 * relativeSpeed / 2;
var impulseX = impulse * normalX;
var impulseY = impulse * normalY;
// Apply impulse with bounce decay
var bounceDecay = 0.8;
ball.velocityX += impulseX * bounceDecay;
ball.velocityY += impulseY * bounceDecay;
otherBall.velocityX -= impulseX * bounceDecay;
otherBall.velocityY -= impulseY * bounceDecay;
// Play bounce sound
LK.getSound('bounce').play();
}
}
}
// Streak is only reset when player misses a shot (handled in Basketball class)
// No automatic reset when no balls are active
// Update timer
if (!gameEnded) {
gameTimer--;
var newTimeLeft = Math.ceil(gameTimer / 60);
if (newTimeLeft !== gameTimeLeft) {
gameTimeLeft = newTimeLeft;
// Format timer as MM:SS
var minutes = Math.floor(gameTimeLeft / 60);
var seconds = gameTimeLeft % 60;
var formattedTime = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(formattedTime);
}
// Check if time is up
if (gameTimer <= 0 && !gameEnded) {
gameEnded = true;
timerTxt.setText('00:00');
// Check and update best score
var currentScore = LK.getScore();
if (currentScore > bestScore) {
bestScore = currentScore;
storage.bestScore = bestScore;
bestScoreTxt.setText('Best: ' + bestScore);
}
LK.showGameOver();
}
}
// Rhythm mode beat system
if (gameMode === 'rhythm' && beatIndicator) {
// Check for beat timing every 0.5 seconds
if (LK.ticks - lastBeatTime >= beatInterval) {
lastBeatTime = LK.ticks;
mustTapGreenZone = true; // Reset requirement to tap green zone
// Animate beat indicator
tween(beatIndicator, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(beatIndicator, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeIn
});
}
});
// Animate green zone to pulse
if (greenZone) {
tween(greenZone, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(greenZone, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Play beat sound
LK.getSound('1').play();
}
}
// Win condition removed - no score target
};
// Title screen is now initialized - gameplay will be initialized when start is pressed
Make picture high definition
Remove everything but net
Remove basketball rings and backboards from picture
Shiny black rectangle frame. In-Game asset. 2d. High contrast. No shadows
Neon green basketball. In-Game asset. 2d. High contrast. No shadows
Change to black warriors uniform
Change to black warriors uniform
Padlock button that says *locked* Purchase for: $100. In-Game asset. 2d. High contrast. No shadows
Remove words "with basketball"
Number 1
Number 2
Number 3
Number 4
Number 5
Number 6
Number 7
Number 8
Make it say $HOP in big letters across the ball
Change it to say Rhythm
Make a shop backdrop with display shelves and framed areas to place items
Remove ball and put his hands down like he just caught a pass
Remove ball and fix hands
Record. In-Game asset. 2d. High contrast. No shadows
Make the net look like it's on fire