/****
* 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 = 150;
// 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 = -80; // Move score text even higher (above the very top)
// Timer variables
timerValue = timerDuration;
timerInterval = null;
// Timer text
timerTxt = new Text2(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('∞');
}
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(timerValue);
if (timerInterval) {
LK.clearInterval(timerInterval);
}
timerInterval = LK.setInterval(function () {
timerValue -= 1;
if (timerValue < 0) {
timerValue = 0;
}
timerTxt.setText(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(timerValue);
} else {
timerTxt.setText('∞');
}
// 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
@@ -382,9 +382,9 @@
});
scoreLabel.anchor.set(0.5, 0.5);
game.addChild(scoreLabel);
scoreLabel.x = 2048 / 2;
- scoreLabel.y = 100;
+ scoreLabel.y = 150;
// Score text (Strokes) at the top center
scoreTxt = new Text2('Strokes: ' + strokes, {
size: 100,
fill: 0xFFFFFF
@@ -396,9 +396,9 @@
// Timer variables
timerValue = timerDuration;
timerInterval = null;
// Timer text
- timerTxt = new Text2('Time: ' + (sandboxMode ? '∞' : timerValue), {
+ timerTxt = new Text2(sandboxMode ? '∞' : timerValue, {
size: 100,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0.5);
@@ -415,9 +415,9 @@
// Start timer if not sandbox
if (!sandboxMode) {
startTimer();
} else {
- timerTxt.setText('Time: ∞');
+ timerTxt.setText('∞');
}
setGameElementsVisible(true);
}
// Attach menu button handlers
@@ -434,18 +434,18 @@
// --- END MENU SYSTEM ---
// Function to start/reset timer
function startTimer() {
timerValue = timerDuration;
- timerTxt.setText('Time: ' + timerValue);
+ timerTxt.setText(timerValue);
if (timerInterval) {
LK.clearInterval(timerInterval);
}
timerInterval = LK.setInterval(function () {
timerValue -= 1;
if (timerValue < 0) {
timerValue = 0;
}
- timerTxt.setText('Time: ' + timerValue);
+ timerTxt.setText(timerValue);
if (timerValue <= 0) {
LK.clearInterval(timerInterval);
LK.showGameOver();
}
@@ -630,11 +630,11 @@
scoreLabel.setText('Score: ' + score);
// Add time increment if not sandbox
if (!sandboxMode) {
timerValue = Math.min(timerValue + timeIncrement, 99);
- timerTxt.setText('Time: ' + timerValue);
+ timerTxt.setText(timerValue);
} else {
- timerTxt.setText('Time: ∞');
+ timerTxt.setText('∞');
}
// 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();