diff --git a/src/globals.h b/src/globals.h index 264fdd1..1888b4b 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1,78 +1,77 @@ /* Copyright 2007-2008 Fela Winkelmolen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GLOBALS_H #define GLOBALS_H // how many pixels it moves each step // should be less than the half of BALL_SIZE const int BALL_SPEED = 2; const qreal MINIMUM_SPEED = 1.2; const int MINIMUM_GIFT_SPEED = 1; const int MAXIMUM_GIFT_SPEED = 3; const int REPAINT_INTERVAL = 16; // should be a power of two // how ofter the position of the item is updated (but not repainted) const int DEFAULT_UPDATE_INTERVAL = 13; //const int MAXIMUM_UPDATE_INTERVAL = REPAINT_INTERVAL; -const int MINIMUM_UPDATE_INTERVAL = 8; // the higher this number the more the game becomes faster over time const qreal AUTO_SPEED_INCREASE = 1.05; const int BURNING_INTERVAL = 200; // how long it burns const int BURNING_SPEED = BURNING_INTERVAL / 2; // lower is faster const int WIDTH = 20; // how many bricks the game is wide const int HEIGHT = 24; // how many bricks the game is high const int BRICK_WIDTH = 30; const int BRICK_HEIGHT = 15; const int BALL_SIZE = 10; const int DEFAULT_BAR_WIDTH = 60; const int MIN_BAR_WIDTH = 30; const int MAX_BAR_WIDTH = 250; const int GIFT_WIDTH = 25; const int GIFT_HEIGHT = 18; // used to enlarge and shrink the bar const qreal RESIZE_BAR_RATIO = 1.4; const qreal CHANGE_SPEED_RATIO = 1.3; // pixels to move the bar each "tick", when using the keyboard const int BAR_MOVEMENT = 5; // points when breaking a brick // decreases over time since last brick was hit const int BRICK_SCORE = 15; // relative score w.r.t the old score const qreal SCORE_AUTO_DECREASE = 0.998; // score when i brick gets "autoremoved" // (for example becouse of a gift, of fire) const int AUTOBRICK_SCORE = BRICK_SCORE / 2; // points avarded when passing a level const int LEVEL_SCORE = 300; const int GIFT_SCORE = 30; const int LOSE_LIFE_SCORE = 0; // disabled // score added for each life when game is won const int LIFE_SCORE = 2000; const int GAME_WON_SCORE = 10000; const int INITIAL_LIVES = 2; const int MAXIMUM_LIVES = 10; // minimum with and height of the game widget (CanvasWidget) const int DEFAULT_WIDTH = 750; const int DEFAULT_HEIGHT = 500; #endif // GLOBALS_H diff --git a/src/qml/Ball.qml b/src/qml/Ball.qml index fe47d00..8058e28 100644 --- a/src/qml/Ball.qml +++ b/src/qml/Ball.qml @@ -1,58 +1,44 @@ /* Copyright 2012 Viranch Mehta This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.3 import org.kde.games.core 0.1 as KgCore import "globals.js" as Globals CanvasItem { id: ball spriteKey: "PlainBall" property string type onTypeChanged: spriteKey = type; property real directionX property real directionY property bool toBeFired: true property real barPosition: 0.6 width: m_scale * Globals.BALL_SIZE height: m_scale * Globals.BALL_SIZE // for preserving position relative // to bgOverlay when canvas is resized property real posX property real posY x: m_scale * posX y: m_scale * posY - - Timer { - interval: gameTimer.interval - running: gameTimer.running - repeat: true - onTriggered: { - if (toBeFired) { - posX = (bar.x + barPosition*bar.width)/m_scale; - } else { - posX += directionX * speed; - posY += directionY * speed; - } - } - } } diff --git a/src/qml/globals.js b/src/qml/globals.js index 402d2ec..98b9017 100644 --- a/src/qml/globals.js +++ b/src/qml/globals.js @@ -1,73 +1,72 @@ /* Copyright 2012 Viranch Mehta This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // how many pixels it moves each step // should be less than the half of BALL_SIZE var BALL_SPEED = 2; var MINIMUM_SPEED = 1.2; var MINIMUM_GIFT_SPEED = 1; var MAXIMUM_GIFT_SPEED = 3; var REPAINT_INTERVAL = 16; // should be a power of two // how ofter the position of the item is updated (but not repainted) var DEFAULT_UPDATE_INTERVAL = 13; //var MAXIMUM_UPDATE_INTERVAL = REPAINT_INTERVAL; -var MINIMUM_UPDATE_INTERVAL = 8; // the higher this number the more the game becomes faster over time var AUTO_SPEED_INCREASE = 1.05; var BURNING_INTERVAL = 200; // how long it burns var BURNING_SPEED = Math.floor(BURNING_INTERVAL/2); // lower is faster var WIDTH = 20; // how many bricks the game is wide var HEIGHT = 24; // how many bricks the game is high var BRICK_WIDTH = 30; var BRICK_HEIGHT = 15; var BALL_SIZE = 10; var DEFAULT_BAR_WIDTH = 60; var MIN_BAR_WIDTH = 30; var MAX_BAR_WIDTH = 250; var GIFT_WIDTH = 25; var GIFT_HEIGHT = 18; // used to enlarge and shrink the bar var RESIZE_BAR_RATIO = 1.4; var CHANGE_SPEED_RATIO = 1.3; // pixels to move the bar each "tick", when using the keyboard var BAR_MOVEMENT = 5; // points when breaking a brick // decreases over time since last brick was hit var BRICK_SCORE = 15; // relative score w.r.t the old score var SCORE_AUTO_DECREASE = 0.998; // score when i brick gets "autoremoved" // (for example becouse of a gift, of fire) var AUTOBRICK_SCORE = Math.floor(BRICK_SCORE / 2); // points avarded when passing a level var LEVEL_SCORE = 300; var GIFT_SCORE = 30; var LOSE_LIFE_SCORE = 0; // disabled // score added for each life when game is won var LIFE_SCORE = 2000; var GAME_WON_SCORE = 10000; var INITIAL_LIVES = 2; var MAXIMUM_LIVES = 10; // minimum with and height of the game widget (CanvasWidget) var DEFAULT_WIDTH = 750; var DEFAULT_HEIGHT = 500; diff --git a/src/qml/logic.js b/src/qml/logic.js index c1a0e77..8dbb8dc 100644 --- a/src/qml/logic.js +++ b/src/qml/logic.js @@ -1,959 +1,963 @@ /* Copyright 2012 Viranch Mehta This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ var ballComponent = Qt.createComponent("Ball.qml"); var balls = new Array; var brickComponent = Qt.createComponent("Brick.qml"); var bricks = new Array; var giftComponent = Qt.createComponent("Gift.qml"); var gifts = new Array; // score to add if you hit a brick // decreases over time since last hit var dScore; // count of remaining bricks // (not counting the unbreakable ones) var remainingBricks=0; var tick = 0; var repaintInterval; var randomCounter = 0; // is set to true when deleteMovingObjects() is called var itemsGotDeleted; var singleShotComponent = Qt.createComponent("Singleshot.qml"); +// Substeps are used to avoid that the ball is moved too far in a single update, +// possibly leading to buggy behavior. The movement is divided into multiple +// substeps and collisions are detected after each substep. +var substeps = 1; + function remove(array, object) { array.splice(array.indexOf(object), 1); } function singleShot(interval, slot, target) { var timer = singleShotComponent.createObject(canvas); timer.interval = interval; timer.target = target; timer.timeout.connect(slot); timer.start(); } function scoreString(score) { var absScore = Math.abs(score); var str = absScore.toString().split(""); // insert spaces every 3 characters var start = str.length%3; if (start==0) start=3; for (; start move this gift to a random position putGiftOnRandomBrick(giftBrick.giftType, index); } giftBrick.giftType = gift; } } else { // Distribute gifts randomly var bricksLeft = giftlessBricks(); if (bricksLeft < times) { print("error:", "Too many gifts of type", gift); return; } for (var i=0; i bgOverlay.height) { remove(gifts, gift); gift.destroy(); i--; } else if (intersects(giftRect, barRect)) { executeGift(gift.type); if (itemsGotDeleted) { return; } remove(gifts, gift); gift.destroy(); i--; } } tick = (tick+1) % repaintInterval; if (tick==0) { updateBallDirection(); } } function fireBall() { if (paused) { print("trying to fire while game is paused!!!"); return; } for (var i in balls) { var ball = balls[i]; if (!ball.toBeFired) continue; var ballCenter = ball.x + ball.width/2; var barCenter = bar.x + bar.width/2; var angle = (Math.PI/3) * (barCenter-ballCenter)/(bar.width/2) + Math.PI/2; ball.directionX = Math.cos(angle) * Globals.BALL_SPEED; ball.directionY = -Math.sin(angle) * Globals.BALL_SPEED; ball.toBeFired = false; } dScore = Globals.BRICK_SCORE; hideFireBallMessage(); randomCounter = 0; } function addLife() { if (lives < Globals.MAXIMUM_LIVES) { lives++; } } function setGamePaused(paused) { if (gameOver || gameWon || canvas.paused==paused) return; canvas.paused = paused; if (paused) { showMessage(i18n("Game Paused!")); elapsedTimeTimer.stop(); gameTimer.stop(); } else { elapsedTimeTimer.start(); gameTimer.start(); hideMessage(); } } function showMessage(text) { if (messageBox.text == text) { messageBox.opacity = 1; } else { messageBox.text = text; } } function showFireBallMessage(text) { fireBallMessage.opacity = 1; } function hideMessage() { if (gameOver || gameWon || paused) return; messageBox.opacity = 0; } function hideFireBallMessage() { fireBallMessage.opacity = 0; } function hideLater(target, interval) { hideTimer.target = target; hideTimer.interval = interval; hideTimer.start(); } function updateBallDirection() { // avoid infinite loops of the ball ++randomCounter; if (randomCounter == 1024) { randomCounter = 0; for (var i in balls) { var ball = balls[i]; if (Math.floor(Math.random()*10) % 2) { ball.directionX += 0.002; } else { ball.directionY += 0.002; } } // increase the speed a little // if there is at least one ball moving // and the game isn't paused var ballMoving = false; for (var i in balls) { if (!balls[i].toBeFired) { ballMoving = true; break; } } if (ballMoving && !paused) { changeSpeed(Globals.AUTO_SPEED_INCREASE); } } } function changeSpeed(ratio) { speed *= ratio; if (speed > 2.0) { - // make sure the minimum update interval is respected - if (gameTimer.interval < Globals.MINIMUM_UPDATE_INTERVAL*2) { - speed = 2.0; - return; - } - // else - // half the speed speed /= 2.0; // and double the number of ticks of the timer per time unit - gameTimer.interval /= 2; - repaintInterval *= 2; - gameTimer.restart(); + substeps *= 2; } if (speed < 1.0) { - if (gameTimer.interval >= Globals.REPAINT_INTERVAL) { - if (speed < Globals.MINIMUM_SPEED) { - speed = Globals.MINIMUM_SPEED; - } + if (substeps == 1) { + speed = Globals.MINIMUM_SPEED return; } // else // double the speed speed *= 2.0; // and half the number of ticks of the timer per time unit - gameTimer.interval *= 2; - repaintInterval /= 2; - gameTimer.restart(); + substeps /= 2; } } function deleteMovingObjects() { itemsGotDeleted = true; var times = balls.length for (var i=0; i r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top); } function intersectArea(r1, r2) { return (Math.min(r1.right, r2.right) - Math.max(r1.left, r2.left)) * (Math.min(r1.bottom, r2.bottom) - Math.max(r1.top, r2.top)); } // never run this function more than two times recursively var firstTime = true; function detectBallCollisions(ball) { // bounce a little early in some cases so the average position is centered var rect = createRect(ball); rect.left += ball.directionX/2; rect.top += ball.directionY/2; var x = rect.left; var y = rect.top; var barRect = createRect(bar); // bounce against the wall if (x < 0 && ball.directionX < 0) { ball.directionX *= -1; } else if (x+ball.width > bgOverlay.width && ball.directionX > 0) { ball.directionX *= -1; } else if (y < 0 && ball.directionY < 0) { ball.directionY *= -1; } else if (y+ball.height > bgOverlay.height && ball.directionY > 0) { // delete the ball remove(balls, ball); ball.destroy(); itemsGotDeleted = true; if (balls.length == 0) { showMessage(i18n("Oops! You have lost the ball!")); handleDeathTimer.start(); gameTimer.stop(); deleteMovingObjects(); } return; } // bounce against the bar else if (intersects(rect, barRect) && ball.directionY > 0) { var ballCenter = ball.x + ball.width/2; var barCenter = bar.x + bar.width/2; if (ballCenter > bar.x && ballCenter < bar.x+bar.width) { // the bar has been hit if (bar.type == "StickyBar") { ball.toBeFired = true; var diff = ball.x - bar.x; ball.barPosition = diff / bar.width; ball.posY = (bar.y-ball.height)/m_scale; } var angle = (Math.PI/3) * (barCenter-ballCenter)/(bar.width/2) + Math.PI/2; - var speed = Math.sqrt(Math.pow(ball.directionX, 2) + - Math.pow(ball.directionY, 2)); - ball.directionX = Math.cos(angle) * speed; - ball.directionY = -Math.sin(angle) * speed; + ball.directionX = Math.cos(angle) * Globals.BALL_SPEED; + ball.directionY = -Math.sin(angle) * Globals.BALL_SPEED; } } else { // bounce against the bricks (and optionally break them) handleBrickCollisions(ball); } // never run this function more than two times recursively if (firstTime) { firstTime = false; // check if there is another collision if (!itemsGotDeleted) { detectBallCollisions(ball); } } else { firstTime = true; return; } } function handleDeath() { hideMessage(); deleteMovingObjects(); bar.reset(); if (lives == 0) { gameOver = true; showMessage(i18n("Game Over!")); elapsedTimeTimer.stop(); bar.stopMoving(); singleShot(10, endGame, null); } else { lives--; resumeGame(); } } function handleBrickCollisions(ball) { if (itemsGotDeleted) { return; } var rect = createRect(ball); var intersectingBricks = new Array; for (var i=0; i area2) { // the area of intersection with the first brick is bigger collideWithBrick(ball, bricks[0]); } else { collideWithBrick(ball, bricks[1]); } } function collideWithBrick(ball, brick) { if (ball.type == "UnstoppableBall") { forcedHit(brick); return; // don't bounce } if (ball.type == "UnstoppableBurningBall") { explode(brick); return; // don't bounce } // calculate bounce var brickRect = createRect(brick); var ballRect = createRect(ball); var top = Math.round(ballRect.bottom - brickRect.top); var bottom = Math.round(brickRect.bottom - ballRect.top); var left = Math.round(ballRect.right - brickRect.left); var right = Math.round(brickRect.right - ballRect.left); var min = Math.min(Math.min(top, bottom), Math.min(left, right)); // bounce if (min == top && ball.directionY > 0) { ball.directionY *= -1; ball.posY -= top/m_scale; } else if (min == bottom && ball.directionY < 0) { ball.directionY *= -1; ball.posY += bottom/m_scale; } else if (min == left && ball.directionX > 0) { ball.directionX *= -1; ball.posX -= left/m_scale; } else if (min == right && ball.directionX < 0) { ball.directionX *= -1; ball.posX += right/m_scale; } else { return; // already bounced } if (ball.type == "BurningBall") { explode(brick); } else { hit(brick); } } function addBrickScore() { score += Math.round(dScore); dScore = Globals.BRICK_SCORE; } function forcedHit(brick) { if (brick == null) return; if (brick.type == "ExplodingBrick") { explode(brick); } else { handleDeletion(brick); } } function hit(brick) { if (brick.type == "HiddenBrick" && !brick.visible) { brick.visible = true; ++remainingBricks; } else if (brick.type == "MultipleBrick3") { brick.type = "MultipleBrick2"; addBrickScore(); } else if (brick.type == "MultipleBrick2") { brick.type = "MultipleBrick1"; addBrickScore(); } else if (brick.type == "ExplodingBrick") { explode(brick); } else if (brick.type != "UnbreakableBrick") { handleDeletion(brick); } } function explode(brick) { if (brick == null) return; burn(brick); singleShot(Globals.BURNING_SPEED, burnNearbyBricks, createRect(brick)); } function burnNearbyBricks(brickRect) { var nearby = nearbyBricks(brickRect); for (var i in nearby) { burn(nearby[i]); } } function burn(brick) { if (brick == null) return; if (brick.type == "ExplodingBrick") { // make sure it doesn't explode twice brick.type = "BurningBrick"; explode(brick); } else { singleShot(Globals.BURNING_SPEED, deleteBrick, new Array(brick.x, brick.y)); } if (brick.type == "HiddenBrick" && !brick.visible || brick.type == "UnbreakableBrick") { // Add to brick count when burning invisible and ubreakable bricks ++remainingBricks; } brick.type = "BurningBrick"; } // wrapper function to get the brick from position // and then passing it to handleDeletion function deleteBrick(pos) { var brick = bgOverlay.childAt(pos[0], pos[1]); if (brick!=null && bricks.indexOf(brick)>=0) { handleDeletion(brick); } } function createGiftAt(brick) { var gift = giftComponent.createObject(bgOverlay); gift.type = brick.giftType; gift.setPosition(brick.x/m_scale, brick.y/m_scale); gift.falling = true; gifts.push(gift); } function handleDeletion(brick) { remove(bricks, brick); if (brick.hasGift) { createGiftAt(brick); } // take the properties we want later before destroying the brick var brickType = brick.type; var brickVisible = brick.visible; brick.destroy(); --remainingBricks; addBrickScore(); // these two kind of bricks aren't counted in remainingBricks if ( (brickType == "HiddenBrick" && !brickVisible) || brickType == "UnbreakableBrick") { ++remainingBricks; return; // never need to load the next level } if (remainingBricks == 0) { loadNextLevel(); } } function nearbyBricks(brickRect) { var result = new Array; // coordinates of the center of the brick var x = (brickRect.left + brickRect.right)/2; var y = (brickRect.top + brickRect.bottom)/2; var width = (brickRect.right - brickRect.left); var height = (brickRect.bottom - brickRect.top); // points to the left, right, top and bottom of the brick var points = new Array; points.push([x-width, y]); points.push([x+width, y]); points.push([x, y-height]); points.push([x, y+height]); for (var j in points) { var px = points[j][0]; var py = points[j][1]; var b = bgOverlay.childAt(px, py); if (b != null && bricks.indexOf(b)>=0) result.push(b); } return result; } function executeGift(type) { score += Globals.GIFT_SCORE; if (type == "Gift100Points") { score += 100 - Globals.GIFT_SCORE; } else if (type == "Gift200Points") { score += 200 - Globals.GIFT_SCORE; } else if (type == "Gift500Points") { score += 500 - Globals.GIFT_SCORE; } else if (type == "Gift1000Points") { score += 1000 - Globals.GIFT_SCORE; } else if (type == "GiftAddLife") { addLife(); } else if (type == "GiftLoseLife") { handleDeath(); } else if (type == "GiftNextLevel") { loadNextLevel(); } else if (type == "GiftMagicEye") { giftMagicEye(); } else if (type == "GiftMagicWand") { giftMagicWand(); } else if (type == "GiftSplitBall") { giftSplitBall(); } else if (type == "GiftAddBall") { createBall(); } else if (type == "GiftUnstoppableBall") { giftUnstoppableBall(); } else if (type == "GiftBurningBall") { giftBurningBall(); } else if (type == "GiftDecreaseSpeed") { changeSpeed(1.0/Globals.CHANGE_SPEED_RATIO); } else if (type == "GiftIncreaseSpeed") { changeSpeed(Globals.CHANGE_SPEED_RATIO); } else if (type == "GiftEnlargeBar") { bar.enlarge(); } else if (type == "GiftShrinkBar") { bar.shrink(); } else if (type == "GiftStickyBar") { bar.type = "StickyBar"; } else if (type == "GiftMoreExplosion") { giftMoreExplosion(); } else { print("Unrecognized gift type!!!", type); } } function giftMagicEye() { // make all hidden bricks visible for (var i in bricks) { var brick = bricks[i]; if (!brick.visible) { brick.visible = true; ++remainingBricks; } } } function giftMagicWand() { for (var i in bricks) { var brick = bricks[i]; // make unbreakable bricks breakable if (brick.type == "UnbreakableBrick") { brick.type = "BreakableBrick"; ++remainingBricks; } // make multiple bricks single if (brick.type == "MultipleBrick3") { brick.type = "MultipleBrick1"; score += Globals.AUTOBRICK_SCORE * 2; } else if (brick.type == "MultipleBrick2") { brick.type = "MultipleBrick1"; score += Globals.AUTOBRICK_SCORE; } } } function giftSplitBall() { var newBalls = new Array; for (var i in balls) { var ball = balls[i]; var newBall = ballComponent.createObject(bgOverlay); // give it a nice direction newBall.directionX = ball.directionX; newBall.directionY = ball.directionY; if (ball.directionY > 0) newBall.directionY *= -1; else newBall.directionX *= -1; newBall.toBeFired = ball.toBeFired; newBall.spriteKey = ball.spriteKey; newBall.type = ball.type; newBall.posX = ball.posX; newBall.posY = ball.posY; newBalls.push(newBall); } balls = balls.concat(newBalls); } function giftUnstoppableBall() { for (var i in balls) { var ball = balls[i]; if (ball.type == "BurningBall") { ball.type = "UnstoppableBurningBall"; } else if (ball.type != "UnstoppableBurningBall") { ball.type = "UnstoppableBall"; } } } function giftBurningBall() { for (var i in balls) { var ball = balls[i]; if (ball.type == "UnstoppableBall") { ball.type = "UnstoppableBurningBall"; } else if (ball.type != "UnstoppableBurningBall") { ball.type = "BurningBall"; } } } function giftMoreExplosion() { var explodingBricks = new Array; for (var i in bricks) { var brick = bricks[i]; if (brick.type == "ExplodingBrick") { explodingBricks.push(brick); } } for (var i in explodingBricks) { var nearby = nearbyBricks(createRect(explodingBricks[i])); for (var j in nearby) { var nearbyBrick = nearby[j]; if (nearbyBrick.type == "UnbreakableBrick") { ++remainingBricks; } if (nearbyBrick.type == "HiddenBrick" && !nearbyBrick.visible) { nearbyBrick.visible = true; ++remainingBricks; } nearbyBrick.type = "ExplodingBrick"; } } } diff --git a/src/qml/main.qml b/src/qml/main.qml index ae3fbed..08214af 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,283 +1,288 @@ /* Copyright 2012 Viranch Mehta This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import QtQuick 2.3 import org.kde.games.core 0.1 as KgCore import "globals.js" as Globals import "logic.js" as Logic Item { id: canvas signal levelComplete() signal gameEnded(int score, int level, int elapsedTime) signal mousePressed() property real speed property bool paused: false property bool gameOver: false property bool gameWon: false function updateGeometry() { var bw = Globals.BRICK_WIDTH*Globals.WIDTH + 1; // bh = (overlayHeight = BRICK_HEIGHT*HEIGHT+1) // +(headerItemsHeight = BRICK_HEIGHT*1.5) // +(margin = headerItemHeight*0.2) var bh = Globals.BRICK_HEIGHT*Globals.HEIGHT + 1 + Globals.BRICK_HEIGHT*1.5*1.2; var w = canvas.height*bw/bh; var h = canvas.width*bh/bw; if (w > canvas.width) { container.width = canvas.width-20; container.height = container.width*bh/bw; } else if (h > canvas.height) { container.height = canvas.height-50; container.width = container.height*bw/bh; } else { // Never happens, don't know what to do in case it does! } } property real m_scale: container.width/(Globals.BRICK_WIDTH*Globals.WIDTH) CanvasItem { id: background spriteKey: "Background" anchors.fill: parent } Item { id: container anchors.centerIn: parent } property int barCenter: mapFromItem(bgOverlay, Math.round(bar.x + bar.width/2), 0).x CanvasItem { id: bgOverlay spriteKey: "BackgroundOverlay" anchors { left: container.left bottom: container.bottom } width: m_scale * (Globals.BRICK_WIDTH*Globals.WIDTH + 1) height: m_scale * (Globals.BRICK_HEIGHT*Globals.HEIGHT + 1) Bar { id: bar anchors { bottom: parent.bottom } posX: 300 } } Rectangle { id: pauseOverlay anchors.fill: parent color: "#646464" opacity: paused ? 0.6 : 0 z: 1 // to show above all the elements (except messageBox) Behavior on opacity { NumberAnimation { duration: 100 } } } function setGamePaused(paused) { Logic.setGamePaused(paused); } TextItem { id: messageBox anchors.centerIn: bgOverlay z: 2 // to make it display above the pause overlay width: m_scale * Globals.BRICK_WIDTH*9 height: m_scale * Globals.BRICK_HEIGHT*5 opacity: 0 Behavior on opacity { NumberAnimation { duration: 100 } } } property string fireShortcut: "Space" TextItem { id: fireBallMessage anchors { horizontalCenter: bgOverlay.horizontalCenter bottom: bgOverlay.bottom bottomMargin: (bgOverlay.height-height)/4 } width: m_scale * Globals.BRICK_WIDTH*9 height: m_scale * Globals.BRICK_HEIGHT*2 text: i18n("Press %1 to fire the ball", fireShortcut) opacity: 0 Behavior on opacity { NumberAnimation { duration: 100 } } } property int score TextItem { id: scoreDisplay width: m_scale * (Globals.BRICK_WIDTH*Globals.WIDTH)/6 height: m_scale * Globals.BRICK_HEIGHT*1.5 anchors { left: bgOverlay.left bottom: bgOverlay.top bottomMargin: height/5 } text: Logic.scoreString(parent.score) } property int level TextItem { id: levelDisplay width: m_scale * (Globals.BRICK_WIDTH*Globals.WIDTH)/5 height: m_scale * Globals.BRICK_HEIGHT*1.5 anchors { left: scoreDisplay.right leftMargin: width-scoreDisplay.width bottom: bgOverlay.top bottomMargin: height/5 } text: i18n("Level %1", parent.level) } property int lives Row { id: lifeBars spacing: m_scale * Globals.BRICK_WIDTH*0.23 anchors { right: bgOverlay.right rightMargin: 20 bottom: bgOverlay.top bottomMargin: levelDisplay.height/5 } Repeater { model: canvas.lives Bar { width: m_scale * Globals.BRICK_WIDTH/1.3 height: m_scale * Globals.BRICK_HEIGHT/1.3 } } } function reset() { Logic.reset(); } function loadLine(line, lineNumber) { Logic.showLine(line, lineNumber); } function loadGift(gift, times, pos) { Logic.putGift(gift, times, pos); } function updateBarDirection(direction) { bar.direction = direction; } function startGame() { Logic.startLevel(); } Timer { id: gameTimer interval: Globals.REPAINT_INTERVAL repeat: true - onTriggered: Logic.detectCollisions(); + onTriggered: { + for (x = 0; x < Logic.substeps; ++x) { + Logic.moveBalls() + Logic.detectCollisions() + } + } } Timer { id: elapsedTimeTimer interval: 1000 repeat: true property int elapsedTime: 0 onTriggered: elapsedTime++; } Timer { id: handleDeathTimer interval: 1000 onTriggered: Logic.handleDeath() } // hides the current showed message by target // unless the game is paused, won or game over Timer { id: hideTimer property variant target onTriggered: { if (paused) return; target.opacity = 0; } } function fire() { Logic.fireBall(); } function cheatAddLife() { Logic.addLife(); } function cheatSkipLevel() { Logic.loadNextLevel(); } Timer { id: burnBricksTimer property variant target interval: Globals.BURNING_SPEED onTriggered: Logic.burnNearbyBricks(target); } MouseArea { id: mouseArea enabled: false anchors.fill: parent hoverEnabled: true onPositionChanged: { if (paused) return; // avoids accidentally moving the mouse while playing using the keys if (bar.direction != 0) return; var barX = canvas.mapToItem(bgOverlay, mouse.x, 0).x - bar.width/2; bar.moveTo(barX/m_scale); } onClicked: canvas.mousePressed() } Item { id: keyEventHandler focus: true anchors.fill: parent Keys.onPressed: { if (event.key == Qt.Key_Right) { updateBarDirection(1); } else if (event.key == Qt.Key_Left) { updateBarDirection(-1); } } Keys.onReleased: { if (event.key == Qt.Key_Right || event.key == Qt.Key_Left) { updateBarDirection(0); } } } }