/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballSprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballSprite.width / 2; self.vx = 0; self.vy = 0; self.maxSpeed = 60; self.friction = 0.96; self.owner = null; // If not null, ball is attached to player self.update = function () { if (self.owner) { // Stick to owner's feet var angle = self.owner.facing; self.x = self.owner.x + Math.cos(angle) * (self.owner.radius + self.radius + 10); self.y = self.owner.y + Math.sin(angle) * (self.owner.radius + self.radius + 10); self.vx = 0; self.vy = 0; } else { // Move ball self.x += self.vx; self.y += self.vy; self.vx *= self.friction; self.vy *= self.friction; if (Math.abs(self.vx) < 0.1) self.vx = 0; if (Math.abs(self.vy) < 0.1) self.vy = 0; } }; // Kick the ball in a direction self.kick = function (angle, power) { self.owner = null; self.vx = Math.cos(angle) * power; self.vy = Math.sin(angle) * power; if (Math.sqrt(self.vx * self.vx + self.vy * self.vy) > self.maxSpeed) { var mag = Math.sqrt(self.vx * self.vx + self.vy * self.vy); self.vx = self.vx / mag * self.maxSpeed; self.vy = self.vy / mag * self.maxSpeed; } LK.getSound('kick').play(); }; return self; }); // Defender class var Defender = Container.expand(function () { var self = Container.call(this); var defenderSprite = self.attachAsset('defender', { anchorX: 0.5, anchorY: 0.5 }); self.radius = defenderSprite.width / 2; self.speed = 12 + Math.random() * 5; self.facing = Math.PI / 2; self.target = null; // Ball or player self.cooldown = 0; // For tackle attempts self.update = function () { if (self.cooldown > 0) self.cooldown--; // Simple AI: move toward ball if not owned, else toward player var tx, ty; if (ball.owner) { tx = player.x; ty = player.y; } else { tx = ball.x; ty = ball.y; } var dx = tx - self.x; var dy = ty - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 1) { self.facing = Math.atan2(dy, dx); var moveDist = Math.min(self.speed, dist); self.x += Math.cos(self.facing) * moveDist; self.y += Math.sin(self.facing) * moveDist; } }; // Tackle attempt self.tryTackle = function () { if (self.cooldown > 0) return false; if (ball.owner === player) { var dx = self.x - player.x; var dy = self.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.radius + player.radius + 2) { // Smaller tackle range // Successful tackle ball.owner = null; ball.vx = (Math.random() - 0.5) * 20; ball.vy = (Math.random() - 0.5) * 20; self.cooldown = 60; LK.getSound('tackle').play(); // Flash player red LK.effects.flashObject(player, 0xff0000, 400); return true; } } return false; }; return self; }); // Goalkeeper class var Goalkeeper = Container.expand(function () { var self = Container.call(this); var keeperSprite = self.attachAsset('goalkeeper', { anchorX: 0.5, anchorY: 0.5 }); self.radius = keeperSprite.width / 2; self.speed = 15; self.facing = -Math.PI / 2; self.goalX = 0; self.goalY = 0; self.range = 320; // How far left/right can move self.update = function () { // Track ball's y, but only move left/right var targetX = Math.max(goal.x - self.range, Math.min(goal.x + self.range, ball.x)); var dx = targetX - self.x; if (Math.abs(dx) > 2) { self.x += Math.sign(dx) * Math.min(self.speed, Math.abs(dx)); } // Try to block ball if close var dist = Math.sqrt((self.x - ball.x) * (self.x - ball.x) + (self.y - ball.y) * (self.y - ball.y)); if (dist < self.radius + ball.radius + 10 && !ball.owner) { // Block: stop ball, send it back ball.vx = -ball.vx * 0.7; ball.vy = -Math.abs(ball.vy) * 0.7; LK.effects.flashObject(self, 0x00ff00, 300); } }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); var playerSprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.radius = playerSprite.width / 2; self.speed = 40; self.facing = -Math.PI / 2; // Up self.hasBall = false; self.update = function () { // Facing is set by movement direction }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x006600 }); /**** * Game Code ****/ // Pitch // Player (footballer) // Ball // Opponent (defender) // Goalkeeper // Goal area // Pitch outline // Sound effects var pitch = LK.getAsset('pitch', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(pitch); // Goal (top) var goal = LK.getAsset('goal', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 120 }); game.addChild(goal); // Goalkeeper var goalkeeper = new Goalkeeper(); goalkeeper.x = 2048 / 2; goalkeeper.y = goal.y + 80; game.addChild(goalkeeper); // Player var player = new Player(); player.x = 2048 / 2; player.y = 2732 - 400; game.addChild(player); // Ball var ball = new Ball(); ball.x = player.x; ball.y = player.y - player.radius - ball.radius - 10; ball.owner = player; game.addChild(ball); // Defenders var defenders = []; function spawnDefenders(level) { // Remove old defenders for (var i = 0; i < defenders.length; ++i) { defenders[i].destroy(); } defenders = []; // Place defenders in a line, with some random offset var n = 1 + Math.floor(level / 3); // Fewer defenders, slower increase for (var i = 0; i < n; ++i) { var d = new Defender(); d.x = 2048 / 2 + (i - (n - 1) / 2) * 320 + (Math.random() - 0.5) * 60; d.y = 1100 + i * 80 + Math.random() * 60; defenders.push(d); game.addChild(d); } } var level = 1; spawnDefenders(level); // Score and timer var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFF700 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var timer = 60; // seconds var timerTxt = new Text2('1:00', { size: 90, fill: 0xFFFFFF }); timerTxt.anchor.set(0.5, 0); LK.gui.top.addChild(timerTxt); timerTxt.y = 120; // Touch controls var dragNode = null; var dragOffsetX = 0; var dragOffsetY = 0; var lastTouchX = 0; var lastTouchY = 0; var isKicking = false; var kickStartX = 0; var kickStartY = 0; var kickStartTime = 0; // Helper: clamp player inside pitch function clampPlayer() { var minX = pitch.x - pitch.width / 2 + player.radius + 20; var maxX = pitch.x + pitch.width / 2 - player.radius - 20; var minY = pitch.y - pitch.height / 2 + player.radius + 20; var maxY = pitch.y + pitch.height / 2 - player.radius - 20; player.x = Math.max(minX, Math.min(maxX, player.x)); player.y = Math.max(minY, Math.min(maxY, player.y)); } // Helper: clamp ball inside pitch function clampBall() { var minX = pitch.x - pitch.width / 2 + ball.radius + 10; var maxX = pitch.x + pitch.width / 2 - ball.radius - 10; var minY = pitch.y - pitch.height / 2 + ball.radius + 10; var maxY = pitch.y + pitch.height / 2 - ball.radius - 10; ball.x = Math.max(minX, Math.min(maxX, ball.x)); ball.y = Math.max(minY, Math.min(maxY, ball.y)); } // Move handler: drag to move player, swipe to kick function handleMove(x, y, obj) { lastTouchX = x; lastTouchY = y; if (dragNode === player) { // Move player toward touch, but limit speed var dx = x - player.x; var dy = y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var moveDist = Math.min(player.speed, dist); player.x += dx / dist * moveDist; player.y += dy / dist * moveDist; player.facing = Math.atan2(dy, dx); } clampPlayer(); } } game.move = handleMove; game.down = function (x, y, obj) { // If touch is near player, start drag var dx = x - player.x; var dy = y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < player.radius + 60) { dragNode = player; kickStartX = x; kickStartY = y; kickStartTime = LK.ticks; isKicking = false; } }; game.up = function (x, y, obj) { if (dragNode === player) { // If swipe is fast and long enough, kick var dx = x - kickStartX; var dy = y - kickStartY; var dist = Math.sqrt(dx * dx + dy * dy); var dt = LK.ticks - kickStartTime; if (dist > 80 && dt < 40 && ball.owner === player) { // Kick in swipe direction, power based on swipe length var angle = Math.atan2(dy, dx); var power = Math.min(60, dist / 2 + 18); ball.kick(angle, power); isKicking = true; } } dragNode = null; }; // Timer logic var timerInterval = LK.setInterval(function () { if (timer > 0) { timer--; var min = Math.floor(timer / 60); var sec = timer % 60; timerTxt.setText(min + ":" + (sec < 10 ? "0" : "") + sec); if (timer === 0) { LK.getSound('whistle').play(); LK.showGameOver(); } } }, 1000); // Goal detection function checkGoal() { // Ball inside goal area (top) if (!ball.owner && ball.y - ball.radius < goal.y + goal.height / 2 && ball.y + ball.radius > goal.y - goal.height / 2 && ball.x > goal.x - goal.width / 2 + 40 && ball.x < goal.x + goal.width / 2 - 40) { // Score! score++; LK.setScore(score); scoreTxt.setText(score); LK.getSound('goal').play(); LK.effects.flashScreen(0xffff00, 600); // Reset positions resetPositions(); // Increase difficulty every 2 goals if (score % 2 === 0) { level++; spawnDefenders(level); } } } // Reset player, ball, keeper positions function resetPositions() { player.x = 2048 / 2; player.y = 2732 - 400; player.facing = -Math.PI / 2; ball.x = player.x; ball.y = player.y - player.radius - ball.radius - 10; ball.owner = player; ball.vx = 0; ball.vy = 0; goalkeeper.x = 2048 / 2; goalkeeper.y = goal.y + 80; for (var i = 0; i < defenders.length; ++i) { defenders[i].x = 2048 / 2 + (i - (defenders.length - 1) / 2) * 320 + (Math.random() - 0.5) * 60; defenders[i].y = 1100 + i * 80 + Math.random() * 60; defenders[i].cooldown = 0; } } // Main update loop game.update = function () { // Update player player.update(); // Update ball ball.update(); clampBall(); // Ball pickup if (!ball.owner) { var dx = player.x - ball.x; var dy = player.y - ball.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < player.radius + ball.radius + 10) { ball.owner = player; } } // Update defenders for (var i = 0; i < defenders.length; ++i) { defenders[i].update(); defenders[i].tryTackle(); } // Update goalkeeper goalkeeper.update(); // Prevent player/defender overlap for (var i = 0; i < defenders.length; ++i) { var d = defenders[i]; var dx = d.x - player.x; var dy = d.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = d.radius + player.radius + 8; if (dist < minDist) { var overlap = minDist - dist; if (dist > 0) { d.x += dx / dist * overlap / 2; d.y += dy / dist * overlap / 2; player.x -= dx / dist * overlap / 2; player.y -= dy / dist * overlap / 2; clampPlayer(); } } } // Prevent defenders from overlapping each other for (var i = 0; i < defenders.length; ++i) { for (var j = i + 1; j < defenders.length; ++j) { var d1 = defenders[i]; var d2 = defenders[j]; var dx = d2.x - d1.x; var dy = d2.y - d1.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = d1.radius + d2.radius + 4; if (dist < minDist && dist > 0) { var overlap = minDist - dist; d1.x -= dx / dist * overlap / 2; d1.y -= dy / dist * overlap / 2; d2.x += dx / dist * overlap / 2; d2.y += dy / dist * overlap / 2; } } } // Prevent ball from sticking to defenders for (var i = 0; i < defenders.length; ++i) { var d = defenders[i]; var dx = d.x - ball.x; var dy = d.y - ball.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = d.radius + ball.radius + 2; if (dist < minDist && !ball.owner) { var overlap = minDist - dist; if (dist > 0) { ball.x -= dx / dist * overlap / 2; ball.y -= dy / dist * overlap / 2; d.x += dx / dist * overlap / 2; d.y += dy / dist * overlap / 2; } } } // Prevent player from entering goal area var goalAreaY = goal.y + goal.height / 2 + player.radius + 10; if (player.y - player.radius < goalAreaY) { player.y = goalAreaY + player.radius; } // Check for goal checkGoal(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballSprite = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballSprite.width / 2;
self.vx = 0;
self.vy = 0;
self.maxSpeed = 60;
self.friction = 0.96;
self.owner = null; // If not null, ball is attached to player
self.update = function () {
if (self.owner) {
// Stick to owner's feet
var angle = self.owner.facing;
self.x = self.owner.x + Math.cos(angle) * (self.owner.radius + self.radius + 10);
self.y = self.owner.y + Math.sin(angle) * (self.owner.radius + self.radius + 10);
self.vx = 0;
self.vy = 0;
} else {
// Move ball
self.x += self.vx;
self.y += self.vy;
self.vx *= self.friction;
self.vy *= self.friction;
if (Math.abs(self.vx) < 0.1) self.vx = 0;
if (Math.abs(self.vy) < 0.1) self.vy = 0;
}
};
// Kick the ball in a direction
self.kick = function (angle, power) {
self.owner = null;
self.vx = Math.cos(angle) * power;
self.vy = Math.sin(angle) * power;
if (Math.sqrt(self.vx * self.vx + self.vy * self.vy) > self.maxSpeed) {
var mag = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
self.vx = self.vx / mag * self.maxSpeed;
self.vy = self.vy / mag * self.maxSpeed;
}
LK.getSound('kick').play();
};
return self;
});
// Defender class
var Defender = Container.expand(function () {
var self = Container.call(this);
var defenderSprite = self.attachAsset('defender', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = defenderSprite.width / 2;
self.speed = 12 + Math.random() * 5;
self.facing = Math.PI / 2;
self.target = null; // Ball or player
self.cooldown = 0; // For tackle attempts
self.update = function () {
if (self.cooldown > 0) self.cooldown--;
// Simple AI: move toward ball if not owned, else toward player
var tx, ty;
if (ball.owner) {
tx = player.x;
ty = player.y;
} else {
tx = ball.x;
ty = ball.y;
}
var dx = tx - self.x;
var dy = ty - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
self.facing = Math.atan2(dy, dx);
var moveDist = Math.min(self.speed, dist);
self.x += Math.cos(self.facing) * moveDist;
self.y += Math.sin(self.facing) * moveDist;
}
};
// Tackle attempt
self.tryTackle = function () {
if (self.cooldown > 0) return false;
if (ball.owner === player) {
var dx = self.x - player.x;
var dy = self.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.radius + player.radius + 2) {
// Smaller tackle range
// Successful tackle
ball.owner = null;
ball.vx = (Math.random() - 0.5) * 20;
ball.vy = (Math.random() - 0.5) * 20;
self.cooldown = 60;
LK.getSound('tackle').play();
// Flash player red
LK.effects.flashObject(player, 0xff0000, 400);
return true;
}
}
return false;
};
return self;
});
// Goalkeeper class
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperSprite = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = keeperSprite.width / 2;
self.speed = 15;
self.facing = -Math.PI / 2;
self.goalX = 0;
self.goalY = 0;
self.range = 320; // How far left/right can move
self.update = function () {
// Track ball's y, but only move left/right
var targetX = Math.max(goal.x - self.range, Math.min(goal.x + self.range, ball.x));
var dx = targetX - self.x;
if (Math.abs(dx) > 2) {
self.x += Math.sign(dx) * Math.min(self.speed, Math.abs(dx));
}
// Try to block ball if close
var dist = Math.sqrt((self.x - ball.x) * (self.x - ball.x) + (self.y - ball.y) * (self.y - ball.y));
if (dist < self.radius + ball.radius + 10 && !ball.owner) {
// Block: stop ball, send it back
ball.vx = -ball.vx * 0.7;
ball.vy = -Math.abs(ball.vy) * 0.7;
LK.effects.flashObject(self, 0x00ff00, 300);
}
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = playerSprite.width / 2;
self.speed = 40;
self.facing = -Math.PI / 2; // Up
self.hasBall = false;
self.update = function () {
// Facing is set by movement direction
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x006600
});
/****
* Game Code
****/
// Pitch
// Player (footballer)
// Ball
// Opponent (defender)
// Goalkeeper
// Goal area
// Pitch outline
// Sound effects
var pitch = LK.getAsset('pitch', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(pitch);
// Goal (top)
var goal = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 120
});
game.addChild(goal);
// Goalkeeper
var goalkeeper = new Goalkeeper();
goalkeeper.x = 2048 / 2;
goalkeeper.y = goal.y + 80;
game.addChild(goalkeeper);
// Player
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 400;
game.addChild(player);
// Ball
var ball = new Ball();
ball.x = player.x;
ball.y = player.y - player.radius - ball.radius - 10;
ball.owner = player;
game.addChild(ball);
// Defenders
var defenders = [];
function spawnDefenders(level) {
// Remove old defenders
for (var i = 0; i < defenders.length; ++i) {
defenders[i].destroy();
}
defenders = [];
// Place defenders in a line, with some random offset
var n = 1 + Math.floor(level / 3); // Fewer defenders, slower increase
for (var i = 0; i < n; ++i) {
var d = new Defender();
d.x = 2048 / 2 + (i - (n - 1) / 2) * 320 + (Math.random() - 0.5) * 60;
d.y = 1100 + i * 80 + Math.random() * 60;
defenders.push(d);
game.addChild(d);
}
}
var level = 1;
spawnDefenders(level);
// Score and timer
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var timer = 60; // seconds
var timerTxt = new Text2('1:00', {
size: 90,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timerTxt);
timerTxt.y = 120;
// Touch controls
var dragNode = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastTouchX = 0;
var lastTouchY = 0;
var isKicking = false;
var kickStartX = 0;
var kickStartY = 0;
var kickStartTime = 0;
// Helper: clamp player inside pitch
function clampPlayer() {
var minX = pitch.x - pitch.width / 2 + player.radius + 20;
var maxX = pitch.x + pitch.width / 2 - player.radius - 20;
var minY = pitch.y - pitch.height / 2 + player.radius + 20;
var maxY = pitch.y + pitch.height / 2 - player.radius - 20;
player.x = Math.max(minX, Math.min(maxX, player.x));
player.y = Math.max(minY, Math.min(maxY, player.y));
}
// Helper: clamp ball inside pitch
function clampBall() {
var minX = pitch.x - pitch.width / 2 + ball.radius + 10;
var maxX = pitch.x + pitch.width / 2 - ball.radius - 10;
var minY = pitch.y - pitch.height / 2 + ball.radius + 10;
var maxY = pitch.y + pitch.height / 2 - ball.radius - 10;
ball.x = Math.max(minX, Math.min(maxX, ball.x));
ball.y = Math.max(minY, Math.min(maxY, ball.y));
}
// Move handler: drag to move player, swipe to kick
function handleMove(x, y, obj) {
lastTouchX = x;
lastTouchY = y;
if (dragNode === player) {
// Move player toward touch, but limit speed
var dx = x - player.x;
var dy = y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(player.speed, dist);
player.x += dx / dist * moveDist;
player.y += dy / dist * moveDist;
player.facing = Math.atan2(dy, dx);
}
clampPlayer();
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// If touch is near player, start drag
var dx = x - player.x;
var dy = y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.radius + 60) {
dragNode = player;
kickStartX = x;
kickStartY = y;
kickStartTime = LK.ticks;
isKicking = false;
}
};
game.up = function (x, y, obj) {
if (dragNode === player) {
// If swipe is fast and long enough, kick
var dx = x - kickStartX;
var dy = y - kickStartY;
var dist = Math.sqrt(dx * dx + dy * dy);
var dt = LK.ticks - kickStartTime;
if (dist > 80 && dt < 40 && ball.owner === player) {
// Kick in swipe direction, power based on swipe length
var angle = Math.atan2(dy, dx);
var power = Math.min(60, dist / 2 + 18);
ball.kick(angle, power);
isKicking = true;
}
}
dragNode = null;
};
// Timer logic
var timerInterval = LK.setInterval(function () {
if (timer > 0) {
timer--;
var min = Math.floor(timer / 60);
var sec = timer % 60;
timerTxt.setText(min + ":" + (sec < 10 ? "0" : "") + sec);
if (timer === 0) {
LK.getSound('whistle').play();
LK.showGameOver();
}
}
}, 1000);
// Goal detection
function checkGoal() {
// Ball inside goal area (top)
if (!ball.owner && ball.y - ball.radius < goal.y + goal.height / 2 && ball.y + ball.radius > goal.y - goal.height / 2 && ball.x > goal.x - goal.width / 2 + 40 && ball.x < goal.x + goal.width / 2 - 40) {
// Score!
score++;
LK.setScore(score);
scoreTxt.setText(score);
LK.getSound('goal').play();
LK.effects.flashScreen(0xffff00, 600);
// Reset positions
resetPositions();
// Increase difficulty every 2 goals
if (score % 2 === 0) {
level++;
spawnDefenders(level);
}
}
}
// Reset player, ball, keeper positions
function resetPositions() {
player.x = 2048 / 2;
player.y = 2732 - 400;
player.facing = -Math.PI / 2;
ball.x = player.x;
ball.y = player.y - player.radius - ball.radius - 10;
ball.owner = player;
ball.vx = 0;
ball.vy = 0;
goalkeeper.x = 2048 / 2;
goalkeeper.y = goal.y + 80;
for (var i = 0; i < defenders.length; ++i) {
defenders[i].x = 2048 / 2 + (i - (defenders.length - 1) / 2) * 320 + (Math.random() - 0.5) * 60;
defenders[i].y = 1100 + i * 80 + Math.random() * 60;
defenders[i].cooldown = 0;
}
}
// Main update loop
game.update = function () {
// Update player
player.update();
// Update ball
ball.update();
clampBall();
// Ball pickup
if (!ball.owner) {
var dx = player.x - ball.x;
var dy = player.y - ball.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.radius + ball.radius + 10) {
ball.owner = player;
}
}
// Update defenders
for (var i = 0; i < defenders.length; ++i) {
defenders[i].update();
defenders[i].tryTackle();
}
// Update goalkeeper
goalkeeper.update();
// Prevent player/defender overlap
for (var i = 0; i < defenders.length; ++i) {
var d = defenders[i];
var dx = d.x - player.x;
var dy = d.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = d.radius + player.radius + 8;
if (dist < minDist) {
var overlap = minDist - dist;
if (dist > 0) {
d.x += dx / dist * overlap / 2;
d.y += dy / dist * overlap / 2;
player.x -= dx / dist * overlap / 2;
player.y -= dy / dist * overlap / 2;
clampPlayer();
}
}
}
// Prevent defenders from overlapping each other
for (var i = 0; i < defenders.length; ++i) {
for (var j = i + 1; j < defenders.length; ++j) {
var d1 = defenders[i];
var d2 = defenders[j];
var dx = d2.x - d1.x;
var dy = d2.y - d1.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = d1.radius + d2.radius + 4;
if (dist < minDist && dist > 0) {
var overlap = minDist - dist;
d1.x -= dx / dist * overlap / 2;
d1.y -= dy / dist * overlap / 2;
d2.x += dx / dist * overlap / 2;
d2.y += dy / dist * overlap / 2;
}
}
}
// Prevent ball from sticking to defenders
for (var i = 0; i < defenders.length; ++i) {
var d = defenders[i];
var dx = d.x - ball.x;
var dy = d.y - ball.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = d.radius + ball.radius + 2;
if (dist < minDist && !ball.owner) {
var overlap = minDist - dist;
if (dist > 0) {
ball.x -= dx / dist * overlap / 2;
ball.y -= dy / dist * overlap / 2;
d.x += dx / dist * overlap / 2;
d.y += dy / dist * overlap / 2;
}
}
}
// Prevent player from entering goal area
var goalAreaY = goal.y + goal.height / 2 + player.radius + 10;
if (player.y - player.radius < goalAreaY) {
player.y = goalAreaY + player.radius;
}
// Check for goal
checkGoal();
};