/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
* Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Oracle Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package brickbreaker;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import java.util.Vector;
import brickbreaker.Config;
public class Level extends CustomNode {
var bricks = new Vector();
var brickCount: Integer;
var fadeBricks: Brick[];
var bonuses: Bonus[];
var group: Group;
var lifes: Bonus[];
var catchedBonus = 0;
var state = 0;
var batDirection = 0;
var ballDirX: Number;
var ballDirY: Number;
public function start() {
startingTimeline.play();
timeline.play();
group.content[0].requestFocus();
updateScore(0);
updateLifes();
}
public function stop() {
startingTimeline.stop();
timeline.stop();
}
public var levelNumber: Integer on replace {
var level = LevelData.getLevelData(levelNumber);
for (row in [0..<sizeof level], col in [0..<Config.FIELD_BRICK_IN_ROW]) {
var rowString = level[row];
var brick: Brick;
if (rowString != null and col < rowString.length()) {
var type = rowString.substring(col, col + 1);
if (type != " ") {
brick = Brick {
type: Brick.getBrickType(type)
translateX: col * Config.brickWidth
translateY: Config.fieldY + row * Config.brickHeight
}
if (brick.type != Brick.TYPE_GREY) {
brickCount++
}
}
}
bricks.addElement(brick)
}
}
function getBrick(row: Integer, col: Integer): Brick {
var i = row * Config.FIELD_BRICK_IN_ROW + col;
if (col < 0 or col >= Config.FIELD_BRICK_IN_ROW or row < 0 or
i >= bricks.size()) {
null
} else {
bricks.elementAt(i) as Brick
}
}
function updateScore(inc: Integer) {
Main.mainFrame.score += inc;
score.content = "{Main.mainFrame.score}";
}
function moveBat(newX: Integer) {
var x = newX;
if (x < 0) {
x = 0
}
if (x + bat.width > Config.fieldWidth) {
x = Config.fieldWidth - bat.width
}
if (state == 1) {
var ballX = ball.translateX + x - bat.translateX;
if (ballX < 0) {
ballX = 0
}
def BALL_MAX_X = Config.fieldWidth - ball.diameter;
if (ballX > BALL_MAX_X) {
ballX = BALL_MAX_X
}
ball.translateX = ballX;
}
bat.translateX = x;
}
function kickBrick(row: Integer, col: Integer) {
var brick = getBrick(row, col);
if (brick == null or
(catchedBonus != Bonus.TYPE_STRIKE and not brick.kick())) {
return;
}
updateScore(10);
if (brick.type != Brick.TYPE_GREY) {
brickCount--;
if (brickCount == 0) {
Main.mainFrame.state++
}
}
bricks.setElementAt(null, row * Config.FIELD_BRICK_IN_ROW + col);
if (Config.isMobile or Config.isTV) {
brick.visible = false;
delete brick from fadeBricks;
} else {
insert brick into fadeBricks;
}
if (Utils.random(8) == 0 and sizeof bonuses < 5) {
var bonus = Bonus {
type: Utils.random(Bonus.COUNT) + 1
translateY: brick.translateY
visible: true
}
bonus.translateX = brick.translateX + (Config.brickWidth - bonus.width) / 2;
insert bonus into group.content;
insert bonus into bonuses;
}
}
function updateLifes() {
while (sizeof lifes > Main.mainFrame.lifeCount) {
var lifeBat = lifes[sizeof lifes - 1];
delete lifeBat from lifes;
delete lifeBat from group.content;
}
def maxVisibleLifes = if (Config.isMobile) 6 else 9;
def scale = if (Config.isMobile) 1 else if (Config.isTV) 1.2 else 0.8;
for (life in [sizeof lifes..<java.lang.Math.min(Main.mainFrame.lifeCount,
maxVisibleLifes)]) {
var lifeBonus = Bonus {
type: Bonus.TYPE_LIFE
scaleX: scale
scaleY: scale
}
def lifeTranslate = if(Config.screenWidth > 250 and Config.isMobile) 80 else 1;
lifeBonus.translateX = lifesCaption.translateX + lifeTranslate +
lifesCaption.boundsInLocal.width + (life mod 3) * lifeBonus.width;
lifeBonus.translateY = lifesCaption.translateY + lifeTranslate/4 +
(life / 3) * lifeBonus.height*mobScaling;
insert lifeBonus into lifes;
insert lifeBonus into group.content;
}
}
function correctBallSpeed() {
var speed = java.lang.Math.sqrt(ballDirX * ballDirX + ballDirY * ballDirY);
if (speed > Config.ballMaxSpeed) {
ballDirX *= Config.ballMaxSpeed / speed;
ballDirY *= Config.ballMaxSpeed / speed;
speed = Config.ballMaxSpeed;
}
if (speed < Config.ballMinSpeed) {
ballDirX *= Config.ballMinSpeed / speed;
ballDirY *= Config.ballMinSpeed / speed;
speed = Config.ballMinSpeed;
}
if (java.lang.Math.abs(ballDirX) < Config.ballMinCoordSpeed) {
ballDirX = Utils.sign(ballDirX) * Config.ballMinCoordSpeed;
ballDirY = Utils.sign(ballDirY) *
java.lang.Math.sqrt(speed * speed - ballDirX * ballDirX);
} else if (java.lang.Math.abs(ballDirY) < Config.ballMinCoordSpeed) {
ballDirY = Utils.sign(ballDirY) * Config.ballMinCoordSpeed;
ballDirX = Utils.sign(ballDirX) *
java.lang.Math.sqrt(speed * speed - ballDirY * ballDirY);
}
}
def bat = Bat {
translateY: Config.batY
visible: false
}
def ball = Ball {
visible: false
}
def roundCaption = Text {
content: "ROUND"
textOrigin: TextOrigin.TOP
}
def round = Text {
translateX: bind roundCaption.translateX +
roundCaption.boundsInLocal.width + Config.infoTextSpace
translateY: bind roundCaption.translateY
content: "{levelNumber}"
textOrigin: TextOrigin.TOP
font: bind roundCaption.font
}
def scoreCaption = Text {
content: "SCORE"
fill: bind roundCaption.fill
textOrigin: TextOrigin.TOP
font: bind roundCaption.font
}
def score = Text {
translateX: bind scoreCaption.translateX +
scoreCaption.boundsInLocal.width + Config.infoTextSpace
translateY: bind scoreCaption.translateY
fill: bind round.fill
textOrigin: TextOrigin.TOP
font: bind roundCaption.font
}
def lifesCaption = Text {
content: "LIFE"
fill: bind roundCaption.fill
textOrigin: TextOrigin.TOP
font: bind roundCaption.font
}
def mobScaling = if(Config.screenWidth > 250) 1.5 else 1;
def infoPanel = if (Config.isMobile) {
roundCaption.fill = Color.rgb(51, 153, 51);
roundCaption.font = Font {
name: "Bitstream Vera Sans Bold"
size: 10
}
roundCaption.translateX = 130;
roundCaption.translateY = 15;
round.fill = Color.rgb(0, 255, 0);
scoreCaption.translateX = 130;
scoreCaption.translateY = 30;
lifesCaption.translateX = 130;
lifesCaption.translateY = 45;
Group {
scaleX: mobScaling
scaleY: mobScaling
translateX: if(Config.screenWidth > 250) 80 else 0
translateY: if(Config.screenWidth > 250) 25 else 0
content: [
Rectangle {
width: Config.screenWidth/mobScaling
height: Config.fieldY/mobScaling
fill: Color.BLACK
},
ImageView {
var image = Config.images[Config.IMAGE_LOGO];
image: image
translateX: 10
translateY: 10
},
roundCaption,
round,
scoreCaption,
score,
lifesCaption
]
}
} else {
var TV_RELOACATE_FACTOR = 1.0;
if(Config.isTV) TV_RELOACATE_FACTOR = 1.1;
roundCaption.fill = Color.rgb(51, 102, 51);
roundCaption.font = Font {
name: "Impact"
size: 18
}
roundCaption.translateX = 750 * TV_RELOACATE_FACTOR;
roundCaption.translateY = 128;
round.fill = Color.rgb(0, 204, 102);
scoreCaption.translateX = 750 * TV_RELOACATE_FACTOR;
scoreCaption.translateY = 164;
lifesCaption.translateX = 750 * TV_RELOACATE_FACTOR;
lifesCaption.translateY = 200;
def INFO_LEGEND_COLOR = Color.rgb(0, 114, 188);
Group {
var infoWidth = Config.screenWidth - Config.fieldWidth;
content: [
Rectangle {
translateX: Config.fieldWidth
width: infoWidth
height: Config.screenHeight
fill: Color.BLACK
},
ImageView {
image: Image {
url: "{Config.IMAGE_DIR}/vline.png"
}
translateX: Config.fieldWidth + 3
},
ImageView {
var image = Config.images[Config.IMAGE_LOGO];
image: image
translateX: 750 * TV_RELOACATE_FACTOR
translateY: 30
},
roundCaption,
round,
scoreCaption,
score,
lifesCaption,
Text {
translateX: 750 * TV_RELOACATE_FACTOR
translateY: 310
content: "LEGEND"
fill: INFO_LEGEND_COLOR
textOrigin: TextOrigin.TOP
font: Font {
name: "Impact"
size: 18
}
},
for (i in [0..<Bonus.COUNT]) {
def bonus = Bonus {
type: i
}
def text = Text {
translateX: 820 * TV_RELOACATE_FACTOR
translateY: 350 + i * 40
content: Bonus.NAMES[i]
fill: INFO_LEGEND_COLOR
textOrigin: TextOrigin.TOP
font: Font {
name: "Arial"
size: 12
}
}
bonus.translateX = (750 + (820 - 750 - bonus.width) / 2) * TV_RELOACATE_FACTOR;
bonus.translateY = text.translateY -
(bonus.height - text.boundsInLocal.height) / 2;
[bonus as Node, text as Node]
}
]
}
}
def message: ImageView = ImageView {
translateX: bind (Config.fieldWidth - message.image.width) / 2
translateY: bind Config.fieldY +
(Config.fieldHeight - message.image.height) / 2
image: Config.images[Config.IMAGE_READY]
visible: false
}
override public function create(): Node {
group = Group {
content: [
ImageView {
focusTraversable: true
image: Config.images[Config.IMAGE_BACKGROUND]
fitWidth: Config.screenWidth
fitHeight: Config.screenHeight
onMouseMoved: function( e: MouseEvent ):Void {
moveBat(e.x - bat.width / 2);
}
onMouseDragged: function( e: MouseEvent ):Void {
moveBat(e.x - bat.width / 2);
}
onMousePressed: function( e: MouseEvent ):Void {
if (state == 2) {
moveBat(e.x - bat.width / 2);
}
if (state == 1) {
state = 2;
}
if (state == 3) {
Main.mainFrame.state = 0;
}
}
onKeyPressed: function( e: KeyEvent ):Void {
if ((e.code == KeyCode.VK_POWER) or (e.code == KeyCode.VK_X)) {
FX.exit();
}
if (state == 1 and (e.code == KeyCode.VK_SPACE or
e.code == KeyCode.VK_ENTER or e.code == KeyCode.VK_PLAY)) {
state = 2;
}
if (state == 3) {
Main.mainFrame.state = 0;
}
if (state == 2 and e.code == KeyCode.VK_Q) {
lostLife();
return;
}
if ((e.code == KeyCode.VK_LEFT or e.code == KeyCode.VK_TRACK_PREV)) {
batDirection = -Config.batSpeed;
}
if ((e.code == KeyCode.VK_RIGHT or e.code == KeyCode.VK_TRACK_NEXT)) {
batDirection = Config.batSpeed;
}
}
onKeyReleased: function( e: KeyEvent ):Void {
if (e.code == KeyCode.VK_LEFT or e.code == KeyCode.VK_RIGHT
or e.code == KeyCode.VK_TRACK_PREV or e.code == KeyCode.VK_TRACK_NEXT) {
batDirection = 0;
}
}
},
for (row in [0..<bricks.size() / Config.FIELD_BRICK_IN_ROW],
col in [0..<Config.FIELD_BRICK_IN_ROW]) {
getBrick(row, col);
},
message,
ball,
bat,
infoPanel
]
}
}
def timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time: Config.ANIMATION_TIME
action: function () {
for (brick in fadeBricks) {
brick.opacity -= 0.1;
if (brick.opacity <= 0) {
brick.visible = false;
delete brick from fadeBricks;
}
}
if (batDirection != 0 and state != 0) {
moveBat(bat.translateX + batDirection);
}
for (bonus in bonuses) {
if (bonus.translateY > Config.screenHeight) {
bonus.visible = false;
delete bonus from bonuses;
delete bonus from group.content;
} else {
bonus.translateY += Config.bonusSpeed;
if (bonus.translateX + bonus.width > bat.translateX and
bonus.translateX < bat.translateX + bat.width and
bonus.translateY + bonus.height > bat.translateY and
bonus.translateY < bat.translateY + bat.height) {
updateScore(100);
catchedBonus = bonus.type;
bonus.visible = false;
delete bonus from bonuses;
delete bonus from group.content;
if (bonus.type == Bonus.TYPE_SLOW) {
ballDirX /= 1.5;
ballDirY /= 1.5;
correctBallSpeed();
}
if (bonus.type == Bonus.TYPE_FAST) {
ballDirX *= 1.5;
ballDirY *= 1.5;
correctBallSpeed();
}
if (bonus.type == Bonus.TYPE_CATCH) {
}
if (bonus.type == Bonus.TYPE_GROW_BAT) {
if (bat.size < Bat.MAX_SIZE) {
bat.size += 1;
if (bat.translateX + bat.width > Config.fieldWidth) {
bat.translateX = Config.fieldWidth - bat.width;
}
}
}
if (bonus.type == Bonus.TYPE_REDUCE_BAT) {
if (bat.size > 0) {
var oldWidth = bat.width;
bat.size -= 1;
bat.translateX += (oldWidth - bat.width) / 2;
}
}
if (bonus.type == Bonus.TYPE_GROW_BALL) {
if (ball.size < Ball.MAX_SIZE) {
ball.size += 1;
if (state == 1) {
ball.translateY = Config.batY - ball.diameter
}
}
}
if (bonus.type == Bonus.TYPE_REDUCE_BALL) {
if (ball.size > 0) {
ball.size -= 1;
if (state == 1) {
ball.translateY = Config.batY - ball.diameter
}
}
}
if (bonus.type == Bonus.TYPE_LIFE) {
Main.mainFrame.lifeCount += 1;
updateLifes();
}
}
}
}
if (state != 2) {
return;
}
var newX = ball.translateX + ballDirX;
var newY = ball.translateY + ballDirY;
var inverseX = false;
var inverseY = false;
if (newX < 0) {
newX = -newX;
inverseX = true;
}
def BALL_MAX_X = Config.fieldWidth - ball.diameter;
if (newX > BALL_MAX_X) {
newX = BALL_MAX_X - (newX - BALL_MAX_X);
inverseX = true;
}
if (newY < Config.fieldY) {
newY = 2 * Config.fieldY - newY;
inverseY = true;
}
if (ballDirY > 0 and
ball.translateY + ball.diameter < Config.batY and
newY + ball.diameter >= Config.batY and
newX >= bat.translateX - ball.diameter and
newX < bat.translateX + bat.width + ball.diameter) {
inverseY = true;
var speed = java.lang.Math.sqrt(ballDirX * ballDirX + ballDirY * ballDirY);
ballDirX *= (speed + Config.ballSpeedInc) / speed;
ballDirY *= (speed + Config.ballSpeedInc) / speed;
var offsetX = newX + ball.diameter / 2 - bat.translateX - bat.width / 2;
if (java.lang.Math.abs(offsetX) > bat.width / 4) {
ballDirX += offsetX / 5;
def MAX_COORD_SPEED = java.lang.Math.sqrt(speed * speed -
Config.ballMinCoordSpeed * Config.ballMinCoordSpeed);
if (java.lang.Math.abs(ballDirX) > MAX_COORD_SPEED) {
ballDirX = Utils.sign(ballDirX) * MAX_COORD_SPEED;
}
ballDirY = Utils.sign(ballDirY) *
java.lang.Math.sqrt(speed * speed - ballDirX * ballDirX);
}
correctBallSpeed();
if (catchedBonus == Bonus.TYPE_CATCH) {
newY = Config.batY - ball.diameter;
state = 1;
}
}
var firstCol: Integer = newX / Config.brickWidth as Integer;
var secondCol: Integer = (newX + ball.diameter) / Config.brickWidth as Integer;
var firstRow: Integer = (newY - Config.fieldY) /
Config.brickHeight as Integer;
var secondRow: Integer = (newY - Config.fieldY + ball.diameter)
/ Config.brickHeight as Integer;
if (ballDirX > 0) {
var temp = secondCol;
secondCol = firstCol;
firstCol = temp;
}
if (ballDirY > 0) {
var temp = secondRow;
secondRow = firstRow;
firstRow = temp;
}
var vertBrick = getBrick(firstRow, secondCol);
var horBrick = getBrick(secondRow, firstCol);
if (vertBrick != null) {
kickBrick(firstRow, secondCol);
if (catchedBonus != Bonus.TYPE_STRIKE) {
inverseY = true;
}
}
if (horBrick != null and
(firstCol != secondCol or firstRow != secondRow)) {
kickBrick(secondRow, firstCol);
if (catchedBonus != Bonus.TYPE_STRIKE) {
inverseX = true;
}
}
if (firstCol != secondCol or firstRow != secondRow) {
var diagBrick = getBrick(firstRow, firstCol);
if (diagBrick != null and diagBrick != vertBrick and
diagBrick != horBrick) {
kickBrick(firstRow, firstCol);
if (vertBrick == null and horBrick == null and
catchedBonus != Bonus.TYPE_STRIKE) {
inverseX = true;
inverseY = true;
}
}
}
ball.translateX = newX;
ball.translateY = newY;
if (inverseX) {
ballDirX = -ballDirX;
}
if (inverseY) {
ballDirY = -ballDirY;
}
if (ball.translateY > Config.screenHeight) {
lostLife();
}
}
}
]
}
function lostLife() {
Main.mainFrame.lifeCount--;
if (Main.mainFrame.lifeCount < 0) {
state = 3;
ball.visible = false;
bat.visible = false;
message.image = Config.images[Config.IMAGE_GAMEOVER];
message.visible = true;
message.opacity = 1;
} else {
updateLifes();
bat.size = Bat.DEFAULT_SIZE;
ball.size = Ball.DEFAULT_SIZE;
bat.translateX = (Config.fieldWidth - bat.width) / 2;
ball.translateX = Config.fieldWidth / 2 - ball.diameter / 2;
ball.translateY = Config.batY - ball.diameter;
state = 1;
catchedBonus = 0;
ballDirX = (Utils.random(2) * 2 - 1) * Config.ballMinCoordSpeed;
ballDirY = -Config.ballMinSpeed;
}
}
def startingTimeline: Timeline = Timeline {
keyFrames : [
at (0.5s) {
state => 0;
message.opacity => 0.0;
message.visible => true;
bat.visible => false;
ball.visible => false;
},
at (1.5s) {
message.opacity => 1.0 tween Interpolator.LINEAR;
},
at (3s) {
message.opacity => 1.0;
},
at (4s) {
message.opacity => 0.0 tween Interpolator.LINEAR;
message.visible => false;
bat.visible => true;
bat.translateX => (Config.fieldWidth - bat.width) / 2;
ball.visible => true;
ball.translateX => Config.fieldWidth / 2 - ball.diameter / 2;
ball.translateY => Config.batY - ball.diameter;
state => 1;
ballDirX => (Utils.random(2) * 2 - 1) * Config.ballMinCoordSpeed;
ballDirY => -Config.ballMinSpeed;
}
]
}
}