/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('football', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball state
self.isMoving = false;
self.vx = 0;
self.vy = 0;
self.targetX = 0;
self.targetY = 0;
self.speed = 60; // pixels per tick
// Called every tick
self.update = function () {
if (self.isMoving) {
// Move towards target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.speed || dist === 0) {
self.x = self.targetX;
self.y = self.targetY;
self.isMoving = false;
// Ball reached target (goal line or missed)
onBallStopped();
} else {
self.x += self.vx;
self.y += self.vy;
}
}
};
// Start moving towards (tx, ty)
self.shootTo = function (tx, ty) {
var dx = tx - self.x;
var dy = ty - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) {
self.vx = 0;
self.vy = 0;
} else {
self.vx = self.speed * dx / dist;
self.vy = self.speed * dy / dist;
}
self.targetX = tx;
self.targetY = ty;
self.isMoving = true;
};
return self;
});
// Goalkeeper class (human-like: head, body, arms, legs)
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -70,
width: 60,
height: 60,
color: 0xffe0b2 // skin tone
});
// Body
var body = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: -40,
width: 54,
height: 90,
color: 0x1e90ff // blue jersey
});
// Left arm
var leftArm = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: -44,
y: -30,
width: 24,
height: 80,
color: 0x1e90ff
});
// Right arm
var rightArm = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 44,
y: -30,
width: 24,
height: 80,
color: 0x1e90ff
});
// Right hand (ellipse, skin color)
var rightHand = self.attachAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: 44,
y: 54,
// y: -30 (arm top) + 80 (arm height) = 50, plus a bit for hand offset
width: 32,
height: 32,
color: 0xffe0b2 // skin tone
});
// Left leg
var leftLeg = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: -18,
y: 50,
width: 22,
height: 70,
color: 0x222222 // dark shorts
});
// Right leg
var rightLeg = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 18,
y: 50,
width: 22,
height: 70,
color: 0x222222
});
// Movement boundaries (goal area)
self.leftLimit = 2048 / 2 - 350 + 60;
self.rightLimit = 2048 / 2 + 350 - 60;
self.topLimit = GOAL_Y + 30;
self.bottomLimit = GOAL_Y + 200 - 60;
self.y = GOAL_Y + 60;
// Move to a random (x, y) within limits
self.moveToRandom = function () {
var newX = self.leftLimit + Math.random() * (self.rightLimit - self.leftLimit);
var newY = self.topLimit + Math.random() * (self.bottomLimit - self.topLimit);
var duration = 600 + Math.random() * 700;
tween(self, {
x: newX,
y: newY
}, {
duration: duration,
easing: tween.cubicInOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e7d32 // Green pitch
});
/****
* Game Code
****/
// Game constants for 3rd person perspective
// The "camera" is now behind the kicker, looking toward the goal at the top of the screen
var GOAL_Y = 420; // Move goal higher up
var PENALTY_SPOT_Y = 2048 - 520; // Place penalty spot near bottom (player's feet)
var PENALTY_SPOT_X = 2048 / 2;
var GOAL_WIDTH = 700;
var GOAL_LEFT = 2048 / 2 - GOAL_WIDTH / 2;
var GOAL_RIGHT = 2048 / 2 + GOAL_WIDTH / 2;
var MAX_SHOTS = 5;
// Add kicker (player) asset for 3rd person view
var kickerAsset = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 1.0,
// Move kicker a bit to the left of the ball for 3rd person view
x: PENALTY_SPOT_X - 100,
y: PENALTY_SPOT_Y + 120,
width: 180,
height: 260,
color: 0xD83318
});
game.addChild(kickerAsset);
// Game state
var shotsTaken = 0;
var playerGoals = 0;
var aiGoals = 0;
var isShooting = false;
var ball = null;
var goalkeeper = null;
var dragStart = null;
var dragEnd = null;
var canShoot = true;
var highScore = storage.highScore || 0;
// New: Track whose turn it is. true = player, false = AI
var isPlayerTurn = true;
// New: For AI shot, store where player wants to move the keeper
var playerKeeperTargetX = null;
var playerKeeperTargetY = null;
// New: For AI shot, store if player has chosen keeper position
var playerKeeperChosen = false;
// UI
// Player score text (left of center)
var playerScoreTxt = new Text2('0/' + MAX_SHOTS, {
size: 120,
fill: 0xFFFFFF
});
playerScoreTxt.anchor.set(1, 0); // right-aligned
playerScoreTxt.x = -40; // offset left from center
LK.gui.top.addChild(playerScoreTxt);
// AI score text (right of center)
var aiScoreTxt = new Text2('0/' + MAX_SHOTS, {
size: 120,
fill: 0xFFFFFF
});
aiScoreTxt.anchor.set(0, 0); // left-aligned
aiScoreTxt.x = 40; // offset right from center
LK.gui.top.addChild(aiScoreTxt);
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 60,
fill: 0xFFFF00
});
highScoreTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(highScoreTxt);
// Draw 3D-like goal using multiple rectangles for posts, bar, and net illusion (3rd person perspective)
// Main bar (crossbar)
var goalBar = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: GOAL_Y - 20,
width: GOAL_WIDTH,
height: 40
});
game.addChild(goalBar);
// Left post (closer to camera, appears longer)
var leftPost = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: GOAL_LEFT,
y: GOAL_Y + 100,
width: 40,
height: 260
});
game.addChild(leftPost);
// Right post (closer to camera, appears longer)
var rightPost = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: GOAL_RIGHT,
y: GOAL_Y + 100,
width: 40,
height: 260
});
game.addChild(rightPost);
// "Net" illusion: a faint bar at the back, lower and wider for 3D effect
var netBar = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: GOAL_Y + 200,
width: GOAL_WIDTH + 120,
height: 24,
color: 0xeeeeee
});
game.addChild(netBar);
// Removed shot target markers and assets
// Draw penalty area line (arc/box below the goal)
var penaltyLineY = GOAL_Y + 260; // Just below the posts/net
var penaltyLineWidth = GOAL_WIDTH + 120;
var penaltyLineHeight = 18;
var penaltyLine = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: penaltyLineY,
width: penaltyLineWidth,
height: penaltyLineHeight,
color: 0xffffff
});
penaltyLine.alpha = 0.5;
game.addChild(penaltyLine);
// Draw penalty spot (closer to bottom for 3rd person)
var spotAsset = LK.getAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: PENALTY_SPOT_X,
y: PENALTY_SPOT_Y
});
game.addChild(spotAsset);
// Create goalkeeper
goalkeeper = new Goalkeeper();
goalkeeper.x = 2048 / 2;
goalkeeper.y = GOAL_Y + 60; // Place keeper slightly in front of goal for 3D effect
game.addChild(goalkeeper);
// Create ball
function resetBall() {
if (ball) {
ball.destroy();
}
ball = new Ball();
ball.x = PENALTY_SPOT_X;
ball.y = PENALTY_SPOT_Y - 40; // Ball slightly in front of spot for 3rd person
game.addChild(ball);
}
resetBall();
// Helper: update score UI
function updateScoreUI() {
playerScoreTxt.setText(playerGoals + '/' + MAX_SHOTS);
aiScoreTxt.setText(aiGoals + '/' + MAX_SHOTS);
if (playerGoals > highScore) {
highScore = playerGoals;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Helper: check if ball scored
function isGoal(ballX, ballY) {
// Ball must be within goal posts and below the crossbar (allow some margin for ball width)
var margin = 60;
var insideX = ballX > GOAL_LEFT + margin && ballX < GOAL_RIGHT - margin;
var insideY = ballY <= GOAL_Y + 200 && ballY >= GOAL_Y;
return insideX && insideY;
}
// Called when ball stops moving (goal line or missed)
function onBallStopped() {
isShooting = false;
canShoot = false;
shotsTaken += 1;
// Check for collision with goalkeeper
var ballRect = new Rectangle(ball.x - 60, ball.y - 60, 120, 120);
var keeperRect = new Rectangle(goalkeeper.x - 160, goalkeeper.y - 60, 320, 120);
var hitKeeper = rectsIntersect(ballRect, keeperRect);
var scored = false;
if (!hitKeeper && isGoal(ball.x, ball.y)) {
// Goal!
if (isPlayerTurn) {
playerGoals += 1;
playerScoreTxt.setText(playerGoals + '/' + MAX_SHOTS);
} else {
aiGoals += 1;
aiScoreTxt.setText(aiGoals + '/' + MAX_SHOTS);
}
scored = true;
LK.effects.flashObject(ball, 0x00ff00, 600);
} else if (hitKeeper && ball.y <= GOAL_Y + 40) {
// Saved!
LK.effects.flashObject(goalkeeper, 0xff0000, 600);
} else {
// Missed
LK.effects.flashObject(ball, 0xffff00, 600);
}
updateScoreUI();
// Next shot or end game
LK.setTimeout(function () {
if (playerGoals >= 5) {
LK.showYouWin();
} else if (aiGoals >= 5) {
LK.showGameOver();
} else if (shotsTaken >= MAX_SHOTS) {
// Game over
LK.showGameOver();
} else {
// Alternate turns
isPlayerTurn = !isPlayerTurn;
resetBall();
canShoot = true;
if (isPlayerTurn) {
// Player's turn: goalkeeper stays still until after shot
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
} else {
// AI's turn: player chooses where to move keeper before AI shoots
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
}
}
}, 900);
}
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Drag/swipe to shoot
dragStart = null;
dragEnd = null;
// Only allow shooting if not already shooting and not waiting for next shot
game.down = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
// Only allow starting drag near the ball
var dx = x - ball.x;
var dy = y - ball.y;
if (dx * dx + dy * dy < 120 * 120) {
dragStart = {
x: x,
y: y
};
dragEnd = null;
}
} else {
// AI's turn: player sets keeper position by tapping/dragging in goal area
// Only allow in goal area
if (y > GOAL_Y - 100 && y < GOAL_Y + 100 && x > GOAL_LEFT && x < GOAL_RIGHT) {
playerKeeperTargetX = x;
playerKeeperChosen = true;
// Instantly move keeper to chosen position
goalkeeper.x = x;
}
}
};
game.move = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
if (dragStart) {
dragEnd = {
x: x,
y: y
};
// Optionally, show a line or indicator (not implemented due to asset limitations)
}
} else {
// AI's turn: allow player to drag keeper in goal area before AI shoots
if (y > goalkeeper.topLimit && y < goalkeeper.bottomLimit && x > goalkeeper.leftLimit && x < goalkeeper.rightLimit) {
playerKeeperTargetX = x;
playerKeeperTargetY = y;
playerKeeperChosen = true;
goalkeeper.x = x;
goalkeeper.y = y;
}
}
};
game.up = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
if (dragStart && dragEnd) {
// Calculate direction
var dx = dragEnd.x - dragStart.x;
var dy = dragEnd.y - dragStart.y;
// Only allow upward shots (towards goal)
if (dy < -60) {
// Allow shot to any point in the goal area
var targetX = ball.x + dx;
var targetY = ball.y + dy;
// Clamp target to inside the goal area
var marginX = 120;
if (targetX < GOAL_LEFT + marginX) targetX = GOAL_LEFT + marginX;
if (targetX > GOAL_RIGHT - marginX) targetX = GOAL_RIGHT - marginX;
if (targetY < GOAL_Y) targetY = GOAL_Y;
if (targetY > GOAL_Y + 200) targetY = GOAL_Y + 200;
isShooting = true;
// After shot, move keeper to a random position in the goal (simulate reaction)
ball.shootTo(targetX, targetY);
LK.setTimeout(function () {
// Keeper jumps to a random (x, y) in the goal area
var keeperX = goalkeeper.leftLimit + Math.random() * (goalkeeper.rightLimit - goalkeeper.leftLimit);
var keeperY = goalkeeper.topLimit + Math.random() * (goalkeeper.bottomLimit - goalkeeper.topLimit);
tween(goalkeeper, {
x: keeperX,
y: keeperY
}, {
duration: 180 + Math.random() * 120,
easing: tween.cubicInOut
});
}, 80 + Math.random() * 120); // Keeper jumps after shot
}
}
dragStart = null;
dragEnd = null;
} else {
// AI's turn: if player has chosen keeper position, trigger AI shot
if (playerKeeperChosen) {
isShooting = true;
// AI picks a random point in the goal area (not just 5 targets)
var aiTargetX = GOAL_LEFT + 120 + Math.random() * (GOAL_WIDTH - 240);
var aiTargetY = GOAL_Y + 20 + Math.random() * 160;
// Keeper is already at chosen position
// After a short delay, AI shoots
LK.setTimeout(function () {
ball.shootTo(aiTargetX, aiTargetY);
// After shot, move keeper to player-chosen target, but allow any X/Y in goal area, move fast
if (playerKeeperTargetX !== null && typeof playerKeeperTargetY !== "undefined") {
// Clamp to goal area
var clampX = playerKeeperTargetX;
var clampY = playerKeeperTargetY;
if (clampX < goalkeeper.leftLimit) clampX = goalkeeper.leftLimit;
if (clampX > goalkeeper.rightLimit) clampX = goalkeeper.rightLimit;
if (clampY < goalkeeper.topLimit) clampY = goalkeeper.topLimit;
if (clampY > goalkeeper.bottomLimit) clampY = goalkeeper.bottomLimit;
tween(goalkeeper, {
x: clampX,
y: clampY
}, {
duration: 120,
easing: tween.cubicInOut
});
}
}, 120); // AI shoots and keeper jumps much faster
canShoot = false;
}
}
};
// Main update loop
game.update = function () {
if (ball) ball.update();
// No idle movement; keeper only moves when required (after shot or by player during AI turn)
};
// Reset game state on new game
function resetGameState() {
shotsTaken = 0;
playerGoals = 0;
aiGoals = 0;
isShooting = false;
canShoot = true;
isPlayerTurn = true;
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
updateScoreUI();
resetBall();
goalkeeper.x = 2048 / 2;
goalkeeper.y = GOAL_Y + 60;
goalkeeper.moveToRandom();
}
resetGameState();
// When game is reset (after game over), re-initialize state
LK.on('gameStart', function () {
resetGameState();
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('football', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball state
self.isMoving = false;
self.vx = 0;
self.vy = 0;
self.targetX = 0;
self.targetY = 0;
self.speed = 60; // pixels per tick
// Called every tick
self.update = function () {
if (self.isMoving) {
// Move towards target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.speed || dist === 0) {
self.x = self.targetX;
self.y = self.targetY;
self.isMoving = false;
// Ball reached target (goal line or missed)
onBallStopped();
} else {
self.x += self.vx;
self.y += self.vy;
}
}
};
// Start moving towards (tx, ty)
self.shootTo = function (tx, ty) {
var dx = tx - self.x;
var dy = ty - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) {
self.vx = 0;
self.vy = 0;
} else {
self.vx = self.speed * dx / dist;
self.vy = self.speed * dy / dist;
}
self.targetX = tx;
self.targetY = ty;
self.isMoving = true;
};
return self;
});
// Goalkeeper class (human-like: head, body, arms, legs)
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -70,
width: 60,
height: 60,
color: 0xffe0b2 // skin tone
});
// Body
var body = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: -40,
width: 54,
height: 90,
color: 0x1e90ff // blue jersey
});
// Left arm
var leftArm = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: -44,
y: -30,
width: 24,
height: 80,
color: 0x1e90ff
});
// Right arm
var rightArm = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 44,
y: -30,
width: 24,
height: 80,
color: 0x1e90ff
});
// Right hand (ellipse, skin color)
var rightHand = self.attachAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: 44,
y: 54,
// y: -30 (arm top) + 80 (arm height) = 50, plus a bit for hand offset
width: 32,
height: 32,
color: 0xffe0b2 // skin tone
});
// Left leg
var leftLeg = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: -18,
y: 50,
width: 22,
height: 70,
color: 0x222222 // dark shorts
});
// Right leg
var rightLeg = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0,
x: 18,
y: 50,
width: 22,
height: 70,
color: 0x222222
});
// Movement boundaries (goal area)
self.leftLimit = 2048 / 2 - 350 + 60;
self.rightLimit = 2048 / 2 + 350 - 60;
self.topLimit = GOAL_Y + 30;
self.bottomLimit = GOAL_Y + 200 - 60;
self.y = GOAL_Y + 60;
// Move to a random (x, y) within limits
self.moveToRandom = function () {
var newX = self.leftLimit + Math.random() * (self.rightLimit - self.leftLimit);
var newY = self.topLimit + Math.random() * (self.bottomLimit - self.topLimit);
var duration = 600 + Math.random() * 700;
tween(self, {
x: newX,
y: newY
}, {
duration: duration,
easing: tween.cubicInOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e7d32 // Green pitch
});
/****
* Game Code
****/
// Game constants for 3rd person perspective
// The "camera" is now behind the kicker, looking toward the goal at the top of the screen
var GOAL_Y = 420; // Move goal higher up
var PENALTY_SPOT_Y = 2048 - 520; // Place penalty spot near bottom (player's feet)
var PENALTY_SPOT_X = 2048 / 2;
var GOAL_WIDTH = 700;
var GOAL_LEFT = 2048 / 2 - GOAL_WIDTH / 2;
var GOAL_RIGHT = 2048 / 2 + GOAL_WIDTH / 2;
var MAX_SHOTS = 5;
// Add kicker (player) asset for 3rd person view
var kickerAsset = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 1.0,
// Move kicker a bit to the left of the ball for 3rd person view
x: PENALTY_SPOT_X - 100,
y: PENALTY_SPOT_Y + 120,
width: 180,
height: 260,
color: 0xD83318
});
game.addChild(kickerAsset);
// Game state
var shotsTaken = 0;
var playerGoals = 0;
var aiGoals = 0;
var isShooting = false;
var ball = null;
var goalkeeper = null;
var dragStart = null;
var dragEnd = null;
var canShoot = true;
var highScore = storage.highScore || 0;
// New: Track whose turn it is. true = player, false = AI
var isPlayerTurn = true;
// New: For AI shot, store where player wants to move the keeper
var playerKeeperTargetX = null;
var playerKeeperTargetY = null;
// New: For AI shot, store if player has chosen keeper position
var playerKeeperChosen = false;
// UI
// Player score text (left of center)
var playerScoreTxt = new Text2('0/' + MAX_SHOTS, {
size: 120,
fill: 0xFFFFFF
});
playerScoreTxt.anchor.set(1, 0); // right-aligned
playerScoreTxt.x = -40; // offset left from center
LK.gui.top.addChild(playerScoreTxt);
// AI score text (right of center)
var aiScoreTxt = new Text2('0/' + MAX_SHOTS, {
size: 120,
fill: 0xFFFFFF
});
aiScoreTxt.anchor.set(0, 0); // left-aligned
aiScoreTxt.x = 40; // offset right from center
LK.gui.top.addChild(aiScoreTxt);
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 60,
fill: 0xFFFF00
});
highScoreTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(highScoreTxt);
// Draw 3D-like goal using multiple rectangles for posts, bar, and net illusion (3rd person perspective)
// Main bar (crossbar)
var goalBar = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: GOAL_Y - 20,
width: GOAL_WIDTH,
height: 40
});
game.addChild(goalBar);
// Left post (closer to camera, appears longer)
var leftPost = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: GOAL_LEFT,
y: GOAL_Y + 100,
width: 40,
height: 260
});
game.addChild(leftPost);
// Right post (closer to camera, appears longer)
var rightPost = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: GOAL_RIGHT,
y: GOAL_Y + 100,
width: 40,
height: 260
});
game.addChild(rightPost);
// "Net" illusion: a faint bar at the back, lower and wider for 3D effect
var netBar = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: GOAL_Y + 200,
width: GOAL_WIDTH + 120,
height: 24,
color: 0xeeeeee
});
game.addChild(netBar);
// Removed shot target markers and assets
// Draw penalty area line (arc/box below the goal)
var penaltyLineY = GOAL_Y + 260; // Just below the posts/net
var penaltyLineWidth = GOAL_WIDTH + 120;
var penaltyLineHeight = 18;
var penaltyLine = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: penaltyLineY,
width: penaltyLineWidth,
height: penaltyLineHeight,
color: 0xffffff
});
penaltyLine.alpha = 0.5;
game.addChild(penaltyLine);
// Draw penalty spot (closer to bottom for 3rd person)
var spotAsset = LK.getAsset('spot', {
anchorX: 0.5,
anchorY: 0.5,
x: PENALTY_SPOT_X,
y: PENALTY_SPOT_Y
});
game.addChild(spotAsset);
// Create goalkeeper
goalkeeper = new Goalkeeper();
goalkeeper.x = 2048 / 2;
goalkeeper.y = GOAL_Y + 60; // Place keeper slightly in front of goal for 3D effect
game.addChild(goalkeeper);
// Create ball
function resetBall() {
if (ball) {
ball.destroy();
}
ball = new Ball();
ball.x = PENALTY_SPOT_X;
ball.y = PENALTY_SPOT_Y - 40; // Ball slightly in front of spot for 3rd person
game.addChild(ball);
}
resetBall();
// Helper: update score UI
function updateScoreUI() {
playerScoreTxt.setText(playerGoals + '/' + MAX_SHOTS);
aiScoreTxt.setText(aiGoals + '/' + MAX_SHOTS);
if (playerGoals > highScore) {
highScore = playerGoals;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Helper: check if ball scored
function isGoal(ballX, ballY) {
// Ball must be within goal posts and below the crossbar (allow some margin for ball width)
var margin = 60;
var insideX = ballX > GOAL_LEFT + margin && ballX < GOAL_RIGHT - margin;
var insideY = ballY <= GOAL_Y + 200 && ballY >= GOAL_Y;
return insideX && insideY;
}
// Called when ball stops moving (goal line or missed)
function onBallStopped() {
isShooting = false;
canShoot = false;
shotsTaken += 1;
// Check for collision with goalkeeper
var ballRect = new Rectangle(ball.x - 60, ball.y - 60, 120, 120);
var keeperRect = new Rectangle(goalkeeper.x - 160, goalkeeper.y - 60, 320, 120);
var hitKeeper = rectsIntersect(ballRect, keeperRect);
var scored = false;
if (!hitKeeper && isGoal(ball.x, ball.y)) {
// Goal!
if (isPlayerTurn) {
playerGoals += 1;
playerScoreTxt.setText(playerGoals + '/' + MAX_SHOTS);
} else {
aiGoals += 1;
aiScoreTxt.setText(aiGoals + '/' + MAX_SHOTS);
}
scored = true;
LK.effects.flashObject(ball, 0x00ff00, 600);
} else if (hitKeeper && ball.y <= GOAL_Y + 40) {
// Saved!
LK.effects.flashObject(goalkeeper, 0xff0000, 600);
} else {
// Missed
LK.effects.flashObject(ball, 0xffff00, 600);
}
updateScoreUI();
// Next shot or end game
LK.setTimeout(function () {
if (playerGoals >= 5) {
LK.showYouWin();
} else if (aiGoals >= 5) {
LK.showGameOver();
} else if (shotsTaken >= MAX_SHOTS) {
// Game over
LK.showGameOver();
} else {
// Alternate turns
isPlayerTurn = !isPlayerTurn;
resetBall();
canShoot = true;
if (isPlayerTurn) {
// Player's turn: goalkeeper stays still until after shot
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
} else {
// AI's turn: player chooses where to move keeper before AI shoots
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
}
}
}, 900);
}
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Drag/swipe to shoot
dragStart = null;
dragEnd = null;
// Only allow shooting if not already shooting and not waiting for next shot
game.down = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
// Only allow starting drag near the ball
var dx = x - ball.x;
var dy = y - ball.y;
if (dx * dx + dy * dy < 120 * 120) {
dragStart = {
x: x,
y: y
};
dragEnd = null;
}
} else {
// AI's turn: player sets keeper position by tapping/dragging in goal area
// Only allow in goal area
if (y > GOAL_Y - 100 && y < GOAL_Y + 100 && x > GOAL_LEFT && x < GOAL_RIGHT) {
playerKeeperTargetX = x;
playerKeeperChosen = true;
// Instantly move keeper to chosen position
goalkeeper.x = x;
}
}
};
game.move = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
if (dragStart) {
dragEnd = {
x: x,
y: y
};
// Optionally, show a line or indicator (not implemented due to asset limitations)
}
} else {
// AI's turn: allow player to drag keeper in goal area before AI shoots
if (y > goalkeeper.topLimit && y < goalkeeper.bottomLimit && x > goalkeeper.leftLimit && x < goalkeeper.rightLimit) {
playerKeeperTargetX = x;
playerKeeperTargetY = y;
playerKeeperChosen = true;
goalkeeper.x = x;
goalkeeper.y = y;
}
}
};
game.up = function (x, y, obj) {
if (!canShoot || isShooting) return;
if (isPlayerTurn) {
if (dragStart && dragEnd) {
// Calculate direction
var dx = dragEnd.x - dragStart.x;
var dy = dragEnd.y - dragStart.y;
// Only allow upward shots (towards goal)
if (dy < -60) {
// Allow shot to any point in the goal area
var targetX = ball.x + dx;
var targetY = ball.y + dy;
// Clamp target to inside the goal area
var marginX = 120;
if (targetX < GOAL_LEFT + marginX) targetX = GOAL_LEFT + marginX;
if (targetX > GOAL_RIGHT - marginX) targetX = GOAL_RIGHT - marginX;
if (targetY < GOAL_Y) targetY = GOAL_Y;
if (targetY > GOAL_Y + 200) targetY = GOAL_Y + 200;
isShooting = true;
// After shot, move keeper to a random position in the goal (simulate reaction)
ball.shootTo(targetX, targetY);
LK.setTimeout(function () {
// Keeper jumps to a random (x, y) in the goal area
var keeperX = goalkeeper.leftLimit + Math.random() * (goalkeeper.rightLimit - goalkeeper.leftLimit);
var keeperY = goalkeeper.topLimit + Math.random() * (goalkeeper.bottomLimit - goalkeeper.topLimit);
tween(goalkeeper, {
x: keeperX,
y: keeperY
}, {
duration: 180 + Math.random() * 120,
easing: tween.cubicInOut
});
}, 80 + Math.random() * 120); // Keeper jumps after shot
}
}
dragStart = null;
dragEnd = null;
} else {
// AI's turn: if player has chosen keeper position, trigger AI shot
if (playerKeeperChosen) {
isShooting = true;
// AI picks a random point in the goal area (not just 5 targets)
var aiTargetX = GOAL_LEFT + 120 + Math.random() * (GOAL_WIDTH - 240);
var aiTargetY = GOAL_Y + 20 + Math.random() * 160;
// Keeper is already at chosen position
// After a short delay, AI shoots
LK.setTimeout(function () {
ball.shootTo(aiTargetX, aiTargetY);
// After shot, move keeper to player-chosen target, but allow any X/Y in goal area, move fast
if (playerKeeperTargetX !== null && typeof playerKeeperTargetY !== "undefined") {
// Clamp to goal area
var clampX = playerKeeperTargetX;
var clampY = playerKeeperTargetY;
if (clampX < goalkeeper.leftLimit) clampX = goalkeeper.leftLimit;
if (clampX > goalkeeper.rightLimit) clampX = goalkeeper.rightLimit;
if (clampY < goalkeeper.topLimit) clampY = goalkeeper.topLimit;
if (clampY > goalkeeper.bottomLimit) clampY = goalkeeper.bottomLimit;
tween(goalkeeper, {
x: clampX,
y: clampY
}, {
duration: 120,
easing: tween.cubicInOut
});
}
}, 120); // AI shoots and keeper jumps much faster
canShoot = false;
}
}
};
// Main update loop
game.update = function () {
if (ball) ball.update();
// No idle movement; keeper only moves when required (after shot or by player during AI turn)
};
// Reset game state on new game
function resetGameState() {
shotsTaken = 0;
playerGoals = 0;
aiGoals = 0;
isShooting = false;
canShoot = true;
isPlayerTurn = true;
playerKeeperTargetX = null;
playerKeeperTargetY = null;
playerKeeperChosen = false;
updateScoreUI();
resetBall();
goalkeeper.x = 2048 / 2;
goalkeeper.y = GOAL_Y + 60;
goalkeeper.moveToRandom();
}
resetGameState();
// When game is reset (after game over), re-initialize state
LK.on('gameStart', function () {
resetGameState();
});
A football player named Ercan Kara. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
draw a soccer ball use a light gray and black. 2d. High contrast. No shadows
gray bar . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
gray iron bar. In-Game asset. High contrast. No shadows