/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class var GolfBall = Container.expand(function () { var self = Container.call(this); var ball = self.attachAsset('golfBall', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ball.width / 2; self.vx = 0; self.vy = 0; self.moving = false; // For drag aiming self.aimArrow = null; // For tracking drag self.isAiming = false; self.aimStartX = 0; self.aimStartY = 0; self.aimEndX = 0; self.aimEndY = 0; // Show aim arrow self.showArrow = function (dx, dy) { if (!self.aimArrow) { self.aimArrow = LK.getAsset('golfArrow', { anchorX: 0.5, anchorY: 1 }); self.addChild(self.aimArrow); } var len = Math.sqrt(dx * dx + dy * dy); // Clamp arrow length for visual var maxLen = 300; var arrowLen = Math.min(len, maxLen); self.aimArrow.height = arrowLen; self.aimArrow.rotation = Math.atan2(dy, dx) + Math.PI / 2; self.aimArrow.visible = true; }; self.hideArrow = function () { if (self.aimArrow) { self.aimArrow.visible = false; } }; // Update ball physics self.update = function () { if (self.moving) { // Move ball self.x += self.vx; self.y += self.vy; // Friction var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); if (speed > 0) { var friction = 0.98; self.vx *= friction; self.vy *= friction; // Stop if very slow if (Math.sqrt(self.vx * self.vx + self.vy * self.vy) < 1.2) { self.vx = 0; self.vy = 0; self.moving = false; } } } }; return self; }); // Hole class var GolfHole = Container.expand(function () { var self = Container.call(this); var hole = self.attachAsset('golfHole', { anchorX: 0.5, anchorY: 0.5 }); self.radius = hole.width / 2; return self; }); // Wall class var GolfWall = Container.expand(function () { var self = Container.call(this); var wall = self.attachAsset('golfWall', { anchorX: 0.5, anchorY: 0.5 }); self.width = wall.width; self.height = wall.height; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x6abf4b // green grass }); /**** * Game Code ****/ // Menu overlay container // --- MENU SYSTEM --- // Ball: white circle // Hole: black circle // Wall: green rectangle // Arrow: yellow rectangle (for aiming indicator) var menuOverlay = new Container(); // Menu background (semi-transparent) var menuBg = LK.getAsset('golfWall', { anchorX: 0.5, anchorY: 0.5, width: 1200, height: 1200 }); menuBg.alpha = 0.92; menuBg.tint = 0x222222; menuOverlay.addChild(menuBg); menuBg.x = 2048 / 2; menuBg.y = 2732 / 2; // Menu title var menuTitle = new Text2('SELECT MODE', { size: 140, fill: 0xFFFFFF }); menuTitle.anchor.set(0.5, 0.5); menuTitle.x = 2048 / 2; menuTitle.y = 2732 / 2 - 400; menuOverlay.addChild(menuTitle); // Menu options var menuOptions = [{ label: "SANDBOX", desc: "NO TIME LIMIT", timer: null, increment: 0 }, { label: "BEGINNER", desc: "START: 30s, +5s/hole", timer: 30, increment: 5 }, { label: "PRO", desc: "START: 10s, +3s/hole", timer: 10, increment: 3 }, { label: "EXPERT", desc: "START: 3s, +2s/hole", timer: 3, increment: 2 }]; var menuButtons = []; var menuDescLabels = []; var menuButtonYStart = 2732 / 2 - 180; var menuButtonSpacing = 220; for (var i = 0; i < menuOptions.length; i++) { // Button var btn = new Text2(menuOptions[i].label, { size: 120, fill: 0xFFFF00 }); btn.anchor.set(0.5, 0.5); btn.x = 2048 / 2; btn.y = menuButtonYStart + i * menuButtonSpacing; btn.interactive = true; btn.buttonMode = true; menuOverlay.addChild(btn); menuButtons.push(btn); // Description // (Removed description text under menu options) } // Add a back button to the menu overlay var menuBackBtn = new Text2('BACK', { size: 90, fill: 0xFF4444 }); menuBackBtn.anchor.set(0.5, 0.5); menuBackBtn.x = 2048 - 200; menuBackBtn.y = 200; menuBackBtn.interactive = true; menuBackBtn.buttonMode = true; menuBackBtn.down = function (x, y, obj) { // Just hide the menu overlay, do not start a game if (menuOverlay.parent) { menuOverlay.parent.removeChild(menuOverlay); } // Optionally, you could reset to a splash screen or do nothing // For now, just show the menu again (if needed, you can add more logic here) game.addChild(menuOverlay); }; menuOverlay.addChild(menuBackBtn); // Add menu overlay to game game.addChild(menuOverlay); // Hide back button when menu is shown var backBtn = null; menuOverlay.onAdded = function () { if (backBtn) backBtn.visible = false; }; // Hide game elements until menu is dismissed var margin = 120; var courseWidth = 2048 - margin * 2; var courseHeight = 2732 - margin * 2; var walls = []; var topWall, bottomWall, leftWall, rightWall; var hole, ball; var strokes = 0; var score = 0; var scoreLabel, scoreTxt, timerDuration, timerValue, timerInterval, timerTxt; var timeIncrement = 0; var sandboxMode = false; // Helper to show/hide game elements function setGameElementsVisible(visible) { if (topWall) { topWall.visible = visible; } if (bottomWall) { bottomWall.visible = visible; } if (leftWall) { leftWall.visible = visible; } if (rightWall) { rightWall.visible = visible; } if (hole) { hole.visible = visible; } if (ball) { ball.visible = visible; } if (scoreLabel) { scoreLabel.visible = visible; } if (scoreTxt) { scoreTxt.visible = visible; } if (timerTxt) { timerTxt.visible = visible; } if (typeof backBtn !== "undefined" && backBtn !== null) { backBtn.visible = visible; } } // Remove menu and start game with selected mode function startGameWithMode(modeIdx) { // Create back button if not already created if (typeof backBtn === "undefined" || backBtn === null) { backBtn = new Text2('BACK', { size: 90, fill: 0xFF4444 }); backBtn.anchor.set(0.5, 0.5); backBtn.x = 2048 - 200; backBtn.y = 200; backBtn.interactive = true; backBtn.buttonMode = true; backBtn.visible = false; backBtn.down = function (x, y, obj) { // Remove all game elements setGameElementsVisible(false); // Remove walls for (var i = 0; i < walls.length; i++) { if (walls[i] && walls[i].parent) { walls[i].parent.removeChild(walls[i]); } } walls = []; // Remove hole and ball if (hole && hole.parent) { hole.parent.removeChild(hole); } if (ball && ball.parent) { ball.parent.removeChild(ball); } hole = null; ball = null; // Remove score and timer if (scoreLabel && scoreLabel.parent) { scoreLabel.parent.removeChild(scoreLabel); } if (scoreTxt && scoreTxt.parent) { scoreTxt.parent.removeChild(scoreTxt); } if (timerTxt && timerTxt.parent) { timerTxt.parent.removeChild(timerTxt); } // Remove back button if (backBtn && backBtn.parent) { backBtn.parent.removeChild(backBtn); } // Stop timer if running if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } // Show menu overlay again if (!menuOverlay.parent) { game.addChild(menuOverlay); } setGameElementsVisible(false); }; // Always add backBtn after all walls and game elements so it's on top game.addChild(backBtn); if (backBtn.parent) { backBtn.parent.removeChild(backBtn); game.addChild(backBtn); } } backBtn.visible = true; if (backBtn.parent) { backBtn.parent.removeChild(backBtn); game.addChild(backBtn); } // Remove menu overlay if (menuOverlay.parent) { menuOverlay.parent.removeChild(menuOverlay); } // Set timer and increment var opt = menuOptions[modeIdx]; sandboxMode = opt.timer === null; timerDuration = opt.timer === null ? 0 : opt.timer; timeIncrement = opt.increment; // Create walls topWall = new GolfWall(); topWall.width = courseWidth; topWall.height = 600; topWall.x = 2048 / 2; topWall.y = margin - 200; topWall.children[0].width = courseWidth; topWall.children[0].height = 120; walls.push(topWall); game.addChild(topWall); bottomWall = new GolfWall(); bottomWall.width = courseWidth; bottomWall.height = 600; bottomWall.x = 2048 / 2; bottomWall.y = 2732 - margin + 200; bottomWall.children[0].width = courseWidth; bottomWall.children[0].height = 120; walls.push(bottomWall); game.addChild(bottomWall); leftWall = new GolfWall(); leftWall.width = 350; leftWall.height = courseHeight; leftWall.x = margin - 75; leftWall.y = 2732 / 2; leftWall.children[0].width = 120; leftWall.children[0].height = courseHeight; walls.push(leftWall); game.addChild(leftWall); rightWall = new GolfWall(); rightWall.width = 350; rightWall.height = courseHeight; rightWall.x = 2048 - margin + 75; rightWall.y = 2732 / 2; rightWall.children[0].width = 120; rightWall.children[0].height = courseHeight; walls.push(rightWall); game.addChild(rightWall); // Create hole hole = new GolfHole(); hole.x = 2048 - margin - 200; hole.y = 2732 - margin - 200; game.addChild(hole); // Create ball ball = new GolfBall(); ball.x = margin + 200; ball.y = 2732 - margin - 400; game.addChild(ball); // Strokes and score strokes = 0; score = 0; // Score label (number of holes) at the center of the screen scoreLabel = new Text2('Score: ' + score, { size: 100, fill: 0xFFFFFF }); scoreLabel.anchor.set(0.5, 0.5); game.addChild(scoreLabel); scoreLabel.x = 2048 / 2; scoreLabel.y = 2732 / 2 - 1100; // Score text (Strokes) at the top center scoreTxt = new Text2('Strokes: ' + strokes, { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.x = 2048 / 2; scoreTxt.y = 0; // Move score text even higher (at the very top) // Timer variables timerValue = timerDuration; timerInterval = null; // Timer text timerTxt = new Text2('Time: ' + (sandboxMode ? '∞' : timerValue), { size: 100, fill: 0xFFFFFF }); timerTxt.anchor.set(0.5, 0.5); game.addChild(timerTxt); timerTxt.x = 2048 / 2; timerTxt.y = 2732 / 2; // Remove the pause button from the GUI overlay if it exists if (LK.gui.pause) { LK.gui.pause.visible = false; if (LK.gui.pause.parent) { LK.gui.pause.parent.removeChild(LK.gui.pause); } } // Start timer if not sandbox if (!sandboxMode) { startTimer(); } else { timerTxt.setText('Time: ∞'); } setGameElementsVisible(true); } // Attach menu button handlers for (var i = 0; i < menuButtons.length; i++) { (function (idx) { menuButtons[idx].down = function (x, y, obj) { setGameElementsVisible(false); startGameWithMode(idx); }; })(i); } // Hide game elements until menu is dismissed setGameElementsVisible(false); // --- END MENU SYSTEM --- // Function to start/reset timer function startTimer() { timerValue = timerDuration; timerTxt.setText('Time: ' + timerValue); if (timerInterval) { LK.clearInterval(timerInterval); } timerInterval = LK.setInterval(function () { timerValue -= 1; if (timerValue < 0) { timerValue = 0; } timerTxt.setText('Time: ' + timerValue); if (timerValue <= 0) { LK.clearInterval(timerInterval); LK.showGameOver(); } }, 1000); } // Drag/aim state var isDragging = false; var dragStartX = 0; var dragStartY = 0; // Helper: clamp ball inside course function clampBallPosition() { // No clamping to invisible walls; allow ball to go to the very edge of the course. } // Helper: ball-wall collision function handleWallCollisions() { for (var i = 0; i < walls.length; i++) { var w = walls[i]; // Axis-aligned rectangle collision var wx = w.x; var wy = w.y; var ww = w.width; var wh = w.height; var bx = ball.x; var by = ball.y; var r = ball.radius; // Rectangle bounds var left = wx - ww / 2; var right = wx + ww / 2; var top = wy - wh / 2; var bottom = wy + wh / 2; // Find closest point on wall to ball var closestX = Math.max(left, Math.min(bx, right)); var closestY = Math.max(top, Math.min(by, bottom)); var dx = bx - closestX; var dy = by - closestY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < r) { // Ball is colliding with wall // Push ball out var overlap = r - dist + 1; if (dist === 0) { // Prevent NaN dx = 1; dy = 0; dist = 1; } // Move ball out of wall ball.x += dx / dist * overlap; ball.y += dy / dist * overlap; // Calculate the normal vector var nx = dx / dist; var ny = dy / dist; // Calculate dot product of velocity and normal var dot = ball.vx * nx + ball.vy * ny; // Reflect velocity using the normal (like center wall) ball.vx = ball.vx - 2 * dot * nx; ball.vy = ball.vy - 2 * dot * ny; // Dampen speed ball.vx *= 0.7; ball.vy *= 0.7; } } } // Helper: ball-in-hole detection function checkHole() { var dx = ball.x - hole.x; var dy = ball.y - hole.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < hole.radius - ball.radius / 2) { // Ball in hole! Instantly hide ball and the hole ball.moving = false; ball.vx = 0; ball.vy = 0; ball.visible = false; hole.visible = false; return true; } return false; } // Helper: reset ball after win (not needed, handled by LK.showYouWin) // Game move handler (for drag aiming) function handleMove(x, y, obj) { if (isDragging && !ball.moving) { // Clamp drag to max length var dx = x - dragStartX; var dy = y - dragStartY; var maxDrag = 400; var dragLen = Math.sqrt(dx * dx + dy * dy); if (dragLen > maxDrag) { var scale = maxDrag / dragLen; dx *= scale; dy *= scale; } // Show aim arrow ball.showArrow(-dx, -dy); } } // Game down handler (start drag) game.down = function (x, y, obj) { // Only allow aiming if ball exists, is not moving, and touch is near ball if (typeof ball !== "undefined" && ball !== null && !ball.moving) { var dx = x - ball.x; var dy = y - ball.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < ball.radius + 40) { isDragging = true; dragStartX = ball.x; dragStartY = ball.y; ball.aimStartX = x; ball.aimStartY = y; ball.showArrow(0, 0); } } }; // Game move handler game.move = function (x, y, obj) { if (isDragging && !ball.moving) { // Update aim arrow var dx = x - dragStartX; var dy = y - dragStartY; var maxDrag = 400; var dragLen = Math.sqrt(dx * dx + dy * dy); if (dragLen > maxDrag) { var scale = maxDrag / dragLen; dx *= scale; dy *= scale; } ball.showArrow(-dx, -dy); } }; // Game up handler (release drag, shoot) game.up = function (x, y, obj) { if (isDragging && !ball.moving) { var dx = x - dragStartX; var dy = y - dragStartY; // Power proportional to drag length var power = Math.sqrt(dx * dx + dy * dy); var maxPower = 38; var minPower = 10; var shotPower = Math.min(power / 10, maxPower); if (shotPower < minPower / 2) { // Too short, ignore ball.hideArrow(); isDragging = false; return; } // Set velocity (opposite direction of drag) var angle = Math.atan2(dy, dx); ball.vx = -Math.cos(angle) * shotPower; ball.vy = -Math.sin(angle) * shotPower; ball.moving = true; ball.hideArrow(); isDragging = false; // Increment strokes strokes += 1; scoreTxt.setText('Strokes: ' + strokes); } }; // Main game update game.update = function () { // Ball physics if (typeof ball !== "undefined" && ball !== null && typeof ball.update === "function") { ball.update(); } // Clamp ball inside course clampBallPosition(); // Ball-wall collisions handleWallCollisions(); // Ball-in-hole if (typeof hole !== "undefined" && hole !== null && hole.visible && ball && ball.visible) { var dx = ball.x - hole.x; var dy = ball.y - hole.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < hole.radius + ball.radius / 2) { // Ball touched the hole! Instantly hide the hole, but do not dequeue or hide the ball if (hole.parent) { hole.parent.removeChild(hole); } hole.visible = false; // Increment score and update label score += 1; scoreLabel.setText('Score: ' + score); // Add time increment if not sandbox if (!sandboxMode) { timerValue = Math.min(timerValue + timeIncrement, 99); timerTxt.setText('Time: ' + timerValue); } else { timerTxt.setText('Time: ∞'); } // Do not stop the ball when it touches the hole; let it keep moving // Create a new hole at a random position (not too close to the ball or walls) var newHole = new GolfHole(); var safeMargin = margin + 200; var minDistFromBall = 400; var placed = false; for (var tries = 0; tries < 20 && !placed; tries++) { var hx = Math.random() * (2048 - 2 * safeMargin) + safeMargin; var hy = Math.random() * (2732 - 2 * safeMargin) + safeMargin; var bdx = hx - ball.x; var bdy = hy - ball.y; var bdist = Math.sqrt(bdx * bdx + bdy * bdy); if (bdist > minDistFromBall) { newHole.x = hx; newHole.y = hy; placed = true; } } if (!placed) { // fallback: just put it somewhere safe newHole.x = 2048 / 2; newHole.y = 2732 / 2; } game.addChild(newHole); hole = newHole; // No timer reset here, just add time increment above } } };
===================================================================
--- original.js
+++ change.js
@@ -389,9 +389,9 @@
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = 2048 / 2;
- scoreTxt.y = 10; // Move score text even higher
+ scoreTxt.y = 0; // Move score text even higher (at the very top)
// Timer variables
timerValue = timerDuration;
timerInterval = null;
// Timer text