/**** * Classes ****/ var BeachToys = Container.expand(function (index) { var self = Container.call(this); self.shadow = self.attachAsset('beachToysShadow', { anchorX: 0.5, anchorY: 0.5, x: 150, y: -150, alpha: 0.3, scaleX: -1, scaleY: 1, tint: 0x000000, rotation: 2.3, visible: !index }); var alt = index ? "2" : ""; var beachToysGraphics = self.attachAsset('beachToys' + alt, { anchorX: 0.5, anchorY: 1.0 }); }); var Boat = Container.expand(function () { var self = Container.call(this); var boatGraphics = self.attachAsset('boat', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; // Speed of the boat movement self.direction = 1; // Direction of the boat movement, 1 for right, -1 for left self.update = function () { if (!self.startTime) { self.startTime = LK.ticks; } var elapsed = (LK.ticks - self.startTime) / 60; // Elapsed time in seconds var targetX = self.x + self.speed * self.direction; self.x = self.x + (targetX - self.x) * 0.1; // Linear interpolation for smoother movement if (self.x > 2048 + boatGraphics.width / 2) { self.x = -boatGraphics.width / 2; // Reset position to the left of the screen } else if (self.x < -boatGraphics.width / 2) { self.x = 2048 + boatGraphics.width / 2; // Reset position to the right of the screen } if (self.y > 2748 + self.height / 2) { self.visible = false; // Make the boat not visible when y > 2748 } }; /** * Reverses the direction of the boat. */ self.reverse = function () { self.direction *= -1; self.scale.x *= -1; self.visible = true; self.x = 2048 + self.width / 2; // Reset position to the right of the screen }; }); var Bucket = Container.expand(function () { var self = Container.call(this); var bucketGraphics = self.attachAsset('bucket', { anchorX: 0.5, anchorY: 0.0 }); self.scoreTxt = new Text2('00', { size: 200, fill: "#03a2c3", weight: 1000 }); self.scoreTxt.anchor.set(0.5, 0); self.scoreTxt.x = 0; self.scoreTxt.y = 330; self.addChild(self.scoreTxt); }); var Cloud = Container.expand(function (index) { var self = Container.call(this); var cloudGraphics = self.attachAsset('cloud' + index, { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.4 + (2 - index) * 0.2 + Math.random() * 0.2; // Speed of the cloud movement self.update = function () { if (!self.visible) { return; } self.x += self.speed; if (self.x > 2048 + cloudGraphics.width / 2) { self.x = -cloudGraphics.width / 2 - Math.random() * 2048; // Reset position to the left of the screen self.y = Math.random() * 512; } }; }); var Confetti = Container.expand(function () { var self = Container.call(this); var confettiColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff]; var confettiPieces = []; var nbConfettis = 10 + level * 10; nbConfettis = Math.min(500, nbConfettis); for (var i = 0; i < nbConfettis; i++) { var confettiPiece = self.attachAsset('confetti', { anchorX: 0.5, anchorY: 0.5, width: 15, height: 50, tint: confettiColors[Math.floor(Math.random() * confettiColors.length)] }); confettiPiece.x = Math.random() * 2048; confettiPiece.y = Math.random() * 2732; confettiPiece.rotation = Math.random() * Math.PI * 2; confettiPiece.speedY = Math.random() * 5 + 4; confettiPiece.speedX = (Math.random() - 0.5) * 2; confettiPieces.push(confettiPiece); } self.update = function () { for (var i = 0; i < confettiPieces.length; i++) { var piece = confettiPieces[i]; piece.y += piece.speedY; piece.x += piece.speedX; if (piece.y > 2732) { piece.y = -piece.height; piece.x = Math.random() * 2048; } } }; }); var FlyingObject = Container.expand(function (index) { var self = Container.call(this); var flyingObjectGraphics = self.attachAsset('flyingObject' + index, { anchorX: 0.5, anchorY: 0.5 }); self.directionY = -1 * (Math.random() > 0.5); self.passed = false; self.speed = 3; // Speed of the flying object movement self.update = function () { if (!self.visible || self.passed) { return; } self.x += self.speed; self.y += self.speed * 0.1 * self.directionY; if (self.x > 2048 + flyingObjectGraphics.width / 2) { self.x = -flyingObjectGraphics.width / 2 - Math.random() * 1024; // Reset position to the left of the screen self.y = Math.random() * 512; // Reset position relative to the camera self.visible = false; self.passed = true; } }; }); var SandBlock = Container.expand(function () { var self = Container.call(this); var sandBlockGraphics = self.attachAsset('sandBlock', { anchorX: 0.5, anchorY: 0 }); self.height = sandBlockAssetHeight * sandBlockHeightBaseRatio; self.velocity = 0; // Initial velocity for natural falling speed increase self.update = function () { if (self.y > 2748 + self.height / 2) { self.visible = false; } }; }); /***********************************************************************************/ /********************************** SANDCASTLE CLASS ************************************/ /***********************************************************************************/ var Sandcastle = Container.expand(function () { var self = Container.call(this); self.shadow = self.attachAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, visible: false }); var sandcastleGraphics = self.attachAsset('sandcastle', { anchorX: 0.5, anchorY: 0.5 }); self.shadow.width = sandcastleGraphics.width; self.shadow.height = sandcastleGraphics.height * 0.5; self.shadow.y = sandcastleGraphics.height * 0.3; self.shadow.visible = false; self.update = function () { // Sandcastle specific update logic }; }); /**** * Initialize Game ****/ // Utility function to draw a polygon using drawLine var game = new LK.Game({ backgroundColor: 0x000050 // Initialize game with a black background }); /**** * Game Code ****/ /****************************************************************************************** */ /************************************** GLOBAL VARIABLES ********************************** */ /****************************************************************************************** */ // Enumeration for game states function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; } var isBucketMovingUp = false; // Flag to track if the bucket is moving up var isBucketMovingDown = false; // Flag to track if the bucket is moving down var GAME_STATE = { INIT: 'INIT', MENU: 'MENU', NEW_ROUND: 'NEW_ROUND', PLAYING: 'PLAYING', SCORE: 'SCORE' }; var gameState = GAME_STATE.INIT; var score = 0; var level = 0; var scoreTxt; var isDebug = false; var debugText; var debugMarker; var backgroundImage; var bucket; var banner; var picketLeft; var picketRight; var sandBlocks = []; var lives = 3; var livesIcons = []; var cameraMoved = false; // Flag to track if the camera has moved for the current sandblock fall var sandBlockDropped = false; // Flag to track if a sand block has been dropped var isCameraMoving = false; // Flag to track if the camera is currently moving var bucketReady = false; // Flag to track if the bucket has reached y=0 var sandcastle; var sandcastleBaseY = 2212; var sandcastleHeight = 1640; var previousBlockX = 1024; var sandBlockWidth = 390; var sandBlockHalfWidth = 195; var sandBlockAssetHeight = 1618; // height of the asset var sandBlockHeightBaseRatio = 0.6; // Extend effect start ratio var sandBlockHeightRatio = sandBlockHeightBaseRatio; // Extend effect current ratio var sandBlockSize = 460; // height of the visible sand block var centralPointY = 1380 - sandBlockSize; var bgHeight = 1152; var bgHalfHeight = 576; var gravity = 2; // Gravity effect for sand block falling var bucketDirection = 1; // 1 for right, -1 for left var isBucketMovingHorizontally = false; // Flag to track if the bucket is moving horizontally var bucketMoveStep = 10; // Initial bucket move step size var currentSandBlock = null; // Global variable to keep track of the current sand block var startButton = null; // Global variable for the start button var isPlaying = false; // Global variable to track if the game is currently in the playing state var sandBlockMoveStep = 2; // Step size for sand block horizontal movement var bgMusic; var globalDelta = 0; var beachToys = null; // Global variable for beach toys var cloud1 = null; // Global variable for cloud1 var cloud2 = null; // Global variable for cloud2 var moveStep = 0; // Global variable for move step var flyingObject1 = null; // Global variable for flyingObject1 var flyingObject2 = null; // Global variable for flyingObject2 var fadeOutStartButtonStartTime = 0; var isCleaningNewRound = false; // Flag to prevent multiple calls to cleanNewRoundState var lastUpdateTime = 0; /****************************************************************************************** */ /*********************************** UTILITY FUNCTIONS ************************************ */ /****************************************************************************************** */ function moveCamera() { var targetY = backgroundImage.y + sandBlockSize; moveStep = sandBlockSize / 60; // Move over 1 second (60 frames) function progressiveMove() { if (backgroundImage.y < targetY) { log("Camera is moving. Current y position: " + backgroundImage.y); backgroundImage.y += moveStep; background2.y += moveStep; background3.y += moveStep; sandcastle.y += moveStep; background4.y += moveStep; background5.y += moveStep; banner.y += moveStep * 1.05; picketLeft.y += moveStep * 1.05; picketRight.y += moveStep * 1.05; for (var j = 0; j < sandBlocks.length; j++) { sandBlocks[j].y += moveStep; } if (cloud1) { cloud1.y += moveStep * 0.5; } if (cloud2) { cloud2.y += moveStep * 0.5; } if (flyingObject1 && flyingObject1.visible) { flyingObject1.y += moveStep * 0.75; } if (flyingObject2 && flyingObject2.visible) { flyingObject2.y += moveStep * 0.75; } if (boat) { boat.y += moveStep; } if (backgroundImage.y < targetY) { LK.setTimeout(progressiveMove, 1000 / 60); // Call progressiveMove every 1/60th of a second } else { isCameraMoving = false; // Set the flag to false when the camera stops moving var checkBucketUpCompletion = function checkBucketUpCompletion() { if (bucket.y <= -bucket.height && !isBucketMovingUp) { isBucketMovingHorizontally = true; // Resume horizontal movement of the bucket moveBucketDown(); // Progressively move the bucket down when the camera reaches the target game.addChild(bucket); // Ensure bucket is above sand blocks } else { LK.setTimeout(checkBucketUpCompletion, 1000 / 60); // Check again in the next frame } }; checkBucketUpCompletion(); } if (background2.y > 2732 + bgHalfHeight) { background2.y = background5.y - background5.height; } if (background3.y > 2732 + bgHalfHeight) { background3.y = background2.y - background2.height; } if (background4.y > 2732 + bgHalfHeight) { background4.y = background3.y - background3.height; } if (background5.y > 2732 + bgHalfHeight) { background5.y = background4.y - background4.height; } } } progressiveMove(); } function log() { if (isDebug) { var _console; (_console = console).log.apply(_console, arguments); } } /****************************************************************************************** */ /************************************** INPUT HANDLERS ************************************ */ /****************************************************************************************** */ game.on('down', function (x, y, obj) { handleGameState(x, y, obj); }); function handleGameState(x, y, obj) { switch (gameState) { case GAME_STATE.MENU: gameMenuDown(x, y, obj); break; case GAME_STATE.NEW_ROUND: gameNewRoundDown(x, y, obj); break; case GAME_STATE.PLAYING: gamePlayingDown(x, y, obj); break; case GAME_STATE.SCORE: gameScoreDown(x, y, obj); break; } } function gameMenuDown(x, y, obj) { log("gameMenuDown..."); if (gameState != GAME_STATE.MENU) { return; } cleanMenuState(); initNewRoundState(); } function gameNewRoundDown(x, y, obj) { log("gameNewRoundDown..."); if (gameState != GAME_STATE.NEW_ROUND || isCleaningNewRound) { return; } log("gameNewRoundDown Ok clean..."); cleanNewRoundState(); } function gamePlayingDown(x, y, obj) { log("gamePlayingDown...", "state=" + (gameState == GAME_STATE.PLAYING), "playing=" + isPlaying, "dropped=" + !sandBlockDropped, "camera=" + !isCameraMoving, "bucket=" + bucketReady); if (gameState != GAME_STATE.PLAYING || !isPlaying || sandBlockDropped || isCameraMoving || !bucketReady) { return; // Prevent taps during camera movement } sandBlockDropped = true; log("gamePlayingDown OK"); // Pause horizontal movement of the bucket isBucketMovingHorizontally = false; lastUpdateTime = 0; // Move the bucket vertically to hide over the screen top with shake moveBucketUp(); LK.setTimeout(createNewSandBlock, 512); // Delay creating new sand block until after shake } function gameScoreDown(x, y, obj) { log("gameScoreDown..."); cleanScoreState(); } /****************************************************************************************** */ /************************************* GAME FUNCTIONS ************************************* */ /****************************************************************************************** */ function moveBucketUp() { if (isBucketMovingDown) { return; } // Prevent concurrent moves isBucketMovingUp = true; // Set flag to true when bucket starts moving up var bucketMoveStep = 15; // Adjust the step size as needed var shakeStep = 40; // Step size for shake movement var shakeCount = 0; // Counter for shake movement function shakeBucket() { if (shakeCount < 5) { // Shake for 10 frames bucket.y += shakeCount % 2 === 0 ? -shakeStep : shakeStep; shakeCount++; LK.setTimeout(shakeBucket, 100); // Call shakeBucket every 1/60th of a second } else { moveBucketUpAfterShake(); // Move bucket up after shaking } } function moveBucketUpAfterShake() { if (isPlaying && bucket.y > -bucket.height) { isBucketMovingUp = true; // Ensure flag remains true while moving up //log("Bucket is moving up. Current y position: " + bucket.y); bucket.y -= bucketMoveStep; game.addChild(bucket); // Ensure bucket is above sand blocks LK.setTimeout(moveBucketUpAfterShake, 5); // Call moveBucketUp every 1/240th of a second } else { //log("Bucket end moving up. Current y position: " + bucket.y + "/-" + bucket.height); isBucketMovingUp = false; // Set flag to false when bucket finishes moving up } } shakeBucket(); // Start shaking the bucket } function moveBucketDown() { if (isBucketMovingUp) { return; } // Prevent concurrent moves isBucketMovingDown = true; // Set flag to true when bucket starts moving down var bucketMoveStep = 15; // Adjust the step size as needed if (isPlaying && bucket.y < -15) { log("Bucket is moving down. Current y position: " + bucket.y); bucketReady = false; // Reset the flag when the bucket is moving down bucket.y += bucketMoveStep; game.addChild(bucket); // Ensure bucket is above sand blocks LK.setTimeout(moveBucketDown, 1000 / 60); // Call moveBucketDown every 1/60th of a second } else { log("Bucket moving down. Ready "); bucketReady = true; // Set the flag to true when the bucket reaches y=0 isBucketMovingDown = false; // Set flag to false when bucket finishes moving down } } function createNewSandBlock() { sandBlockHeightRatio = sandBlockHeightBaseRatio; currentSandBlock = new SandBlock(); var sandBlock = currentSandBlock; sandBlock.x = bucket.x; sandBlock.y = 256; game.addChild(sandBlock); game.addChild(bucket); cameraMoved = false; // Reset the flag when a new sandblock is created sandBlockDropped = true; // Set the flag to true when a new sand block is created } function updateSandBlockPosition(sandBlock) { bucketReady = false; if (sandBlock.y < 512) { // Restore sand block height when out of bucket if (sandBlockHeightRatio < 1) { sandBlockHeightRatio += 0.05; sandBlock.height = sandBlockAssetHeight * Math.min(1, sandBlockHeightRatio); } else { sandBlockHeightRatio = 1; sandBlock.height = sandBlockAssetHeight; } //log("sandBlockHeightRatio=" + sandBlockHeightRatio); } if (sandBlock.y < centralPointY) { // Start of fall until center if (!sandBlock.falling) { LK.getSound('sandFall').play(); sandBlock.falling = true; } sandBlock.velocity += gravity; // Increase velocity due to gravity sandBlock.y += sandBlock.velocity; // Update position based on velocity } else { // Center : Check if on previous block or out if (Math.abs(sandBlock.x - previousBlockX) > sandBlockHalfWidth) { // Out : Continue falling if (sandBlock.y < 2732 + sandBlockSize) { // Fall until botom sandBlock.rotation += 0.07 * Math.sign(sandBlock.x - previousBlockX); sandBlock.x += 20 * Math.sign(sandBlock.x - previousBlockX); sandBlock.velocity += gravity; // Increase velocity due to gravity sandBlock.y += sandBlock.velocity; // Update position based on velocity } else { var checkBucketUpCompletion = function checkBucketUpCompletion() { if (!isBucketMovingUp) { sandBlockDropped = false; isBucketMovingHorizontally = true; // Resume horizontal movement of the bucket moveBucketDown(); // Progressively move the bucket down when the camera reaches the target game.addChild(bucket); // Ensure bucket is above sand blocks } else { LK.setTimeout(checkBucketUpCompletion, 1000 / 60); // Check again in the next frame } }; // Reached bottom checkBlockAlignmentAndUpdate(sandBlock); sandBlock.fallen = true; sandBlock.destroy(); currentSandBlock = null; // Ensure bucket has finished moving up before running the next steps checkBucketUpCompletion(); } } else { // Ok : Stop sandBlocks.push(currentSandBlock); globalDelta = Math.max(globalDelta, Math.abs(currentSandBlock.x - 1024)); sandBlockMoveStep = Math.floor(globalDelta / 100); previousBlockX = currentSandBlock.x; // Move background, sandcastle, and fallen sandblock vertically by sandBlockSize if (!cameraMoved) { cameraMoved = true; isCameraMoving = true; // Set the flag to true when the camera starts moving LK.setTimeout(function () { moveCamera(); // Set the flag to true after moving the camera //bucket.y = 0; // Reset bucket position when the camera moves }, 640); // Delay of 1 second before moving the camera // Check if sand block reached the base checkBlockAlignmentAndUpdate(sandBlock); } currentSandBlock = null; } } } function moveBucketHorizontally() { if (!level) { return; } if (!lastUpdateTime) { lastUpdateTime = Date.now(); } var currentTime = Date.now(); var dt = (currentTime - lastUpdateTime) / 1000; // Delta time in seconds lastUpdateTime = currentTime; var moveDistance = bucketMoveStep * bucketDirection * dt * 60; // Adjust for 60 FPS if (bucket.x >= 2048 - bucket.width / 2) { bucketDirection = -1; // Change direction to left } else if (bucket.x <= bucket.width / 2) { bucketDirection = 1; // Change direction to right } log("moveBucketHorizontally: x=" + bucket.x, "moveDistance=" + moveDistance, "dt=" + dt); bucket.x += moveDistance; } function checkBlockAlignmentAndUpdate(sandBlock) { if (Math.abs(sandBlock.x - previousBlockX) < sandBlockHalfWidth) { LK.getSound('dropped').play(); // Check for perfect alignment level += 1; score += 1; // Bonus points for perfect alignment LK.setScore(score); // Store the score in LK score if (level > 0 && level % 3 === 0) { bucketMoveStep += 4; // Increase bucket speed by 2 units } } else { LK.getSound('missed').play(); lives--; livesIcons[lives].destroy(); livesIcons.pop(); if (lives <= 0) { var checkBucketUpCompletionForScore = function checkBucketUpCompletionForScore() { if (!isBucketMovingUp) { cleanPlayingState(); initScoreState(); } else { LK.setTimeout(checkBucketUpCompletionForScore, 1000 / 60); // Check again in the next frame } }; checkBucketUpCompletionForScore(); return; } } sandBlock.falling = false; // Reset the falling flag sandBlockDropped = false; // Reset the flag after handling sand block reaching the base game.addChild(bucket); // Ensure bucket is above sand blocks } function initializeBackgrounds() { var mainBgY = 1566; var mainBgHeight = 2732; //var bgHeight = 1152; //var bgHalfHeight = 576; backgroundImage = LK.getAsset('backgroundImage', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: mainBgY }); game.addChild(backgroundImage); background2 = LK.getAsset('background2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 }); background2.y = backgroundImage.y - mainBgHeight / 2 - bgHalfHeight; game.addChild(background2); background3 = LK.getAsset('background3', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 }); background3.y = background2.y - bgHeight; background4 = LK.getAsset('background2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 }); background4.y = background3.y - bgHeight; game.addChild(background4); background5 = LK.getAsset('background3', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 }); background5.y = background4.y - bgHeight; ; game.addChild(background5); game.addChild(background3); } function loopBgMusic() { if (bgMusic && Date.now() - bgMusic.lastPlayTime > 10000) { bgMusic.lastPlayTime = Date.now(); bgMusic.play(); } } /****************************************************************************************** */ /************************************* GAME STATES **************************************** */ /****************************************************************************************** */ function gameInitialize() { log("Game initialize..."); initializeBackgrounds(); boat = new Boat(); boat.x = boat.width; boat.y = 1090; boat.visible = true; game.addChild(boat); sandcastle = new Sandcastle(); sandcastle.x = 2048 / 2; sandcastle.y = -sandcastleHeight; // Hide the sandcastle above the screen initially game.addChild(sandcastle); picketLeft = LK.getAsset('picket', { anchorX: 0.5, anchorY: 1, x: 30, y: 2140 }); game.addChild(picketLeft); picketRight = LK.getAsset('picket', { anchorX: 0.5, anchorY: 1, scaleX: -1, x: 2018, y: 2140 }); game.addChild(picketRight); banner = LK.getAsset('banner', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 400 }); game.addChild(banner); game.addChild(sandcastle); level = 0; score = 0; LK.setScore(score); // Initialize LK score to zero at game start bgMusic = LK.getSound('music'); if (bgMusic && bgMusic.isPlaying) { bgMusic.stop(); } bgMusic.lastPlayTime = 0; startButton = LK.getAsset('startButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1420 // Center of the screen }); startButton.visible = false; // Initially hidden game.addChild(startButton); beachToys = new BeachToys(); beachToys.x = 2048 / 2; beachToys.y = 2922; game.addChild(beachToys); // Initialize clouds at the beginning but keep them invisible cloud1 = new Cloud(1); cloud1.x = -cloud1.width; cloud1.y = -cloud1.height; cloud1.visible = false; game.addChild(cloud1); cloud2 = new Cloud(2); cloud2.x = -cloud2.width; cloud2.y = -cloud2.height; cloud2.visible = false; game.addChild(cloud2); flyingObject1 = new FlyingObject(1); flyingObject1.x = -Math.random() * 512; flyingObject1.y = flyingObject1.height + 256 * Math.random(); flyingObject1.visible = false; flyingObject1.passed = false; game.addChild(flyingObject1); flyingObject2 = new FlyingObject(2); flyingObject2.x = -Math.random() * 512; flyingObject2.y = flyingObject2.height + 256 * Math.random(); flyingObject2.visible = false; flyingObject2.passed = false; game.addChild(flyingObject2); initMenuState(); if (isDebug) { debugMarker = LK.getAsset('debugMarker', { anchorX: 0.5, anchorY: 0.5, x: 1300, y: 160 }); game.addChild(debugMarker); debugText = new Text2('Debug Info', { size: 50, fill: "#ffffff" }); debugText.anchor.set(0.5, 1); // Anchor to the bottom-right LK.gui.bottom.addChild(debugText); } } // GAME MENU function initMenuState() { log("initMenuState..."); gameState = GAME_STATE.MENU; } function handleMenuLoop() { // Menu animations here } function cleanMenuState() { log("cleanMenuState..."); } // NEW ROUND function initNewRoundState() { log("initNewRoundState..."); gameState = GAME_STATE.NEW_ROUND; // Fade in the start button startButton.visible = true; startButton.alpha = 0; var fadeInStep = 0.05; function fadeInStartButton() { if (startButton.alpha < 1) { startButton.alpha += fadeInStep; LK.setTimeout(fadeInStartButton, 1000 / 60); // Call fadeInStartButton every 1/60th of a second } } fadeInStartButton(); // Reset flying objects passed property if (flyingObject1) { flyingObject1.passed = false; } if (flyingObject2) { flyingObject2.passed = false; } // Round preparation logic here. } function handleNewRoundLoop() { // New Round animations here loopBgMusic(); } function cleanNewRoundState() { if (isCleaningNewRound) { return; } // Prevent multiple calls isCleaningNewRound = true; log("cleanNewRoundState..."); // Animate the start button removal var fadeOutStep = 0.05; function fadeOutStartButton() { log("fadeOutStartButton...", startButton.alpha, "step=" + fadeOutStep); if (startButton.alpha > 0 && Date.now() - fadeOutStartButtonStartTime < 1000) { startButton.alpha -= fadeOutStep; LK.setTimeout(fadeOutStartButton, 1000 / 60); // Call fadeOutStartButton every 1/60th of a second } else { log("fadeOutStartButton End"); startButton.alpha = 0; var fadeOutBeachToys = function fadeOutBeachToys() { log("fadeOutBeachToys..."); if (beachToys.alpha > 0) { beachToys.alpha -= fadeOutStep; LK.setTimeout(fadeOutBeachToys, 1000 / 60); } else { log("fadeOutBeachToys End"); beachToys.visible = false; beachToys.alpha = 1; // Reset alpha for future use var animateSandcastleFall = function animateSandcastleFall() { log("animateSandcastleFall..."); if (sandcastle.y < sandcastleBaseY) { sandcastle.velocity += gravity; // Increase velocity due to gravity sandcastle.y += sandcastle.velocity; // Update position based on velocity LK.setTimeout(animateSandcastleFall, 1000 / 60); // Call animateSandcastleFall every 1/60th of a second } else { log("animateSandcastleFall End"); sandcastle.y = sandcastleBaseY; // Ensure it stops exactly at the base LK.getSound('dropped').play(); // Play drop sound when it reaches its position initPlayingState(); isCleaningNewRound = false; // Reset the flag after completion } }; startButton.visible = false; startButton.alpha = 1; // Reset alpha for future use sandcastle.velocity = 0; // Initial velocity for natural falling speed increase animateSandcastleFall(); } }; fadeOutBeachToys(); } } fadeOutStartButtonStartTime = Date.now(); // Prevent Bug alpha stucked on tap fadeOutStartButton(); } // PLAYING function initPlayingState() { log("initPlayingState..."); gameState = GAME_STATE.PLAYING; isPlaying = true; for (var i = 0; i < lives; i++) { var lifeIcon = LK.getAsset('bucketIcon', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 100, y: 1166 + i * 130 }); livesIcons.push(lifeIcon); game.addChild(lifeIcon); } bucket = new Bucket(); bucket.x = 2048 / 2; bucket.y = -bucket.height; game.addChild(bucket); // Ensure bucket is above sand blocks moveBucketDown(); } function handlePlayingLoop() { loopBgMusic(); if (isPlaying && currentSandBlock) { updateSandBlockPosition(currentSandBlock); //game.addChild(bucket); // Ensure bucket is above sand blocks } if (level >= 4) { for (var i = 0; i < sandBlocks.length; i++) { var sandBlock = sandBlocks[i]; sandBlock.x += Math.sin(LK.ticks / 20) * sandBlockMoveStep; // Move sand blocks back and forth } } if (bucket && bucket.scoreTxt) { if (score === 0) { bucket.scoreTxt.visible = false; } else { bucket.scoreTxt.visible = true; bucket.scoreTxt.setText(score); } } if (isBucketMovingHorizontally) { moveBucketHorizontally(); // Ensure horizontal movement is restored } if (level >= 6 && cloud1 && cloud2 && !cloud1.visible) { cloud1.visible = true; cloud2.visible = true; } if (level && flyingObject1 && level == 5 && !flyingObject1.visible && !flyingObject1.passed) { flyingObject1.visible = true; flyingObject1.passed = false; } if (level && flyingObject2 && level % 10 == 0 && !flyingObject2.visible && !flyingObject2.passed) { flyingObject2.visible = true; flyingObject2.passed = false; } if (isDebug) { //debugText.setText("X: " + bucket.x + " Y: " + bucket.y); debugText.setText("Delta: " + globalDelta); } } function cleanPlayingState() { log("cleanPlayingState..."); isPlaying = false; // TODO Remove elements } // SCORE function initScoreState() { log("initScoreState..."); gameState = GAME_STATE.SCORE; // Play camera sound LK.getSound('camera').play(); // Prepare final animation // Add a 1-second white screen flash LK.effects.flashScreen(0xffffff, 2000); LK.setTimeout(setupFinalPhoto, 1000); confetti = new Confetti(); confetti.visible = false; game.addChild(confetti); } function setupFinalPhoto() { // Set the background size to 2048x2732 backgroundImage.width = 2048; backgroundImage.height = 2732; // Set the background position to the center of the screen backgroundImage.x = 2048 / 2; backgroundImage.y = 2732 / 2; background2.visible = false; background3.visible = false; background4.visible = false; background5.visible = false; cloud1.visible = false; cloud2.visible = false; var finalScoreTxt = new Text2(score.toString(), { size: 150, fill: "#FFFFFF", weight: 1000, dropShadow: true }); finalScoreTxt.anchor.set(0.5, -0.50); LK.gui.top.addChild(finalScoreTxt); // Scale the sandcastle and the sandblocks down to fit in the screen var ratio = 1 / Math.ceil(sandBlocks.length / 3); sandcastle.width *= ratio; sandcastle.height *= ratio; sandcastle.x = 2048 / 2; sandcastle.y = sandcastleBaseY; sandcastle.shadow.visible = true; if (ratio <= 0.5) { beachToys = new BeachToys(); beachToys.width *= ratio; beachToys.height *= ratio; beachToys.x = 1724; beachToys.y = sandcastleBaseY + 300; game.addChild(beachToys); var sandcastle2 = new Sandcastle(); sandcastle2.x = 312; sandcastle2.y = sandcastleBaseY - 250; // Hide the sandcastle above the screen initially sandcastle2.width *= 0.8; sandcastle2.height *= 0.8; sandcastle2.width *= ratio; sandcastle2.height *= ratio; game.addChild(sandcastle2); sandcastle2.shadow.visible = true; beachToys2 = new BeachToys(2); beachToys2.width *= ratio; beachToys2.height *= ratio; beachToys2.shadow.visible = false; beachToys2.x = sandcastle2.x; // sandcastle2.x - sandcastle2.width / 2 - beachToys2.width / 2; beachToys2.y = sandcastle2.y + sandcastle2.height / 2 + 300 * ratio; //sandcastleBaseY + 350; game.addChild(beachToys2); } banner.width *= ratio; banner.height *= ratio; banner.x = 2048 / 2; banner.y = 2140 - picketLeft.height * ratio; picketLeft.width *= ratio; picketLeft.height *= ratio; picketLeft.x = 1024 - (1024 - 30) * ratio; picketLeft.y = 2140; picketRight.width *= ratio; picketRight.height *= ratio; picketRight.x = 1024 + (1024 - 30) * ratio; picketRight.y = 2140; var baseY = sandcastle.y - sandcastle.height / 2 - sandBlockSize * ratio; log("baseY ", baseY); for (var i = 0; i < sandBlocks.length; i++) { sandBlocks[i].width *= ratio; sandBlocks[i].height *= ratio; sandBlocks[i].x = 1024 - (1024 - sandBlocks[i].x) * ratio; sandBlocks[i].y = baseY - i * sandBlockSize * ratio; sandBlocks[i].visible = true; //log("block #", i, " h=" + sandBlockSize * ratio, " / y = ", sandBlocks[i].y); } // Show confetti confetti.visible = true; boat.y = 1000; boat.width *= ratio; boat.height *= ratio; boat.reverse(); } function handleScoreLoop() { // Score display logic here loopBgMusic(); } function cleanScoreState() { log("cleanScoreState..."); LK.showGameOver(); } /***********************************************************************************/ /******************************** MAIN GAME LOOP ***********************************/ /***********************************************************************************/ game.update = function () { switch (gameState) { case GAME_STATE.MENU: handleMenuLoop(); break; case GAME_STATE.NEW_ROUND: handleNewRoundLoop(); break; case GAME_STATE.PLAYING: handlePlayingLoop(); break; case GAME_STATE.SCORE: handleScoreLoop(); break; } }; gameInitialize(); // Initialize the game /********************************************************************************/ /**************************** GAME DESCRIPTION *********************************/ /******************************************************************************/ /* **Game Title**: **Sand Tower** **Description**: Build towering sandcastles, perfect your alignment, and climb to the top! Sand Tower awaits! 🏖️🏰 **Objective**: - Players aim to construct the tallest sandcastle tower. - Perfect alignment of sand blocks is crucial for stability and bonus points. **Visuals**: - The game features a sandy beach backdrop. - A basic sandcastle stands in the center, serving as the base. - The central tower is the focal point. **Gameplay Mechanics**: - **Beach Bucket Mechanism**: - Players use an upside-down beach bucket. - When the player taps, the bucket tips over, releasing sand. - **Sand Block Placement**: - Sand falls vertically from the bucket onto the base. - The player must time their taps to stack the sand blocks. - Perfect alignment grants bonus points. - **Lives System**: - The player starts with 3 lives (represented by small bucket icons). - Each time a sand block isn't centered enough and falls, 1 life is lost. - **Scoring**: - Points increase with tower height. - Bonus points for precise alignment. - **Game Over**: - The game ends if the player loses all their lives (buckets). **Audio**: - Gentle beach sounds (waves, distant chatter). - Encouraging music during gameplay. */
/****
* Classes
****/
var BeachToys = Container.expand(function (index) {
var self = Container.call(this);
self.shadow = self.attachAsset('beachToysShadow', {
anchorX: 0.5,
anchorY: 0.5,
x: 150,
y: -150,
alpha: 0.3,
scaleX: -1,
scaleY: 1,
tint: 0x000000,
rotation: 2.3,
visible: !index
});
var alt = index ? "2" : "";
var beachToysGraphics = self.attachAsset('beachToys' + alt, {
anchorX: 0.5,
anchorY: 1.0
});
});
var Boat = Container.expand(function () {
var self = Container.call(this);
var boatGraphics = self.attachAsset('boat', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2; // Speed of the boat movement
self.direction = 1; // Direction of the boat movement, 1 for right, -1 for left
self.update = function () {
if (!self.startTime) {
self.startTime = LK.ticks;
}
var elapsed = (LK.ticks - self.startTime) / 60; // Elapsed time in seconds
var targetX = self.x + self.speed * self.direction;
self.x = self.x + (targetX - self.x) * 0.1; // Linear interpolation for smoother movement
if (self.x > 2048 + boatGraphics.width / 2) {
self.x = -boatGraphics.width / 2; // Reset position to the left of the screen
} else if (self.x < -boatGraphics.width / 2) {
self.x = 2048 + boatGraphics.width / 2; // Reset position to the right of the screen
}
if (self.y > 2748 + self.height / 2) {
self.visible = false; // Make the boat not visible when y > 2748
}
};
/**
* Reverses the direction of the boat.
*/
self.reverse = function () {
self.direction *= -1;
self.scale.x *= -1;
self.visible = true;
self.x = 2048 + self.width / 2; // Reset position to the right of the screen
};
});
var Bucket = Container.expand(function () {
var self = Container.call(this);
var bucketGraphics = self.attachAsset('bucket', {
anchorX: 0.5,
anchorY: 0.0
});
self.scoreTxt = new Text2('00', {
size: 200,
fill: "#03a2c3",
weight: 1000
});
self.scoreTxt.anchor.set(0.5, 0);
self.scoreTxt.x = 0;
self.scoreTxt.y = 330;
self.addChild(self.scoreTxt);
});
var Cloud = Container.expand(function (index) {
var self = Container.call(this);
var cloudGraphics = self.attachAsset('cloud' + index, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.4 + (2 - index) * 0.2 + Math.random() * 0.2; // Speed of the cloud movement
self.update = function () {
if (!self.visible) {
return;
}
self.x += self.speed;
if (self.x > 2048 + cloudGraphics.width / 2) {
self.x = -cloudGraphics.width / 2 - Math.random() * 2048; // Reset position to the left of the screen
self.y = Math.random() * 512;
}
};
});
var Confetti = Container.expand(function () {
var self = Container.call(this);
var confettiColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];
var confettiPieces = [];
var nbConfettis = 10 + level * 10;
nbConfettis = Math.min(500, nbConfettis);
for (var i = 0; i < nbConfettis; i++) {
var confettiPiece = self.attachAsset('confetti', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 50,
tint: confettiColors[Math.floor(Math.random() * confettiColors.length)]
});
confettiPiece.x = Math.random() * 2048;
confettiPiece.y = Math.random() * 2732;
confettiPiece.rotation = Math.random() * Math.PI * 2;
confettiPiece.speedY = Math.random() * 5 + 4;
confettiPiece.speedX = (Math.random() - 0.5) * 2;
confettiPieces.push(confettiPiece);
}
self.update = function () {
for (var i = 0; i < confettiPieces.length; i++) {
var piece = confettiPieces[i];
piece.y += piece.speedY;
piece.x += piece.speedX;
if (piece.y > 2732) {
piece.y = -piece.height;
piece.x = Math.random() * 2048;
}
}
};
});
var FlyingObject = Container.expand(function (index) {
var self = Container.call(this);
var flyingObjectGraphics = self.attachAsset('flyingObject' + index, {
anchorX: 0.5,
anchorY: 0.5
});
self.directionY = -1 * (Math.random() > 0.5);
self.passed = false;
self.speed = 3; // Speed of the flying object movement
self.update = function () {
if (!self.visible || self.passed) {
return;
}
self.x += self.speed;
self.y += self.speed * 0.1 * self.directionY;
if (self.x > 2048 + flyingObjectGraphics.width / 2) {
self.x = -flyingObjectGraphics.width / 2 - Math.random() * 1024; // Reset position to the left of the screen
self.y = Math.random() * 512; // Reset position relative to the camera
self.visible = false;
self.passed = true;
}
};
});
var SandBlock = Container.expand(function () {
var self = Container.call(this);
var sandBlockGraphics = self.attachAsset('sandBlock', {
anchorX: 0.5,
anchorY: 0
});
self.height = sandBlockAssetHeight * sandBlockHeightBaseRatio;
self.velocity = 0; // Initial velocity for natural falling speed increase
self.update = function () {
if (self.y > 2748 + self.height / 2) {
self.visible = false;
}
};
});
/***********************************************************************************/
/********************************** SANDCASTLE CLASS ************************************/
/***********************************************************************************/
var Sandcastle = Container.expand(function () {
var self = Container.call(this);
self.shadow = self.attachAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5,
visible: false
});
var sandcastleGraphics = self.attachAsset('sandcastle', {
anchorX: 0.5,
anchorY: 0.5
});
self.shadow.width = sandcastleGraphics.width;
self.shadow.height = sandcastleGraphics.height * 0.5;
self.shadow.y = sandcastleGraphics.height * 0.3;
self.shadow.visible = false;
self.update = function () {
// Sandcastle specific update logic
};
});
/****
* Initialize Game
****/
// Utility function to draw a polygon using drawLine
var game = new LK.Game({
backgroundColor: 0x000050 // Initialize game with a black background
});
/****
* Game Code
****/
/****************************************************************************************** */
/************************************** GLOBAL VARIABLES ********************************** */
/****************************************************************************************** */
// Enumeration for game states
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
var isBucketMovingUp = false; // Flag to track if the bucket is moving up
var isBucketMovingDown = false; // Flag to track if the bucket is moving down
var GAME_STATE = {
INIT: 'INIT',
MENU: 'MENU',
NEW_ROUND: 'NEW_ROUND',
PLAYING: 'PLAYING',
SCORE: 'SCORE'
};
var gameState = GAME_STATE.INIT;
var score = 0;
var level = 0;
var scoreTxt;
var isDebug = false;
var debugText;
var debugMarker;
var backgroundImage;
var bucket;
var banner;
var picketLeft;
var picketRight;
var sandBlocks = [];
var lives = 3;
var livesIcons = [];
var cameraMoved = false; // Flag to track if the camera has moved for the current sandblock fall
var sandBlockDropped = false; // Flag to track if a sand block has been dropped
var isCameraMoving = false; // Flag to track if the camera is currently moving
var bucketReady = false; // Flag to track if the bucket has reached y=0
var sandcastle;
var sandcastleBaseY = 2212;
var sandcastleHeight = 1640;
var previousBlockX = 1024;
var sandBlockWidth = 390;
var sandBlockHalfWidth = 195;
var sandBlockAssetHeight = 1618; // height of the asset
var sandBlockHeightBaseRatio = 0.6; // Extend effect start ratio
var sandBlockHeightRatio = sandBlockHeightBaseRatio; // Extend effect current ratio
var sandBlockSize = 460; // height of the visible sand block
var centralPointY = 1380 - sandBlockSize;
var bgHeight = 1152;
var bgHalfHeight = 576;
var gravity = 2; // Gravity effect for sand block falling
var bucketDirection = 1; // 1 for right, -1 for left
var isBucketMovingHorizontally = false; // Flag to track if the bucket is moving horizontally
var bucketMoveStep = 10; // Initial bucket move step size
var currentSandBlock = null; // Global variable to keep track of the current sand block
var startButton = null; // Global variable for the start button
var isPlaying = false; // Global variable to track if the game is currently in the playing state
var sandBlockMoveStep = 2; // Step size for sand block horizontal movement
var bgMusic;
var globalDelta = 0;
var beachToys = null; // Global variable for beach toys
var cloud1 = null; // Global variable for cloud1
var cloud2 = null; // Global variable for cloud2
var moveStep = 0; // Global variable for move step
var flyingObject1 = null; // Global variable for flyingObject1
var flyingObject2 = null; // Global variable for flyingObject2
var fadeOutStartButtonStartTime = 0;
var isCleaningNewRound = false; // Flag to prevent multiple calls to cleanNewRoundState
var lastUpdateTime = 0;
/****************************************************************************************** */
/*********************************** UTILITY FUNCTIONS ************************************ */
/****************************************************************************************** */
function moveCamera() {
var targetY = backgroundImage.y + sandBlockSize;
moveStep = sandBlockSize / 60; // Move over 1 second (60 frames)
function progressiveMove() {
if (backgroundImage.y < targetY) {
log("Camera is moving. Current y position: " + backgroundImage.y);
backgroundImage.y += moveStep;
background2.y += moveStep;
background3.y += moveStep;
sandcastle.y += moveStep;
background4.y += moveStep;
background5.y += moveStep;
banner.y += moveStep * 1.05;
picketLeft.y += moveStep * 1.05;
picketRight.y += moveStep * 1.05;
for (var j = 0; j < sandBlocks.length; j++) {
sandBlocks[j].y += moveStep;
}
if (cloud1) {
cloud1.y += moveStep * 0.5;
}
if (cloud2) {
cloud2.y += moveStep * 0.5;
}
if (flyingObject1 && flyingObject1.visible) {
flyingObject1.y += moveStep * 0.75;
}
if (flyingObject2 && flyingObject2.visible) {
flyingObject2.y += moveStep * 0.75;
}
if (boat) {
boat.y += moveStep;
}
if (backgroundImage.y < targetY) {
LK.setTimeout(progressiveMove, 1000 / 60); // Call progressiveMove every 1/60th of a second
} else {
isCameraMoving = false; // Set the flag to false when the camera stops moving
var checkBucketUpCompletion = function checkBucketUpCompletion() {
if (bucket.y <= -bucket.height && !isBucketMovingUp) {
isBucketMovingHorizontally = true; // Resume horizontal movement of the bucket
moveBucketDown(); // Progressively move the bucket down when the camera reaches the target
game.addChild(bucket); // Ensure bucket is above sand blocks
} else {
LK.setTimeout(checkBucketUpCompletion, 1000 / 60); // Check again in the next frame
}
};
checkBucketUpCompletion();
}
if (background2.y > 2732 + bgHalfHeight) {
background2.y = background5.y - background5.height;
}
if (background3.y > 2732 + bgHalfHeight) {
background3.y = background2.y - background2.height;
}
if (background4.y > 2732 + bgHalfHeight) {
background4.y = background3.y - background3.height;
}
if (background5.y > 2732 + bgHalfHeight) {
background5.y = background4.y - background4.height;
}
}
}
progressiveMove();
}
function log() {
if (isDebug) {
var _console;
(_console = console).log.apply(_console, arguments);
}
}
/****************************************************************************************** */
/************************************** INPUT HANDLERS ************************************ */
/****************************************************************************************** */
game.on('down', function (x, y, obj) {
handleGameState(x, y, obj);
});
function handleGameState(x, y, obj) {
switch (gameState) {
case GAME_STATE.MENU:
gameMenuDown(x, y, obj);
break;
case GAME_STATE.NEW_ROUND:
gameNewRoundDown(x, y, obj);
break;
case GAME_STATE.PLAYING:
gamePlayingDown(x, y, obj);
break;
case GAME_STATE.SCORE:
gameScoreDown(x, y, obj);
break;
}
}
function gameMenuDown(x, y, obj) {
log("gameMenuDown...");
if (gameState != GAME_STATE.MENU) {
return;
}
cleanMenuState();
initNewRoundState();
}
function gameNewRoundDown(x, y, obj) {
log("gameNewRoundDown...");
if (gameState != GAME_STATE.NEW_ROUND || isCleaningNewRound) {
return;
}
log("gameNewRoundDown Ok clean...");
cleanNewRoundState();
}
function gamePlayingDown(x, y, obj) {
log("gamePlayingDown...", "state=" + (gameState == GAME_STATE.PLAYING), "playing=" + isPlaying, "dropped=" + !sandBlockDropped, "camera=" + !isCameraMoving, "bucket=" + bucketReady);
if (gameState != GAME_STATE.PLAYING || !isPlaying || sandBlockDropped || isCameraMoving || !bucketReady) {
return; // Prevent taps during camera movement
}
sandBlockDropped = true;
log("gamePlayingDown OK");
// Pause horizontal movement of the bucket
isBucketMovingHorizontally = false;
lastUpdateTime = 0;
// Move the bucket vertically to hide over the screen top with shake
moveBucketUp();
LK.setTimeout(createNewSandBlock, 512); // Delay creating new sand block until after shake
}
function gameScoreDown(x, y, obj) {
log("gameScoreDown...");
cleanScoreState();
}
/****************************************************************************************** */
/************************************* GAME FUNCTIONS ************************************* */
/****************************************************************************************** */
function moveBucketUp() {
if (isBucketMovingDown) {
return;
} // Prevent concurrent moves
isBucketMovingUp = true; // Set flag to true when bucket starts moving up
var bucketMoveStep = 15; // Adjust the step size as needed
var shakeStep = 40; // Step size for shake movement
var shakeCount = 0; // Counter for shake movement
function shakeBucket() {
if (shakeCount < 5) {
// Shake for 10 frames
bucket.y += shakeCount % 2 === 0 ? -shakeStep : shakeStep;
shakeCount++;
LK.setTimeout(shakeBucket, 100); // Call shakeBucket every 1/60th of a second
} else {
moveBucketUpAfterShake(); // Move bucket up after shaking
}
}
function moveBucketUpAfterShake() {
if (isPlaying && bucket.y > -bucket.height) {
isBucketMovingUp = true; // Ensure flag remains true while moving up
//log("Bucket is moving up. Current y position: " + bucket.y);
bucket.y -= bucketMoveStep;
game.addChild(bucket); // Ensure bucket is above sand blocks
LK.setTimeout(moveBucketUpAfterShake, 5); // Call moveBucketUp every 1/240th of a second
} else {
//log("Bucket end moving up. Current y position: " + bucket.y + "/-" + bucket.height);
isBucketMovingUp = false; // Set flag to false when bucket finishes moving up
}
}
shakeBucket(); // Start shaking the bucket
}
function moveBucketDown() {
if (isBucketMovingUp) {
return;
} // Prevent concurrent moves
isBucketMovingDown = true; // Set flag to true when bucket starts moving down
var bucketMoveStep = 15; // Adjust the step size as needed
if (isPlaying && bucket.y < -15) {
log("Bucket is moving down. Current y position: " + bucket.y);
bucketReady = false; // Reset the flag when the bucket is moving down
bucket.y += bucketMoveStep;
game.addChild(bucket); // Ensure bucket is above sand blocks
LK.setTimeout(moveBucketDown, 1000 / 60); // Call moveBucketDown every 1/60th of a second
} else {
log("Bucket moving down. Ready ");
bucketReady = true; // Set the flag to true when the bucket reaches y=0
isBucketMovingDown = false; // Set flag to false when bucket finishes moving down
}
}
function createNewSandBlock() {
sandBlockHeightRatio = sandBlockHeightBaseRatio;
currentSandBlock = new SandBlock();
var sandBlock = currentSandBlock;
sandBlock.x = bucket.x;
sandBlock.y = 256;
game.addChild(sandBlock);
game.addChild(bucket);
cameraMoved = false; // Reset the flag when a new sandblock is created
sandBlockDropped = true; // Set the flag to true when a new sand block is created
}
function updateSandBlockPosition(sandBlock) {
bucketReady = false;
if (sandBlock.y < 512) {
// Restore sand block height when out of bucket
if (sandBlockHeightRatio < 1) {
sandBlockHeightRatio += 0.05;
sandBlock.height = sandBlockAssetHeight * Math.min(1, sandBlockHeightRatio);
} else {
sandBlockHeightRatio = 1;
sandBlock.height = sandBlockAssetHeight;
}
//log("sandBlockHeightRatio=" + sandBlockHeightRatio);
}
if (sandBlock.y < centralPointY) {
// Start of fall until center
if (!sandBlock.falling) {
LK.getSound('sandFall').play();
sandBlock.falling = true;
}
sandBlock.velocity += gravity; // Increase velocity due to gravity
sandBlock.y += sandBlock.velocity; // Update position based on velocity
} else {
// Center : Check if on previous block or out
if (Math.abs(sandBlock.x - previousBlockX) > sandBlockHalfWidth) {
// Out : Continue falling
if (sandBlock.y < 2732 + sandBlockSize) {
// Fall until botom
sandBlock.rotation += 0.07 * Math.sign(sandBlock.x - previousBlockX);
sandBlock.x += 20 * Math.sign(sandBlock.x - previousBlockX);
sandBlock.velocity += gravity; // Increase velocity due to gravity
sandBlock.y += sandBlock.velocity; // Update position based on velocity
} else {
var checkBucketUpCompletion = function checkBucketUpCompletion() {
if (!isBucketMovingUp) {
sandBlockDropped = false;
isBucketMovingHorizontally = true; // Resume horizontal movement of the bucket
moveBucketDown(); // Progressively move the bucket down when the camera reaches the target
game.addChild(bucket); // Ensure bucket is above sand blocks
} else {
LK.setTimeout(checkBucketUpCompletion, 1000 / 60); // Check again in the next frame
}
};
// Reached bottom
checkBlockAlignmentAndUpdate(sandBlock);
sandBlock.fallen = true;
sandBlock.destroy();
currentSandBlock = null;
// Ensure bucket has finished moving up before running the next steps
checkBucketUpCompletion();
}
} else {
// Ok : Stop
sandBlocks.push(currentSandBlock);
globalDelta = Math.max(globalDelta, Math.abs(currentSandBlock.x - 1024));
sandBlockMoveStep = Math.floor(globalDelta / 100);
previousBlockX = currentSandBlock.x;
// Move background, sandcastle, and fallen sandblock vertically by sandBlockSize
if (!cameraMoved) {
cameraMoved = true;
isCameraMoving = true; // Set the flag to true when the camera starts moving
LK.setTimeout(function () {
moveCamera();
// Set the flag to true after moving the camera
//bucket.y = 0; // Reset bucket position when the camera moves
}, 640); // Delay of 1 second before moving the camera
// Check if sand block reached the base
checkBlockAlignmentAndUpdate(sandBlock);
}
currentSandBlock = null;
}
}
}
function moveBucketHorizontally() {
if (!level) {
return;
}
if (!lastUpdateTime) {
lastUpdateTime = Date.now();
}
var currentTime = Date.now();
var dt = (currentTime - lastUpdateTime) / 1000; // Delta time in seconds
lastUpdateTime = currentTime;
var moveDistance = bucketMoveStep * bucketDirection * dt * 60; // Adjust for 60 FPS
if (bucket.x >= 2048 - bucket.width / 2) {
bucketDirection = -1; // Change direction to left
} else if (bucket.x <= bucket.width / 2) {
bucketDirection = 1; // Change direction to right
}
log("moveBucketHorizontally: x=" + bucket.x, "moveDistance=" + moveDistance, "dt=" + dt);
bucket.x += moveDistance;
}
function checkBlockAlignmentAndUpdate(sandBlock) {
if (Math.abs(sandBlock.x - previousBlockX) < sandBlockHalfWidth) {
LK.getSound('dropped').play();
// Check for perfect alignment
level += 1;
score += 1; // Bonus points for perfect alignment
LK.setScore(score); // Store the score in LK score
if (level > 0 && level % 3 === 0) {
bucketMoveStep += 4; // Increase bucket speed by 2 units
}
} else {
LK.getSound('missed').play();
lives--;
livesIcons[lives].destroy();
livesIcons.pop();
if (lives <= 0) {
var checkBucketUpCompletionForScore = function checkBucketUpCompletionForScore() {
if (!isBucketMovingUp) {
cleanPlayingState();
initScoreState();
} else {
LK.setTimeout(checkBucketUpCompletionForScore, 1000 / 60); // Check again in the next frame
}
};
checkBucketUpCompletionForScore();
return;
}
}
sandBlock.falling = false; // Reset the falling flag
sandBlockDropped = false; // Reset the flag after handling sand block reaching the base
game.addChild(bucket); // Ensure bucket is above sand blocks
}
function initializeBackgrounds() {
var mainBgY = 1566;
var mainBgHeight = 2732;
//var bgHeight = 1152;
//var bgHalfHeight = 576;
backgroundImage = LK.getAsset('backgroundImage', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: mainBgY
});
game.addChild(backgroundImage);
background2 = LK.getAsset('background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2
});
background2.y = backgroundImage.y - mainBgHeight / 2 - bgHalfHeight;
game.addChild(background2);
background3 = LK.getAsset('background3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2
});
background3.y = background2.y - bgHeight;
background4 = LK.getAsset('background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2
});
background4.y = background3.y - bgHeight;
game.addChild(background4);
background5 = LK.getAsset('background3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2
});
background5.y = background4.y - bgHeight;
;
game.addChild(background5);
game.addChild(background3);
}
function loopBgMusic() {
if (bgMusic && Date.now() - bgMusic.lastPlayTime > 10000) {
bgMusic.lastPlayTime = Date.now();
bgMusic.play();
}
}
/****************************************************************************************** */
/************************************* GAME STATES **************************************** */
/****************************************************************************************** */
function gameInitialize() {
log("Game initialize...");
initializeBackgrounds();
boat = new Boat();
boat.x = boat.width;
boat.y = 1090;
boat.visible = true;
game.addChild(boat);
sandcastle = new Sandcastle();
sandcastle.x = 2048 / 2;
sandcastle.y = -sandcastleHeight; // Hide the sandcastle above the screen initially
game.addChild(sandcastle);
picketLeft = LK.getAsset('picket', {
anchorX: 0.5,
anchorY: 1,
x: 30,
y: 2140
});
game.addChild(picketLeft);
picketRight = LK.getAsset('picket', {
anchorX: 0.5,
anchorY: 1,
scaleX: -1,
x: 2018,
y: 2140
});
game.addChild(picketRight);
banner = LK.getAsset('banner', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 400
});
game.addChild(banner);
game.addChild(sandcastle);
level = 0;
score = 0;
LK.setScore(score); // Initialize LK score to zero at game start
bgMusic = LK.getSound('music');
if (bgMusic && bgMusic.isPlaying) {
bgMusic.stop();
}
bgMusic.lastPlayTime = 0;
startButton = LK.getAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1420 // Center of the screen
});
startButton.visible = false; // Initially hidden
game.addChild(startButton);
beachToys = new BeachToys();
beachToys.x = 2048 / 2;
beachToys.y = 2922;
game.addChild(beachToys);
// Initialize clouds at the beginning but keep them invisible
cloud1 = new Cloud(1);
cloud1.x = -cloud1.width;
cloud1.y = -cloud1.height;
cloud1.visible = false;
game.addChild(cloud1);
cloud2 = new Cloud(2);
cloud2.x = -cloud2.width;
cloud2.y = -cloud2.height;
cloud2.visible = false;
game.addChild(cloud2);
flyingObject1 = new FlyingObject(1);
flyingObject1.x = -Math.random() * 512;
flyingObject1.y = flyingObject1.height + 256 * Math.random();
flyingObject1.visible = false;
flyingObject1.passed = false;
game.addChild(flyingObject1);
flyingObject2 = new FlyingObject(2);
flyingObject2.x = -Math.random() * 512;
flyingObject2.y = flyingObject2.height + 256 * Math.random();
flyingObject2.visible = false;
flyingObject2.passed = false;
game.addChild(flyingObject2);
initMenuState();
if (isDebug) {
debugMarker = LK.getAsset('debugMarker', {
anchorX: 0.5,
anchorY: 0.5,
x: 1300,
y: 160
});
game.addChild(debugMarker);
debugText = new Text2('Debug Info', {
size: 50,
fill: "#ffffff"
});
debugText.anchor.set(0.5, 1); // Anchor to the bottom-right
LK.gui.bottom.addChild(debugText);
}
}
// GAME MENU
function initMenuState() {
log("initMenuState...");
gameState = GAME_STATE.MENU;
}
function handleMenuLoop() {
// Menu animations here
}
function cleanMenuState() {
log("cleanMenuState...");
}
// NEW ROUND
function initNewRoundState() {
log("initNewRoundState...");
gameState = GAME_STATE.NEW_ROUND;
// Fade in the start button
startButton.visible = true;
startButton.alpha = 0;
var fadeInStep = 0.05;
function fadeInStartButton() {
if (startButton.alpha < 1) {
startButton.alpha += fadeInStep;
LK.setTimeout(fadeInStartButton, 1000 / 60); // Call fadeInStartButton every 1/60th of a second
}
}
fadeInStartButton();
// Reset flying objects passed property
if (flyingObject1) {
flyingObject1.passed = false;
}
if (flyingObject2) {
flyingObject2.passed = false;
}
// Round preparation logic here.
}
function handleNewRoundLoop() {
// New Round animations here
loopBgMusic();
}
function cleanNewRoundState() {
if (isCleaningNewRound) {
return;
} // Prevent multiple calls
isCleaningNewRound = true;
log("cleanNewRoundState...");
// Animate the start button removal
var fadeOutStep = 0.05;
function fadeOutStartButton() {
log("fadeOutStartButton...", startButton.alpha, "step=" + fadeOutStep);
if (startButton.alpha > 0 && Date.now() - fadeOutStartButtonStartTime < 1000) {
startButton.alpha -= fadeOutStep;
LK.setTimeout(fadeOutStartButton, 1000 / 60); // Call fadeOutStartButton every 1/60th of a second
} else {
log("fadeOutStartButton End");
startButton.alpha = 0;
var fadeOutBeachToys = function fadeOutBeachToys() {
log("fadeOutBeachToys...");
if (beachToys.alpha > 0) {
beachToys.alpha -= fadeOutStep;
LK.setTimeout(fadeOutBeachToys, 1000 / 60);
} else {
log("fadeOutBeachToys End");
beachToys.visible = false;
beachToys.alpha = 1; // Reset alpha for future use
var animateSandcastleFall = function animateSandcastleFall() {
log("animateSandcastleFall...");
if (sandcastle.y < sandcastleBaseY) {
sandcastle.velocity += gravity; // Increase velocity due to gravity
sandcastle.y += sandcastle.velocity; // Update position based on velocity
LK.setTimeout(animateSandcastleFall, 1000 / 60); // Call animateSandcastleFall every 1/60th of a second
} else {
log("animateSandcastleFall End");
sandcastle.y = sandcastleBaseY; // Ensure it stops exactly at the base
LK.getSound('dropped').play(); // Play drop sound when it reaches its position
initPlayingState();
isCleaningNewRound = false; // Reset the flag after completion
}
};
startButton.visible = false;
startButton.alpha = 1; // Reset alpha for future use
sandcastle.velocity = 0; // Initial velocity for natural falling speed increase
animateSandcastleFall();
}
};
fadeOutBeachToys();
}
}
fadeOutStartButtonStartTime = Date.now(); // Prevent Bug alpha stucked on tap
fadeOutStartButton();
}
// PLAYING
function initPlayingState() {
log("initPlayingState...");
gameState = GAME_STATE.PLAYING;
isPlaying = true;
for (var i = 0; i < lives; i++) {
var lifeIcon = LK.getAsset('bucketIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 1166 + i * 130
});
livesIcons.push(lifeIcon);
game.addChild(lifeIcon);
}
bucket = new Bucket();
bucket.x = 2048 / 2;
bucket.y = -bucket.height;
game.addChild(bucket); // Ensure bucket is above sand blocks
moveBucketDown();
}
function handlePlayingLoop() {
loopBgMusic();
if (isPlaying && currentSandBlock) {
updateSandBlockPosition(currentSandBlock);
//game.addChild(bucket); // Ensure bucket is above sand blocks
}
if (level >= 4) {
for (var i = 0; i < sandBlocks.length; i++) {
var sandBlock = sandBlocks[i];
sandBlock.x += Math.sin(LK.ticks / 20) * sandBlockMoveStep; // Move sand blocks back and forth
}
}
if (bucket && bucket.scoreTxt) {
if (score === 0) {
bucket.scoreTxt.visible = false;
} else {
bucket.scoreTxt.visible = true;
bucket.scoreTxt.setText(score);
}
}
if (isBucketMovingHorizontally) {
moveBucketHorizontally(); // Ensure horizontal movement is restored
}
if (level >= 6 && cloud1 && cloud2 && !cloud1.visible) {
cloud1.visible = true;
cloud2.visible = true;
}
if (level && flyingObject1 && level == 5 && !flyingObject1.visible && !flyingObject1.passed) {
flyingObject1.visible = true;
flyingObject1.passed = false;
}
if (level && flyingObject2 && level % 10 == 0 && !flyingObject2.visible && !flyingObject2.passed) {
flyingObject2.visible = true;
flyingObject2.passed = false;
}
if (isDebug) {
//debugText.setText("X: " + bucket.x + " Y: " + bucket.y);
debugText.setText("Delta: " + globalDelta);
}
}
function cleanPlayingState() {
log("cleanPlayingState...");
isPlaying = false;
// TODO Remove elements
}
// SCORE
function initScoreState() {
log("initScoreState...");
gameState = GAME_STATE.SCORE;
// Play camera sound
LK.getSound('camera').play();
// Prepare final animation
// Add a 1-second white screen flash
LK.effects.flashScreen(0xffffff, 2000);
LK.setTimeout(setupFinalPhoto, 1000);
confetti = new Confetti();
confetti.visible = false;
game.addChild(confetti);
}
function setupFinalPhoto() {
// Set the background size to 2048x2732
backgroundImage.width = 2048;
backgroundImage.height = 2732;
// Set the background position to the center of the screen
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
background2.visible = false;
background3.visible = false;
background4.visible = false;
background5.visible = false;
cloud1.visible = false;
cloud2.visible = false;
var finalScoreTxt = new Text2(score.toString(), {
size: 150,
fill: "#FFFFFF",
weight: 1000,
dropShadow: true
});
finalScoreTxt.anchor.set(0.5, -0.50);
LK.gui.top.addChild(finalScoreTxt);
// Scale the sandcastle and the sandblocks down to fit in the screen
var ratio = 1 / Math.ceil(sandBlocks.length / 3);
sandcastle.width *= ratio;
sandcastle.height *= ratio;
sandcastle.x = 2048 / 2;
sandcastle.y = sandcastleBaseY;
sandcastle.shadow.visible = true;
if (ratio <= 0.5) {
beachToys = new BeachToys();
beachToys.width *= ratio;
beachToys.height *= ratio;
beachToys.x = 1724;
beachToys.y = sandcastleBaseY + 300;
game.addChild(beachToys);
var sandcastle2 = new Sandcastle();
sandcastle2.x = 312;
sandcastle2.y = sandcastleBaseY - 250; // Hide the sandcastle above the screen initially
sandcastle2.width *= 0.8;
sandcastle2.height *= 0.8;
sandcastle2.width *= ratio;
sandcastle2.height *= ratio;
game.addChild(sandcastle2);
sandcastle2.shadow.visible = true;
beachToys2 = new BeachToys(2);
beachToys2.width *= ratio;
beachToys2.height *= ratio;
beachToys2.shadow.visible = false;
beachToys2.x = sandcastle2.x; // sandcastle2.x - sandcastle2.width / 2 - beachToys2.width / 2;
beachToys2.y = sandcastle2.y + sandcastle2.height / 2 + 300 * ratio; //sandcastleBaseY + 350;
game.addChild(beachToys2);
}
banner.width *= ratio;
banner.height *= ratio;
banner.x = 2048 / 2;
banner.y = 2140 - picketLeft.height * ratio;
picketLeft.width *= ratio;
picketLeft.height *= ratio;
picketLeft.x = 1024 - (1024 - 30) * ratio;
picketLeft.y = 2140;
picketRight.width *= ratio;
picketRight.height *= ratio;
picketRight.x = 1024 + (1024 - 30) * ratio;
picketRight.y = 2140;
var baseY = sandcastle.y - sandcastle.height / 2 - sandBlockSize * ratio;
log("baseY ", baseY);
for (var i = 0; i < sandBlocks.length; i++) {
sandBlocks[i].width *= ratio;
sandBlocks[i].height *= ratio;
sandBlocks[i].x = 1024 - (1024 - sandBlocks[i].x) * ratio;
sandBlocks[i].y = baseY - i * sandBlockSize * ratio;
sandBlocks[i].visible = true;
//log("block #", i, " h=" + sandBlockSize * ratio, " / y = ", sandBlocks[i].y);
}
// Show confetti
confetti.visible = true;
boat.y = 1000;
boat.width *= ratio;
boat.height *= ratio;
boat.reverse();
}
function handleScoreLoop() {
// Score display logic here
loopBgMusic();
}
function cleanScoreState() {
log("cleanScoreState...");
LK.showGameOver();
}
/***********************************************************************************/
/******************************** MAIN GAME LOOP ***********************************/
/***********************************************************************************/
game.update = function () {
switch (gameState) {
case GAME_STATE.MENU:
handleMenuLoop();
break;
case GAME_STATE.NEW_ROUND:
handleNewRoundLoop();
break;
case GAME_STATE.PLAYING:
handlePlayingLoop();
break;
case GAME_STATE.SCORE:
handleScoreLoop();
break;
}
};
gameInitialize(); // Initialize the game
/********************************************************************************/
/**************************** GAME DESCRIPTION *********************************/
/******************************************************************************/
/*
**Game Title**: **Sand Tower**
**Description**: Build towering sandcastles, perfect your alignment, and climb to the top! Sand Tower awaits! 🏖️🏰
**Objective**:
- Players aim to construct the tallest sandcastle tower.
- Perfect alignment of sand blocks is crucial for stability and bonus points.
**Visuals**:
- The game features a sandy beach backdrop.
- A basic sandcastle stands in the center, serving as the base.
- The central tower is the focal point.
**Gameplay Mechanics**:
- **Beach Bucket Mechanism**:
- Players use an upside-down beach bucket.
- When the player taps, the bucket tips over, releasing sand.
- **Sand Block Placement**:
- Sand falls vertically from the bucket onto the base.
- The player must time their taps to stack the sand blocks.
- Perfect alignment grants bonus points.
- **Lives System**:
- The player starts with 3 lives (represented by small bucket icons).
- Each time a sand block isn't centered enough and falls, 1 life is lost.
- **Scoring**:
- Points increase with tower height.
- Bonus points for precise alignment.
- **Game Over**:
- The game ends if the player loses all their lives (buckets).
**Audio**:
- Gentle beach sounds (waves, distant chatter).
- Encouraging music during gameplay.
*/
Front close view of a calm sea from the beach. nothing on the beach just flat sand. no sun... photorealistic
Start button. Beach themed
face view of a red beach bucket with a blue handle.. photo
beach toys. photorealistic
beach construction toys. red bucket with blue handle.. photorealistic
an horizontal cloud. photorealistic
an air ballon. photorealistic
simple rectangular white ribon.
a soaring gull. lateral view