User prompt
Red teams control the ai
User prompt
When player touch ball ball start moving and 2 players can move
User prompt
Player can touch ball
User prompt
Player move is normally and can be touch ball but players cant go other Side
User prompt
Make player can move
User prompt
Make goal like real life
User prompt
Make field blue
User prompt
Make field color blue
User prompt
Make 2 teams left color is blue,right color is red
User prompt
Restart
User prompt
Reset all
User prompt
Do medium goal
User prompt
Make a goal
User prompt
Make a goal white
User prompt
Not 1 side do 2 side
User prompt
Make a goal color white and slim
User prompt
Remove in front of goal
User prompt
Gets line is slım and get a futsal ball
User prompt
Make a real futsal court
User prompt
Do futsal court
User prompt
Make Me game look like haxball
Code edit (1 edits merged)
Please save this source code
User prompt
Mini Haxball Arena
Initial prompt
Make Me game look like haxball
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
// Attach ball asset (ellipse, white)
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball physics properties
self.radius = ballAsset.width / 2;
self.vx = 0;
self.vy = 0;
self.friction = 0.985; // Friction for slowing down
self.maxSpeed = 60;
// Ball update: move, apply friction, clamp speed, bounce off walls
self.update = function () {
// Move
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= self.friction;
self.vy *= self.friction;
// Clamp speed
var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
if (speed > self.maxSpeed) {
self.vx = self.vx / speed * self.maxSpeed;
self.vy = self.vy / speed * self.maxSpeed;
}
// Bounce off field edges (except goals)
// Top
if (self.y - self.radius < fieldTop) {
self.y = fieldTop + self.radius;
self.vy = -self.vy * 0.7;
}
// Bottom
if (self.y + self.radius > fieldBottom) {
self.y = fieldBottom - self.radius;
self.vy = -self.vy * 0.7;
}
// Left wall (except goal area)
if (self.x - self.radius < fieldLeft) {
// If not in goal area, bounce
if (self.y < goalTop || self.y > goalBottom) {
self.x = fieldLeft + self.radius;
self.vx = -self.vx * 0.7;
}
}
// Right wall (except goal area)
if (self.x + self.radius > fieldRight) {
if (self.y < goalTop || self.y > goalBottom) {
self.x = fieldRight - self.radius;
self.vx = -self.vx * 0.7;
}
}
};
// Reset ball to center
self.reset = function () {
self.x = fieldCenterX;
self.y = fieldCenterY;
self.vx = 0;
self.vy = 0;
};
return self;
});
// Goal class (for left and right goals)
var Goal = Container.expand(function () {
var self = Container.call(this);
// Attach goal asset (rectangle)
var goalAsset = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = goalAsset.width;
self.height = goalAsset.height;
// Set color
self.setColor = function (color) {
goalAsset.color = color;
};
return self;
});
// Player class (for both human and AI)
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset (ellipse, blue or red)
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = playerAsset.width / 2;
self.vx = 0;
self.vy = 0;
self.maxSpeed = 40;
self.friction = 0.90;
// For AI: target position
self.targetX = self.x;
self.targetY = self.y;
// For drag
self.isDragging = false;
// Update: move, apply friction, clamp speed, stay in field
self.update = function () {
// If not dragging (AI or after drag), move towards target
if (!self.isDragging) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
var moveSpeed = Math.min(self.maxSpeed, dist * 0.3);
self.vx = dx / dist * moveSpeed;
self.vy = dy / dist * moveSpeed;
} else {
self.vx *= self.friction;
self.vy *= self.friction;
}
}
// Move
self.x += self.vx;
self.y += self.vy;
// Clamp to field
if (self.x - self.radius < fieldLeft) self.x = fieldLeft + self.radius;
if (self.x + self.radius > fieldRight) self.x = fieldRight - self.radius;
if (self.y - self.radius < fieldTop) self.y = fieldTop + self.radius;
if (self.y + self.radius > fieldBottom) self.y = fieldBottom - self.radius;
};
// Set color
self.setColor = function (color) {
playerAsset.color = color;
};
// Set position instantly
self.setPosition = function (x, y) {
self.x = x;
self.y = y;
self.vx = 0;
self.vy = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a7a2e // Green field
});
/****
* Game Code
****/
// Field dimensions
var fieldMargin = 120;
var fieldLeft = fieldMargin;
var fieldRight = 2048 - fieldMargin;
var fieldTop = 300;
var fieldBottom = 2732 - 300;
var fieldWidth = fieldRight - fieldLeft;
var fieldHeight = fieldBottom - fieldTop;
var fieldCenterX = (fieldLeft + fieldRight) / 2;
var fieldCenterY = (fieldTop + fieldBottom) / 2;
// Goal dimensions
var goalWidth = 60;
var goalHeight = 500;
var goalTop = fieldCenterY - goalHeight / 2;
var goalBottom = fieldCenterY + goalHeight / 2;
// Create field background (rectangle, green)
var fieldBg = LK.getAsset('field', {
width: fieldWidth,
height: fieldHeight,
color: 0x2ecc40,
shape: 'box',
anchorX: 0,
anchorY: 0,
x: fieldLeft,
y: fieldTop
});
game.addChild(fieldBg);
// Center line
var centerLine = LK.getAsset('centerLine', {
width: fieldWidth,
height: 12,
color: 0xffffff,
shape: 'box',
anchorX: 0,
anchorY: 0.5,
x: fieldLeft,
y: fieldCenterY
});
game.addChild(centerLine);
// Center circle
var centerCircle = LK.getAsset('centerCircle', {
width: 320,
height: 320,
color: 0xffffff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: fieldCenterX,
y: fieldCenterY
});
centerCircle.alpha = 0.2;
game.addChild(centerCircle);
// Left goal
var leftGoal = new Goal();
leftGoal.x = fieldLeft - goalWidth / 2 + 2;
leftGoal.y = fieldCenterY;
leftGoal.setColor(0x3498db);
leftGoal.width = goalWidth;
leftGoal.height = goalHeight;
game.addChild(leftGoal);
// Right goal
var rightGoal = new Goal();
rightGoal.x = fieldRight + goalWidth / 2 - 2;
rightGoal.y = fieldCenterY;
rightGoal.setColor(0xe74c3c);
rightGoal.width = goalWidth;
rightGoal.height = goalHeight;
game.addChild(rightGoal);
// Ball
var ball = new Ball();
var ballSize = 90;
ball.width = ballSize;
ball.height = ballSize;
ball.x = fieldCenterX;
ball.y = fieldCenterY;
game.addChild(ball);
// Player 1 (human, blue)
var player1 = new Player();
var playerSize = 160;
player1.width = playerSize;
player1.height = playerSize;
player1.setColor(0x3498db);
player1.setPosition(fieldLeft + 300, fieldCenterY);
game.addChild(player1);
// Player 2 (AI, red)
var player2 = new Player();
player2.width = playerSize;
player2.height = playerSize;
player2.setColor(0xe74c3c);
player2.setPosition(fieldRight - 300, fieldCenterY);
game.addChild(player2);
// Score
var score1 = 0;
var score2 = 0;
var scoreTxt = new Text2('0 : 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Timer
var gameDuration = 60; // seconds
var timeLeft = gameDuration;
var timerTxt = new Text2('01:00', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timerTxt);
timerTxt.y = 120;
// Place score and timer
scoreTxt.x = 0;
scoreTxt.y = 0;
timerTxt.x = 0;
// Dragging
var dragNode = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Helper: clamp position to field
function clampToField(x, y, radius) {
var cx = Math.max(fieldLeft + radius, Math.min(fieldRight - radius, x));
var cy = Math.max(fieldTop + radius, Math.min(fieldBottom - radius, y));
return {
x: cx,
y: cy
};
}
// Helper: check collision between two circles
function circlesCollide(ax, ay, ar, bx, by, br) {
var dx = ax - bx;
var dy = ay - by;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < ar + br;
}
// Helper: resolve collision between two circles (elastic)
function resolveCircleCollision(a, b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) return;
var overlap = a.radius + b.radius - dist;
if (overlap > 0) {
// Push them apart
var nx = dx / dist;
var ny = dy / dist;
a.x -= nx * overlap / 2;
a.y -= ny * overlap / 2;
b.x += nx * overlap / 2;
b.y += ny * overlap / 2;
// Exchange velocity (simple elastic)
var k = 0.7;
var dvx = b.vx - a.vx;
var dvy = b.vy - a.vy;
var impact = dvx * nx + dvy * ny;
if (impact < 0) {
var impulse = -impact * k;
a.vx -= impulse * nx;
a.vy -= impulse * ny;
b.vx += impulse * nx;
b.vy += impulse * ny;
}
}
}
// Helper: check if ball is in goal
function isGoal(ball) {
// Left goal
if (ball.x - ball.radius < fieldLeft - goalWidth / 2 + 10 && ball.y > goalTop && ball.y < goalBottom) {
return 2; // Player 2 scores
}
// Right goal
if (ball.x + ball.radius > fieldRight + goalWidth / 2 - 10 && ball.y > goalTop && ball.y < goalBottom) {
return 1; // Player 1 scores
}
return 0;
}
// Update score display
function updateScore() {
scoreTxt.setText(score1 + " : " + score2);
}
// Update timer display
function updateTimer() {
var min = Math.floor(timeLeft / 60);
var sec = Math.floor(timeLeft % 60);
var t = (min < 10 ? "0" : "") + min + ":" + (sec < 10 ? "0" : "") + sec;
timerTxt.setText(t);
}
// Reset positions after goal
function resetPositions() {
ball.reset();
player1.setPosition(fieldLeft + 300, fieldCenterY);
player2.setPosition(fieldRight - 300, fieldCenterY);
}
// AI logic for player2
function aiMove() {
// Simple AI: move towards ball, but stay on own half
var targetX = Math.max(fieldCenterX + 40, Math.min(ball.x, fieldRight - player2.radius));
var targetY = Math.max(fieldTop + player2.radius, Math.min(ball.y, fieldBottom - player2.radius));
player2.targetX = targetX;
player2.targetY = targetY;
}
// Game move handler (dragging player1)
function handleMove(x, y, obj) {
if (dragNode === player1) {
// Clamp to field
var pos = clampToField(x - dragOffsetX, y - dragOffsetY, player1.radius);
player1.x = pos.x;
player1.y = pos.y;
player1.vx = 0;
player1.vy = 0;
}
}
game.move = handleMove;
// Down handler (start drag if on player1)
game.down = function (x, y, obj) {
// Only allow drag if touch is on player1
var dx = x - player1.x;
var dy = y - player1.y;
if (dx * dx + dy * dy < player1.radius * player1.radius) {
dragNode = player1;
dragNode.isDragging = true;
dragOffsetX = x - player1.x;
dragOffsetY = y - player1.y;
}
};
game.up = function (x, y, obj) {
if (dragNode === player1) {
dragNode.isDragging = false;
dragNode = null;
}
};
// Game update loop
game.update = function () {
// Timer
if (LK.ticks % 60 === 0 && timeLeft > 0) {
timeLeft -= 1;
updateTimer();
if (timeLeft <= 0) {
// Game over
if (score1 > score2) {
LK.showYouWin();
} else if (score2 > score1) {
LK.showGameOver();
} else {
LK.showGameOver();
}
return;
}
}
// AI
aiMove();
// Update players and ball
player1.update();
player2.update();
ball.update();
// Player-ball collision
if (circlesCollide(player1.x, player1.y, player1.radius, ball.x, ball.y, ball.radius)) {
// Calculate impact direction
var dx = ball.x - player1.x;
var dy = ball.y - player1.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
var nx = dx / dist;
var ny = dy / dist;
// Ball gets velocity from player movement or drag
var impactVx = player1.vx;
var impactVy = player1.vy;
if (player1.isDragging) {
// If dragging, use difference between last and current position
impactVx = player1.x - (player1.x - player1.vx);
impactVy = player1.y - (player1.y - player1.vy);
}
// Add velocity to ball
ball.vx += nx * 18 + impactVx * 0.5;
ball.vy += ny * 18 + impactVy * 0.5;
// Separate
resolveCircleCollision(player1, ball);
}
if (circlesCollide(player2.x, player2.y, player2.radius, ball.x, ball.y, ball.radius)) {
var dx2 = ball.x - player2.x;
var dy2 = ball.y - player2.y;
var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
var nx2 = dx2 / dist2;
var ny2 = dy2 / dist2;
ball.vx += nx2 * 18 + player2.vx * 0.5;
ball.vy += ny2 * 18 + player2.vy * 0.5;
resolveCircleCollision(player2, ball);
}
// Player-player collision
if (circlesCollide(player1.x, player1.y, player1.radius, player2.x, player2.y, player2.radius)) {
resolveCircleCollision(player1, player2);
}
// Goal check
var goal = isGoal(ball);
if (goal === 1) {
score1 += 1;
updateScore();
LK.effects.flashScreen(0x3498db, 500);
resetPositions();
} else if (goal === 2) {
score2 += 1;
updateScore();
LK.effects.flashScreen(0xe74c3c, 500);
resetPositions();
}
};
// Initial score/timer display
updateScore();
updateTimer(); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,462 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Ball class
+var Ball = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach ball asset (ellipse, white)
+ var ballAsset = self.attachAsset('ball', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Ball physics properties
+ self.radius = ballAsset.width / 2;
+ self.vx = 0;
+ self.vy = 0;
+ self.friction = 0.985; // Friction for slowing down
+ self.maxSpeed = 60;
+ // Ball update: move, apply friction, clamp speed, bounce off walls
+ self.update = function () {
+ // Move
+ self.x += self.vx;
+ self.y += self.vy;
+ // Friction
+ self.vx *= self.friction;
+ self.vy *= self.friction;
+ // Clamp speed
+ var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
+ if (speed > self.maxSpeed) {
+ self.vx = self.vx / speed * self.maxSpeed;
+ self.vy = self.vy / speed * self.maxSpeed;
+ }
+ // Bounce off field edges (except goals)
+ // Top
+ if (self.y - self.radius < fieldTop) {
+ self.y = fieldTop + self.radius;
+ self.vy = -self.vy * 0.7;
+ }
+ // Bottom
+ if (self.y + self.radius > fieldBottom) {
+ self.y = fieldBottom - self.radius;
+ self.vy = -self.vy * 0.7;
+ }
+ // Left wall (except goal area)
+ if (self.x - self.radius < fieldLeft) {
+ // If not in goal area, bounce
+ if (self.y < goalTop || self.y > goalBottom) {
+ self.x = fieldLeft + self.radius;
+ self.vx = -self.vx * 0.7;
+ }
+ }
+ // Right wall (except goal area)
+ if (self.x + self.radius > fieldRight) {
+ if (self.y < goalTop || self.y > goalBottom) {
+ self.x = fieldRight - self.radius;
+ self.vx = -self.vx * 0.7;
+ }
+ }
+ };
+ // Reset ball to center
+ self.reset = function () {
+ self.x = fieldCenterX;
+ self.y = fieldCenterY;
+ self.vx = 0;
+ self.vy = 0;
+ };
+ return self;
+});
+// Goal class (for left and right goals)
+var Goal = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach goal asset (rectangle)
+ var goalAsset = self.attachAsset('goal', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.width = goalAsset.width;
+ self.height = goalAsset.height;
+ // Set color
+ self.setColor = function (color) {
+ goalAsset.color = color;
+ };
+ return self;
+});
+// Player class (for both human and AI)
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach player asset (ellipse, blue or red)
+ var playerAsset = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = playerAsset.width / 2;
+ self.vx = 0;
+ self.vy = 0;
+ self.maxSpeed = 40;
+ self.friction = 0.90;
+ // For AI: target position
+ self.targetX = self.x;
+ self.targetY = self.y;
+ // For drag
+ self.isDragging = false;
+ // Update: move, apply friction, clamp speed, stay in field
+ self.update = function () {
+ // If not dragging (AI or after drag), move towards target
+ if (!self.isDragging) {
+ var dx = self.targetX - self.x;
+ var dy = self.targetY - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist > 1) {
+ var moveSpeed = Math.min(self.maxSpeed, dist * 0.3);
+ self.vx = dx / dist * moveSpeed;
+ self.vy = dy / dist * moveSpeed;
+ } else {
+ self.vx *= self.friction;
+ self.vy *= self.friction;
+ }
+ }
+ // Move
+ self.x += self.vx;
+ self.y += self.vy;
+ // Clamp to field
+ if (self.x - self.radius < fieldLeft) self.x = fieldLeft + self.radius;
+ if (self.x + self.radius > fieldRight) self.x = fieldRight - self.radius;
+ if (self.y - self.radius < fieldTop) self.y = fieldTop + self.radius;
+ if (self.y + self.radius > fieldBottom) self.y = fieldBottom - self.radius;
+ };
+ // Set color
+ self.setColor = function (color) {
+ playerAsset.color = color;
+ };
+ // Set position instantly
+ self.setPosition = function (x, y) {
+ self.x = x;
+ self.y = y;
+ self.vx = 0;
+ self.vy = 0;
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x1a7a2e // Green field
+});
+
+/****
+* Game Code
+****/
+// Field dimensions
+var fieldMargin = 120;
+var fieldLeft = fieldMargin;
+var fieldRight = 2048 - fieldMargin;
+var fieldTop = 300;
+var fieldBottom = 2732 - 300;
+var fieldWidth = fieldRight - fieldLeft;
+var fieldHeight = fieldBottom - fieldTop;
+var fieldCenterX = (fieldLeft + fieldRight) / 2;
+var fieldCenterY = (fieldTop + fieldBottom) / 2;
+// Goal dimensions
+var goalWidth = 60;
+var goalHeight = 500;
+var goalTop = fieldCenterY - goalHeight / 2;
+var goalBottom = fieldCenterY + goalHeight / 2;
+// Create field background (rectangle, green)
+var fieldBg = LK.getAsset('field', {
+ width: fieldWidth,
+ height: fieldHeight,
+ color: 0x2ecc40,
+ shape: 'box',
+ anchorX: 0,
+ anchorY: 0,
+ x: fieldLeft,
+ y: fieldTop
+});
+game.addChild(fieldBg);
+// Center line
+var centerLine = LK.getAsset('centerLine', {
+ width: fieldWidth,
+ height: 12,
+ color: 0xffffff,
+ shape: 'box',
+ anchorX: 0,
+ anchorY: 0.5,
+ x: fieldLeft,
+ y: fieldCenterY
+});
+game.addChild(centerLine);
+// Center circle
+var centerCircle = LK.getAsset('centerCircle', {
+ width: 320,
+ height: 320,
+ color: 0xffffff,
+ shape: 'ellipse',
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: fieldCenterX,
+ y: fieldCenterY
+});
+centerCircle.alpha = 0.2;
+game.addChild(centerCircle);
+// Left goal
+var leftGoal = new Goal();
+leftGoal.x = fieldLeft - goalWidth / 2 + 2;
+leftGoal.y = fieldCenterY;
+leftGoal.setColor(0x3498db);
+leftGoal.width = goalWidth;
+leftGoal.height = goalHeight;
+game.addChild(leftGoal);
+// Right goal
+var rightGoal = new Goal();
+rightGoal.x = fieldRight + goalWidth / 2 - 2;
+rightGoal.y = fieldCenterY;
+rightGoal.setColor(0xe74c3c);
+rightGoal.width = goalWidth;
+rightGoal.height = goalHeight;
+game.addChild(rightGoal);
+// Ball
+var ball = new Ball();
+var ballSize = 90;
+ball.width = ballSize;
+ball.height = ballSize;
+ball.x = fieldCenterX;
+ball.y = fieldCenterY;
+game.addChild(ball);
+// Player 1 (human, blue)
+var player1 = new Player();
+var playerSize = 160;
+player1.width = playerSize;
+player1.height = playerSize;
+player1.setColor(0x3498db);
+player1.setPosition(fieldLeft + 300, fieldCenterY);
+game.addChild(player1);
+// Player 2 (AI, red)
+var player2 = new Player();
+player2.width = playerSize;
+player2.height = playerSize;
+player2.setColor(0xe74c3c);
+player2.setPosition(fieldRight - 300, fieldCenterY);
+game.addChild(player2);
+// Score
+var score1 = 0;
+var score2 = 0;
+var scoreTxt = new Text2('0 : 0', {
+ size: 120,
+ fill: 0xFFFFFF
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+// Timer
+var gameDuration = 60; // seconds
+var timeLeft = gameDuration;
+var timerTxt = new Text2('01:00', {
+ size: 80,
+ fill: 0xFFFFFF
+});
+timerTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(timerTxt);
+timerTxt.y = 120;
+// Place score and timer
+scoreTxt.x = 0;
+scoreTxt.y = 0;
+timerTxt.x = 0;
+// Dragging
+var dragNode = null;
+var dragOffsetX = 0;
+var dragOffsetY = 0;
+// Helper: clamp position to field
+function clampToField(x, y, radius) {
+ var cx = Math.max(fieldLeft + radius, Math.min(fieldRight - radius, x));
+ var cy = Math.max(fieldTop + radius, Math.min(fieldBottom - radius, y));
+ return {
+ x: cx,
+ y: cy
+ };
+}
+// Helper: check collision between two circles
+function circlesCollide(ax, ay, ar, bx, by, br) {
+ var dx = ax - bx;
+ var dy = ay - by;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ return dist < ar + br;
+}
+// Helper: resolve collision between two circles (elastic)
+function resolveCircleCollision(a, b) {
+ var dx = b.x - a.x;
+ var dy = b.y - a.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist === 0) return;
+ var overlap = a.radius + b.radius - dist;
+ if (overlap > 0) {
+ // Push them apart
+ var nx = dx / dist;
+ var ny = dy / dist;
+ a.x -= nx * overlap / 2;
+ a.y -= ny * overlap / 2;
+ b.x += nx * overlap / 2;
+ b.y += ny * overlap / 2;
+ // Exchange velocity (simple elastic)
+ var k = 0.7;
+ var dvx = b.vx - a.vx;
+ var dvy = b.vy - a.vy;
+ var impact = dvx * nx + dvy * ny;
+ if (impact < 0) {
+ var impulse = -impact * k;
+ a.vx -= impulse * nx;
+ a.vy -= impulse * ny;
+ b.vx += impulse * nx;
+ b.vy += impulse * ny;
+ }
+ }
+}
+// Helper: check if ball is in goal
+function isGoal(ball) {
+ // Left goal
+ if (ball.x - ball.radius < fieldLeft - goalWidth / 2 + 10 && ball.y > goalTop && ball.y < goalBottom) {
+ return 2; // Player 2 scores
+ }
+ // Right goal
+ if (ball.x + ball.radius > fieldRight + goalWidth / 2 - 10 && ball.y > goalTop && ball.y < goalBottom) {
+ return 1; // Player 1 scores
+ }
+ return 0;
+}
+// Update score display
+function updateScore() {
+ scoreTxt.setText(score1 + " : " + score2);
+}
+// Update timer display
+function updateTimer() {
+ var min = Math.floor(timeLeft / 60);
+ var sec = Math.floor(timeLeft % 60);
+ var t = (min < 10 ? "0" : "") + min + ":" + (sec < 10 ? "0" : "") + sec;
+ timerTxt.setText(t);
+}
+// Reset positions after goal
+function resetPositions() {
+ ball.reset();
+ player1.setPosition(fieldLeft + 300, fieldCenterY);
+ player2.setPosition(fieldRight - 300, fieldCenterY);
+}
+// AI logic for player2
+function aiMove() {
+ // Simple AI: move towards ball, but stay on own half
+ var targetX = Math.max(fieldCenterX + 40, Math.min(ball.x, fieldRight - player2.radius));
+ var targetY = Math.max(fieldTop + player2.radius, Math.min(ball.y, fieldBottom - player2.radius));
+ player2.targetX = targetX;
+ player2.targetY = targetY;
+}
+// Game move handler (dragging player1)
+function handleMove(x, y, obj) {
+ if (dragNode === player1) {
+ // Clamp to field
+ var pos = clampToField(x - dragOffsetX, y - dragOffsetY, player1.radius);
+ player1.x = pos.x;
+ player1.y = pos.y;
+ player1.vx = 0;
+ player1.vy = 0;
+ }
+}
+game.move = handleMove;
+// Down handler (start drag if on player1)
+game.down = function (x, y, obj) {
+ // Only allow drag if touch is on player1
+ var dx = x - player1.x;
+ var dy = y - player1.y;
+ if (dx * dx + dy * dy < player1.radius * player1.radius) {
+ dragNode = player1;
+ dragNode.isDragging = true;
+ dragOffsetX = x - player1.x;
+ dragOffsetY = y - player1.y;
+ }
+};
+game.up = function (x, y, obj) {
+ if (dragNode === player1) {
+ dragNode.isDragging = false;
+ dragNode = null;
+ }
+};
+// Game update loop
+game.update = function () {
+ // Timer
+ if (LK.ticks % 60 === 0 && timeLeft > 0) {
+ timeLeft -= 1;
+ updateTimer();
+ if (timeLeft <= 0) {
+ // Game over
+ if (score1 > score2) {
+ LK.showYouWin();
+ } else if (score2 > score1) {
+ LK.showGameOver();
+ } else {
+ LK.showGameOver();
+ }
+ return;
+ }
+ }
+ // AI
+ aiMove();
+ // Update players and ball
+ player1.update();
+ player2.update();
+ ball.update();
+ // Player-ball collision
+ if (circlesCollide(player1.x, player1.y, player1.radius, ball.x, ball.y, ball.radius)) {
+ // Calculate impact direction
+ var dx = ball.x - player1.x;
+ var dy = ball.y - player1.y;
+ var dist = Math.sqrt(dx * dx + dy * dy) || 1;
+ var nx = dx / dist;
+ var ny = dy / dist;
+ // Ball gets velocity from player movement or drag
+ var impactVx = player1.vx;
+ var impactVy = player1.vy;
+ if (player1.isDragging) {
+ // If dragging, use difference between last and current position
+ impactVx = player1.x - (player1.x - player1.vx);
+ impactVy = player1.y - (player1.y - player1.vy);
+ }
+ // Add velocity to ball
+ ball.vx += nx * 18 + impactVx * 0.5;
+ ball.vy += ny * 18 + impactVy * 0.5;
+ // Separate
+ resolveCircleCollision(player1, ball);
+ }
+ if (circlesCollide(player2.x, player2.y, player2.radius, ball.x, ball.y, ball.radius)) {
+ var dx2 = ball.x - player2.x;
+ var dy2 = ball.y - player2.y;
+ var dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
+ var nx2 = dx2 / dist2;
+ var ny2 = dy2 / dist2;
+ ball.vx += nx2 * 18 + player2.vx * 0.5;
+ ball.vy += ny2 * 18 + player2.vy * 0.5;
+ resolveCircleCollision(player2, ball);
+ }
+ // Player-player collision
+ if (circlesCollide(player1.x, player1.y, player1.radius, player2.x, player2.y, player2.radius)) {
+ resolveCircleCollision(player1, player2);
+ }
+ // Goal check
+ var goal = isGoal(ball);
+ if (goal === 1) {
+ score1 += 1;
+ updateScore();
+ LK.effects.flashScreen(0x3498db, 500);
+ resetPositions();
+ } else if (goal === 2) {
+ score2 += 1;
+ updateScore();
+ LK.effects.flashScreen(0xe74c3c, 500);
+ resetPositions();
+ }
+};
+// Initial score/timer display
+updateScore();
+updateTimer();
\ No newline at end of file