import javafx.application.Frame;
import javafx.application.Stage;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.geometry.Circle;
import javafx.scene.geometry.Rectangle;
import javafx.scene.geometry.Shape;
import javafx.scene.text.Text;
import javafx.scene.Font;
import javafx.scene.FontStyle;
import javafx.input.MouseEvent;
/* Fade variable modified in some animations and used in the fadescreen */
var fade = 0.0;
/* The Bomb */
var lock = false;
var tx = 0.0;
var ty = 0.0;
var bomb:Node = Group{
opacity: bind bombfade;
content: [
ImageView {
image: Image {
url: "{__DIR__}/bomb.png"
}
},
Circle {
centerX: 45, centerY: 21, radius: 7, fill: Color.LIME
opacity: bind led
},
Circle {
centerX: 30, centerY: 30, fill: Color.WHITE
radius: bind fireradius
},
],
var startX = 0.0;
var startY = 0.0;
translateX: bind tx
translateY: bind ty
onMousePressed: function( e: MouseEvent ):Void {
if (lock) {return;}
startX = e.getDragX() - tx;
startY = e.getDragY() - ty;
}
onMouseDragged: function(e:MouseEvent):Void {
if (lock) {return;}
tx = e.getDragX() - startX;
ty = e.getDragY() - startY;
checkcollissions();
}
}
/* Big rectangle that covers all the screen (bomb explosion or game end) */
var fadescreen = Rectangle {
x: 0, y: 0, width: 640, height: 480, fill: Color.WHITE
opacity: bind fade
}
/* The wood floor image for the scenario. */
var floor = ImageView {
image: Image {
url: "{__DIR__}/floor.png"
}
}
/* The goal image where the bomb should be placed. */
var goal = ImageView {
x: 470, y: 360
image: Image {
url: "{__DIR__}/goal.png"
}
}
/* List of obstacles nodes that the bomb can collide with. */
var obstacles = [
Rectangle { x: 120, y: 0, width: 100, height: 300, fill: Color.BLACK},
Rectangle { x: 350, y: 200, width: 100, height: 300, fill: Color.BLACK},
Rectangle { x: 370, y: 50, width: 50, height: 50, fill: Color.BLACK},
Rectangle {
x: 250, y: 120, translateX: bind move, width: 100, height: 50
fill: Color.BLACK
},
];
/* Visible representations of obstacles */
var wallimage = Image {
url: "{__DIR__}/wall.png"
}
var walls = for(obs in obstacles){
ImageView {
x: obs.x, y: obs.y, translateX: bind obs.translateX
clip: obs, image: wallimage
}
}
/* Animation for a blinking green led */
var led = 0.0;
var bombclock = Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames : [
KeyFrame {
time : 0s
values : led => 0.0 tween Interpolator.LINEAR
},
KeyFrame {
time : 1s
values : led => 1.0 tween Interpolator.LINEAR
}
]
}
/* Animation for the bomb explosion and game reset */
var fireradius = 0.0;
var explosion:Timeline = Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 0s
values : [
fireradius => 0.0,
fade => 0.0
]
},
KeyFrame {
time : 2s
values : [
fireradius => 200.0 tween Interpolator.LINEAR,
fade => 1.0 tween Interpolator.LINEAR
]
action: gamereset
},
KeyFrame {
time : 3s
values: fade => 0.0 tween Interpolator.LINEAR
},
]
}
/* Reset variables for initial values */
function gamereset(){
lock = false;
fireradius = 0.0;
tx = 0.0;
ty = 0.0;
bombfade = 1.0;
moveblock.start();
specialcollison.start();
bombclock.start();
}
/* Animation when the bomb reaches the goal. Bomb disapear. */
var bombfade = 1.0;
var bomdisapear = Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 1s
values: [
bombfade => 0.0 tween Interpolator.EASEBOTH,
fade => 0.0
]
},
KeyFrame {
time : 2s
values:
fade => 1.0 tween Interpolator.LINEAR;
action: gamereset
},
KeyFrame {
time : 3s
values:
fade => 0.0 tween Interpolator.LINEAR;
},
]
}
/* Animation for a moving block. */
var move = 0.0;
var moveblock = Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames : [
KeyFrame {
time : 0s
values :
move => 0.0
},
KeyFrame {
time : 3s
values :
move => 200.0 tween Interpolator.EASEBOTH
},
]
}
/* Check and handle possible collisions. */
function checkcollissions(): Void {
if(checkobstacles()){
lock = true;
specialcollison.stop();
moveblock.stop();
explosion.start();
}
if (insidenode(bomb,goal)) {
lock = true;
moveblock.stop();
bomdisapear.start();
}
}
/* There was a bug, when the bomb is stopped, not been gragged, in front of
the moving block, it could pass through it because checkcollissions() was
only called on mouse moving. This make sure checking this special case. */
var specialcollison:Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames : [
KeyFrame {
time : 1s/5
action: function(){
if(hitnode(obstacles[sizeof obstacles-1], bomb)){
lock = true;
moveblock.stop();
explosion.start();
specialcollison.stop();
}
}
}
]
}
/*
* The next four functions are for collision detection.
* @See http://silveiraneto.net/2008/10/30/javafx-rectangular-collision-detection/
*/
/*
* Check collision between two rectangles.
*/
function collission(ax, ay, bx, by, cx, cy, dx, dy): Boolean {
return not ((ax > dx)or(bx < cx)or(ay > dy)or(by < cy));
}
/*
* Check if the first rectangle are inside the second.
*/
function inside (ax, ay, bx, by, cx, cy, dx, dy):Boolean{
return ((ax > cx) and (bx < dx) and (ay > cy) and (by < dy));
}
function hitnode(a: Node, b:Node): Boolean {
return (collission(
a.getBoundsX(), a.getBoundsY(),
a.getBoundsX() + a.getWidth(), a.getBoundsY() + a.getHeight(),
b.getBoundsX(), b.getBoundsY(),
b.getBoundsX() + b.getWidth(), b.getBoundsY() + b.getHeight()
));
}
function insidenode(a:Node,b:Node):Boolean {
return (inside(
a.getBoundsX(), a.getBoundsY(),
a.getBoundsX() + a.getWidth(), a.getBoundsY() + a.getHeight(),
b.getBoundsX(), b.getBoundsY(),
b.getBoundsX() + b.getWidth(), b.getBoundsY() + b.getHeight()
));
}
/*
* Check collision of bomb against each obstacle.
*/
function checkobstacles(): Boolean{
for(obst in obstacles){
if (hitnode(obst, bomb)){
return true;
}
}
return false;
}
/* Pack visual game elements in a Frame's Stage, unresizable. */
Frame {
title: "Defuse the Bomb"
width: 640
height: 480
resizable: false
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true
stage: Stage {
content: bind [floor, goal, walls, bomb, fadescreen]
}
}
/* Call gamereset to set initial values and start animations */
gamereset();