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
/**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1976d2 // Blue field background }); /**** * Game Code ****/ ; // --- Timer --- var timerSeconds = 0; var timerRunning = true; var timerTxt = new Text2('0:00', { size: 90, fill: 0xffffff, font: "Impact, Arial Black, Tahoma" }); timerTxt.anchor.set(0.5, 1); timerTxt.x = LK.gui.width / 2; // Place timer at top center LK.gui.top.addChild(timerTxt); timerTxt.y = 0; ; // Track dragging state for both players by touchId var draggingPlayers = {}; // { touchId: player } // Convert global (event) coordinates to local game coordinates function toGameCoords(x, y) { // LK events provide coordinates in game space already return { x: x, y: y }; } // Handle touch/mouse down: start dragging if on player game.down = function (x, y, obj) { // Use obj.event.identifier for multi-touch, fallback to 0 for mouse var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0; // Check if the down event is on the blue player var dx = x - player.x; var dy = y - player.y; var r = player.width / 2; if (dx * dx + dy * dy <= r * r) { draggingPlayers[touchId] = player; return; } // Check if the down event is on the red player (opponent) // (Red is AI controlled, so do not allow dragging) // var dx2 = x - opponent.x; // var dy2 = y - opponent.y; // var r2 = opponent.width / 2; // if (dx2 * dx2 + dy2 * dy2 <= r2 * r2) { // draggingPlayers[touchId] = opponent; // return; // } }; // Handle touch/mouse move: move player if dragging game.move = function (x, y, obj) { // Use obj.event.identifier for multi-touch, fallback to 0 for mouse var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0; var draggingPlayer = draggingPlayers[touchId]; if (draggingPlayer) { if (draggingPlayer === player) { // Blue player (top side) var minX = field.x - field.width / 2 + player.width / 2; var maxX = field.x + field.width / 2 - player.width / 2; var minY = field.y - field.height / 2 + player.height / 2; var maxY = field.y; // Center line is the max for top player draggingPlayer.x = Math.max(minX, Math.min(maxX, x)); draggingPlayer.y = Math.max(minY, Math.min(maxY - player.height / 2, y)); } else if (draggingPlayer === opponent) { // Red player (bottom side) - AI controlled, do not allow dragging } } }; // Handle touch/mouse up: stop dragging game.up = function (x, y, obj) { // Use obj.event.identifier for multi-touch, fallback to 0 for mouse var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0; delete draggingPlayers[touchId]; }; ; ; // Track last intersection state between player and ball var lastPlayerBallIntersecting = false; // Update loop to check for player touching ball game.update = function () { // --- Timer update --- if (timerRunning) { if (typeof game._lastTimerTick === "undefined") { game._lastTimerTick = Date.now(); } var now = Date.now(); if (now - game._lastTimerTick >= 1000) { timerSeconds += 1; game._lastTimerTick += 1000; // Format as M:SS var min = Math.floor(timerSeconds / 60); var sec = timerSeconds % 60; timerTxt.setText(min + ":" + (sec < 10 ? "0" : "") + sec); } } // --- AI logic: Move red (opponent) player toward the ball, sometimes randomly --- // Only move if timer is running (not after win/game over) if (timerRunning) { // AI target: try to stay on the same side as the ball (bottom half of field) // Only move if ball is on or below center line var aiTargetY = Math.max(field.y, ball.y); // Clamp AI movement to bottom half of field var minX = field.x - field.width / 2 + opponent.width / 2; var maxX = field.x + field.width / 2 - opponent.width / 2; var minY = field.y; var maxY = field.y + field.height / 2 - opponent.height / 2; // --- Random movement logic --- if (typeof opponent.aiRandomCooldown === "undefined") opponent.aiRandomCooldown = 0; if (typeof opponent.aiRandomTargetX === "undefined") opponent.aiRandomTargetX = opponent.x; if (typeof opponent.aiRandomTargetY === "undefined") opponent.aiRandomTargetY = opponent.y; if (typeof opponent.aiRandomActive === "undefined") opponent.aiRandomActive = false; // Occasionally (about every 1.5-3s), pick a random target in the AI's allowed area if (opponent.aiRandomCooldown <= 0 && Math.random() < 0.012) { opponent.aiRandomActive = true; // Pick a random point in the lower half of the field opponent.aiRandomTargetX = minX + Math.random() * (maxX - minX); opponent.aiRandomTargetY = minY + Math.random() * (maxY - minY); // Random movement lasts 30-60 frames opponent.aiRandomCooldown = 30 + Math.floor(Math.random() * 30); } // If random movement is active, move toward the random target if (opponent.aiRandomActive && opponent.aiRandomCooldown > 0) { var targetX = opponent.aiRandomTargetX; var targetY = opponent.aiRandomTargetY; opponent.aiRandomCooldown--; // If close to the random target, stop random movement var distToRandom = Math.sqrt((targetX - opponent.x) * (targetX - opponent.x) + (targetY - opponent.y) * (targetY - opponent.y)); if (distToRandom < 40 || opponent.aiRandomCooldown <= 0) { opponent.aiRandomActive = false; opponent.aiRandomCooldown = 60 + Math.floor(Math.random() * 60); // Wait before next random } } else { // Normal AI: follow the ball var targetX = Math.max(minX, Math.min(maxX, ball.x)); var targetY = Math.max(minY, Math.min(maxY, aiTargetY)); if (opponent.aiRandomCooldown > 0) { opponent.aiRandomCooldown--; } } // Move opponent toward target position with a max speed var aiSpeed = 32; // px per frame (tune for difficulty) var dx = targetX - opponent.x; var dy = targetY - opponent.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; if (dist > aiSpeed) { opponent.x += dx / dist * aiSpeed; opponent.y += dy / dist * aiSpeed; } else { opponent.x = targetX; opponent.y = targetY; } } // Check if player is touching the ball (collision/intersection) var isIntersecting = player.intersects(ball); // Detect the exact moment player starts touching the ball if (!lastPlayerBallIntersecting && isIntersecting) { // Calculate direction from player to ball var dx = ball.x - player.x; var dy = ball.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; // Give the ball a velocity away from the player (EVEN FASTER than before) ball.vx = dx / dist * 70; ball.vy = dy / dist * 70; LK.effects.flashObject(ball, 0x1976d2, 200); } // AI: Red player can also kick the ball if touching, from any side if (opponent.intersects(ball)) { // Check if ball is in a corner var minBallX = field.x - field.width / 2 + ball.width / 2; var maxBallX = field.x + field.width / 2 - ball.width / 2; var minBallY = field.y - field.height / 2 + ball.height / 2; var maxBallY = field.y + field.height / 2 - ball.height / 2; var atLeft = ball.x <= minBallX + 1; var atRight = ball.x >= maxBallX - 1; var atTop = ball.y <= minBallY + 1; var atBottom = ball.y >= maxBallY - 1; // --- Ball stuck detection --- if (typeof ball.stuckFrames === "undefined") ball.stuckFrames = 0; if (typeof ball.lastStuckX === "undefined") ball.lastStuckX = ball.x; if (typeof ball.lastStuckY === "undefined") ball.lastStuckY = ball.y; // If the ball hasn't moved much in 10 frames, consider it stuck var stuckThreshold = 6; if (Math.abs(ball.x - ball.lastStuckX) < stuckThreshold && Math.abs(ball.y - ball.lastStuckY) < stuckThreshold) { ball.stuckFrames++; } else { ball.stuckFrames = 0; ball.lastStuckX = ball.x; ball.lastStuckY = ball.y; } // If the ball is stuck for too long, red gives up and the game finishes if (ball.stuckFrames > 90) { // 90 frames ~1.5 seconds at 60fps timerRunning = false; LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); return; } var dxr, dyr; if ((atLeft || atRight) && (atTop || atBottom)) { // Ball is in a corner, AI tries to hit it toward the center of the field dxr = field.x - ball.x; dyr = field.y - ball.y; } else if (ball.stuckFrames > 12) { // Ball is stuck: AI tries to hit the right side of the ball // Always aim to the right (positive X direction) dxr = 1; dyr = 0; // Add a little bias toward the field center to avoid infinite right wall hits dxr += (field.x - ball.x) * 0.005; dyr += (field.y - ball.y) * 0.01; } else { // Normal: hit from AI to ball direction dxr = ball.x - opponent.x; dyr = ball.y - opponent.y; } var distr = Math.sqrt(dxr * dxr + dyr * dyr) || 1; ball.vx = dxr / distr * 50; ball.vy = dyr / distr * 50; LK.effects.flashObject(ball, 0xd32f2f, 200); } // Ball activation: Ball is stopped at start, but after first touch, it never stops and goes faster if (typeof ball.activated === "undefined") ball.activated = false; if (!ball.activated && (player.intersects(ball) || opponent.intersects(ball))) { ball.activated = true; // Give a small nudge in a random direction if ball is perfectly still if (ball.vx === 0 && ball.vy === 0) { var angle = Math.random() * Math.PI * 2; ball.vx = Math.cos(angle) * 18; ball.vy = Math.sin(angle) * 18; } } // Move the ball if it has velocity if (typeof ball.vx === "number" && typeof ball.vy === "number") { // --- Ball bounce off goal posts (horizontal) --- // Track last intersection for each post if (typeof ball.lastLeftGoalPostLeftIntersect === "undefined") ball.lastLeftGoalPostLeftIntersect = false; if (typeof ball.lastLeftGoalPostRightIntersect === "undefined") ball.lastLeftGoalPostRightIntersect = false; if (typeof ball.lastRightGoalPostLeftIntersect === "undefined") ball.lastRightGoalPostLeftIntersect = false; if (typeof ball.lastRightGoalPostRightIntersect === "undefined") ball.lastRightGoalPostRightIntersect = false; // Left goal post left var leftLeftNow = ball.intersects(leftGoalPostLeft); if (!ball.lastLeftGoalPostLeftIntersect && leftLeftNow) { // Bounce: reflect velocity, add a little randomness var dx = ball.x - (leftGoalPostLeft.x - leftGoalPostLeft.width / 2); var dy = ball.y - leftGoalPostLeft.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30); ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30); LK.effects.flashObject(ball, 0xffff00, 120); } ball.lastLeftGoalPostLeftIntersect = leftLeftNow; // Left goal post right var leftRightNow = ball.intersects(leftGoalPostRight); if (!ball.lastLeftGoalPostRightIntersect && leftRightNow) { var dx = ball.x - (leftGoalPostRight.x + leftGoalPostRight.width / 2); var dy = ball.y - leftGoalPostRight.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30); ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30); LK.effects.flashObject(ball, 0xffff00, 120); } ball.lastLeftGoalPostRightIntersect = leftRightNow; // Right goal post left var rightLeftNow = ball.intersects(rightGoalPostLeft); if (!ball.lastRightGoalPostLeftIntersect && rightLeftNow) { var dx = ball.x - (rightGoalPostLeft.x - rightGoalPostLeft.width / 2); var dy = ball.y - rightGoalPostLeft.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30); ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30); LK.effects.flashObject(ball, 0xffff00, 120); } ball.lastRightGoalPostLeftIntersect = rightLeftNow; // Right goal post right var rightRightNow = ball.intersects(rightGoalPostRight); if (!ball.lastRightGoalPostRightIntersect && rightRightNow) { var dx = ball.x - (rightGoalPostRight.x + rightGoalPostRight.width / 2); var dy = ball.y - rightGoalPostRight.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30); ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30); LK.effects.flashObject(ball, 0xffff00, 120); } ball.lastRightGoalPostRightIntersect = rightRightNow; // Track lastX for side detection if (typeof ball.lastX === "undefined") ball.lastX = ball.x; // Detect crossing from left to right side (player to opponent) var centerX = field.x; if (ball.lastX <= centerX && ball.x > centerX) { // Ball just crossed to right side, speed up! ball.vx *= 1.25; ball.vy *= 1.25; } // Detect crossing from right to left side (opponent to player) if (ball.lastX >= centerX && ball.x < centerX) { // Ball just crossed to left side, speed up! ball.vx *= 1.25; ball.vy *= 1.25; } if (ball.activated) { ball.x += ball.vx; ball.y += ball.vy; } // Friction ball.vx *= 0.96; ball.vy *= 0.96; // Clamp to field bounds and bounce off corners (horizontal) var minBallX = field.x - field.width / 2 + ball.width / 2; var maxBallX = field.x + field.width / 2 - ball.width / 2; var minBallY = field.y - field.height / 2 + ball.height / 2; var maxBallY = field.y + field.height / 2 - ball.height / 2; // Detect if ball is in a corner (within 1px of both X and Y bounds) var atLeft = ball.x <= minBallX + 1; var atRight = ball.x >= maxBallX - 1; var atTop = ball.y <= minBallY + 1; var atBottom = ball.y >= maxBallY - 1; // Bounce off corners: if at a corner, reflect both vx and vy if (atLeft && atTop || atLeft && atBottom || atRight && atTop || atRight && atBottom) { // Snap to corner ball.x = atLeft ? minBallX : maxBallX; ball.y = atTop ? minBallY : maxBallY; ball.vx *= -0.5; ball.vy *= -0.5; } else { // Bounce off left/right walls if (ball.x < minBallX) { ball.x = minBallX; ball.vx *= -0.5; } if (ball.x > maxBallX) { ball.x = maxBallX; ball.vx *= -0.5; } // Bounce off top/bottom walls if (ball.y < minBallY) { ball.y = minBallY; ball.vy *= -0.5; } if (ball.y > maxBallY) { ball.y = maxBallY; ball.vy *= -0.5; } } // --- Goal detection (horizontal) --- // If ball intersects leftGoal or rightGoal, score! // Only trigger on the exact frame the ball enters the goal (use lastBallInLeftGoal/lastBallInRightGoal) if (typeof ball.lastInLeftGoal === "undefined") ball.lastInLeftGoal = false; if (typeof ball.lastInRightGoal === "undefined") ball.lastInRightGoal = false; var inLeftGoal = ball.intersects(leftGoal); var inRightGoal = ball.intersects(rightGoal); // Score for blue (player) if ball enters bottom goal if (!ball.lastInRightGoal && inRightGoal) { timerRunning = false; LK.effects.flashScreen(0x00ff00, 600); LK.showYouWin(); return; } // Score for red (opponent) if ball enters top goal if (!ball.lastInLeftGoal && inLeftGoal) { timerRunning = false; LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); return; } ball.lastInLeftGoal = inLeftGoal; ball.lastInRightGoal = inRightGoal; // Prevent the ball from ever stopping: enforce a minimum velocity if it's moving var minBallSpeed = ball.activated ? 8 : 1.2; if (typeof ball.vx === "number" && typeof ball.vy === "number") { var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); if (speed < minBallSpeed) { if (speed === 0) { // If completely stopped, nudge in a random direction var angle = Math.random() * Math.PI * 2; ball.vx = Math.cos(angle) * minBallSpeed; ball.vy = Math.sin(angle) * minBallSpeed; } else { // If moving but too slow, scale up to minimum speed ball.vx = ball.vx / (speed || 1) * minBallSpeed; ball.vy = ball.vy / (speed || 1) * minBallSpeed; } } } // Never allow the ball to stop: if both vx and vy are 0, nudge in a random direction if (ball.activated && typeof ball.vx === "number" && typeof ball.vy === "number" && ball.vx === 0 && ball.vy === 0) { var angle = Math.random() * Math.PI * 2; ball.vx = Math.cos(angle) * minBallSpeed; ball.vy = Math.sin(angle) * minBallSpeed; } // Update lastX for next frame ball.lastX = ball.x; // --- Goal detection --- // If ball intersects leftGoal or rightGoal, score! // Only trigger on the exact frame the ball enters the goal (use lastBallInLeftGoal/lastBallInRightGoal) if (typeof ball.lastInLeftGoal === "undefined") ball.lastInLeftGoal = false; if (typeof ball.lastInRightGoal === "undefined") ball.lastInRightGoal = false; var inLeftGoal = ball.intersects(leftGoal); var inRightGoal = ball.intersects(rightGoal); // Score for blue (player) if ball enters right goal if (!ball.lastInRightGoal && inRightGoal) { timerRunning = false; LK.effects.flashScreen(0x00ff00, 600); LK.showYouWin(); return; } // Score for red (opponent) if ball enters left goal if (!ball.lastInLeftGoal && inLeftGoal) { timerRunning = false; LK.effects.flashScreen(0xff0000, 600); LK.showGameOver(); return; } ball.lastInLeftGoal = inLeftGoal; ball.lastInRightGoal = inRightGoal; } // Update last intersection state lastPlayerBallIntersecting = isIntersecting; }; // Futsal ball (white) // Center circle (white, ellipse) // Center line (white, box) // Main field (futsal green) // Goal area (penalty box, white) // Goal (yellow, box) // Player (blue, ellipse) // --- VERTICAL FIELD (portrait) --- // Field: centered, tall, normal width var field = LK.getAsset('field', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(field); // Center line: horizontal (for portrait/vertical field) var centerLine = LK.getAsset('centerLine', { anchorX: 0.5, anchorY: 0.5, width: 1900, // match field width height: 10, // thin horizontal line x: 2048 / 2, y: 2732 / 2 }); game.addChild(centerLine); // Center circle var centerCircle = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(centerCircle); // Draw top goal (top in portrait) var topGoal = LK.getAsset('goal', { anchorX: 0.5, anchorY: 0.5, width: 400, // wide height: 40, // short x: 2048 / 2, // centered y: (2732 - 1200) / 2 + 20 // flush with top field edge }); game.addChild(topGoal); // Define leftGoal for goal detection (top goal is leftGoal in vertical/portrait) var leftGoal = topGoal; // Top goal posts (left and right) - horizontal orientation var goalPostWidth = 70; var goalPostHeight = 12; var goalPostOffsetY = topGoal.height / 2 - goalPostHeight / 2; var topGoalPostLeft = LK.getAsset('goal', { anchorX: 1.0, anchorY: 0.5, width: goalPostWidth, height: goalPostHeight, x: topGoal.x - topGoal.width / 2, // near the left of the goal y: topGoal.y - goalPostOffsetY }); game.addChild(topGoalPostLeft); var topGoalPostRight = LK.getAsset('goal', { anchorX: 0.0, anchorY: 0.5, width: goalPostWidth, height: goalPostHeight, x: topGoal.x + topGoal.width / 2, // near the right of the goal y: topGoal.y - goalPostOffsetY }); game.addChild(topGoalPostRight); // Define leftGoalPostLeft and leftGoalPostRight for collision logic // For vertical field, leftGoalPostLeft is the left post, leftGoalPostRight is the right post var leftGoalPostLeft = topGoalPostLeft; var leftGoalPostRight = topGoalPostRight; // Draw bottom goal (bottom in portrait) var bottomGoal = LK.getAsset('goal', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 40, x: 2048 / 2, // centered y: (2732 + 1200) / 2 - 20 // flush with bottom field edge }); game.addChild(bottomGoal); // Define rightGoal for goal detection (bottom goal is rightGoal in vertical/portrait) var rightGoal = bottomGoal; // Bottom goal posts (left and right) - horizontal orientation var bottomGoalPostLeft = LK.getAsset('goal', { anchorX: 1.0, anchorY: 0.5, width: goalPostWidth, height: goalPostHeight, x: bottomGoal.x - bottomGoal.width / 2, // near the left of the goal y: bottomGoal.y + goalPostOffsetY }); game.addChild(bottomGoalPostLeft); var bottomGoalPostRight = LK.getAsset('goal', { anchorX: 0.0, anchorY: 0.5, width: goalPostWidth, height: goalPostHeight, x: bottomGoal.x + bottomGoal.width / 2, // near the right of the goal y: bottomGoal.y + goalPostOffsetY }); game.addChild(bottomGoalPostRight); // Define rightGoalPostLeft and rightGoalPostRight for collision logic // For vertical field, rightGoalPostLeft is the left post, rightGoalPostRight is the right post var rightGoalPostLeft = bottomGoalPostLeft; var rightGoalPostRight = bottomGoalPostRight; // Draw ball at center var ball = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); ball.vx = 0; ball.vy = 0; game.addChild(ball); // Draw player at top side (blue) var player = LK.getAsset('playerBlue', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, // center y: 2732 / 2 - 350 // above center }); game.addChild(player); // Draw opponent at bottom side (red) var opponent = LK.getAsset('playerRed', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, // center y: 2732 / 2 + 350 // below center }); game.addChild(opponent); ;
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1976d2 // Blue field background
});
/****
* Game Code
****/
;
// --- Timer ---
var timerSeconds = 0;
var timerRunning = true;
var timerTxt = new Text2('0:00', {
size: 90,
fill: 0xffffff,
font: "Impact, Arial Black, Tahoma"
});
timerTxt.anchor.set(0.5, 1);
timerTxt.x = LK.gui.width / 2;
// Place timer at top center
LK.gui.top.addChild(timerTxt);
timerTxt.y = 0;
;
// Track dragging state for both players by touchId
var draggingPlayers = {}; // { touchId: player }
// Convert global (event) coordinates to local game coordinates
function toGameCoords(x, y) {
// LK events provide coordinates in game space already
return {
x: x,
y: y
};
}
// Handle touch/mouse down: start dragging if on player
game.down = function (x, y, obj) {
// Use obj.event.identifier for multi-touch, fallback to 0 for mouse
var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0;
// Check if the down event is on the blue player
var dx = x - player.x;
var dy = y - player.y;
var r = player.width / 2;
if (dx * dx + dy * dy <= r * r) {
draggingPlayers[touchId] = player;
return;
}
// Check if the down event is on the red player (opponent)
// (Red is AI controlled, so do not allow dragging)
// var dx2 = x - opponent.x;
// var dy2 = y - opponent.y;
// var r2 = opponent.width / 2;
// if (dx2 * dx2 + dy2 * dy2 <= r2 * r2) {
// draggingPlayers[touchId] = opponent;
// return;
// }
};
// Handle touch/mouse move: move player if dragging
game.move = function (x, y, obj) {
// Use obj.event.identifier for multi-touch, fallback to 0 for mouse
var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0;
var draggingPlayer = draggingPlayers[touchId];
if (draggingPlayer) {
if (draggingPlayer === player) {
// Blue player (top side)
var minX = field.x - field.width / 2 + player.width / 2;
var maxX = field.x + field.width / 2 - player.width / 2;
var minY = field.y - field.height / 2 + player.height / 2;
var maxY = field.y; // Center line is the max for top player
draggingPlayer.x = Math.max(minX, Math.min(maxX, x));
draggingPlayer.y = Math.max(minY, Math.min(maxY - player.height / 2, y));
} else if (draggingPlayer === opponent) {
// Red player (bottom side) - AI controlled, do not allow dragging
}
}
};
// Handle touch/mouse up: stop dragging
game.up = function (x, y, obj) {
// Use obj.event.identifier for multi-touch, fallback to 0 for mouse
var touchId = obj && obj.event && typeof obj.event.identifier !== "undefined" ? obj.event.identifier : 0;
delete draggingPlayers[touchId];
};
;
;
// Track last intersection state between player and ball
var lastPlayerBallIntersecting = false;
// Update loop to check for player touching ball
game.update = function () {
// --- Timer update ---
if (timerRunning) {
if (typeof game._lastTimerTick === "undefined") {
game._lastTimerTick = Date.now();
}
var now = Date.now();
if (now - game._lastTimerTick >= 1000) {
timerSeconds += 1;
game._lastTimerTick += 1000;
// Format as M:SS
var min = Math.floor(timerSeconds / 60);
var sec = timerSeconds % 60;
timerTxt.setText(min + ":" + (sec < 10 ? "0" : "") + sec);
}
}
// --- AI logic: Move red (opponent) player toward the ball, sometimes randomly ---
// Only move if timer is running (not after win/game over)
if (timerRunning) {
// AI target: try to stay on the same side as the ball (bottom half of field)
// Only move if ball is on or below center line
var aiTargetY = Math.max(field.y, ball.y);
// Clamp AI movement to bottom half of field
var minX = field.x - field.width / 2 + opponent.width / 2;
var maxX = field.x + field.width / 2 - opponent.width / 2;
var minY = field.y;
var maxY = field.y + field.height / 2 - opponent.height / 2;
// --- Random movement logic ---
if (typeof opponent.aiRandomCooldown === "undefined") opponent.aiRandomCooldown = 0;
if (typeof opponent.aiRandomTargetX === "undefined") opponent.aiRandomTargetX = opponent.x;
if (typeof opponent.aiRandomTargetY === "undefined") opponent.aiRandomTargetY = opponent.y;
if (typeof opponent.aiRandomActive === "undefined") opponent.aiRandomActive = false;
// Occasionally (about every 1.5-3s), pick a random target in the AI's allowed area
if (opponent.aiRandomCooldown <= 0 && Math.random() < 0.012) {
opponent.aiRandomActive = true;
// Pick a random point in the lower half of the field
opponent.aiRandomTargetX = minX + Math.random() * (maxX - minX);
opponent.aiRandomTargetY = minY + Math.random() * (maxY - minY);
// Random movement lasts 30-60 frames
opponent.aiRandomCooldown = 30 + Math.floor(Math.random() * 30);
}
// If random movement is active, move toward the random target
if (opponent.aiRandomActive && opponent.aiRandomCooldown > 0) {
var targetX = opponent.aiRandomTargetX;
var targetY = opponent.aiRandomTargetY;
opponent.aiRandomCooldown--;
// If close to the random target, stop random movement
var distToRandom = Math.sqrt((targetX - opponent.x) * (targetX - opponent.x) + (targetY - opponent.y) * (targetY - opponent.y));
if (distToRandom < 40 || opponent.aiRandomCooldown <= 0) {
opponent.aiRandomActive = false;
opponent.aiRandomCooldown = 60 + Math.floor(Math.random() * 60); // Wait before next random
}
} else {
// Normal AI: follow the ball
var targetX = Math.max(minX, Math.min(maxX, ball.x));
var targetY = Math.max(minY, Math.min(maxY, aiTargetY));
if (opponent.aiRandomCooldown > 0) {
opponent.aiRandomCooldown--;
}
}
// Move opponent toward target position with a max speed
var aiSpeed = 32; // px per frame (tune for difficulty)
var dx = targetX - opponent.x;
var dy = targetY - opponent.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
if (dist > aiSpeed) {
opponent.x += dx / dist * aiSpeed;
opponent.y += dy / dist * aiSpeed;
} else {
opponent.x = targetX;
opponent.y = targetY;
}
}
// Check if player is touching the ball (collision/intersection)
var isIntersecting = player.intersects(ball);
// Detect the exact moment player starts touching the ball
if (!lastPlayerBallIntersecting && isIntersecting) {
// Calculate direction from player to ball
var dx = ball.x - player.x;
var dy = ball.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
// Give the ball a velocity away from the player (EVEN FASTER than before)
ball.vx = dx / dist * 70;
ball.vy = dy / dist * 70;
LK.effects.flashObject(ball, 0x1976d2, 200);
}
// AI: Red player can also kick the ball if touching, from any side
if (opponent.intersects(ball)) {
// Check if ball is in a corner
var minBallX = field.x - field.width / 2 + ball.width / 2;
var maxBallX = field.x + field.width / 2 - ball.width / 2;
var minBallY = field.y - field.height / 2 + ball.height / 2;
var maxBallY = field.y + field.height / 2 - ball.height / 2;
var atLeft = ball.x <= minBallX + 1;
var atRight = ball.x >= maxBallX - 1;
var atTop = ball.y <= minBallY + 1;
var atBottom = ball.y >= maxBallY - 1;
// --- Ball stuck detection ---
if (typeof ball.stuckFrames === "undefined") ball.stuckFrames = 0;
if (typeof ball.lastStuckX === "undefined") ball.lastStuckX = ball.x;
if (typeof ball.lastStuckY === "undefined") ball.lastStuckY = ball.y;
// If the ball hasn't moved much in 10 frames, consider it stuck
var stuckThreshold = 6;
if (Math.abs(ball.x - ball.lastStuckX) < stuckThreshold && Math.abs(ball.y - ball.lastStuckY) < stuckThreshold) {
ball.stuckFrames++;
} else {
ball.stuckFrames = 0;
ball.lastStuckX = ball.x;
ball.lastStuckY = ball.y;
}
// If the ball is stuck for too long, red gives up and the game finishes
if (ball.stuckFrames > 90) {
// 90 frames ~1.5 seconds at 60fps
timerRunning = false;
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
var dxr, dyr;
if ((atLeft || atRight) && (atTop || atBottom)) {
// Ball is in a corner, AI tries to hit it toward the center of the field
dxr = field.x - ball.x;
dyr = field.y - ball.y;
} else if (ball.stuckFrames > 12) {
// Ball is stuck: AI tries to hit the right side of the ball
// Always aim to the right (positive X direction)
dxr = 1;
dyr = 0;
// Add a little bias toward the field center to avoid infinite right wall hits
dxr += (field.x - ball.x) * 0.005;
dyr += (field.y - ball.y) * 0.01;
} else {
// Normal: hit from AI to ball direction
dxr = ball.x - opponent.x;
dyr = ball.y - opponent.y;
}
var distr = Math.sqrt(dxr * dxr + dyr * dyr) || 1;
ball.vx = dxr / distr * 50;
ball.vy = dyr / distr * 50;
LK.effects.flashObject(ball, 0xd32f2f, 200);
}
// Ball activation: Ball is stopped at start, but after first touch, it never stops and goes faster
if (typeof ball.activated === "undefined") ball.activated = false;
if (!ball.activated && (player.intersects(ball) || opponent.intersects(ball))) {
ball.activated = true;
// Give a small nudge in a random direction if ball is perfectly still
if (ball.vx === 0 && ball.vy === 0) {
var angle = Math.random() * Math.PI * 2;
ball.vx = Math.cos(angle) * 18;
ball.vy = Math.sin(angle) * 18;
}
}
// Move the ball if it has velocity
if (typeof ball.vx === "number" && typeof ball.vy === "number") {
// --- Ball bounce off goal posts (horizontal) ---
// Track last intersection for each post
if (typeof ball.lastLeftGoalPostLeftIntersect === "undefined") ball.lastLeftGoalPostLeftIntersect = false;
if (typeof ball.lastLeftGoalPostRightIntersect === "undefined") ball.lastLeftGoalPostRightIntersect = false;
if (typeof ball.lastRightGoalPostLeftIntersect === "undefined") ball.lastRightGoalPostLeftIntersect = false;
if (typeof ball.lastRightGoalPostRightIntersect === "undefined") ball.lastRightGoalPostRightIntersect = false;
// Left goal post left
var leftLeftNow = ball.intersects(leftGoalPostLeft);
if (!ball.lastLeftGoalPostLeftIntersect && leftLeftNow) {
// Bounce: reflect velocity, add a little randomness
var dx = ball.x - (leftGoalPostLeft.x - leftGoalPostLeft.width / 2);
var dy = ball.y - leftGoalPostLeft.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30);
ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30);
LK.effects.flashObject(ball, 0xffff00, 120);
}
ball.lastLeftGoalPostLeftIntersect = leftLeftNow;
// Left goal post right
var leftRightNow = ball.intersects(leftGoalPostRight);
if (!ball.lastLeftGoalPostRightIntersect && leftRightNow) {
var dx = ball.x - (leftGoalPostRight.x + leftGoalPostRight.width / 2);
var dy = ball.y - leftGoalPostRight.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30);
ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30);
LK.effects.flashObject(ball, 0xffff00, 120);
}
ball.lastLeftGoalPostRightIntersect = leftRightNow;
// Right goal post left
var rightLeftNow = ball.intersects(rightGoalPostLeft);
if (!ball.lastRightGoalPostLeftIntersect && rightLeftNow) {
var dx = ball.x - (rightGoalPostLeft.x - rightGoalPostLeft.width / 2);
var dy = ball.y - rightGoalPostLeft.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30);
ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30);
LK.effects.flashObject(ball, 0xffff00, 120);
}
ball.lastRightGoalPostLeftIntersect = rightLeftNow;
// Right goal post right
var rightRightNow = ball.intersects(rightGoalPostRight);
if (!ball.lastRightGoalPostRightIntersect && rightRightNow) {
var dx = ball.x - (rightGoalPostRight.x + rightGoalPostRight.width / 2);
var dy = ball.y - rightGoalPostRight.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
ball.vx = dx / dist * Math.max(Math.abs(ball.vx), 30);
ball.vy = dy / dist * Math.max(Math.abs(ball.vy), 30);
LK.effects.flashObject(ball, 0xffff00, 120);
}
ball.lastRightGoalPostRightIntersect = rightRightNow;
// Track lastX for side detection
if (typeof ball.lastX === "undefined") ball.lastX = ball.x;
// Detect crossing from left to right side (player to opponent)
var centerX = field.x;
if (ball.lastX <= centerX && ball.x > centerX) {
// Ball just crossed to right side, speed up!
ball.vx *= 1.25;
ball.vy *= 1.25;
}
// Detect crossing from right to left side (opponent to player)
if (ball.lastX >= centerX && ball.x < centerX) {
// Ball just crossed to left side, speed up!
ball.vx *= 1.25;
ball.vy *= 1.25;
}
if (ball.activated) {
ball.x += ball.vx;
ball.y += ball.vy;
}
// Friction
ball.vx *= 0.96;
ball.vy *= 0.96;
// Clamp to field bounds and bounce off corners (horizontal)
var minBallX = field.x - field.width / 2 + ball.width / 2;
var maxBallX = field.x + field.width / 2 - ball.width / 2;
var minBallY = field.y - field.height / 2 + ball.height / 2;
var maxBallY = field.y + field.height / 2 - ball.height / 2;
// Detect if ball is in a corner (within 1px of both X and Y bounds)
var atLeft = ball.x <= minBallX + 1;
var atRight = ball.x >= maxBallX - 1;
var atTop = ball.y <= minBallY + 1;
var atBottom = ball.y >= maxBallY - 1;
// Bounce off corners: if at a corner, reflect both vx and vy
if (atLeft && atTop || atLeft && atBottom || atRight && atTop || atRight && atBottom) {
// Snap to corner
ball.x = atLeft ? minBallX : maxBallX;
ball.y = atTop ? minBallY : maxBallY;
ball.vx *= -0.5;
ball.vy *= -0.5;
} else {
// Bounce off left/right walls
if (ball.x < minBallX) {
ball.x = minBallX;
ball.vx *= -0.5;
}
if (ball.x > maxBallX) {
ball.x = maxBallX;
ball.vx *= -0.5;
}
// Bounce off top/bottom walls
if (ball.y < minBallY) {
ball.y = minBallY;
ball.vy *= -0.5;
}
if (ball.y > maxBallY) {
ball.y = maxBallY;
ball.vy *= -0.5;
}
}
// --- Goal detection (horizontal) ---
// If ball intersects leftGoal or rightGoal, score!
// Only trigger on the exact frame the ball enters the goal (use lastBallInLeftGoal/lastBallInRightGoal)
if (typeof ball.lastInLeftGoal === "undefined") ball.lastInLeftGoal = false;
if (typeof ball.lastInRightGoal === "undefined") ball.lastInRightGoal = false;
var inLeftGoal = ball.intersects(leftGoal);
var inRightGoal = ball.intersects(rightGoal);
// Score for blue (player) if ball enters bottom goal
if (!ball.lastInRightGoal && inRightGoal) {
timerRunning = false;
LK.effects.flashScreen(0x00ff00, 600);
LK.showYouWin();
return;
}
// Score for red (opponent) if ball enters top goal
if (!ball.lastInLeftGoal && inLeftGoal) {
timerRunning = false;
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
ball.lastInLeftGoal = inLeftGoal;
ball.lastInRightGoal = inRightGoal;
// Prevent the ball from ever stopping: enforce a minimum velocity if it's moving
var minBallSpeed = ball.activated ? 8 : 1.2;
if (typeof ball.vx === "number" && typeof ball.vy === "number") {
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
if (speed < minBallSpeed) {
if (speed === 0) {
// If completely stopped, nudge in a random direction
var angle = Math.random() * Math.PI * 2;
ball.vx = Math.cos(angle) * minBallSpeed;
ball.vy = Math.sin(angle) * minBallSpeed;
} else {
// If moving but too slow, scale up to minimum speed
ball.vx = ball.vx / (speed || 1) * minBallSpeed;
ball.vy = ball.vy / (speed || 1) * minBallSpeed;
}
}
}
// Never allow the ball to stop: if both vx and vy are 0, nudge in a random direction
if (ball.activated && typeof ball.vx === "number" && typeof ball.vy === "number" && ball.vx === 0 && ball.vy === 0) {
var angle = Math.random() * Math.PI * 2;
ball.vx = Math.cos(angle) * minBallSpeed;
ball.vy = Math.sin(angle) * minBallSpeed;
}
// Update lastX for next frame
ball.lastX = ball.x;
// --- Goal detection ---
// If ball intersects leftGoal or rightGoal, score!
// Only trigger on the exact frame the ball enters the goal (use lastBallInLeftGoal/lastBallInRightGoal)
if (typeof ball.lastInLeftGoal === "undefined") ball.lastInLeftGoal = false;
if (typeof ball.lastInRightGoal === "undefined") ball.lastInRightGoal = false;
var inLeftGoal = ball.intersects(leftGoal);
var inRightGoal = ball.intersects(rightGoal);
// Score for blue (player) if ball enters right goal
if (!ball.lastInRightGoal && inRightGoal) {
timerRunning = false;
LK.effects.flashScreen(0x00ff00, 600);
LK.showYouWin();
return;
}
// Score for red (opponent) if ball enters left goal
if (!ball.lastInLeftGoal && inLeftGoal) {
timerRunning = false;
LK.effects.flashScreen(0xff0000, 600);
LK.showGameOver();
return;
}
ball.lastInLeftGoal = inLeftGoal;
ball.lastInRightGoal = inRightGoal;
}
// Update last intersection state
lastPlayerBallIntersecting = isIntersecting;
};
// Futsal ball (white)
// Center circle (white, ellipse)
// Center line (white, box)
// Main field (futsal green)
// Goal area (penalty box, white)
// Goal (yellow, box)
// Player (blue, ellipse)
// --- VERTICAL FIELD (portrait) ---
// Field: centered, tall, normal width
var field = LK.getAsset('field', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(field);
// Center line: horizontal (for portrait/vertical field)
var centerLine = LK.getAsset('centerLine', {
anchorX: 0.5,
anchorY: 0.5,
width: 1900,
// match field width
height: 10,
// thin horizontal line
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(centerLine);
// Center circle
var centerCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(centerCircle);
// Draw top goal (top in portrait)
var topGoal = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
// wide
height: 40,
// short
x: 2048 / 2,
// centered
y: (2732 - 1200) / 2 + 20
// flush with top field edge
});
game.addChild(topGoal);
// Define leftGoal for goal detection (top goal is leftGoal in vertical/portrait)
var leftGoal = topGoal;
// Top goal posts (left and right) - horizontal orientation
var goalPostWidth = 70;
var goalPostHeight = 12;
var goalPostOffsetY = topGoal.height / 2 - goalPostHeight / 2;
var topGoalPostLeft = LK.getAsset('goal', {
anchorX: 1.0,
anchorY: 0.5,
width: goalPostWidth,
height: goalPostHeight,
x: topGoal.x - topGoal.width / 2,
// near the left of the goal
y: topGoal.y - goalPostOffsetY
});
game.addChild(topGoalPostLeft);
var topGoalPostRight = LK.getAsset('goal', {
anchorX: 0.0,
anchorY: 0.5,
width: goalPostWidth,
height: goalPostHeight,
x: topGoal.x + topGoal.width / 2,
// near the right of the goal
y: topGoal.y - goalPostOffsetY
});
game.addChild(topGoalPostRight);
// Define leftGoalPostLeft and leftGoalPostRight for collision logic
// For vertical field, leftGoalPostLeft is the left post, leftGoalPostRight is the right post
var leftGoalPostLeft = topGoalPostLeft;
var leftGoalPostRight = topGoalPostRight;
// Draw bottom goal (bottom in portrait)
var bottomGoal = LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 40,
x: 2048 / 2,
// centered
y: (2732 + 1200) / 2 - 20
// flush with bottom field edge
});
game.addChild(bottomGoal);
// Define rightGoal for goal detection (bottom goal is rightGoal in vertical/portrait)
var rightGoal = bottomGoal;
// Bottom goal posts (left and right) - horizontal orientation
var bottomGoalPostLeft = LK.getAsset('goal', {
anchorX: 1.0,
anchorY: 0.5,
width: goalPostWidth,
height: goalPostHeight,
x: bottomGoal.x - bottomGoal.width / 2,
// near the left of the goal
y: bottomGoal.y + goalPostOffsetY
});
game.addChild(bottomGoalPostLeft);
var bottomGoalPostRight = LK.getAsset('goal', {
anchorX: 0.0,
anchorY: 0.5,
width: goalPostWidth,
height: goalPostHeight,
x: bottomGoal.x + bottomGoal.width / 2,
// near the right of the goal
y: bottomGoal.y + goalPostOffsetY
});
game.addChild(bottomGoalPostRight);
// Define rightGoalPostLeft and rightGoalPostRight for collision logic
// For vertical field, rightGoalPostLeft is the left post, rightGoalPostRight is the right post
var rightGoalPostLeft = bottomGoalPostLeft;
var rightGoalPostRight = bottomGoalPostRight;
// Draw ball at center
var ball = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
ball.vx = 0;
ball.vy = 0;
game.addChild(ball);
// Draw player at top side (blue)
var player = LK.getAsset('playerBlue', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
// center
y: 2732 / 2 - 350
// above center
});
game.addChild(player);
// Draw opponent at bottom side (red)
var opponent = LK.getAsset('playerRed', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
// center
y: 2732 / 2 + 350
// below center
});
game.addChild(opponent);
;