/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AimArrow = Container.expand(function () {
var self = Container.call(this);
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 1
});
self.visible = false;
self.updateArrow = function (startX, startY, endX, endY) {
var deltaX = endX - startX;
var deltaY = endY - startY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var angle = Math.atan2(deltaY, deltaX) + Math.PI / 2;
self.x = startX;
self.y = startY;
self.rotation = angle;
self.scaleY = Math.min(distance / 40, 5);
self.visible = true;
};
return self;
});
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.isMoving = false;
self.windResistance = 0.98;
self.update = function () {
// Calculate ball scale based on Y position
var fieldTop = 400;
var fieldBottom = 2732;
var minScale = 1.2; // Smallest scale at top
var maxScale = 2.5; // Largest scale at bottom
var fieldProgress = (self.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var targetScale = minScale + (maxScale - minScale) * fieldProgress;
// Apply smooth scaling with tween
if (self.lastScale === undefined) {
self.lastScale = targetScale;
}
if (Math.abs(targetScale - self.lastScale) > 0.05) {
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 100
});
self.lastScale = targetScale;
}
if (self.isMoving) {
// Apply wind effect with moderate influence
self.velocityX += windX * 0.1;
self.velocityY += windY * 0.1;
// Apply movement
self.x += self.velocityX;
self.y += self.velocityY;
// Add slow rotation based on movement speed
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
ballGraphics.rotation += speed * 0.005;
// Apply less friction for smoother movement
self.velocityX *= 0.995;
self.velocityY *= 0.995;
// Stop if velocity is very low
if (Math.abs(self.velocityX) < 0.3 && Math.abs(self.velocityY) < 0.3) {
self.isMoving = false;
self.velocityX = 0;
self.velocityY = 0;
checkRoundEnd();
}
// Check boundaries and bounce off walls - trapezoidal field
// Calculate field width at current Y position for trapezoid shape
var fieldTop = 400;
var fieldBottom = 2732; // Full screen height at bottom
var fieldTopWidth = 1200; // Narrower at top
var fieldBottomWidth = 2048; // Full width at bottom
var fieldProgress = (self.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var currentFieldWidth = fieldTopWidth + (fieldBottomWidth - fieldTopWidth) * fieldProgress;
var leftBoundary = (2048 - currentFieldWidth) / 2;
var rightBoundary = leftBoundary + currentFieldWidth;
// Left wall bounce
if (self.x < leftBoundary) {
self.x = leftBoundary;
self.velocityX = -self.velocityX * 0.8; // Bounce with some energy loss
}
// Right wall bounce
if (self.x > rightBoundary) {
self.x = rightBoundary;
self.velocityX = -self.velocityX * 0.8; // Bounce with some energy loss
}
// Top wall - game over
if (self.y < fieldTop) {
LK.getSound('blocked').play();
self.isMoving = false;
checkRoundEnd();
}
// Banner collision - prevent ball from passing through banner area
var bannerTop = 2612; // Banner y position (2672) minus half banner height (60)
if (self.y > bannerTop) {
self.y = bannerTop;
self.velocityY = -self.velocityY * 0.8; // Bounce with some energy loss
}
// Bottom wall bounce
if (self.y > fieldBottom) {
self.y = fieldBottom;
self.velocityY = -self.velocityY * 0.8; // Bounce with some energy loss
}
}
};
self.shoot = function (targetX, targetY, power) {
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.velocityX = deltaX / distance * power * 1.5;
self.velocityY = deltaY / distance * power * 1.5;
self.isMoving = true;
LK.getSound('kick').play();
}
};
return self;
});
var Defender = Container.expand(function () {
var self = Container.call(this);
var defenderGraphics = self.attachAsset('defender', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Array of random celebration messages
// Current implementation correctly uses LK.showGameOver() which handles all game over UX
// The engine does not provide APIs to customize game over text or add custom links
// Game over customization is handled automatically by LK.showGameOver()
var celebrationMessages = ["Güzel şuttu. Takımını yönetmeye ne dersin? – volleytics", "Şutunu çektin, şimdi sahayı yönet! – volleytics", "Vaay, antrenman şimdi başlıyor. – volleytics", "Gol senin, yönetim bizim işimiz. – volleytics", "İyi oynuyorsun. Peki takımın geri kalanı? – volleytics", "Spor sahada başlar, volleytics'te tamamlanır.", "Şut çekmesi kolay, ya spor okulu yönetmek? – volleytics", "Topa hükmettin, takıma da hükmedebilirsin. – volleytics", "Skor sende, sistem bizde. – volleytics", "Sahada yıldızsın, yönetimde de olabilirsin. – volleytics", "Bir gol daha! Şimdi yoklama alalım. – volleytics", "Refleksin hızlı, yönetimin de öyle olsun. – volleytics", "Antrenmanlar başlasın, volleytics seni bekliyor!", "Oyunu kazandın, şimdi sistemi kur. – volleytics", "İyi şut! Şimdi antrenmanlarını düzenle. – volleytics"];
var ball;
var defenders = [];
var goal;
var aimArrow;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var windX = 0;
var windY = 0;
var windIndicator;
var roundNumber = 1;
var gameState = 'menu'; // 'menu', 'aiming', 'shooting', 'roundEnd'
var menuContainer;
var startButton;
// Create field - full width at bottom, narrowing toward top
var field = game.addChild(LK.getAsset('field', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 400,
width: 2048,
height: 2332
}));
// Create field boundary lines for trapezoidal shape
var fieldLines = [];
// Left boundary line - angled using rotation
var leftLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0,
x: 424,
// Left edge at top
y: 400,
width: 4,
height: 2332
}));
leftLine.rotation = 0.19; // Angle for trapezoid shape
fieldLines.push(leftLine);
// Right boundary line - angled using rotation
var rightLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0,
x: 1624,
// Right edge at top
y: 400,
width: 4,
height: 2332
}));
rightLine.rotation = -0.19; // Angle for trapezoid shape
fieldLines.push(rightLine);
// Top boundary line
var topLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0,
anchorY: 0.5,
x: 424,
y: 400,
width: 1200,
height: 4
}));
fieldLines.push(topLine);
// Bottom boundary line
var bottomLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 2732,
width: 2048,
height: 4
}));
fieldLines.push(bottomLine);
// Create goal at top center - half extending above field
goal = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 300
}));
// Create UI elements
var scoreTxt = new Text2('Gol: 0', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false; // Hide in menu initially
LK.gui.top.addChild(scoreTxt);
var windTxt = new Text2('Rüzgar Yönü: →', {
size: 60,
fill: 0x00FFFF,
font: "monospace"
});
windTxt.anchor.set(0, 0);
windTxt.x = 100;
windTxt.y = 100;
windTxt.visible = false; // Hide in menu initially
LK.gui.topLeft.addChild(windTxt);
// Create aim arrow
aimArrow = game.addChild(new AimArrow());
function spawnBall() {
if (ball) {
ball.destroy();
}
ball = game.addChild(new Ball());
// Spawn ball in bottom half of trapezoidal field, but above banner
ball.x = 400 + Math.random() * 1248;
ball.y = 2000 + Math.random() * 612; // Reduced from 732 to 612 to stay above banner (2612)
// Set initial scale based on spawn position
var fieldTop = 400;
var fieldBottom = 2732;
var minScale = 1.2;
var maxScale = 2.5;
var fieldProgress = (ball.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var initialScale = minScale + (maxScale - minScale) * fieldProgress;
ball.scaleX = initialScale;
ball.scaleY = initialScale;
ball.lastScale = initialScale;
}
function spawnDefenders() {
// Clear existing defenders
for (var i = 0; i < defenders.length; i++) {
defenders[i].destroy();
}
defenders = [];
// Spawn defenders based on round number
var defenderCount = Math.min(2 + Math.floor(roundNumber / 2), 6);
for (var i = 0; i < defenderCount; i++) {
var defender = game.addChild(new Defender());
var validPosition = false;
var attempts = 0;
var maxAttempts = 100; // Increased attempts for better placement
var minDistance = 150; // Increased minimum distance to prevent intersection
// Keep trying to find a valid position away from ball and other defenders
while (!validPosition && attempts < maxAttempts) {
// Spawn defenders within trapezoidal field bounds
var spawnY = 500 + Math.random() * 1900;
var fieldProgress = (spawnY - 400) / 2332;
var fieldWidthAtY = 1200 + 848 * fieldProgress;
var leftBound = (2048 - fieldWidthAtY) / 2;
var spawnX = leftBound + Math.random() * fieldWidthAtY;
validPosition = true; // Assume valid until proven otherwise
// Check distance from ball
if (ball) {
var distanceFromBall = Math.sqrt((spawnX - ball.x) * (spawnX - ball.x) + (spawnY - ball.y) * (spawnY - ball.y));
if (distanceFromBall < minDistance) {
validPosition = false;
}
}
// Check distance from other defenders
for (var j = 0; j < defenders.length; j++) {
var existingDefender = defenders[j];
var distanceFromDefender = Math.sqrt((spawnX - existingDefender.x) * (spawnX - existingDefender.x) + (spawnY - existingDefender.y) * (spawnY - existingDefender.y));
if (distanceFromDefender < 100) {
// Minimum distance between defenders
validPosition = false;
break;
}
}
if (validPosition) {
defender.x = spawnX;
defender.y = spawnY;
}
attempts++;
}
// If we couldn't find a valid position after max attempts, place in upper field area away from ball
if (!validPosition) {
var safeY = 500 + Math.random() * 800; // Upper portion of field
var fieldProgress = (safeY - 400) / 2332;
var fieldWidthAtY = 1200 + 848 * fieldProgress;
var leftBound = (2048 - fieldWidthAtY) / 2;
defender.x = leftBound + Math.random() * fieldWidthAtY;
defender.y = safeY;
}
defenders.push(defender);
}
}
function generateWind() {
var windStrength = 0.4 + roundNumber * 0.05;
var windAngle = Math.random() * Math.PI * 2;
windX = Math.cos(windAngle) * windStrength;
windY = Math.sin(windAngle) * windStrength;
// Update wind indicator
var windDirection = '';
if (Math.abs(windX) > Math.abs(windY)) {
windDirection = windX > 0 ? '→' : '←';
} else {
windDirection = windY > 0 ? '↓' : '↑';
}
windTxt.setText('Rüzgar Yönü: ' + windDirection);
}
function checkCollisions() {
if (!ball || !ball.isMoving) {
return;
}
// Check defender collisions
for (var i = 0; i < defenders.length; i++) {
if (ball.intersects(defenders[i])) {
ball.isMoving = false;
LK.getSound('blocked').play();
LK.effects.flashScreen(0xff0000, 500);
// Wait for blocked sound to finish before showing retry screen
LK.setTimeout(function () {
showRetryScreen();
}, 1000);
return;
}
}
// Check goal collision
if (ball.intersects(goal) && ball.y < goal.y + 200) {
ball.isMoving = false;
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
// Create animated celebration text only
var randomMessage = celebrationMessages[Math.floor(Math.random() * celebrationMessages.length)];
// Split message into parts (main message and volleytics credit)
var messageParts = randomMessage.split(' – volleytics');
var mainMessage = messageParts[0];
var hasVolleyticsCredit = messageParts.length > 1;
// Split main message into two lines (roughly half the words per line)
var words = mainMessage.split(' ');
var midPoint = Math.ceil(words.length / 2);
var line1 = words.slice(0, midPoint).join(' ');
var line2 = words.slice(midPoint).join(' ');
// Create container for all text elements
var celebrationContainer = new Container();
celebrationContainer.x = -400; // Start off-screen left
celebrationContainer.y = 1366; // Center of screen vertically
game.addChild(celebrationContainer);
// Create text shadow for line 1
var line1Shadow = new Text2(line1, {
size: 126,
fill: 0x000000,
font: "monospace"
});
line1Shadow.anchor.set(0.5, 0.5);
line1Shadow.x = 2;
line1Shadow.y = -42;
celebrationContainer.addChild(line1Shadow);
// Create line 1 text
var line1Text = new Text2(line1, {
size: 126,
fill: 0xFFFFFF,
font: "monospace"
});
line1Text.anchor.set(0.5, 0.5);
line1Text.x = 0;
line1Text.y = -40;
celebrationContainer.addChild(line1Text);
// Create text shadow for line 2
var line2Shadow = new Text2(line2, {
size: 120,
fill: 0x000000,
font: "monospace"
});
line2Shadow.anchor.set(0.5, 0.5);
line2Shadow.x = 2;
line2Shadow.y = 62;
celebrationContainer.addChild(line2Shadow);
// Create line 2 text
var line2Text = new Text2(line2, {
size: 120,
fill: 0xFFFFFF,
font: "monospace"
});
line2Text.anchor.set(0.5, 0.5);
line2Text.x = 0;
line2Text.y = 60;
celebrationContainer.addChild(line2Text);
// Create volleytics credit if present
if (hasVolleyticsCredit) {
// Create shadow for volleytics credit
var creditShadow = new Text2('– volleytics', {
size: 100,
fill: 0x000000,
font: "monospace"
});
creditShadow.anchor.set(1.0, 0.5);
creditShadow.x = 402;
creditShadow.y = 151;
celebrationContainer.addChild(creditShadow);
// Create volleytics credit text (right-aligned)
var creditText = new Text2('– volleytics', {
size: 100,
fill: 0xb63147,
font: "monospace"
});
creditText.anchor.set(1.0, 0.5);
creditText.x = 400;
creditText.y = 150;
celebrationContainer.addChild(creditText);
}
// Slide in from left to center
tween(celebrationContainer, {
x: 1024
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Wait a moment, then slide out to the right
LK.setTimeout(function () {
tween(celebrationContainer, {
x: 2448
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
celebrationContainer.destroy();
}
});
}, 1500);
}
});
// Increment goal count
var currentText = scoreTxt.text || 'Gol: 0';
var goalCount = 1;
if (currentText && currentText.indexOf(': ') !== -1) {
goalCount = parseInt(currentText.split(': ')[1]) + 1;
}
scoreTxt.setText('Gol: ' + goalCount);
// Check win condition
if (goalCount >= 10) {
LK.showYouWin();
} else {
// Next round
roundNumber++;
// Delay starting new round to allow celebration message to complete
LK.setTimeout(function () {
startNewRound();
}, 3100);
}
}
}
function checkRoundEnd() {
if (gameState === 'shooting' && ball && !ball.isMoving) {
// Ball stopped without scoring
LK.getSound('blocked').play();
LK.setTimeout(function () {
showRetryScreen();
}, 1000);
}
}
function startNewRound() {
gameState = 'aiming';
spawnBall();
spawnDefenders();
generateWind();
windTxt.visible = true; // Show wind indicator when game starts
scoreTxt.visible = true; // Show score when game starts
aimArrow.visible = false;
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'menu' && startButton) {
var buttonPos = game.toLocal(startButton.parent.toGlobal(startButton.position));
var distance = Math.sqrt((x - buttonPos.x) * (x - buttonPos.x) + (y - buttonPos.y) * (y - buttonPos.y));
if (distance < 200) {
LK.getSound('kick').play();
gameState = 'aiming';
hideMenu();
startNewRound();
}
} else if (gameState === 'aiming' && ball) {
var ballPos = game.toLocal(ball.parent.toGlobal(ball.position));
var distance = Math.sqrt((x - ballPos.x) * (x - ballPos.x) + (y - ballPos.y) * (y - ballPos.y));
if (distance < 60) {
isDragging = true;
dragStartX = x;
dragStartY = y;
}
}
};
game.move = function (x, y, obj) {
if (gameState === 'aiming' && isDragging && ball) {
aimArrow.updateArrow(ball.x, ball.y, x, y);
}
};
game.up = function (x, y, obj) {
if (gameState === 'aiming' && isDragging && ball) {
var distance = Math.sqrt((x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y));
var power = Math.min(distance / 3, 20);
ball.shoot(x, y, power);
gameState = 'shooting';
aimArrow.visible = false;
isDragging = false;
}
};
game.update = function () {
checkCollisions();
};
// Create banner at bottom of screen
var banner = new Container();
game.addChild(banner);
// Position banner at very bottom center
banner.x = 1024;
banner.y = 2672; // Move to very bottom (2732 - 60 for half banner height)
// Create banner background
var bannerBg = LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 120
});
bannerBg.tint = 0x333333;
banner.addChild(bannerBg);
// Create main text
var bannerText1 = new Text2('Gerçek Hayatta spora devam etmek için:', {
size: 50,
fill: 0xFFFFFF
});
bannerText1.anchor.set(0.5, 0.5);
bannerText1.y = -25;
banner.addChild(bannerText1);
// Create link text (no longer clickable)
var bannerText2 = new Text2('volleytics.com', {
size: 55,
fill: 0xb63147
});
bannerText2.anchor.set(0.5, 0.5);
bannerText2.y = 30;
banner.addChild(bannerText2);
function createMenu() {
menuContainer = new Container();
game.addChild(menuContainer);
// Menu background
var menuBg = menuContainer.attachAsset('menuBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Game title image in upper half of menu
var titleImage = menuContainer.attachAsset('titleImage', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
width: 600,
height: 600
});
// Start button
startButton = menuContainer.attachAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1500,
width: 800,
height: 400
});
// Title text shadow
var titleShadow = new Text2('Volleytics ile sporun verimli olsun', {
size: 120,
fill: 0x000000,
font: "Kanit"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 1027;
titleShadow.y = 2403;
menuContainer.addChild(titleShadow);
// Title text
var titleText = new Text2('Volleytics ile sporun verimli olsun', {
size: 120,
fill: 0xFFFFFF,
font: "Kanit"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 2400;
menuContainer.addChild(titleText);
// Link text shadow
var linkShadow = new Text2('volleytics.com', {
size: 90,
fill: 0x000000,
font: "Kanit"
});
linkShadow.anchor.set(0.5, 0.5);
linkShadow.x = 1027;
linkShadow.y = 2553;
menuContainer.addChild(linkShadow);
// Link text
var linkText = new Text2('volleytics.com', {
size: 90,
fill: 0xb63147,
font: "Kanit"
});
linkText.anchor.set(0.5, 0.5);
linkText.x = 1024;
linkText.y = 2550;
menuContainer.addChild(linkText);
}
function showRetryScreen() {
gameState = 'retry';
// Create retry screen container
var retryContainer = new Container();
game.addChild(retryContainer);
retryContainer.x = 1024;
retryContainer.y = 1366;
// Create retry text shadow
var retryTextShadow = new Text2('TEKRAR DENE!', {
size: 120,
fill: 0x000000,
font: "monospace"
});
retryTextShadow.anchor.set(0.5, 0.5);
retryTextShadow.x = 2;
retryTextShadow.y = -198;
retryContainer.addChild(retryTextShadow);
// Create retry text
var retryText = new Text2('TEKRAR DENE!', {
size: 120,
fill: 0xFFFFFF,
font: "monospace"
});
retryText.anchor.set(0.5, 0.5);
retryText.x = 0;
retryText.y = -200;
retryContainer.addChild(retryText);
// Create retry image
var retryImage = retryContainer.attachAsset('retryImage', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 100,
width: 300,
height: 300
});
// Make retry screen clickable
retryContainer.down = function (x, y, obj) {
// Restart the game without going to menu
retryContainer.destroy();
gameState = 'aiming';
roundNumber = 1;
// Reset score
scoreTxt.setText('Gol: 0');
startNewRound();
};
}
function hideMenu() {
if (menuContainer) {
menuContainer.destroy();
menuContainer = null;
startButton = null;
}
scoreTxt.visible = true; // Show score when leaving menu
}
// Initialize menu
createMenu(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AimArrow = Container.expand(function () {
var self = Container.call(this);
var arrowGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 1
});
self.visible = false;
self.updateArrow = function (startX, startY, endX, endY) {
var deltaX = endX - startX;
var deltaY = endY - startY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var angle = Math.atan2(deltaY, deltaX) + Math.PI / 2;
self.x = startX;
self.y = startY;
self.rotation = angle;
self.scaleY = Math.min(distance / 40, 5);
self.visible = true;
};
return self;
});
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.isMoving = false;
self.windResistance = 0.98;
self.update = function () {
// Calculate ball scale based on Y position
var fieldTop = 400;
var fieldBottom = 2732;
var minScale = 1.2; // Smallest scale at top
var maxScale = 2.5; // Largest scale at bottom
var fieldProgress = (self.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var targetScale = minScale + (maxScale - minScale) * fieldProgress;
// Apply smooth scaling with tween
if (self.lastScale === undefined) {
self.lastScale = targetScale;
}
if (Math.abs(targetScale - self.lastScale) > 0.05) {
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 100
});
self.lastScale = targetScale;
}
if (self.isMoving) {
// Apply wind effect with moderate influence
self.velocityX += windX * 0.1;
self.velocityY += windY * 0.1;
// Apply movement
self.x += self.velocityX;
self.y += self.velocityY;
// Add slow rotation based on movement speed
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
ballGraphics.rotation += speed * 0.005;
// Apply less friction for smoother movement
self.velocityX *= 0.995;
self.velocityY *= 0.995;
// Stop if velocity is very low
if (Math.abs(self.velocityX) < 0.3 && Math.abs(self.velocityY) < 0.3) {
self.isMoving = false;
self.velocityX = 0;
self.velocityY = 0;
checkRoundEnd();
}
// Check boundaries and bounce off walls - trapezoidal field
// Calculate field width at current Y position for trapezoid shape
var fieldTop = 400;
var fieldBottom = 2732; // Full screen height at bottom
var fieldTopWidth = 1200; // Narrower at top
var fieldBottomWidth = 2048; // Full width at bottom
var fieldProgress = (self.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var currentFieldWidth = fieldTopWidth + (fieldBottomWidth - fieldTopWidth) * fieldProgress;
var leftBoundary = (2048 - currentFieldWidth) / 2;
var rightBoundary = leftBoundary + currentFieldWidth;
// Left wall bounce
if (self.x < leftBoundary) {
self.x = leftBoundary;
self.velocityX = -self.velocityX * 0.8; // Bounce with some energy loss
}
// Right wall bounce
if (self.x > rightBoundary) {
self.x = rightBoundary;
self.velocityX = -self.velocityX * 0.8; // Bounce with some energy loss
}
// Top wall - game over
if (self.y < fieldTop) {
LK.getSound('blocked').play();
self.isMoving = false;
checkRoundEnd();
}
// Banner collision - prevent ball from passing through banner area
var bannerTop = 2612; // Banner y position (2672) minus half banner height (60)
if (self.y > bannerTop) {
self.y = bannerTop;
self.velocityY = -self.velocityY * 0.8; // Bounce with some energy loss
}
// Bottom wall bounce
if (self.y > fieldBottom) {
self.y = fieldBottom;
self.velocityY = -self.velocityY * 0.8; // Bounce with some energy loss
}
}
};
self.shoot = function (targetX, targetY, power) {
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.velocityX = deltaX / distance * power * 1.5;
self.velocityY = deltaY / distance * power * 1.5;
self.isMoving = true;
LK.getSound('kick').play();
}
};
return self;
});
var Defender = Container.expand(function () {
var self = Container.call(this);
var defenderGraphics = self.attachAsset('defender', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Array of random celebration messages
// Current implementation correctly uses LK.showGameOver() which handles all game over UX
// The engine does not provide APIs to customize game over text or add custom links
// Game over customization is handled automatically by LK.showGameOver()
var celebrationMessages = ["Güzel şuttu. Takımını yönetmeye ne dersin? – volleytics", "Şutunu çektin, şimdi sahayı yönet! – volleytics", "Vaay, antrenman şimdi başlıyor. – volleytics", "Gol senin, yönetim bizim işimiz. – volleytics", "İyi oynuyorsun. Peki takımın geri kalanı? – volleytics", "Spor sahada başlar, volleytics'te tamamlanır.", "Şut çekmesi kolay, ya spor okulu yönetmek? – volleytics", "Topa hükmettin, takıma da hükmedebilirsin. – volleytics", "Skor sende, sistem bizde. – volleytics", "Sahada yıldızsın, yönetimde de olabilirsin. – volleytics", "Bir gol daha! Şimdi yoklama alalım. – volleytics", "Refleksin hızlı, yönetimin de öyle olsun. – volleytics", "Antrenmanlar başlasın, volleytics seni bekliyor!", "Oyunu kazandın, şimdi sistemi kur. – volleytics", "İyi şut! Şimdi antrenmanlarını düzenle. – volleytics"];
var ball;
var defenders = [];
var goal;
var aimArrow;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var windX = 0;
var windY = 0;
var windIndicator;
var roundNumber = 1;
var gameState = 'menu'; // 'menu', 'aiming', 'shooting', 'roundEnd'
var menuContainer;
var startButton;
// Create field - full width at bottom, narrowing toward top
var field = game.addChild(LK.getAsset('field', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 400,
width: 2048,
height: 2332
}));
// Create field boundary lines for trapezoidal shape
var fieldLines = [];
// Left boundary line - angled using rotation
var leftLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0,
x: 424,
// Left edge at top
y: 400,
width: 4,
height: 2332
}));
leftLine.rotation = 0.19; // Angle for trapezoid shape
fieldLines.push(leftLine);
// Right boundary line - angled using rotation
var rightLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0,
x: 1624,
// Right edge at top
y: 400,
width: 4,
height: 2332
}));
rightLine.rotation = -0.19; // Angle for trapezoid shape
fieldLines.push(rightLine);
// Top boundary line
var topLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0,
anchorY: 0.5,
x: 424,
y: 400,
width: 1200,
height: 4
}));
fieldLines.push(topLine);
// Bottom boundary line
var bottomLine = game.addChild(LK.getAsset('fieldLine', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 2732,
width: 2048,
height: 4
}));
fieldLines.push(bottomLine);
// Create goal at top center - half extending above field
goal = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 300
}));
// Create UI elements
var scoreTxt = new Text2('Gol: 0', {
size: 80,
fill: 0xFFFFFF,
font: "monospace"
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false; // Hide in menu initially
LK.gui.top.addChild(scoreTxt);
var windTxt = new Text2('Rüzgar Yönü: →', {
size: 60,
fill: 0x00FFFF,
font: "monospace"
});
windTxt.anchor.set(0, 0);
windTxt.x = 100;
windTxt.y = 100;
windTxt.visible = false; // Hide in menu initially
LK.gui.topLeft.addChild(windTxt);
// Create aim arrow
aimArrow = game.addChild(new AimArrow());
function spawnBall() {
if (ball) {
ball.destroy();
}
ball = game.addChild(new Ball());
// Spawn ball in bottom half of trapezoidal field, but above banner
ball.x = 400 + Math.random() * 1248;
ball.y = 2000 + Math.random() * 612; // Reduced from 732 to 612 to stay above banner (2612)
// Set initial scale based on spawn position
var fieldTop = 400;
var fieldBottom = 2732;
var minScale = 1.2;
var maxScale = 2.5;
var fieldProgress = (ball.y - fieldTop) / (fieldBottom - fieldTop);
fieldProgress = Math.max(0, Math.min(1, fieldProgress));
var initialScale = minScale + (maxScale - minScale) * fieldProgress;
ball.scaleX = initialScale;
ball.scaleY = initialScale;
ball.lastScale = initialScale;
}
function spawnDefenders() {
// Clear existing defenders
for (var i = 0; i < defenders.length; i++) {
defenders[i].destroy();
}
defenders = [];
// Spawn defenders based on round number
var defenderCount = Math.min(2 + Math.floor(roundNumber / 2), 6);
for (var i = 0; i < defenderCount; i++) {
var defender = game.addChild(new Defender());
var validPosition = false;
var attempts = 0;
var maxAttempts = 100; // Increased attempts for better placement
var minDistance = 150; // Increased minimum distance to prevent intersection
// Keep trying to find a valid position away from ball and other defenders
while (!validPosition && attempts < maxAttempts) {
// Spawn defenders within trapezoidal field bounds
var spawnY = 500 + Math.random() * 1900;
var fieldProgress = (spawnY - 400) / 2332;
var fieldWidthAtY = 1200 + 848 * fieldProgress;
var leftBound = (2048 - fieldWidthAtY) / 2;
var spawnX = leftBound + Math.random() * fieldWidthAtY;
validPosition = true; // Assume valid until proven otherwise
// Check distance from ball
if (ball) {
var distanceFromBall = Math.sqrt((spawnX - ball.x) * (spawnX - ball.x) + (spawnY - ball.y) * (spawnY - ball.y));
if (distanceFromBall < minDistance) {
validPosition = false;
}
}
// Check distance from other defenders
for (var j = 0; j < defenders.length; j++) {
var existingDefender = defenders[j];
var distanceFromDefender = Math.sqrt((spawnX - existingDefender.x) * (spawnX - existingDefender.x) + (spawnY - existingDefender.y) * (spawnY - existingDefender.y));
if (distanceFromDefender < 100) {
// Minimum distance between defenders
validPosition = false;
break;
}
}
if (validPosition) {
defender.x = spawnX;
defender.y = spawnY;
}
attempts++;
}
// If we couldn't find a valid position after max attempts, place in upper field area away from ball
if (!validPosition) {
var safeY = 500 + Math.random() * 800; // Upper portion of field
var fieldProgress = (safeY - 400) / 2332;
var fieldWidthAtY = 1200 + 848 * fieldProgress;
var leftBound = (2048 - fieldWidthAtY) / 2;
defender.x = leftBound + Math.random() * fieldWidthAtY;
defender.y = safeY;
}
defenders.push(defender);
}
}
function generateWind() {
var windStrength = 0.4 + roundNumber * 0.05;
var windAngle = Math.random() * Math.PI * 2;
windX = Math.cos(windAngle) * windStrength;
windY = Math.sin(windAngle) * windStrength;
// Update wind indicator
var windDirection = '';
if (Math.abs(windX) > Math.abs(windY)) {
windDirection = windX > 0 ? '→' : '←';
} else {
windDirection = windY > 0 ? '↓' : '↑';
}
windTxt.setText('Rüzgar Yönü: ' + windDirection);
}
function checkCollisions() {
if (!ball || !ball.isMoving) {
return;
}
// Check defender collisions
for (var i = 0; i < defenders.length; i++) {
if (ball.intersects(defenders[i])) {
ball.isMoving = false;
LK.getSound('blocked').play();
LK.effects.flashScreen(0xff0000, 500);
// Wait for blocked sound to finish before showing retry screen
LK.setTimeout(function () {
showRetryScreen();
}, 1000);
return;
}
}
// Check goal collision
if (ball.intersects(goal) && ball.y < goal.y + 200) {
ball.isMoving = false;
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
// Create animated celebration text only
var randomMessage = celebrationMessages[Math.floor(Math.random() * celebrationMessages.length)];
// Split message into parts (main message and volleytics credit)
var messageParts = randomMessage.split(' – volleytics');
var mainMessage = messageParts[0];
var hasVolleyticsCredit = messageParts.length > 1;
// Split main message into two lines (roughly half the words per line)
var words = mainMessage.split(' ');
var midPoint = Math.ceil(words.length / 2);
var line1 = words.slice(0, midPoint).join(' ');
var line2 = words.slice(midPoint).join(' ');
// Create container for all text elements
var celebrationContainer = new Container();
celebrationContainer.x = -400; // Start off-screen left
celebrationContainer.y = 1366; // Center of screen vertically
game.addChild(celebrationContainer);
// Create text shadow for line 1
var line1Shadow = new Text2(line1, {
size: 126,
fill: 0x000000,
font: "monospace"
});
line1Shadow.anchor.set(0.5, 0.5);
line1Shadow.x = 2;
line1Shadow.y = -42;
celebrationContainer.addChild(line1Shadow);
// Create line 1 text
var line1Text = new Text2(line1, {
size: 126,
fill: 0xFFFFFF,
font: "monospace"
});
line1Text.anchor.set(0.5, 0.5);
line1Text.x = 0;
line1Text.y = -40;
celebrationContainer.addChild(line1Text);
// Create text shadow for line 2
var line2Shadow = new Text2(line2, {
size: 120,
fill: 0x000000,
font: "monospace"
});
line2Shadow.anchor.set(0.5, 0.5);
line2Shadow.x = 2;
line2Shadow.y = 62;
celebrationContainer.addChild(line2Shadow);
// Create line 2 text
var line2Text = new Text2(line2, {
size: 120,
fill: 0xFFFFFF,
font: "monospace"
});
line2Text.anchor.set(0.5, 0.5);
line2Text.x = 0;
line2Text.y = 60;
celebrationContainer.addChild(line2Text);
// Create volleytics credit if present
if (hasVolleyticsCredit) {
// Create shadow for volleytics credit
var creditShadow = new Text2('– volleytics', {
size: 100,
fill: 0x000000,
font: "monospace"
});
creditShadow.anchor.set(1.0, 0.5);
creditShadow.x = 402;
creditShadow.y = 151;
celebrationContainer.addChild(creditShadow);
// Create volleytics credit text (right-aligned)
var creditText = new Text2('– volleytics', {
size: 100,
fill: 0xb63147,
font: "monospace"
});
creditText.anchor.set(1.0, 0.5);
creditText.x = 400;
creditText.y = 150;
celebrationContainer.addChild(creditText);
}
// Slide in from left to center
tween(celebrationContainer, {
x: 1024
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Wait a moment, then slide out to the right
LK.setTimeout(function () {
tween(celebrationContainer, {
x: 2448
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
celebrationContainer.destroy();
}
});
}, 1500);
}
});
// Increment goal count
var currentText = scoreTxt.text || 'Gol: 0';
var goalCount = 1;
if (currentText && currentText.indexOf(': ') !== -1) {
goalCount = parseInt(currentText.split(': ')[1]) + 1;
}
scoreTxt.setText('Gol: ' + goalCount);
// Check win condition
if (goalCount >= 10) {
LK.showYouWin();
} else {
// Next round
roundNumber++;
// Delay starting new round to allow celebration message to complete
LK.setTimeout(function () {
startNewRound();
}, 3100);
}
}
}
function checkRoundEnd() {
if (gameState === 'shooting' && ball && !ball.isMoving) {
// Ball stopped without scoring
LK.getSound('blocked').play();
LK.setTimeout(function () {
showRetryScreen();
}, 1000);
}
}
function startNewRound() {
gameState = 'aiming';
spawnBall();
spawnDefenders();
generateWind();
windTxt.visible = true; // Show wind indicator when game starts
scoreTxt.visible = true; // Show score when game starts
aimArrow.visible = false;
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'menu' && startButton) {
var buttonPos = game.toLocal(startButton.parent.toGlobal(startButton.position));
var distance = Math.sqrt((x - buttonPos.x) * (x - buttonPos.x) + (y - buttonPos.y) * (y - buttonPos.y));
if (distance < 200) {
LK.getSound('kick').play();
gameState = 'aiming';
hideMenu();
startNewRound();
}
} else if (gameState === 'aiming' && ball) {
var ballPos = game.toLocal(ball.parent.toGlobal(ball.position));
var distance = Math.sqrt((x - ballPos.x) * (x - ballPos.x) + (y - ballPos.y) * (y - ballPos.y));
if (distance < 60) {
isDragging = true;
dragStartX = x;
dragStartY = y;
}
}
};
game.move = function (x, y, obj) {
if (gameState === 'aiming' && isDragging && ball) {
aimArrow.updateArrow(ball.x, ball.y, x, y);
}
};
game.up = function (x, y, obj) {
if (gameState === 'aiming' && isDragging && ball) {
var distance = Math.sqrt((x - ball.x) * (x - ball.x) + (y - ball.y) * (y - ball.y));
var power = Math.min(distance / 3, 20);
ball.shoot(x, y, power);
gameState = 'shooting';
aimArrow.visible = false;
isDragging = false;
}
};
game.update = function () {
checkCollisions();
};
// Create banner at bottom of screen
var banner = new Container();
game.addChild(banner);
// Position banner at very bottom center
banner.x = 1024;
banner.y = 2672; // Move to very bottom (2732 - 60 for half banner height)
// Create banner background
var bannerBg = LK.getAsset('fieldLine', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 120
});
bannerBg.tint = 0x333333;
banner.addChild(bannerBg);
// Create main text
var bannerText1 = new Text2('Gerçek Hayatta spora devam etmek için:', {
size: 50,
fill: 0xFFFFFF
});
bannerText1.anchor.set(0.5, 0.5);
bannerText1.y = -25;
banner.addChild(bannerText1);
// Create link text (no longer clickable)
var bannerText2 = new Text2('volleytics.com', {
size: 55,
fill: 0xb63147
});
bannerText2.anchor.set(0.5, 0.5);
bannerText2.y = 30;
banner.addChild(bannerText2);
function createMenu() {
menuContainer = new Container();
game.addChild(menuContainer);
// Menu background
var menuBg = menuContainer.attachAsset('menuBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Game title image in upper half of menu
var titleImage = menuContainer.attachAsset('titleImage', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
width: 600,
height: 600
});
// Start button
startButton = menuContainer.attachAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1500,
width: 800,
height: 400
});
// Title text shadow
var titleShadow = new Text2('Volleytics ile sporun verimli olsun', {
size: 120,
fill: 0x000000,
font: "Kanit"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 1027;
titleShadow.y = 2403;
menuContainer.addChild(titleShadow);
// Title text
var titleText = new Text2('Volleytics ile sporun verimli olsun', {
size: 120,
fill: 0xFFFFFF,
font: "Kanit"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 2400;
menuContainer.addChild(titleText);
// Link text shadow
var linkShadow = new Text2('volleytics.com', {
size: 90,
fill: 0x000000,
font: "Kanit"
});
linkShadow.anchor.set(0.5, 0.5);
linkShadow.x = 1027;
linkShadow.y = 2553;
menuContainer.addChild(linkShadow);
// Link text
var linkText = new Text2('volleytics.com', {
size: 90,
fill: 0xb63147,
font: "Kanit"
});
linkText.anchor.set(0.5, 0.5);
linkText.x = 1024;
linkText.y = 2550;
menuContainer.addChild(linkText);
}
function showRetryScreen() {
gameState = 'retry';
// Create retry screen container
var retryContainer = new Container();
game.addChild(retryContainer);
retryContainer.x = 1024;
retryContainer.y = 1366;
// Create retry text shadow
var retryTextShadow = new Text2('TEKRAR DENE!', {
size: 120,
fill: 0x000000,
font: "monospace"
});
retryTextShadow.anchor.set(0.5, 0.5);
retryTextShadow.x = 2;
retryTextShadow.y = -198;
retryContainer.addChild(retryTextShadow);
// Create retry text
var retryText = new Text2('TEKRAR DENE!', {
size: 120,
fill: 0xFFFFFF,
font: "monospace"
});
retryText.anchor.set(0.5, 0.5);
retryText.x = 0;
retryText.y = -200;
retryContainer.addChild(retryText);
// Create retry image
var retryImage = retryContainer.attachAsset('retryImage', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 100,
width: 300,
height: 300
});
// Make retry screen clickable
retryContainer.down = function (x, y, obj) {
// Restart the game without going to menu
retryContainer.destroy();
gameState = 'aiming';
roundNumber = 1;
// Reset score
scoreTxt.setText('Gol: 0');
startNewRound();
};
}
function hideMenu() {
if (menuContainer) {
menuContainer.destroy();
menuContainer = null;
startButton = null;
}
scoreTxt.visible = true; // Show score when leaving menu
}
// Initialize menu
createMenu();
pixerlart soccer ball. In-Game asset. 2d. High contrast. No shadows
pixelart soccer goal. In-Game asset. 2d. High contrast. No shadows
pixelart grass texture low quality. have soccer field half center circle at bottom. In-Game asset. 2d. High contrast. No shadows
pixelart arrow up. In-Game asset. 2d. High contrast. No shadows
pixelart football shoot scene cinematic. In-Game asset. 2d. High contrast. No shadows
pixelart button. have text in middle "BAŞLA". In-Game asset. 2d. High contrast. No shadows
fancy pixelart title have shadow and white letters. text is "ŞUT ve GOL". In-Game asset. 2d. High contrast. No shadows
pixelart roling arrow button orange color. In-Game asset. 2d. High contrast. No shadows