Leveraging Java for Great User Experiences and the Internet of Things

Compiled JavaFX Script

May 01, 2008

You may know that I've been progressively building a Tetris game in JavaFX on this blog, the most recent post being Game Over: Improving upon the Compiled JavaFX Tetris Program. In each post I've shown you the code and pointed out some highlights. Since then I've added some finishing touches, and would be honored if you've try it out and give me (kind) feedback for improving it further. The screenshot below shows what TetrisJFX should look like when you click this Java Web Start link. By the way, you'll need the JRE (Java Runtime Environment) 1.5 or later. Also, please keep in mind that the JavaFX Script JAR files will be included with the JRE at some point. Until then please understand that when you click this link those JAR files will be downloaded as well, causing a bit of a delay.

In addition to clicking the image buttons at the bottom of the game, you can use keystrokes (hover over the images to see tooltips that tell you what the keystrokes are). In a future version, the arrow keys will be used for game control. I also plan to provide the ability to cause the current tetromino to fall faster. By the way, the tetrominoes fall progressively faster as your score increases, so be warned. :-)

By the way, Bruno Ghisi (a fellow Java Champion) and Lucas Torri have developed a Java Bluetooth Framework called Marge. Using that framework they developed a mobile phone game controller for this TetrisJFX program. You can see a video of this in action in one of Bruno's blog posts.

As you can see, I've added some functionality since the previous post, but here are some highlights:

A preview area in the upper right corner that shows the next tetromino shape that will drop.

Scoring is more Tetris-like, in that each tetromino is worth 25 points when it is finished dropping, and each row that fills up is worth 100 points. Full rows are removed from the playing field and the tetrominoes above are moved down.

The button on the left toggles between Play and Stop. If you choose to Stop the game, "Game Over" will appear in the background, followed by which pressing Play will start a new game.

When the tetrominoes stack up to the top, as shown in the sceenshot above, "Game Over" appears in the background and the game stops.

Here is the code for this version of TetrisJFX. Note that I've added a new class named TetrisNextShapeNode that shows the next tetromino shape to drop.

TetrisMain.fx:

/* * TetrisMain.fx - The main program for a compiled JavaFX Script Tetris game * * Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com) * to serve as a compiled JavaFX Script example. */package tetris_ui;

/* * TetrisPlayingField.fx - * A custom graphical component that is the UI for the * playing field. * * Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com) * to serve as a compiled JavaFX Script example. * */package tetris_ui;

