/****
* Plugins
****/
var tween = LK.import("@upit/tween.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.5;
self.isFlying = false;
self.startX = 0;
self.startY = 0;
self.reset = function () {
self.x = 1024;
self.y = 2500;
self.velocityX = 0;
self.velocityY = 0;
self.isFlying = false;
self.startX = self.x;
self.startY = self.y;
self.lastY = self.y;
};
self.flick = function (targetX, targetY, power) {
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
self.velocityX = deltaX / distance * power * 0.1;
self.velocityY = deltaY / distance * power * 0.1;
// Apply wind effect
self.velocityX += windStrength * 0.01;
self.isFlying = true;
LK.getSound('kick').play();
};
self.update = function () {
if (self.isFlying) {
self.lastY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate rotation based on velocity for spinning effect
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
ballGraphics.rotation += speed * 0.02;
// Apply air resistance
self.velocityX *= 0.998;
self.velocityY *= 0.998;
// Ground collision
if (self.y >= 2500) {
self.y = 2500;
self.velocityY = -self.velocityY * 0.6;
self.velocityX *= 0.8;
if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 1) {
self.isFlying = false;
}
}
// Side boundaries
if (self.x <= 30 || self.x >= 2018) {
self.velocityX = -self.velocityX * 0.7;
self.x = Math.max(30, Math.min(2018, self.x));
}
// Top boundary
if (self.y <= 30) {
self.y = 30;
self.velocityY = Math.abs(self.velocityY);
}
}
};
return self;
});
var Goal = Container.expand(function () {
var self = Container.call(this);
var crossbar = self.attachAsset('crossbar', {
anchorX: 0.5,
anchorY: 0.5
});
self.goalWidth = 200;
self.goalHeight = 200;
self.setup = function (x, y) {
self.x = x;
self.y = y;
crossbar.x = 0;
crossbar.y = -self.goalHeight;
};
self.checkGoal = function (ballX, ballY, ballLastY) {
var leftPostX = self.x - self.goalWidth / 2;
var rightPostX = self.x + self.goalWidth / 2;
var crossbarY = self.y - self.goalHeight;
// Check if ball is within goal area horizontally
var inGoalAreaHorizontally = ballX > leftPostX && ballX < rightPostX;
// Check if ball is currently above crossbar (inside goal vertically)
var aboveCrossbar = ballY < crossbarY;
// Check if ball entered from below the crossbar (was below crossbar and now above it)
var enteredFromBelow = ballLastY >= crossbarY && ballY < crossbarY;
return inGoalAreaHorizontally && aboveCrossbar && enteredFromBelow;
};
return self;
});
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
self.moveSpeed = 2;
self.direction = 1;
self.goalX = 0;
self.goalWidth = 200;
self.setup = function (goalX, goalWidth) {
self.goalX = goalX;
self.goalWidth = goalWidth;
self.x = goalX;
self.y = 800; // Position at ground level in front of goal
};
self.update = function () {
// Move horizontally within goal area
self.x += self.moveSpeed * self.direction;
// Bounce between goal posts
var leftBound = self.goalX - self.goalWidth / 2;
var rightBound = self.goalX + self.goalWidth / 2;
if (self.x <= leftBound || self.x >= rightBound) {
self.direction *= -1;
// Use tween for smooth direction change
tween(self, {
scaleX: self.direction
}, {
duration: 200,
easing: tween.easeOut
});
}
};
return self;
});
var WindIndicator = Container.expand(function () {
var self = Container.call(this);
var arrow = self.attachAsset('windArrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
arrow.rotation = windDirection;
arrow.scaleX = Math.abs(windStrength) * 0.5 + 0.5;
arrow.alpha = Math.abs(windStrength) * 0.3 + 0.7;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a90e2
});
/****
* Game Code
****/
var ball = new Ball();
var goal = new Goal();
var goalkeeper = new Goalkeeper();
var windIndicator = new WindIndicator();
// Game state variables
var gameState = 'aiming'; // 'aiming', 'flying', 'resetting'
var shots = 0;
var maxShots = 10;
var windStrength = 0;
var windDirection = 0;
var difficulty = 1;
var goalMoveSpeed = 0;
var goalDirection = 1;
// Hold-to-power variables
var isHolding = false;
var holdStartTime = 0;
var holdTargetX = 0;
var holdTargetY = 0;
var maxHoldTime = 2000; // 2 seconds for maximum power
// Double tap variables
var lastTapTime = 0;
var doubleTapDelay = 300; // 300ms window for double tap
// Initialize ground
var ground = game.attachAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2632
});
// Initialize game objects
ball.reset();
goal.setup(1024, 800);
goalkeeper.setup(1024, 200);
windIndicator.x = 200;
windIndicator.y = 200;
game.addChild(ball);
game.addChild(goal);
game.addChild(goalkeeper);
game.addChild(windIndicator);
// Goals display
var goalsScoredCount = 0;
var scoreTxt = new Text2('Goals: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Shots remaining display
var shotsTxt = new Text2('Shots: 10', {
size: 60,
fill: 0xFFFFFF
});
shotsTxt.anchor.set(1, 0);
shotsTxt.x = -50;
shotsTxt.y = 100;
LK.gui.topRight.addChild(shotsTxt);
// Wind display
var windTxt = new Text2('Wind: None', {
size: 50,
fill: 0xFFFFFF
});
windTxt.anchor.set(0, 0);
windTxt.x = 50;
windTxt.y = 100;
LK.gui.topLeft.addChild(windTxt);
// Power display
var powerTxt = new Text2('Power: 0%', {
size: 60,
fill: 0xFFFF00
});
powerTxt.anchor.set(0.5, 0);
powerTxt.x = 0;
powerTxt.y = 150;
LK.gui.top.addChild(powerTxt);
function updateWind() {
windDirection = (Math.random() - 0.5) * Math.PI;
windStrength = (Math.random() - 0.5) * 2;
var windText = 'Wind: ';
if (Math.abs(windStrength) < 0.3) {
windText += 'Light';
} else if (Math.abs(windStrength) < 0.7) {
windText += 'Moderate';
} else {
windText += 'Strong';
}
if (windStrength > 0) {
windText += ' →';
} else {
windText += ' ←';
}
windTxt.setText(windText);
}
function resetRound() {
ball.reset();
gameState = 'aiming';
shots++;
// Update shots display
shotsTxt.setText('Shots: ' + (maxShots - shots));
// Check game over
if (shots >= maxShots) {
LK.showGameOver();
return;
}
// Increase difficulty
difficulty = Math.floor(shots / 3) + 1;
goalMoveSpeed = Math.min(difficulty * 0.5, 3);
// Update wind
updateWind();
// Move goal to new position
var newGoalX = 500 + Math.random() * 1048;
var newGoalY = 300 + Math.random() * 500;
goal.setup(newGoalX, newGoalY);
goalkeeper.setup(newGoalX, goal.goalWidth);
}
function calculatePower(startX, startY, endX, endY) {
var deltaX = endX - startX;
var deltaY = endY - startY;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
// Initialize first round
updateWind();
game.down = function (x, y, obj) {
var currentTime = Date.now();
// Check for double tap
if (currentTime - lastTapTime < doubleTapDelay) {
// Double tap detected - skip to next shot
if (gameState === 'flying' || gameState === 'aiming') {
resetRound();
return;
}
}
lastTapTime = currentTime;
if (gameState === 'aiming' && !ball.isFlying) {
// Start holding for power calculation
isHolding = true;
holdStartTime = Date.now();
holdTargetX = x;
holdTargetY = y;
}
};
game.up = function (x, y, obj) {
if (gameState === 'aiming' && !ball.isFlying && isHolding) {
// Calculate hold time and convert to power
var holdTime = Date.now() - holdStartTime;
var power = Math.min(holdTime / maxHoldTime * 400, 400); // Scale to max 400 power
power = Math.max(power, 50); // Ensure minimum power
// Shoot toward the target point
ball.flick(holdTargetX, holdTargetY, power);
gameState = 'flying';
isHolding = false;
}
};
game.update = function () {
// Update wind indicator
windIndicator.update();
// Update goalkeeper movement
goalkeeper.update();
// Update power display during hold
if (isHolding) {
var holdTime = Date.now() - holdStartTime;
var powerPercent = Math.min(holdTime / maxHoldTime * 100, 100);
powerTxt.setText('Power: ' + Math.floor(powerPercent) + '%');
powerTxt.visible = true;
} else {
powerTxt.visible = false;
}
// Move goal based on difficulty
if (difficulty > 2) {
goal.x += goalMoveSpeed * goalDirection;
if (goal.x <= 300 || goal.x >= 1748) {
goalDirection *= -1;
}
}
// Check for collision with goalkeeper
if (ball.isFlying && ball.intersects(goalkeeper)) {
// Calculate reflection angle based on where ball hits goalkeeper
var relativeHitX = ball.x - goalkeeper.x;
var reflectionAngle = relativeHitX * 0.02; // Increased reflection sensitivity
// Reflect velocity with energy loss - ensure ball goes back towards player
ball.velocityX = -Math.abs(ball.velocityX) * 0.9 + reflectionAngle; // Force negative X (back towards player)
ball.velocityY = -Math.abs(ball.velocityY) * 0.8; // Always bounce upward with more energy
// Add goalkeeper movement influence
ball.velocityX += goalkeeper.direction * goalkeeper.moveSpeed * 0.5;
// Add some spin effect
tween(ball, {
rotation: ball.rotation + Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut
});
// Move ball away from goalkeeper to prevent multiple collisions
ball.x = goalkeeper.x - 60; // Position ball in front of goalkeeper
ball.y -= 10; // Lift ball slightly
}
// Check for goal
if (ball.isFlying && goal.checkGoal(ball.x, ball.y, ball.lastY)) {
goalsScoredCount++;
scoreTxt.setText('Goals: ' + goalsScoredCount);
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
resetRound();
}
// Check if ball has stopped and missed
if (gameState === 'flying' && !ball.isFlying) {
resetRound();
}
// Victory condition
if (goalsScoredCount >= 5) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.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.5;
self.isFlying = false;
self.startX = 0;
self.startY = 0;
self.reset = function () {
self.x = 1024;
self.y = 2500;
self.velocityX = 0;
self.velocityY = 0;
self.isFlying = false;
self.startX = self.x;
self.startY = self.y;
self.lastY = self.y;
};
self.flick = function (targetX, targetY, power) {
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
self.velocityX = deltaX / distance * power * 0.1;
self.velocityY = deltaY / distance * power * 0.1;
// Apply wind effect
self.velocityX += windStrength * 0.01;
self.isFlying = true;
LK.getSound('kick').play();
};
self.update = function () {
if (self.isFlying) {
self.lastY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate rotation based on velocity for spinning effect
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
ballGraphics.rotation += speed * 0.02;
// Apply air resistance
self.velocityX *= 0.998;
self.velocityY *= 0.998;
// Ground collision
if (self.y >= 2500) {
self.y = 2500;
self.velocityY = -self.velocityY * 0.6;
self.velocityX *= 0.8;
if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 1) {
self.isFlying = false;
}
}
// Side boundaries
if (self.x <= 30 || self.x >= 2018) {
self.velocityX = -self.velocityX * 0.7;
self.x = Math.max(30, Math.min(2018, self.x));
}
// Top boundary
if (self.y <= 30) {
self.y = 30;
self.velocityY = Math.abs(self.velocityY);
}
}
};
return self;
});
var Goal = Container.expand(function () {
var self = Container.call(this);
var crossbar = self.attachAsset('crossbar', {
anchorX: 0.5,
anchorY: 0.5
});
self.goalWidth = 200;
self.goalHeight = 200;
self.setup = function (x, y) {
self.x = x;
self.y = y;
crossbar.x = 0;
crossbar.y = -self.goalHeight;
};
self.checkGoal = function (ballX, ballY, ballLastY) {
var leftPostX = self.x - self.goalWidth / 2;
var rightPostX = self.x + self.goalWidth / 2;
var crossbarY = self.y - self.goalHeight;
// Check if ball is within goal area horizontally
var inGoalAreaHorizontally = ballX > leftPostX && ballX < rightPostX;
// Check if ball is currently above crossbar (inside goal vertically)
var aboveCrossbar = ballY < crossbarY;
// Check if ball entered from below the crossbar (was below crossbar and now above it)
var enteredFromBelow = ballLastY >= crossbarY && ballY < crossbarY;
return inGoalAreaHorizontally && aboveCrossbar && enteredFromBelow;
};
return self;
});
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
self.moveSpeed = 2;
self.direction = 1;
self.goalX = 0;
self.goalWidth = 200;
self.setup = function (goalX, goalWidth) {
self.goalX = goalX;
self.goalWidth = goalWidth;
self.x = goalX;
self.y = 800; // Position at ground level in front of goal
};
self.update = function () {
// Move horizontally within goal area
self.x += self.moveSpeed * self.direction;
// Bounce between goal posts
var leftBound = self.goalX - self.goalWidth / 2;
var rightBound = self.goalX + self.goalWidth / 2;
if (self.x <= leftBound || self.x >= rightBound) {
self.direction *= -1;
// Use tween for smooth direction change
tween(self, {
scaleX: self.direction
}, {
duration: 200,
easing: tween.easeOut
});
}
};
return self;
});
var WindIndicator = Container.expand(function () {
var self = Container.call(this);
var arrow = self.attachAsset('windArrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
arrow.rotation = windDirection;
arrow.scaleX = Math.abs(windStrength) * 0.5 + 0.5;
arrow.alpha = Math.abs(windStrength) * 0.3 + 0.7;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a90e2
});
/****
* Game Code
****/
var ball = new Ball();
var goal = new Goal();
var goalkeeper = new Goalkeeper();
var windIndicator = new WindIndicator();
// Game state variables
var gameState = 'aiming'; // 'aiming', 'flying', 'resetting'
var shots = 0;
var maxShots = 10;
var windStrength = 0;
var windDirection = 0;
var difficulty = 1;
var goalMoveSpeed = 0;
var goalDirection = 1;
// Hold-to-power variables
var isHolding = false;
var holdStartTime = 0;
var holdTargetX = 0;
var holdTargetY = 0;
var maxHoldTime = 2000; // 2 seconds for maximum power
// Double tap variables
var lastTapTime = 0;
var doubleTapDelay = 300; // 300ms window for double tap
// Initialize ground
var ground = game.attachAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2632
});
// Initialize game objects
ball.reset();
goal.setup(1024, 800);
goalkeeper.setup(1024, 200);
windIndicator.x = 200;
windIndicator.y = 200;
game.addChild(ball);
game.addChild(goal);
game.addChild(goalkeeper);
game.addChild(windIndicator);
// Goals display
var goalsScoredCount = 0;
var scoreTxt = new Text2('Goals: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Shots remaining display
var shotsTxt = new Text2('Shots: 10', {
size: 60,
fill: 0xFFFFFF
});
shotsTxt.anchor.set(1, 0);
shotsTxt.x = -50;
shotsTxt.y = 100;
LK.gui.topRight.addChild(shotsTxt);
// Wind display
var windTxt = new Text2('Wind: None', {
size: 50,
fill: 0xFFFFFF
});
windTxt.anchor.set(0, 0);
windTxt.x = 50;
windTxt.y = 100;
LK.gui.topLeft.addChild(windTxt);
// Power display
var powerTxt = new Text2('Power: 0%', {
size: 60,
fill: 0xFFFF00
});
powerTxt.anchor.set(0.5, 0);
powerTxt.x = 0;
powerTxt.y = 150;
LK.gui.top.addChild(powerTxt);
function updateWind() {
windDirection = (Math.random() - 0.5) * Math.PI;
windStrength = (Math.random() - 0.5) * 2;
var windText = 'Wind: ';
if (Math.abs(windStrength) < 0.3) {
windText += 'Light';
} else if (Math.abs(windStrength) < 0.7) {
windText += 'Moderate';
} else {
windText += 'Strong';
}
if (windStrength > 0) {
windText += ' →';
} else {
windText += ' ←';
}
windTxt.setText(windText);
}
function resetRound() {
ball.reset();
gameState = 'aiming';
shots++;
// Update shots display
shotsTxt.setText('Shots: ' + (maxShots - shots));
// Check game over
if (shots >= maxShots) {
LK.showGameOver();
return;
}
// Increase difficulty
difficulty = Math.floor(shots / 3) + 1;
goalMoveSpeed = Math.min(difficulty * 0.5, 3);
// Update wind
updateWind();
// Move goal to new position
var newGoalX = 500 + Math.random() * 1048;
var newGoalY = 300 + Math.random() * 500;
goal.setup(newGoalX, newGoalY);
goalkeeper.setup(newGoalX, goal.goalWidth);
}
function calculatePower(startX, startY, endX, endY) {
var deltaX = endX - startX;
var deltaY = endY - startY;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
// Initialize first round
updateWind();
game.down = function (x, y, obj) {
var currentTime = Date.now();
// Check for double tap
if (currentTime - lastTapTime < doubleTapDelay) {
// Double tap detected - skip to next shot
if (gameState === 'flying' || gameState === 'aiming') {
resetRound();
return;
}
}
lastTapTime = currentTime;
if (gameState === 'aiming' && !ball.isFlying) {
// Start holding for power calculation
isHolding = true;
holdStartTime = Date.now();
holdTargetX = x;
holdTargetY = y;
}
};
game.up = function (x, y, obj) {
if (gameState === 'aiming' && !ball.isFlying && isHolding) {
// Calculate hold time and convert to power
var holdTime = Date.now() - holdStartTime;
var power = Math.min(holdTime / maxHoldTime * 400, 400); // Scale to max 400 power
power = Math.max(power, 50); // Ensure minimum power
// Shoot toward the target point
ball.flick(holdTargetX, holdTargetY, power);
gameState = 'flying';
isHolding = false;
}
};
game.update = function () {
// Update wind indicator
windIndicator.update();
// Update goalkeeper movement
goalkeeper.update();
// Update power display during hold
if (isHolding) {
var holdTime = Date.now() - holdStartTime;
var powerPercent = Math.min(holdTime / maxHoldTime * 100, 100);
powerTxt.setText('Power: ' + Math.floor(powerPercent) + '%');
powerTxt.visible = true;
} else {
powerTxt.visible = false;
}
// Move goal based on difficulty
if (difficulty > 2) {
goal.x += goalMoveSpeed * goalDirection;
if (goal.x <= 300 || goal.x >= 1748) {
goalDirection *= -1;
}
}
// Check for collision with goalkeeper
if (ball.isFlying && ball.intersects(goalkeeper)) {
// Calculate reflection angle based on where ball hits goalkeeper
var relativeHitX = ball.x - goalkeeper.x;
var reflectionAngle = relativeHitX * 0.02; // Increased reflection sensitivity
// Reflect velocity with energy loss - ensure ball goes back towards player
ball.velocityX = -Math.abs(ball.velocityX) * 0.9 + reflectionAngle; // Force negative X (back towards player)
ball.velocityY = -Math.abs(ball.velocityY) * 0.8; // Always bounce upward with more energy
// Add goalkeeper movement influence
ball.velocityX += goalkeeper.direction * goalkeeper.moveSpeed * 0.5;
// Add some spin effect
tween(ball, {
rotation: ball.rotation + Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut
});
// Move ball away from goalkeeper to prevent multiple collisions
ball.x = goalkeeper.x - 60; // Position ball in front of goalkeeper
ball.y -= 10; // Lift ball slightly
}
// Check for goal
if (ball.isFlying && goal.checkGoal(ball.x, ball.y, ball.lastY)) {
goalsScoredCount++;
scoreTxt.setText('Goals: ' + goalsScoredCount);
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
resetRound();
}
// Check if ball has stopped and missed
if (gameState === 'flying' && !ball.isFlying) {
resetRound();
}
// Victory condition
if (goalsScoredCount >= 5) {
LK.showYouWin();
}
};