/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0;
self.friction = 0.98;
self.isMoving = false;
self.spinForce = 0;
self.spinDecay = 0.98;
self.shoot = function (power, angle, spin) {
self.velocityX = Math.cos(angle) * power;
self.velocityY = Math.sin(angle) * power;
self.spinForce = spin || 0;
self.isMoving = true;
LK.getSound('kick').play();
};
self.reset = function () {
self.x = ballStartX;
self.y = ballStartY;
self.velocityX = 0;
self.velocityY = 0;
self.isMoving = false;
self.spinForce = 0;
};
self.update = function () {
if (self.isMoving) {
// Apply spin curve effect
if (self.spinForce !== 0) {
// Calculate perpendicular force to current velocity for curve
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (speed > 0) {
var normalX = -self.velocityY / speed;
var normalY = self.velocityX / speed;
self.velocityX += normalX * self.spinForce * 0.1;
self.velocityY += normalY * self.spinForce * 0.1;
}
// Decay spin over time
self.spinForce *= self.spinDecay;
}
self.x += self.velocityX;
self.y += self.velocityY;
// No friction applied - ball maintains constant velocity
// Bounds checking
if (self.x < 0 || self.x > 2048 || self.y > 2732) {
self.isMoving = false;
ballMissed();
}
}
};
return self;
});
var Defender = Container.expand(function () {
var self = Container.call(this);
var defenderGraphics = self.attachAsset('defender', {
anchorX: 0.5,
anchorY: 1.0
});
return self;
});
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
return self;
});
var TrajectoryDot = Container.expand(function () {
var self = Container.call(this);
var dotGraphics = self.attachAsset('trajectory', {
anchorX: 0.5,
anchorY: 0.5
});
dotGraphics.alpha = 0.6;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7DD87D
});
/****
* Game Code
****/
// Game variables
var currentRound = 1;
var attemptsLeft = 3;
var ballStartX = 1024;
var ballStartY = 2500;
var goalX = 1024;
var goalY = 400;
var isAiming = false;
var dragStartX = 0;
var dragStartY = 0;
var dragCurrentX = 0;
var dragCurrentY = 0;
var trajectoryDots = [];
var defenders = [];
// Player name and leaderboard variables
var playerName = '';
var nameEntered = false;
var nameInputText = '';
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var currentLetterIndex = 0;
var leaderboardNames = storage.leaderboardNames || [];
var leaderboardScores = storage.leaderboardScores || [];
var showingLeaderboard = false;
// Swipe tracking variables
var swipePath = [];
var swipeStartTime = 0;
var swipeSpeed = 0;
var swipeCurve = 0;
var maxSwipePoints = 10;
// Create grass field
var grass = game.addChild(LK.getAsset('grass', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
// Create goal structure
var goal = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: goalX,
y: goalY + 100
}));
goal.alpha = 0.3; // Make goal semi-transparent so we can see the ball
// Create goalkeeper
var goalkeeper = game.addChild(new Goalkeeper());
goalkeeper.x = goalX;
goalkeeper.y = goalY + 450; // Move goalkeeper in front of the goal
// Store goalkeeper's initial position for reset
var goalkeeperInitialX = goalkeeper.x;
var goalkeeperInitialY = goalkeeper.y;
// Create ball
var ball = game.addChild(new Ball());
ball.reset();
// UI Elements
var roundText = new Text2('Round: 1', {
size: 100,
fill: 0xFFFFFF
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
var attemptsText = new Text2('Attempts: 3', {
size: 75,
fill: 0xFFFFFF
});
attemptsText.anchor.set(1.0, 0);
LK.gui.topRight.addChild(attemptsText);
var personalBest = storage.personalBest || 0;
var personalBestText = new Text2('Best: ' + personalBest, {
size: 75,
fill: 0x00FF00
});
personalBestText.anchor.set(0, 0);
LK.gui.topLeft.addChild(personalBestText);
personalBestText.x = 120; // Move away from platform menu icon
var powerText = new Text2('', {
size: 62,
fill: 0xFFFF00
});
powerText.anchor.set(0.5, 0.5);
game.addChild(powerText);
var curveText = new Text2('Spin: 0', {
size: 75,
fill: 0xFFFFFF
});
curveText.anchor.set(0.5, 0.5);
curveText.x = 1024;
curveText.y = 200;
game.addChild(curveText);
var selectedCurve = 0;
var maxCurve = 20;
var curveValues = [-20, -18, -16, -14, -12, -10, -8, -6, -4, -2, -1, 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
var curveIndex = 11; // Start at 0 curve (index 11 in the new array)
var fixedSpeed = 35;
// Create curve adjustment buttons
var leftCurveButton = new Text2('<', {
size: 125,
fill: 0xFFFF00
});
leftCurveButton.anchor.set(0.5, 0.5);
leftCurveButton.x = 850;
leftCurveButton.y = 200;
game.addChild(leftCurveButton);
var rightCurveButton = new Text2('>', {
size: 125,
fill: 0xFFFF00
});
rightCurveButton.anchor.set(0.5, 0.5);
rightCurveButton.x = 1198;
rightCurveButton.y = 200;
game.addChild(rightCurveButton);
// Function to update curve display
function updateCurveDisplay() {
selectedCurve = curveValues[curveIndex];
if (selectedCurve === 0) {
curveText.setText('Spin: 0');
} else {
curveText.setText('Spin: ' + (selectedCurve > 0 ? '+' : '') + selectedCurve);
}
console.log('Curve updated to:', selectedCurve, 'Index:', curveIndex);
}
// Initialize curve display
updateCurveDisplay();
// Initialize defenders for current round
function setupRound() {
// Clear existing defenders
for (var i = 0; i < defenders.length; i++) {
defenders[i].destroy();
}
defenders = [];
// Add defenders based on round
var numDefenders = Math.min(currentRound - 1, 4);
for (var i = 0; i < numDefenders; i++) {
var defender = game.addChild(new Defender());
if (i < 2) {
// First 2 defenders stay side by side in fixed positions
var spacing = 120;
var startX = goalX - spacing / 2;
defender.x = startX + i * spacing;
defender.y = goalY + 450 + currentRound * 30;
} else {
// Other 2 defenders are placed randomly
var randomX = goalX + (Math.random() - 0.5) * 600; // Random X within a reasonable area
var randomY = goalY + 400 + Math.random() * 200; // Random Y in front of goal
defender.x = randomX;
defender.y = randomY;
}
defenders.push(defender);
}
// Starting from round 10, add 1 random defender in front of the goal
if (currentRound >= 10) {
var randomDefender = game.addChild(new Defender());
// Random position in front of goal area
var randomX = goalX + (Math.random() - 0.5) * 800; // Random X within goal width area
var randomY = goalY + 350 + Math.random() * 150; // Random Y in front of goal
randomDefender.x = randomX;
randomDefender.y = randomY;
defenders.push(randomDefender);
}
roundText.setText('Round: ' + currentRound);
attemptsText.setText('Attempts: ' + attemptsLeft);
}
// Create trajectory preview
function createTrajectoryPreview(power, angle, spin) {
// Clear existing dots
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
// Calculate trajectory points
var steps = 15;
var stepTime = 0.8;
var tempVelX = Math.cos(angle) * power * 0.7;
var tempVelY = Math.sin(angle) * power * 0.7;
var tempX = ball.x;
var tempY = ball.y;
var tempSpin = spin || 0;
for (var i = 0; i < steps; i++) {
// Apply spin curve effect in preview
if (tempSpin !== 0) {
var speed = Math.sqrt(tempVelX * tempVelX + tempVelY * tempVelY);
if (speed > 0) {
var normalX = -tempVelY / speed;
var normalY = tempVelX / speed;
tempVelX += normalX * tempSpin * 0.1;
tempVelY += normalY * tempSpin * 0.1;
}
tempSpin *= 0.98;
}
tempX += tempVelX * stepTime;
tempY += tempVelY * stepTime;
// No gravity applied to trajectory preview
tempVelX *= 0.98;
tempVelY *= 0.98;
if (tempY > 2732 || tempX < 0 || tempX > 2048) break;
var dot = game.addChild(new TrajectoryDot());
dot.x = tempX;
dot.y = tempY;
trajectoryDots.push(dot);
}
}
// Clear trajectory preview
function clearTrajectoryPreview() {
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
}
// Calculate curve from swipe path
function calculateSwipeCurve() {
if (swipePath.length < 3) return 0;
var totalCurvature = 0;
var validSegments = 0;
var curvatureSum = 0;
var pathLength = 0;
// Calculate curvature at each point in the path
for (var i = 1; i < swipePath.length - 1; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
var p3 = swipePath[i + 1];
// Calculate vectors
var v1x = p2.x - p1.x;
var v1y = p2.y - p1.y;
var v2x = p3.x - p2.x;
var v2y = p3.y - p2.y;
// Calculate cross product to determine curve direction
var crossProduct = v1x * v2y - v1y * v2x;
// Calculate magnitudes
var mag1 = Math.sqrt(v1x * v1x + v1y * v1y);
var mag2 = Math.sqrt(v2x * v2x + v2y * v2y);
if (mag1 > 5 && mag2 > 5) {
// Only consider significant movements
curvatureSum += crossProduct / (mag1 * mag2);
validSegments++;
pathLength += mag1;
}
}
if (validSegments > 0) {
totalCurvature = curvatureSum / validSegments;
// Scale based on path length and speed
var lengthFactor = Math.min(pathLength / 200, 2);
var speedFactor = Math.min(swipeSpeed / 500, 2);
var finalCurve = totalCurvature * lengthFactor * speedFactor * 15;
return Math.max(-10, Math.min(10, finalCurve));
}
return 0;
}
// Calculate swipe speed
function calculateSwipeSpeed() {
if (swipePath.length < 2) return 0;
var totalDistance = 0;
var totalTime = 0;
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
totalDistance += Math.sqrt(dx * dx + dy * dy);
totalTime += p2.time - p1.time;
}
return totalTime > 0 ? totalDistance / totalTime * 1000 : 0;
}
// Ball collision detection
function checkCollisions() {
// Check goalkeeper collision first - this prevents scoring
if (ball.intersects(goalkeeper)) {
// Enhanced goalkeeper save logic based on ball position relative to goal
var goalLeft = goalX - 400; // Left side of goal
var goalRight = goalX + 400; // Right side of goal
var goalWidth = 800; // Total goal width
var goalCenter = goalX; // Goal center X position
var distanceFromCenter = Math.abs(ball.x - goalCenter);
var distanceFromGoalEdge = Math.min(Math.abs(ball.x - goalLeft), Math.abs(ball.x - goalRight));
var ballSpeed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY);
// Calculate save chance based on position in goal
var saveChance = 0;
// Define middle zone (center 60% of goal) and corner zones (outer 20% each side)
var middleZoneWidth = goalWidth * 0.6; // 480 pixels from center
var cornerZoneWidth = goalWidth * 0.2; // 160 pixels each corner
if (distanceFromCenter <= middleZoneWidth / 2) {
// Ball is in the middle zone - high save chance
saveChance = 0.95; // 95% save chance in middle
} else if (distanceFromGoalEdge <= cornerZoneWidth) {
// Ball is in corner zone - very low save chance
saveChance = 0.1; // Only 10% save chance in corners
} else {
// Ball is in transition zone between middle and corner
saveChance = 0.4; // Moderate save chance in transition
}
// Adjust save chance based on ball speed (faster balls harder to save)
if (ballSpeed > 30) saveChance -= 0.2;
if (ballSpeed > 40) saveChance -= 0.2;
// Ensure save chance stays within reasonable bounds
saveChance = Math.max(0.05, Math.min(0.98, saveChance));
// Random save attempt - increased base save chances
var adjustedSaveChance = saveChance;
// Boost save chances significantly
if (distanceFromCenter <= middleZoneWidth / 2) {
// Ball is in the middle zone - very high save chance
adjustedSaveChance = 0.85; // 85% save chance in middle
} else if (distanceFromGoalEdge <= cornerZoneWidth) {
// Ball is in corner zone - still some save chance
adjustedSaveChance = 0.25; // 25% save chance in corners
} else {
// Ball is in transition zone
adjustedSaveChance = 0.55; // 55% save chance in transition
}
// Adjust save chance based on ball speed (faster balls harder to save)
if (ballSpeed > 30) adjustedSaveChance -= 0.15;
if (ballSpeed > 40) adjustedSaveChance -= 0.15;
// Ensure save chance stays within reasonable bounds
adjustedSaveChance = Math.max(0.15, Math.min(0.95, adjustedSaveChance));
console.log('Save attempt - Ball X:', ball.x, 'Distance from center:', distanceFromCenter, 'Save chance:', adjustedSaveChance);
if (Math.random() < adjustedSaveChance) {
// Successful save
console.log('GOALKEEPER SAVE!');
ball.isMoving = false;
LK.getSound('save').play();
LK.effects.flashObject(goalkeeper, 0x00ff00, 800); // Green flash for save
LK.effects.flashScreen(0x00ff00, 300); // Screen flash for dramatic effect
// Animate goalkeeper dive with tween - more dramatic
tween(goalkeeper, {
scaleX: 1.5,
scaleY: 0.7,
rotation: ball.x > goalkeeper.x ? 0.3 : -0.3 // Dive towards ball
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(goalkeeper, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 300,
onFinish: function onFinish() {
resetGoalkeeper(); // Reset goalkeeper position after dive animation
}
});
}
});
ballMissed();
return;
} else {
// Goalkeeper fails to save - ball continues
console.log('Goalkeeper missed the save');
LK.effects.flashObject(goalkeeper, 0xff6666, 300);
}
}
// Check goal scoring - ball must be inside goal area including side lines
// Goal asset is positioned at goalY + 100 and has height 584.38, width 800
var goalLeft = goalX - 400; // Left side line of goal (800/2 = 400)
var goalRight = goalX + 400; // Right side line of goal
var goalTop = goalY + 100 - 292; // Top of goal (goalY + 100 - height/2)
var goalBottom = goalY + 100 + 292; // Bottom of goal (goalY + 100 + height/2)
if (ball.x >= goalLeft && ball.x <= goalRight && ball.y >= goalTop && ball.y <= goalBottom) {
// Goal scored! Ball touched inside goal area or side lines
ball.isMoving = false;
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
LK.setScore(LK.getScore() + 1);
// Check and update personal best
var currentScore = LK.getScore();
if (currentScore > personalBest) {
personalBest = currentScore;
storage.personalBest = personalBest;
personalBestText.setText('Best: ' + personalBest);
LK.effects.flashObject(personalBestText, 0xFFFF00, 1000);
}
currentRound++;
// Reset attempts for next round
attemptsLeft = 3;
LK.setTimeout(function () {
ball.reset();
resetGoalkeeper(); // Reset goalkeeper position
setupRound();
}, 1000);
return;
}
// Check defender collisions
for (var i = 0; i < defenders.length; i++) {
if (ball.intersects(defenders[i])) {
ball.isMoving = false;
LK.effects.flashObject(defenders[i], 0xff0000, 500);
ballMissed();
return;
}
}
// Goal structure collision removed to allow ball to pass through for scoring
}
// Function to reset goalkeeper to initial position
function resetGoalkeeper() {
tween(goalkeeper, {
x: goalkeeperInitialX,
y: goalkeeperInitialY
}, {
duration: 800,
easing: tween.easeOut
});
}
// Function to update name display
function updateNameDisplay() {
nameDisplayText.setText(nameInputText);
currentLetterText.setText(alphabet[currentLetterIndex]);
}
// Function to add current letter to name
function addCurrentLetter() {
if (nameInputText.length < 8) {
// Limit name length
nameInputText += alphabet[currentLetterIndex];
updateNameDisplay();
}
}
// Function to finish name entry
function finishNameEntry() {
if (nameInputText.length > 0) {
playerName = nameInputText;
nameEntered = true;
// Hide name input UI
namePromptText.alpha = 0;
nameDisplayText.alpha = 0;
currentLetterText.alpha = 0;
leftNameButton.alpha = 0;
rightNameButton.alpha = 0;
addLetterButton.alpha = 0;
finishNameButton.alpha = 0;
// Show game instructions
instructionsText.alpha = 1;
startButton.alpha = 1;
}
}
// Function to add score to leaderboard
function addToLeaderboard(name, score) {
leaderboardNames.push(name);
leaderboardScores.push(score);
// Create temporary array for sorting
var tempLeaderboard = [];
for (var i = 0; i < leaderboardNames.length; i++) {
tempLeaderboard.push({
name: leaderboardNames[i],
score: leaderboardScores[i]
});
}
// Sort by score descending
tempLeaderboard.sort(function (a, b) {
return b.score - a.score;
});
// Keep only top 10
if (tempLeaderboard.length > 10) {
tempLeaderboard = tempLeaderboard.slice(0, 10);
}
// Update arrays with sorted data
leaderboardNames = [];
leaderboardScores = [];
for (var i = 0; i < tempLeaderboard.length; i++) {
leaderboardNames.push(tempLeaderboard[i].name);
leaderboardScores.push(tempLeaderboard[i].score);
}
// Save to storage as separate arrays
storage.leaderboardNames = leaderboardNames;
storage.leaderboardScores = leaderboardScores;
}
// Function to show leaderboard
function showLeaderboard() {
showingLeaderboard = true;
// Create leaderboard display
var leaderboardText = 'TOP 10 PLAYERS:\n\n';
for (var i = 0; i < Math.min(10, leaderboardNames.length); i++) {
leaderboardText += i + 1 + '. ' + leaderboardNames[i] + ' - ' + leaderboardScores[i] + '\n';
}
if (leaderboardNames.length === 0) {
leaderboardText += 'No scores yet!';
}
var leaderboardDisplay = new Text2(leaderboardText, {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
leaderboardDisplay.anchor.set(0.5, 0.5);
leaderboardDisplay.x = 1024;
leaderboardDisplay.y = 1366;
game.addChild(leaderboardDisplay);
// Auto-hide after 5 seconds
LK.setTimeout(function () {
if (leaderboardDisplay && leaderboardDisplay.parent) {
leaderboardDisplay.destroy();
showingLeaderboard = false;
}
}, 5000);
}
function ballMissed() {
attemptsLeft--;
attemptsText.setText('Attempts: ' + attemptsLeft);
if (attemptsLeft <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
// Add score to leaderboard before game over
if (playerName && LK.getScore() > 0) {
addToLeaderboard(playerName, LK.getScore());
}
LK.setTimeout(function () {
// Show leaderboard before game over
if (leaderboardNames.length > 0) {
showLeaderboard();
LK.setTimeout(function () {
LK.showGameOver(); // Show game over screen and reset game
}, 3000);
} else {
LK.showGameOver(); // Show game over screen and reset game
}
}, 1500);
} else {
LK.setTimeout(function () {
ball.reset();
resetGoalkeeper(); // Reset goalkeeper position
}, 1000);
}
}
// Game controls are now defined as variables above
game.update = function () {
if (ball.isMoving) {
// Goalkeeper AI - move towards ball's predicted position
if (ball.y < goalY + 400) {
// Only react when ball is getting close to goal
var ballTargetX = ball.x + ball.velocityX * 15; // Predict where ball will be (increased prediction)
var keeperSpeed = 15; // Increased goalkeeper movement speed
var maxKeeperX = goalX + 350; // Right boundary for keeper movement
var minKeeperX = goalX - 350; // Left boundary for keeper movement
// Clamp keeper target position within goal area
ballTargetX = Math.max(minKeeperX, Math.min(maxKeeperX, ballTargetX));
// Move keeper towards predicted ball position with tween animation
var deltaX = ballTargetX - goalkeeper.x;
if (Math.abs(deltaX) > 5) {
// Use tween for smooth goalkeeper movement
tween(goalkeeper, {
x: ballTargetX
}, {
duration: 200,
easing: tween.easeOut
});
}
}
checkCollisions();
}
};
// Game instructions
// Name input UI
var namePromptText = new Text2('ENTER YOUR NAME:', {
size: 80,
fill: 0xFFFFFF,
align: 'center'
});
namePromptText.anchor.set(0.5, 0.5);
namePromptText.x = 1024;
namePromptText.y = 1200;
game.addChild(namePromptText);
var nameDisplayText = new Text2('', {
size: 100,
fill: 0x00FF00,
align: 'center'
});
nameDisplayText.anchor.set(0.5, 0.5);
nameDisplayText.x = 1024;
nameDisplayText.y = 1350;
game.addChild(nameDisplayText);
var currentLetterText = new Text2('A', {
size: 120,
fill: 0xFFFF00,
align: 'center'
});
currentLetterText.anchor.set(0.5, 0.5);
currentLetterText.x = 1024;
currentLetterText.y = 1500;
game.addChild(currentLetterText);
var leftNameButton = new Text2('<', {
size: 100,
fill: 0xFFFF00
});
leftNameButton.anchor.set(0.5, 0.5);
leftNameButton.x = 900;
leftNameButton.y = 1500;
game.addChild(leftNameButton);
var rightNameButton = new Text2('>', {
size: 100,
fill: 0xFFFF00
});
rightNameButton.anchor.set(0.5, 0.5);
rightNameButton.x = 1148;
rightNameButton.y = 1500;
game.addChild(rightNameButton);
var addLetterButton = new Text2('ADD LETTER', {
size: 60,
fill: 0x00FF00
});
addLetterButton.anchor.set(0.5, 0.5);
addLetterButton.x = 1024;
addLetterButton.y = 1650;
game.addChild(addLetterButton);
var finishNameButton = new Text2('START GAME', {
size: 80,
fill: 0xFF0000
});
finishNameButton.anchor.set(0.5, 0.5);
finishNameButton.x = 1024;
finishNameButton.y = 1750;
game.addChild(finishNameButton);
var instructionsText = new Text2('Swipe to aim and shoot!\nUse < > buttons to adjust spin.\nSwipe direction sets aim, spin is manual.\nInside of the lines are counted as scores as well.', {
size: 75,
fill: 0xFFFFFF,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1366;
instructionsText.alpha = 0; // Hidden initially
game.addChild(instructionsText);
var startButton = new Text2('TAP TO START', {
size: 100,
fill: 0x00FF00
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1600;
startButton.alpha = 0; // Hidden initially
game.addChild(startButton);
var gameStarted = false;
// Store the actual game control functions
var originalDown = function originalDown(x, y, obj) {
if (!ball.isMoving && ball.y > 2000) {
isAiming = true;
dragStartX = x;
dragStartY = y;
swipePath = [{
x: x,
y: y,
time: Date.now()
}];
swipeStartTime = Date.now();
}
};
var originalMove = function originalMove(x, y, obj) {
if (isAiming && !ball.isMoving) {
dragCurrentX = x;
dragCurrentY = y;
// Track swipe path
var currentTime = Date.now();
swipePath.push({
x: x,
y: y,
time: currentTime
});
// Keep only recent points
if (swipePath.length > maxSwipePoints) {
swipePath.shift();
}
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var power = fixedSpeed;
var angle = Math.atan2(-deltaY, deltaX);
powerText.x = ball.x;
powerText.y = ball.y - 100;
powerText.setText('');
// Show trajectory with current manual curve setting
createTrajectoryPreview(power, angle, selectedCurve);
}
};
var originalUp = function originalUp(x, y, obj) {
if (isAiming && !ball.isMoving) {
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 20) {
var power = fixedSpeed;
var angle = Math.atan2(-deltaY, deltaX);
// Use manually selected curve
var spin = selectedCurve;
ball.shoot(power, angle, spin);
}
isAiming = false;
powerText.setText('');
clearTrajectoryPreview();
// Clear swipe path
swipePath = [];
}
};
// Add click handlers to name input buttons
leftNameButton.down = function (x, y, obj) {
if (!nameEntered) {
if (currentLetterIndex > 0) {
currentLetterIndex--;
} else {
currentLetterIndex = alphabet.length - 1;
}
updateNameDisplay();
LK.effects.flashObject(leftNameButton, 0x00ff00, 200);
}
};
rightNameButton.down = function (x, y, obj) {
if (!nameEntered) {
if (currentLetterIndex < alphabet.length - 1) {
currentLetterIndex++;
} else {
currentLetterIndex = 0;
}
updateNameDisplay();
LK.effects.flashObject(rightNameButton, 0x00ff00, 200);
}
};
addLetterButton.down = function (x, y, obj) {
if (!nameEntered) {
addCurrentLetter();
LK.effects.flashObject(addLetterButton, 0x00ff00, 200);
}
};
finishNameButton.down = function (x, y, obj) {
if (!nameEntered) {
finishNameEntry();
LK.effects.flashObject(finishNameButton, 0x00ff00, 200);
}
};
// Add click handlers to curve buttons
leftCurveButton.down = function (x, y, obj) {
if (gameStarted && !ball.isMoving) {
if (curveIndex > 0) {
curveIndex--;
updateCurveDisplay();
LK.effects.flashObject(leftCurveButton, 0x00ff00, 200);
}
}
};
rightCurveButton.down = function (x, y, obj) {
if (gameStarted && !ball.isMoving) {
if (curveIndex < curveValues.length - 1) {
curveIndex++;
updateCurveDisplay();
LK.effects.flashObject(rightCurveButton, 0x00ff00, 200);
}
}
};
// Initialize name display
updateNameDisplay();
// Override game controls until game starts
game.down = function (x, y, obj) {
if (!nameEntered) {
// Still in name input phase, do nothing (buttons handle their own events)
return;
}
if (!gameStarted) {
// Start the game
gameStarted = true;
instructionsText.destroy();
startButton.destroy();
// Restore original controls
game.down = originalDown;
game.move = originalMove;
game.up = originalUp;
// Initialize first round
setupRound();
// Call the original down handler for this event
originalDown(x, y, obj);
return;
}
};
game.move = function (x, y, obj) {
// Do nothing until game starts
};
game.up = function (x, y, obj) {
// Do nothing until game starts
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0;
self.friction = 0.98;
self.isMoving = false;
self.spinForce = 0;
self.spinDecay = 0.98;
self.shoot = function (power, angle, spin) {
self.velocityX = Math.cos(angle) * power;
self.velocityY = Math.sin(angle) * power;
self.spinForce = spin || 0;
self.isMoving = true;
LK.getSound('kick').play();
};
self.reset = function () {
self.x = ballStartX;
self.y = ballStartY;
self.velocityX = 0;
self.velocityY = 0;
self.isMoving = false;
self.spinForce = 0;
};
self.update = function () {
if (self.isMoving) {
// Apply spin curve effect
if (self.spinForce !== 0) {
// Calculate perpendicular force to current velocity for curve
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (speed > 0) {
var normalX = -self.velocityY / speed;
var normalY = self.velocityX / speed;
self.velocityX += normalX * self.spinForce * 0.1;
self.velocityY += normalY * self.spinForce * 0.1;
}
// Decay spin over time
self.spinForce *= self.spinDecay;
}
self.x += self.velocityX;
self.y += self.velocityY;
// No friction applied - ball maintains constant velocity
// Bounds checking
if (self.x < 0 || self.x > 2048 || self.y > 2732) {
self.isMoving = false;
ballMissed();
}
}
};
return self;
});
var Defender = Container.expand(function () {
var self = Container.call(this);
var defenderGraphics = self.attachAsset('defender', {
anchorX: 0.5,
anchorY: 1.0
});
return self;
});
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
return self;
});
var TrajectoryDot = Container.expand(function () {
var self = Container.call(this);
var dotGraphics = self.attachAsset('trajectory', {
anchorX: 0.5,
anchorY: 0.5
});
dotGraphics.alpha = 0.6;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7DD87D
});
/****
* Game Code
****/
// Game variables
var currentRound = 1;
var attemptsLeft = 3;
var ballStartX = 1024;
var ballStartY = 2500;
var goalX = 1024;
var goalY = 400;
var isAiming = false;
var dragStartX = 0;
var dragStartY = 0;
var dragCurrentX = 0;
var dragCurrentY = 0;
var trajectoryDots = [];
var defenders = [];
// Player name and leaderboard variables
var playerName = '';
var nameEntered = false;
var nameInputText = '';
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var currentLetterIndex = 0;
var leaderboardNames = storage.leaderboardNames || [];
var leaderboardScores = storage.leaderboardScores || [];
var showingLeaderboard = false;
// Swipe tracking variables
var swipePath = [];
var swipeStartTime = 0;
var swipeSpeed = 0;
var swipeCurve = 0;
var maxSwipePoints = 10;
// Create grass field
var grass = game.addChild(LK.getAsset('grass', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
// Create goal structure
var goal = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: goalX,
y: goalY + 100
}));
goal.alpha = 0.3; // Make goal semi-transparent so we can see the ball
// Create goalkeeper
var goalkeeper = game.addChild(new Goalkeeper());
goalkeeper.x = goalX;
goalkeeper.y = goalY + 450; // Move goalkeeper in front of the goal
// Store goalkeeper's initial position for reset
var goalkeeperInitialX = goalkeeper.x;
var goalkeeperInitialY = goalkeeper.y;
// Create ball
var ball = game.addChild(new Ball());
ball.reset();
// UI Elements
var roundText = new Text2('Round: 1', {
size: 100,
fill: 0xFFFFFF
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
var attemptsText = new Text2('Attempts: 3', {
size: 75,
fill: 0xFFFFFF
});
attemptsText.anchor.set(1.0, 0);
LK.gui.topRight.addChild(attemptsText);
var personalBest = storage.personalBest || 0;
var personalBestText = new Text2('Best: ' + personalBest, {
size: 75,
fill: 0x00FF00
});
personalBestText.anchor.set(0, 0);
LK.gui.topLeft.addChild(personalBestText);
personalBestText.x = 120; // Move away from platform menu icon
var powerText = new Text2('', {
size: 62,
fill: 0xFFFF00
});
powerText.anchor.set(0.5, 0.5);
game.addChild(powerText);
var curveText = new Text2('Spin: 0', {
size: 75,
fill: 0xFFFFFF
});
curveText.anchor.set(0.5, 0.5);
curveText.x = 1024;
curveText.y = 200;
game.addChild(curveText);
var selectedCurve = 0;
var maxCurve = 20;
var curveValues = [-20, -18, -16, -14, -12, -10, -8, -6, -4, -2, -1, 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
var curveIndex = 11; // Start at 0 curve (index 11 in the new array)
var fixedSpeed = 35;
// Create curve adjustment buttons
var leftCurveButton = new Text2('<', {
size: 125,
fill: 0xFFFF00
});
leftCurveButton.anchor.set(0.5, 0.5);
leftCurveButton.x = 850;
leftCurveButton.y = 200;
game.addChild(leftCurveButton);
var rightCurveButton = new Text2('>', {
size: 125,
fill: 0xFFFF00
});
rightCurveButton.anchor.set(0.5, 0.5);
rightCurveButton.x = 1198;
rightCurveButton.y = 200;
game.addChild(rightCurveButton);
// Function to update curve display
function updateCurveDisplay() {
selectedCurve = curveValues[curveIndex];
if (selectedCurve === 0) {
curveText.setText('Spin: 0');
} else {
curveText.setText('Spin: ' + (selectedCurve > 0 ? '+' : '') + selectedCurve);
}
console.log('Curve updated to:', selectedCurve, 'Index:', curveIndex);
}
// Initialize curve display
updateCurveDisplay();
// Initialize defenders for current round
function setupRound() {
// Clear existing defenders
for (var i = 0; i < defenders.length; i++) {
defenders[i].destroy();
}
defenders = [];
// Add defenders based on round
var numDefenders = Math.min(currentRound - 1, 4);
for (var i = 0; i < numDefenders; i++) {
var defender = game.addChild(new Defender());
if (i < 2) {
// First 2 defenders stay side by side in fixed positions
var spacing = 120;
var startX = goalX - spacing / 2;
defender.x = startX + i * spacing;
defender.y = goalY + 450 + currentRound * 30;
} else {
// Other 2 defenders are placed randomly
var randomX = goalX + (Math.random() - 0.5) * 600; // Random X within a reasonable area
var randomY = goalY + 400 + Math.random() * 200; // Random Y in front of goal
defender.x = randomX;
defender.y = randomY;
}
defenders.push(defender);
}
// Starting from round 10, add 1 random defender in front of the goal
if (currentRound >= 10) {
var randomDefender = game.addChild(new Defender());
// Random position in front of goal area
var randomX = goalX + (Math.random() - 0.5) * 800; // Random X within goal width area
var randomY = goalY + 350 + Math.random() * 150; // Random Y in front of goal
randomDefender.x = randomX;
randomDefender.y = randomY;
defenders.push(randomDefender);
}
roundText.setText('Round: ' + currentRound);
attemptsText.setText('Attempts: ' + attemptsLeft);
}
// Create trajectory preview
function createTrajectoryPreview(power, angle, spin) {
// Clear existing dots
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
// Calculate trajectory points
var steps = 15;
var stepTime = 0.8;
var tempVelX = Math.cos(angle) * power * 0.7;
var tempVelY = Math.sin(angle) * power * 0.7;
var tempX = ball.x;
var tempY = ball.y;
var tempSpin = spin || 0;
for (var i = 0; i < steps; i++) {
// Apply spin curve effect in preview
if (tempSpin !== 0) {
var speed = Math.sqrt(tempVelX * tempVelX + tempVelY * tempVelY);
if (speed > 0) {
var normalX = -tempVelY / speed;
var normalY = tempVelX / speed;
tempVelX += normalX * tempSpin * 0.1;
tempVelY += normalY * tempSpin * 0.1;
}
tempSpin *= 0.98;
}
tempX += tempVelX * stepTime;
tempY += tempVelY * stepTime;
// No gravity applied to trajectory preview
tempVelX *= 0.98;
tempVelY *= 0.98;
if (tempY > 2732 || tempX < 0 || tempX > 2048) break;
var dot = game.addChild(new TrajectoryDot());
dot.x = tempX;
dot.y = tempY;
trajectoryDots.push(dot);
}
}
// Clear trajectory preview
function clearTrajectoryPreview() {
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].destroy();
}
trajectoryDots = [];
}
// Calculate curve from swipe path
function calculateSwipeCurve() {
if (swipePath.length < 3) return 0;
var totalCurvature = 0;
var validSegments = 0;
var curvatureSum = 0;
var pathLength = 0;
// Calculate curvature at each point in the path
for (var i = 1; i < swipePath.length - 1; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
var p3 = swipePath[i + 1];
// Calculate vectors
var v1x = p2.x - p1.x;
var v1y = p2.y - p1.y;
var v2x = p3.x - p2.x;
var v2y = p3.y - p2.y;
// Calculate cross product to determine curve direction
var crossProduct = v1x * v2y - v1y * v2x;
// Calculate magnitudes
var mag1 = Math.sqrt(v1x * v1x + v1y * v1y);
var mag2 = Math.sqrt(v2x * v2x + v2y * v2y);
if (mag1 > 5 && mag2 > 5) {
// Only consider significant movements
curvatureSum += crossProduct / (mag1 * mag2);
validSegments++;
pathLength += mag1;
}
}
if (validSegments > 0) {
totalCurvature = curvatureSum / validSegments;
// Scale based on path length and speed
var lengthFactor = Math.min(pathLength / 200, 2);
var speedFactor = Math.min(swipeSpeed / 500, 2);
var finalCurve = totalCurvature * lengthFactor * speedFactor * 15;
return Math.max(-10, Math.min(10, finalCurve));
}
return 0;
}
// Calculate swipe speed
function calculateSwipeSpeed() {
if (swipePath.length < 2) return 0;
var totalDistance = 0;
var totalTime = 0;
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
totalDistance += Math.sqrt(dx * dx + dy * dy);
totalTime += p2.time - p1.time;
}
return totalTime > 0 ? totalDistance / totalTime * 1000 : 0;
}
// Ball collision detection
function checkCollisions() {
// Check goalkeeper collision first - this prevents scoring
if (ball.intersects(goalkeeper)) {
// Enhanced goalkeeper save logic based on ball position relative to goal
var goalLeft = goalX - 400; // Left side of goal
var goalRight = goalX + 400; // Right side of goal
var goalWidth = 800; // Total goal width
var goalCenter = goalX; // Goal center X position
var distanceFromCenter = Math.abs(ball.x - goalCenter);
var distanceFromGoalEdge = Math.min(Math.abs(ball.x - goalLeft), Math.abs(ball.x - goalRight));
var ballSpeed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY);
// Calculate save chance based on position in goal
var saveChance = 0;
// Define middle zone (center 60% of goal) and corner zones (outer 20% each side)
var middleZoneWidth = goalWidth * 0.6; // 480 pixels from center
var cornerZoneWidth = goalWidth * 0.2; // 160 pixels each corner
if (distanceFromCenter <= middleZoneWidth / 2) {
// Ball is in the middle zone - high save chance
saveChance = 0.95; // 95% save chance in middle
} else if (distanceFromGoalEdge <= cornerZoneWidth) {
// Ball is in corner zone - very low save chance
saveChance = 0.1; // Only 10% save chance in corners
} else {
// Ball is in transition zone between middle and corner
saveChance = 0.4; // Moderate save chance in transition
}
// Adjust save chance based on ball speed (faster balls harder to save)
if (ballSpeed > 30) saveChance -= 0.2;
if (ballSpeed > 40) saveChance -= 0.2;
// Ensure save chance stays within reasonable bounds
saveChance = Math.max(0.05, Math.min(0.98, saveChance));
// Random save attempt - increased base save chances
var adjustedSaveChance = saveChance;
// Boost save chances significantly
if (distanceFromCenter <= middleZoneWidth / 2) {
// Ball is in the middle zone - very high save chance
adjustedSaveChance = 0.85; // 85% save chance in middle
} else if (distanceFromGoalEdge <= cornerZoneWidth) {
// Ball is in corner zone - still some save chance
adjustedSaveChance = 0.25; // 25% save chance in corners
} else {
// Ball is in transition zone
adjustedSaveChance = 0.55; // 55% save chance in transition
}
// Adjust save chance based on ball speed (faster balls harder to save)
if (ballSpeed > 30) adjustedSaveChance -= 0.15;
if (ballSpeed > 40) adjustedSaveChance -= 0.15;
// Ensure save chance stays within reasonable bounds
adjustedSaveChance = Math.max(0.15, Math.min(0.95, adjustedSaveChance));
console.log('Save attempt - Ball X:', ball.x, 'Distance from center:', distanceFromCenter, 'Save chance:', adjustedSaveChance);
if (Math.random() < adjustedSaveChance) {
// Successful save
console.log('GOALKEEPER SAVE!');
ball.isMoving = false;
LK.getSound('save').play();
LK.effects.flashObject(goalkeeper, 0x00ff00, 800); // Green flash for save
LK.effects.flashScreen(0x00ff00, 300); // Screen flash for dramatic effect
// Animate goalkeeper dive with tween - more dramatic
tween(goalkeeper, {
scaleX: 1.5,
scaleY: 0.7,
rotation: ball.x > goalkeeper.x ? 0.3 : -0.3 // Dive towards ball
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(goalkeeper, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 300,
onFinish: function onFinish() {
resetGoalkeeper(); // Reset goalkeeper position after dive animation
}
});
}
});
ballMissed();
return;
} else {
// Goalkeeper fails to save - ball continues
console.log('Goalkeeper missed the save');
LK.effects.flashObject(goalkeeper, 0xff6666, 300);
}
}
// Check goal scoring - ball must be inside goal area including side lines
// Goal asset is positioned at goalY + 100 and has height 584.38, width 800
var goalLeft = goalX - 400; // Left side line of goal (800/2 = 400)
var goalRight = goalX + 400; // Right side line of goal
var goalTop = goalY + 100 - 292; // Top of goal (goalY + 100 - height/2)
var goalBottom = goalY + 100 + 292; // Bottom of goal (goalY + 100 + height/2)
if (ball.x >= goalLeft && ball.x <= goalRight && ball.y >= goalTop && ball.y <= goalBottom) {
// Goal scored! Ball touched inside goal area or side lines
ball.isMoving = false;
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
LK.setScore(LK.getScore() + 1);
// Check and update personal best
var currentScore = LK.getScore();
if (currentScore > personalBest) {
personalBest = currentScore;
storage.personalBest = personalBest;
personalBestText.setText('Best: ' + personalBest);
LK.effects.flashObject(personalBestText, 0xFFFF00, 1000);
}
currentRound++;
// Reset attempts for next round
attemptsLeft = 3;
LK.setTimeout(function () {
ball.reset();
resetGoalkeeper(); // Reset goalkeeper position
setupRound();
}, 1000);
return;
}
// Check defender collisions
for (var i = 0; i < defenders.length; i++) {
if (ball.intersects(defenders[i])) {
ball.isMoving = false;
LK.effects.flashObject(defenders[i], 0xff0000, 500);
ballMissed();
return;
}
}
// Goal structure collision removed to allow ball to pass through for scoring
}
// Function to reset goalkeeper to initial position
function resetGoalkeeper() {
tween(goalkeeper, {
x: goalkeeperInitialX,
y: goalkeeperInitialY
}, {
duration: 800,
easing: tween.easeOut
});
}
// Function to update name display
function updateNameDisplay() {
nameDisplayText.setText(nameInputText);
currentLetterText.setText(alphabet[currentLetterIndex]);
}
// Function to add current letter to name
function addCurrentLetter() {
if (nameInputText.length < 8) {
// Limit name length
nameInputText += alphabet[currentLetterIndex];
updateNameDisplay();
}
}
// Function to finish name entry
function finishNameEntry() {
if (nameInputText.length > 0) {
playerName = nameInputText;
nameEntered = true;
// Hide name input UI
namePromptText.alpha = 0;
nameDisplayText.alpha = 0;
currentLetterText.alpha = 0;
leftNameButton.alpha = 0;
rightNameButton.alpha = 0;
addLetterButton.alpha = 0;
finishNameButton.alpha = 0;
// Show game instructions
instructionsText.alpha = 1;
startButton.alpha = 1;
}
}
// Function to add score to leaderboard
function addToLeaderboard(name, score) {
leaderboardNames.push(name);
leaderboardScores.push(score);
// Create temporary array for sorting
var tempLeaderboard = [];
for (var i = 0; i < leaderboardNames.length; i++) {
tempLeaderboard.push({
name: leaderboardNames[i],
score: leaderboardScores[i]
});
}
// Sort by score descending
tempLeaderboard.sort(function (a, b) {
return b.score - a.score;
});
// Keep only top 10
if (tempLeaderboard.length > 10) {
tempLeaderboard = tempLeaderboard.slice(0, 10);
}
// Update arrays with sorted data
leaderboardNames = [];
leaderboardScores = [];
for (var i = 0; i < tempLeaderboard.length; i++) {
leaderboardNames.push(tempLeaderboard[i].name);
leaderboardScores.push(tempLeaderboard[i].score);
}
// Save to storage as separate arrays
storage.leaderboardNames = leaderboardNames;
storage.leaderboardScores = leaderboardScores;
}
// Function to show leaderboard
function showLeaderboard() {
showingLeaderboard = true;
// Create leaderboard display
var leaderboardText = 'TOP 10 PLAYERS:\n\n';
for (var i = 0; i < Math.min(10, leaderboardNames.length); i++) {
leaderboardText += i + 1 + '. ' + leaderboardNames[i] + ' - ' + leaderboardScores[i] + '\n';
}
if (leaderboardNames.length === 0) {
leaderboardText += 'No scores yet!';
}
var leaderboardDisplay = new Text2(leaderboardText, {
size: 60,
fill: 0xFFFFFF,
align: 'center'
});
leaderboardDisplay.anchor.set(0.5, 0.5);
leaderboardDisplay.x = 1024;
leaderboardDisplay.y = 1366;
game.addChild(leaderboardDisplay);
// Auto-hide after 5 seconds
LK.setTimeout(function () {
if (leaderboardDisplay && leaderboardDisplay.parent) {
leaderboardDisplay.destroy();
showingLeaderboard = false;
}
}, 5000);
}
function ballMissed() {
attemptsLeft--;
attemptsText.setText('Attempts: ' + attemptsLeft);
if (attemptsLeft <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
// Add score to leaderboard before game over
if (playerName && LK.getScore() > 0) {
addToLeaderboard(playerName, LK.getScore());
}
LK.setTimeout(function () {
// Show leaderboard before game over
if (leaderboardNames.length > 0) {
showLeaderboard();
LK.setTimeout(function () {
LK.showGameOver(); // Show game over screen and reset game
}, 3000);
} else {
LK.showGameOver(); // Show game over screen and reset game
}
}, 1500);
} else {
LK.setTimeout(function () {
ball.reset();
resetGoalkeeper(); // Reset goalkeeper position
}, 1000);
}
}
// Game controls are now defined as variables above
game.update = function () {
if (ball.isMoving) {
// Goalkeeper AI - move towards ball's predicted position
if (ball.y < goalY + 400) {
// Only react when ball is getting close to goal
var ballTargetX = ball.x + ball.velocityX * 15; // Predict where ball will be (increased prediction)
var keeperSpeed = 15; // Increased goalkeeper movement speed
var maxKeeperX = goalX + 350; // Right boundary for keeper movement
var minKeeperX = goalX - 350; // Left boundary for keeper movement
// Clamp keeper target position within goal area
ballTargetX = Math.max(minKeeperX, Math.min(maxKeeperX, ballTargetX));
// Move keeper towards predicted ball position with tween animation
var deltaX = ballTargetX - goalkeeper.x;
if (Math.abs(deltaX) > 5) {
// Use tween for smooth goalkeeper movement
tween(goalkeeper, {
x: ballTargetX
}, {
duration: 200,
easing: tween.easeOut
});
}
}
checkCollisions();
}
};
// Game instructions
// Name input UI
var namePromptText = new Text2('ENTER YOUR NAME:', {
size: 80,
fill: 0xFFFFFF,
align: 'center'
});
namePromptText.anchor.set(0.5, 0.5);
namePromptText.x = 1024;
namePromptText.y = 1200;
game.addChild(namePromptText);
var nameDisplayText = new Text2('', {
size: 100,
fill: 0x00FF00,
align: 'center'
});
nameDisplayText.anchor.set(0.5, 0.5);
nameDisplayText.x = 1024;
nameDisplayText.y = 1350;
game.addChild(nameDisplayText);
var currentLetterText = new Text2('A', {
size: 120,
fill: 0xFFFF00,
align: 'center'
});
currentLetterText.anchor.set(0.5, 0.5);
currentLetterText.x = 1024;
currentLetterText.y = 1500;
game.addChild(currentLetterText);
var leftNameButton = new Text2('<', {
size: 100,
fill: 0xFFFF00
});
leftNameButton.anchor.set(0.5, 0.5);
leftNameButton.x = 900;
leftNameButton.y = 1500;
game.addChild(leftNameButton);
var rightNameButton = new Text2('>', {
size: 100,
fill: 0xFFFF00
});
rightNameButton.anchor.set(0.5, 0.5);
rightNameButton.x = 1148;
rightNameButton.y = 1500;
game.addChild(rightNameButton);
var addLetterButton = new Text2('ADD LETTER', {
size: 60,
fill: 0x00FF00
});
addLetterButton.anchor.set(0.5, 0.5);
addLetterButton.x = 1024;
addLetterButton.y = 1650;
game.addChild(addLetterButton);
var finishNameButton = new Text2('START GAME', {
size: 80,
fill: 0xFF0000
});
finishNameButton.anchor.set(0.5, 0.5);
finishNameButton.x = 1024;
finishNameButton.y = 1750;
game.addChild(finishNameButton);
var instructionsText = new Text2('Swipe to aim and shoot!\nUse < > buttons to adjust spin.\nSwipe direction sets aim, spin is manual.\nInside of the lines are counted as scores as well.', {
size: 75,
fill: 0xFFFFFF,
align: 'center'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1366;
instructionsText.alpha = 0; // Hidden initially
game.addChild(instructionsText);
var startButton = new Text2('TAP TO START', {
size: 100,
fill: 0x00FF00
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1600;
startButton.alpha = 0; // Hidden initially
game.addChild(startButton);
var gameStarted = false;
// Store the actual game control functions
var originalDown = function originalDown(x, y, obj) {
if (!ball.isMoving && ball.y > 2000) {
isAiming = true;
dragStartX = x;
dragStartY = y;
swipePath = [{
x: x,
y: y,
time: Date.now()
}];
swipeStartTime = Date.now();
}
};
var originalMove = function originalMove(x, y, obj) {
if (isAiming && !ball.isMoving) {
dragCurrentX = x;
dragCurrentY = y;
// Track swipe path
var currentTime = Date.now();
swipePath.push({
x: x,
y: y,
time: currentTime
});
// Keep only recent points
if (swipePath.length > maxSwipePoints) {
swipePath.shift();
}
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var power = fixedSpeed;
var angle = Math.atan2(-deltaY, deltaX);
powerText.x = ball.x;
powerText.y = ball.y - 100;
powerText.setText('');
// Show trajectory with current manual curve setting
createTrajectoryPreview(power, angle, selectedCurve);
}
};
var originalUp = function originalUp(x, y, obj) {
if (isAiming && !ball.isMoving) {
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 20) {
var power = fixedSpeed;
var angle = Math.atan2(-deltaY, deltaX);
// Use manually selected curve
var spin = selectedCurve;
ball.shoot(power, angle, spin);
}
isAiming = false;
powerText.setText('');
clearTrajectoryPreview();
// Clear swipe path
swipePath = [];
}
};
// Add click handlers to name input buttons
leftNameButton.down = function (x, y, obj) {
if (!nameEntered) {
if (currentLetterIndex > 0) {
currentLetterIndex--;
} else {
currentLetterIndex = alphabet.length - 1;
}
updateNameDisplay();
LK.effects.flashObject(leftNameButton, 0x00ff00, 200);
}
};
rightNameButton.down = function (x, y, obj) {
if (!nameEntered) {
if (currentLetterIndex < alphabet.length - 1) {
currentLetterIndex++;
} else {
currentLetterIndex = 0;
}
updateNameDisplay();
LK.effects.flashObject(rightNameButton, 0x00ff00, 200);
}
};
addLetterButton.down = function (x, y, obj) {
if (!nameEntered) {
addCurrentLetter();
LK.effects.flashObject(addLetterButton, 0x00ff00, 200);
}
};
finishNameButton.down = function (x, y, obj) {
if (!nameEntered) {
finishNameEntry();
LK.effects.flashObject(finishNameButton, 0x00ff00, 200);
}
};
// Add click handlers to curve buttons
leftCurveButton.down = function (x, y, obj) {
if (gameStarted && !ball.isMoving) {
if (curveIndex > 0) {
curveIndex--;
updateCurveDisplay();
LK.effects.flashObject(leftCurveButton, 0x00ff00, 200);
}
}
};
rightCurveButton.down = function (x, y, obj) {
if (gameStarted && !ball.isMoving) {
if (curveIndex < curveValues.length - 1) {
curveIndex++;
updateCurveDisplay();
LK.effects.flashObject(rightCurveButton, 0x00ff00, 200);
}
}
};
// Initialize name display
updateNameDisplay();
// Override game controls until game starts
game.down = function (x, y, obj) {
if (!nameEntered) {
// Still in name input phase, do nothing (buttons handle their own events)
return;
}
if (!gameStarted) {
// Start the game
gameStarted = true;
instructionsText.destroy();
startButton.destroy();
// Restore original controls
game.down = originalDown;
game.move = originalMove;
game.up = originalUp;
// Initialize first round
setupRound();
// Call the original down handler for this event
originalDown(x, y, obj);
return;
}
};
game.move = function (x, y, obj) {
// Do nothing until game starts
};
game.up = function (x, y, obj) {
// Do nothing until game starts
};