/****
* 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();
};