/** * This value is incremented via the KeyFrame animation mechanism, * and represents the row in which the pivotal block is currently residing. */ public attribute a:Integer on replace oldVal { if (a == pieceAppearRow) { activeShapeType = nextShapeType;

// Remove the current "next shape type" from the preview area updateNextShapePreviewCells(true);

nextShapeType = TetrisShapeType.randomShapeType(); // Load the new "next shape type" into the preview area updateNextShapePreviewCells(false);

public function rotate90():Void { if (stopDropping) {return;} // Don't try to process if a piece isn't falling updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, true); for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(activeShapeType, computeNewAngle(tetrominoAngle, 90))) { // First check to see if the rotated tetromino is past the // left side of the playing field if (tetrominoHorzPos + oldCell.x < 0) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } // Then check to see if the tetromino is at the right of // the playing field if (tetrominoHorzPos + oldCell.x > NUM_COLS - 1) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } // Now check to see if another tetromino is preventing it from rotating if (fieldCells[((a + oldCell.y) * NUM_COLS + tetrominoHorzPos - 1 + oldCell.x) as Integer] <> TetrisShapeType.NONE) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } } tetrominoAngle = computeNewAngle(tetrominoAngle, 90); updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); }

public function moveLeft():Void { if (stopDropping) {return;} // Don't try to process if a piece isn't falling updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, true); for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(activeShapeType, tetrominoAngle)) { // First check to see if the tetromino is at the left of // the playing field if (tetrominoHorzPos + oldCell.x <= 0) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } // Now check to see if another tetromino is preventing it from moving left if (fieldCells[((a + oldCell.y) * NUM_COLS + tetrominoHorzPos - 1 + oldCell.x) as Integer] <> TetrisShapeType.NONE) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } } tetrominoHorzPos--; updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); } // TODO: Refactor with moveLeft method public function moveRight():Void { if (stopDropping) {return;} // Don't try to process if a piece isn't falling updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, true); for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(activeShapeType, tetrominoAngle)) { // First check to see if the tetromino is at the left of // the playing field if (tetrominoHorzPos + oldCell.x >= NUM_COLS - 1) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } // Now check to see if another tetromino is preventing it from moving left if (fieldCells[((a + oldCell.y) * NUM_COLS + tetrominoHorzPos + 1 + oldCell.x) as Integer] <> TetrisShapeType.NONE) { updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); return; } } tetrominoHorzPos++; updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false); }

/** * Keeps the fieldCells sequence (that represents the * cells in the playing field) updated to reflect reality. * pass in the row and column that the pivotal block was * located, as well as the former angle of the tetromino. * The new row, column, angle, as well as the shape type, is * held in the model, so don't have to be passed in. */ public function updateFieldCells(oldX:Integer, oldY:Integer, oldAngle:Integer, remove:Boolean):Void { // Place (or remove) the shape on the playing field for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(activeShapeType, oldAngle)) { fieldCells[(oldY + oldCell.y) * NUM_COLS + oldX + oldCell.x] = if (remove) TetrisShapeType.NONE else activeShapeType; } }

public function canMoveDown(oldX:Integer, oldY:Integer, oldAngle:Integer):Boolean { var retVal = true; for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(activeShapeType, oldAngle)) { // First check to see if the tetromino is at the bottom of // the playing field if (oldY + oldCell.y >= NUM_ROWS) { return false; } // Now check to see if another tetromino is preventing it from moving down if (fieldCells[(oldY + oldCell.y) * NUM_COLS + oldX + oldCell.x] <> TetrisShapeType.NONE) { return false; } } return true; }

/** * A sequence containing positions of each square in a * tetromino type. The first element in the sequence is * the one around which the tetromino will rotate. * Note that the "O" tetromino type doesn't rotate. */ public attribute squarePositions:Point[];

Notice that only the Start button is enabled. As shown in the screenshot below, when you click the Start button the animation starts, and the enabled state of some of the buttons change:

When you click the Pause button, it becomes enabled and the Resume button is disabled:

Clicking the Stop button causes the animation to stop and for the buttons to have the same states shown in the first screenshot. In the code for this example, notice that the buttons' enabled attributes are bound to the running and paused attributes of the Timeline instance:

April 16, 2008

As mentioned in the first Tetris post, the JavaFX Script animation syntax is undergoing simplification. Today, I'd like to show you a very basic example of this simplified syntax. As shown in the screenshot below, this example consists of a line that moves back and forth like a metronome:

When you click the Start button, the top of the line will move from one side to the other in one second, and then travel back to its starting place in one second, repeating this indefinitely. Clicking the Pause button causes the animation to pause, and clicking Resume causes a paused animation to resume. Clicking the Stop button stops the animation, requiring it to be started again. Take a look at the code for this example to see the animation-related syntax and functions:

The Timeline class allows you to articulate the "key frames" that will be in the animation. You can have as many as you need, but in this simple case we have two:

One KeyFrame that occurs at the beginning of the animation. Note the use of the new literal syntax for durations, in this case 0s, which means 0 seconds. 100ms would be 100 milliseconds, and 3m would represent 3 minutes.

One KeyFrame that occurs 1 second after the animation starts.

The autoReverse attribute allows you to specify that the animation should run in reverse when it reaches the last KeyFrame. The repeatCount attribute allows you to control how many times the animation will run, in this case, indefinitely.

The values attribute of the KeyFrame uses the new, more concise, animation syntax. The values attribute in the first KeyFrame sets the inital value of the x2Val variable to 100. The values attribute in the second KeyFrame causes the x2Val variable to change in value between its previous value to 300, 1 second after the animation started. Because of the Interpolator.LINEAR constant, this change of value will be linear (as opposed to, for example, slowing down at the end, which is what Interpolator.EASEOUT would do). The x2 attribute of the Line is bound to this changing x2Val variable, which is what causes the line to move on the screen.

By the way, I'm furiously preparing JavaFX presentations and demos (as are lots of other folks) for JavaOne, so please excuse the lapse of a few days here and there between posts. I'll be blogging daily from JavaOne, as there will be lots to tell you ;-)

March 27, 2008

I'm still at The Server Side Java Symposium in Las Vegas, and I think that
walking by all these slot machines has inspired this blog post. Today's example builds on the program in the Roll the Dice post in order to create a Yahtzee dice roller and scorer. Each time you click the Roll button, the five dice assume random values, and the combination of values is scored according to the possible categories in the bottom portion of a Yahtzee scoring sheet. Here's a screenshot:

In addition to the Dice.fx and PipPlacement.fx files from the Roll the Dice post, this program consists of the following source code files.

A JavaFX Script concept that we haven't covered yet is the set of functions available in the new javafx.lang.Sequences package. These functions enable you to perform operations (such as sorting) on a sequence. In this program I'm using the max and min functions of that class to find the largest and smallest value in a dice roll.

March 25, 2008

As you may know, in order to develop compiled JavaFX Script applications, it has been necessary to compile and run from the command line. That is no longer true, as the compiled JavaFX Script plug-in is now available for NetBeans (6.1 beta). Here's a screenshot of the program example (a Yahtzee dice roller and scorer) for my next blog post being developed in my new favorite IDE :-)

The compiled JavaFX Script plug-in is built in the same continuous build that compiled JavaFX Script is, so updates will be available frequently. To get both NetBeans 6.1 beta and the compiled JavaFX Script plug-in, visit the OpenJFX Community site and check for the JavaFX Script Plugin Daily Builds Available news item.

March 22, 2008

I'm on my way to Vegas to speak at The Server Side Java Symposium, so in keeping with that theme I wanted to create a simple dice program as today's example. When you click the Roll button, random values appear on the dice. Here's a screenshot of this application:

/*
* PipPlacement.fx -
* The placement of the pips on a dice
*
* Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
* to serve as a JavaFX Script example.
*/
public class PipPlacement {
public attribute pipLocsX:Number[];
public attribute pipLocsY:Number[];
}