Compiled JavaFX Script Demos

September 29, 2008

Some Perspective in JavaFX

I've been asked to post the slides for the JavaFX presentation that I delivered at JavaZone 2008.  Because the presentation itself was written in JavaFX, I've bundled it up so that you can run it from a Java Web Start link.  First, however, I'd like to show you some screenshots and provide an explanation of what you'll be looking at:

JavaZone08_Cube1

As shown above, the presentation (entitled Vikings and Wizards in JavaFX) is shown on the faces of a rotating cube.  The Norwegian Java Users Group, named javaBin, does a great job in organizing the JavaZone conference, which also explains the graphics on the first "slide".

To view the next or previous slide, you can click the image buttons that have a right or left pointing triangle, respectively.  This will rotate the cube, which is accomplished using the PerspectiveTransform effect, located in the javafx.scene.effect package.

JavaZone08_Cube2

Some of the slides are "live" (running JavaFX functionality within them).  For example, you can interact with the TetrixJFX game, the "Are You A Viking" wizard, the custom node examples (MenuNode and DeckNode), and the morphing example.  For the latter, just click on the yellow circle and watch the morphing begin!  To see more information about these applications, visit the blog posts for TetrisJFX, Vikings and Wizards, Rolling Your Own Custom Nodes, and Getting Decked

JavaZone08_Cube4

By the way, the presentation runs in an undecorated Frame, which means that there is no border, Close box, etc.  I make my desktop background black and minimize any open windows before presenting so that the cube appears to be rotating in a big dark space.  As soon as the graphics designer (Mark Dingman of Malden Labs) gets a chance to create a cool looking graphical Close box, I'll put it in the application.  For now, however, you'll have to end the application manually.  You'll need JRE 6 to run this, and Java SE 6 update 10 will give you a faster deployment experience.  Go ahead and give it a whirl by clicking the Java Web Start button below!

Note: Please keep in mind that this is running with the JavaFX Preview SDK which has not been optimized for performance.  Behavior and performance of the PerspectiveTransform on Vista, for example, are definitely sub-optimal at the moment.  These are known issues that the JavaFX GUI team is confident have already been corrected in the upcoming JavaFX SDK 1.0 release.

Webstartsmall2

Regards,

Jim Weaver
JavaFXpert.com

May 15, 2008

Video of Cool JavaFX Demos at JavaOne 2008

Note from Jim Weaver: Since this was written, an updated version of JavaFX has been released.  You can get the new version of JavaFX at at JavaFX.com.

During the Tuesday afternoon general session at JavaOne 2008, there were some cool JavaFX-related announcements and demos.  Grab some popcorn and watch this video of a portion of the session, which includes:

  • An announcement that ON2 and Sun now have a relationship that will make the ON2 video codec available as part of the Java and JavaFX platforms.  This will allow video to be converted to a common format and played on any machine or device that supports Java.
  • The JavaFX version of the parleys.com website, which is the Belgium Java Users Group's (JUG) site for sharing video presentations about Java.  Stephan Janssen, who leads the JUG as well as the JavaPolis conference in Belgium, presented the site with Jo Voordeckers, the JavaFX developer of the site.  Stephan also announced that any JUG can post presentation videos to the new parleys.com site, which up to now has received most of its content from the JavaPolis conferences.
  • An early peek at a design tool for JavaFX in which graphics can be created in Photoshop and Illustrator, and exported for use by a JavaFX program.  This tool is currently named Distiller, and will support the collaboration of graphic designers and software developers.  As a side note, ReportMill is also making some great progress on their JavaFX designer tool.
  • A demo of a three-dimensional multi-player game named MoonTank developed in JavaFX.  One of the demonstrators is Chris Oliver, the founder of JavaFX.

Enjoy the video.  This JavaFX stuff is getting exciting!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

May 01, 2008

Watch for Falling Blocks: Take TetrisJFX for a Spin!

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.

Tetrisjfx_w_image_buttons

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. :-)

170x93_speaker_v4_4 If you have any questions or input for improvements, please post a comment.  Also, if you'll be at JavaOne 2008, please attend my JavaFX Script Programming Language Tutorial session and introduce yourself afterward!

 

Have fun!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

April 29, 2008

Game Over: Improving upon the Compiled JavaFX Tetris Program

In the Knowing the State of a JavaFX Script Animation post I showed you the more succinct syntax for controlling animations.  Now I'd like to show you some improvements to the TetrisJFX program that I've been developing in this blog, including switching to the new animation syntax.  Here is a screenshot of the TetrisJFX game after the blocks stacked up to the top and the game is over.

Tetrisjfx_4_gameover

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;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import tetris_model.*;

Frame {
  var model = TetrisModel {}
  var canvas:Canvas
  width: 350
  height: 500
  title: "TetrisJFX"
  background: Color.LIGHTGREY
  content:
    StackPanel {
      content: [
        Canvas {
          content: [
            ImageView {
              image:
                Image {
                  url: "{__DIR__}images/background.jpg"
                }
            },
            Text {
              transform: bind [
                Translate.translate(30, 350),
                Rotate.rotate(290, 0, 0),
              ]
              content: "Game Over"
              fill: Color.WHITE
              opacity: bind if (model.timeline.running) 0
                            else 1
              font:
                Font {
                  face: FontFace.SANSSERIF
                  size: 60
                  style: FontStyle.BOLD
                }
            }
          ]
        },
        BorderPanel {
          center:
            FlowPanel {
              alignment: Alignment.LEADING
              vgap: 10
              hgap: 10
              content: [
                Canvas {
                  content:
                    TetrisPlayingField {
                      model: model
                    }
                }
              ]
            }
          right:
            GridPanel {
            var sansSerif24 =
              Font {
                face: FontFace.SANSSERIF
                size: 24
                style: FontStyle.BOLD
              }
              rows: 2
              columns: 1
              cells: [
                BorderPanel {
                  top:
                    FlowPanel {
                      vgap: 10
                      alignment: Alignment.LEADING
                      content:
                        Label {
                          foreground: Color.LIGHTBLUE
                          text: "NEXT:"
                          font: sansSerif24
                        }
                    }
                  center: 
                    Canvas {
                      content:
                        TetrisNextShapeNode {
                          model: model
                        }
                    }
                },
                BorderPanel {
                  top:
                    Label {
                      foreground: Color.LIGHTBLUE
                      text: "SCORE:"
                      font: sansSerif24
                    }
                  center:
                    FlowPanel {
                      alignment: Alignment.TRAILING
                      content:
                        Label {
                          foreground: Color.LIGHTBLUE
                          text: bind "{model.score}"
                          font: sansSerif24
                        }
                    }
                }
              ]
            }
          bottom:
            FlowPanel {
              alignment: Alignment.CENTER
              content: [
                Button {
                  text: bind
                    if (model.timeline.running)
                      "Stop"
                    else
                      "Play"
                  action:
                    function():Void {
                      if (model.timeline.running) {
                        model.stopGame();
                      }
                      else {
                        model.startGame();
                      }
                    }
                },
                Button {
                  text: "Rotate"
                  enabled: bind not model.stopDropping
                  action:
                    function():Void {
                      model.rotate90();
                    }
                },
                Button {
                  text: "Left"
                  enabled: bind not model.stopDropping
                  action:
                    function():Void {
                      model.moveLeft();
                    }
                },
                Button {
                  text: "Right"
                  enabled: bind not model.stopDropping
                  action:
                    function():Void {
                      model.moveRight();
                    }
                }
              ]
            }
        }
      ]
    }
  visible: true
  onClose:
    function():Void {
      System.exit(0);
    }
}

TetrisPlayingField.fx: 

/*
*  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;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import tetris_model.*;

class TetrisPlayingField extends CompositeNode {
  private static attribute squareOutlineColor = Color.BLACK;
  private static attribute squareOutlineWidth = 1;
  public attribute model:TetrisModel;
  private attribute numHiddenTopRows = 2;
  public function composeNode():Node {
    Group {
      content: bind [
        Rect {
          x: 0
          y: 0
          width: model.NUM_COLS * model.SQUARE_SIZE
          height: (model.NUM_ROWS - numHiddenTopRows) * model.SQUARE_SIZE
          strokeWidth: 1
          stroke: Color.BLACK
          fill: Color.BLUE
          opacity: bind if (model.timeline.running) .5
                        else .2
        },
        for (cell in model.fieldCells
               where cell <> TetrisShapeType.NONE) {
          Rect {
            var yPos:Integer = indexof cell / model.NUM_COLS
            //x: (indexof cell % model.NUM_COLS).intValue() * model.SQUARE_SIZE
            x: indexof cell % model.NUM_COLS * model.SQUARE_SIZE
            y: (yPos - numHiddenTopRows) * model.SQUARE_SIZE
            width: model.SQUARE_SIZE
            height: model.SQUARE_SIZE
            fill: cell.squareColor
            stroke: squareOutlineColor
            strokeWidth: squareOutlineWidth
          }
        }
      ]
    }
  }
}

TetrisNextShapeNode.fx: 

/*
*  TetrisNextShapeNode.fx -
*  A custom graphical component that shows the next shape to fall
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_ui;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import tetris_model.*;

class TetrisNextShapeNode extends CompositeNode {
  private static attribute squareOutlineColor = Color.BLACK;
  private static attribute squareOutlineWidth = 1;
  public attribute model:TetrisModel;
  public function composeNode():Node {
    Group {
      content: bind [
        Rect {
          x: 0
          y: 0
          width: model.PREVIEW_NUM_COLS * model.SQUARE_SIZE
          height: model.PREVIEW_NUM_ROWS * model.SQUARE_SIZE
          strokeWidth: 1
          stroke: Color.BLACK
          fill: Color.BLUE
          opacity: bind if (model.timeline.running) .7
                        else .2
        },
        for (cell in model.nextShapePreviewCells
               where cell <> TetrisShapeType.NONE) {
          Rect {
            var yPos:Integer = indexof cell / model.PREVIEW_NUM_COLS
            x: indexof cell % model.PREVIEW_NUM_COLS * model.SQUARE_SIZE
            y: yPos * model.SQUARE_SIZE
            width: model.SQUARE_SIZE
            height: model.SQUARE_SIZE
            fill: cell.squareColor
            stroke: squareOutlineColor
            strokeWidth: squareOutlineWidth
          }
        }
      ]
    }
  }
}

TetrisModel.fx: 

/*
*  TetrisModel.fx - The model behind the Tetris UI
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_model;

import javafx.animation.*;
import javafx.lang.*;
import java.lang.System;

public class TetrisModel {
  /** Size of each tetromino in pixels */
  public static attribute SQUARE_SIZE = 19;
 
  /** Number of rows in the playing field */
  public static attribute NUM_ROWS = 23;
 
  /** Number of columns in the playing field */
  public static attribute NUM_COLS = 10;
 
  /** Number of rows in the next shape viewing area */
  public static attribute PREVIEW_NUM_ROWS = 4;
 
  /** Number of columns in the next shape viewing area  */
  public static attribute PREVIEW_NUM_COLS = 6;
 
  public attribute stopDropping:Boolean;

  /**
   * Sequence of objects that represent the
   * type of shapes (including no shape) in each playing field cell
   */
  public attribute fieldCells:TetrisShapeType[];

  /**
   * Sequence of objects that represent the
   * type of shapes (including no shape) in each next shape preview cell
   */
  public attribute nextShapePreviewCells:TetrisShapeType[];
   
  /**
   * The active tetromino shape type.
   */
  public attribute activeShapeType:TetrisShapeType;
  public attribute nextShapeType:TetrisShapeType;
 
  public attribute score:Integer = 0;
  private attribute pieceScore = 25;
  private attribute rowScore = 100;
 
  private attribute startDropRow = 2;
  private attribute stopDropRow = NUM_ROWS;
  private attribute pieceAppearRow = 3;
  private attribute fieldFullRow = 5;
 
  /**
   * 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);
      
      tetrominoHorzPos = NUM_COLS / 2 - 1 as Integer;
      stopDropping = false;
    }
    if (not stopDropping) {
      // Remove the tetromino from the playing field
      updateFieldCells(tetrominoHorzPos, oldVal, tetrominoAngle, true);

      if (canMoveDown(tetrominoHorzPos, a, tetrominoAngle)) {   
        // Was able to move down, so place the tetromino accordingly
        updateFieldCells(tetrominoHorzPos, a, tetrominoAngle, false);
      }
      else {
        score += pieceScore;
        updateFieldCells(tetrominoHorzPos, oldVal, tetrominoAngle, false);
        stopDropping = true;
        if (a <= fieldFullRow) {
          // The blocks have stacked up too high, and game is over
          timeline.stop();
        }
      }
      removeFilledRows();
    }
  };
  public attribute timeline =
    Timeline {
      keyFrames: [
        KeyFrame {
          time: 0s
          values: a => startDropRow tween Interpolator.LINEAR
        },
        KeyFrame {
          time: 10s
          values: a => stopDropRow tween Interpolator.LINEAR
        }
      ]
      repeatCount: Timeline.INDEFINITE
    };
  public attribute tetrominoAngle:Number;
  public attribute tetrominoHorzPos:Number;

  public function startGame():Void {
    fieldCells = for (i in [1..(NUM_ROWS * NUM_COLS)]) {
      TetrisShapeType.NONE
    };
    nextShapePreviewCells = for (i in [1..(PREVIEW_NUM_ROWS * PREVIEW_NUM_COLS)]) {
      TetrisShapeType.NONE
    };
    nextShapeType = TetrisShapeType.randomShapeType();
    activeShapeType = null;
    score = 0;
    timeline.start();
  }

  public function stopGame():Void {
    timeline.stop();
    stopDropping = true;
  }

  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 computeNewAngle(originalRotationAngle:Integer, degreesToRotate):Integer {
    ((originalRotationAngle + degreesToRotate) % (activeShapeType.rotateStates * 90)) as Integer;
  }

  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;
    }
  }

  /**
   * Keeps the nextShapePreviewCells sequence (that represents the
   * cells in the next shape preview area) updated to reflect reality.
   */
  public function updateNextShapePreviewCells(remove:Boolean):Void {
    // Place (or remove) the shape on the next shape preview field
    var pivotPosX = 2;
    var pivotPosY = 1;
   
    for (oldCell in TetrisShapeType.squarePositionsForRotatedShape(nextShapeType, 0)) {
      nextShapePreviewCells[(pivotPosY + oldCell.y) * PREVIEW_NUM_COLS + pivotPosX + oldCell.x] =
        if (remove) TetrisShapeType.NONE else nextShapeType;
    }
  }

  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;
  }
 
  private function removeFilledRows():Void {
    if (not stopDropping) {
      return;
    }
    for (row in [0..NUM_ROWS - 1]) {
      // First see if the beginning of the row is empty
      // before doing the more expensive check of the whole row
      //TODO: Consider not doing this, as perhaps the indexOf isn't expensive
      if (fieldCells[row * NUM_COLS] <> TetrisShapeType.NONE) {
        var fieldRowSlice = fieldCells[row * NUM_COLS .. (row + 1) * NUM_COLS -1];
        if (Sequences.indexOf(fieldRowSlice, TetrisShapeType.NONE) < 0) {
          score += rowScore;
          stopDropping = true;
          //TODO: Use fieldRowSlice in delete statement
          delete fieldCells[row * NUM_COLS .. (row + 1) * NUM_COLS -1];
          insert
            for (i in [1..NUM_COLS]) {
              TetrisShapeType.NONE
            }
            before fieldCells[0];
        }
      }
    }
  }
}

TetrisShapeType.fx:

/*
*  TetrisShapeType.fx - A Tetris shape type, which are
*                       I, J, L, O, S, T, and Z
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_model;

import javafx.ui.*;
import java.awt.Point;
import java.lang.Math;
import java.lang.System;

/**
* This class contains the model information for each type
* of tetromino (Tetris piece).
*/
public class TetrisShapeType {
  public attribute id: Integer;
  public attribute name: String;
 
  /**
   * 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[];
 
  //public attribute allowRotate:Boolean = true;
 
  /**
   * Valid values are 1 (for "O" shape, 2 (for "S", "Z", and "I" shapes)
   * and 4 (for "L", "J", and "T" shapes)
   */
  public attribute rotateStates:Integer =  4;
 
  public attribute squareColor:Color;

  /** The "NONE" shape (represents the absence of a shape) */
  public static attribute NONE =
    TetrisShapeType {
      id: 0
      name: "."
      squarePositions: [
        new Point(0, 0),
        new Point(0, 0),
        new Point(0, 0),
        new Point(0, 0)
      ]
      squareColor: Color.SILVER
    };

  /** The "I" shape (four squares in a straight line) */
  public static attribute I =
    TetrisShapeType {
      id: 1
      name: "I"
      squarePositions: [
        new Point(0, 0),
        new Point(-1, 0),
        new Point(1, 0),
        new Point(2, 0)
      ]
      squareColor: Color.SILVER
      rotateStates: 2
    };

  /** The "T" shape (looks like a stout "T") */
  public static attribute T =
    TetrisShapeType {
      id: 2
      name: "T"
      squarePositions: [
        new Point(0, 0),
        new Point(-1, 0),
        new Point(1, 0),
        new Point(0, 1)
      ]
      squareColor: Color.HOTPINK
    };

  /** The "L" shape (looks like an "L") */
  public static attribute L =
    TetrisShapeType {
      id: 3
      name: "L"
      squarePositions: [
        new Point(0, 0),
        new Point(-1, 0),
        new Point(1, 0),
        new Point(-1, 1)
      ]
      squareColor: Color.LIGHTBLUE
    };

  /** The "J" shape (looks sort of like a "J", but
   *  more like a backwards "L") */
  public static attribute J =
    TetrisShapeType {
      id: 4
      name: "J"
      squarePositions: [
        new Point(0, 0),
        new Point(-1, 0),
        new Point(1, 0),
        new Point(1, 1)
      ]
      squareColor: Color.YELLOW
    };

  /** The "S" shape (looks sort of like an "S") */
  public static attribute S =
    TetrisShapeType {
      id: 5
      name: "S"
      squarePositions: [
        new Point(0, 0),
        new Point(1, 0),
        new Point(-1, 1),
        new Point(0, 1)
      ]
      squareColor: Color.LIGHTGREEN
      rotateStates: 2
    };

  public static attribute Z =
    TetrisShapeType {
      id: 6
      name: "Z"
      squarePositions: [
        new Point(0, 0),
        new Point(-1, 0),
        new Point(0, 1),
        new Point(1, 1)
      ]
      squareColor: Color.ORANGE
      rotateStates: 2
    };

  /** The "O" shape (looks sort of like an "O", but
   *  more like a square) */
  public static attribute O =
    TetrisShapeType {
      id: 7
      name: "O"
      squarePositions: [
        new Point(0, 0),
        new Point(0, 1),
        new Point(1, 0),
        new Point(1, 1)
      ]
      squareColor: Color.RED
      rotateStates: 1
    };
   
  /**
   * A sequence of the shape types for use in generating a
   * random shape type.
   */
  private static attribute allShapeTypes:TetrisShapeType[];
 
  /**
   * A function that returns a random TetrisShapeType
   */
  public static function randomShapeType():TetrisShapeType {
    if (sizeof allShapeTypes <= 0) {
      insert I into allShapeTypes;
      insert T into allShapeTypes;
      insert L into allShapeTypes;
      insert J into allShapeTypes;
      insert S into allShapeTypes;
      insert Z into allShapeTypes;
      insert O into allShapeTypes;
    }
    allShapeTypes[(Math.random() * sizeof allShapeTypes) as Integer]
  }

  /**
   * A function that returns the squarePositions of a given TetrisShapeType
   * at a given angle of rotation.  This could have used trig functions,
   * but the cases are simple enough to use if/else expressions.
   */
  public static function
      squarePositionsForRotatedShape(shapeType:TetrisShapeType, rotAngle:Integer):Point[] {
    for (position in shapeType.squarePositions) {
      var newX = position.x;
      var newY = position.y;
      if (rotAngle == 90) {
        newX = if (position.y == 0) 0 else position.y * -1;
        newY = if (position.x == 0) 0 else position.x;
      }
      else if (rotAngle == 180) {
        newX = position.x * -1;
        newY = position.y * -1;
      }
      else if (rotAngle == 270) {
        newX = if (position.y == 0) 0 else position.y;
        newY = if (position.x == 0) 0 else position.x * -1;
      }
      new Point(newX, newY);
    }
  }
}

You may have noticed some TODO comments in the code, which will remind me to do some refactoring and add some enhacements (like create a button that will accelerate a tetromino's rate of decent).

 

170x93_speaker_v4_4 If you have any question, please post a comment.  Also, if you'll be at JavaOne 2008, please attend my JavaFX Script Progamming Language Tutorial session and introduce yourself afterward!

 

Regards,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

March 19, 2008

A JavaFX Script Calendar that Displays a Google Calendar Feed

Today I'd like to show you the humble beginnings of a JavaFX Script calendar that displays a Google Calendar feed.  It is currently pointing to my Google Calendar, which I've sparsely populated with some sample data.  You can change the program to point to your Google Calendar if you choose. 

I'll improve this Calendar program over time, but wanted to demonstrate JavaFX Script's ability to easily parse an XML stream over the Internet.  You may also want to take a look at the Compiled JavaFX Script Now Speaks JSON post which demonstrates parsing JSON streams over the Internet.  Here's a screenshot of today's example, followed by the program's code:

Calendarjfx


The Code Behind the User Interface

Obviously there are many improvements that I can make to this calendar, but here are the four source files that comprise this example in its current state:


CalendarJFX.fx

/*
*  CalendarJFX.fx -
*  The main program for a compiled JavaFX calendar program
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.
*/

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;

Frame {
  var calModel =
    CalendarModel {}
  title: "CalendarJFX"
  width: 600
  height: 600
  visible: true
  content:
    BorderPanel {
      top:
        BorderPanel {
          left:
            FlowPanel {
              content: [
                Button {
                  text: "<<"
                  action:
                    function():Void {
                      calModel.prevYear();
                    }
                },
                Button {
                  text: "<"
                  action:
                    function():Void {
                      calModel.prevMonth();
                    }
                }
              ]
            }
          center:
            FlowPanel {
              content:
                SimpleLabel {
                  text: bind "{calModel.selectedMonthStr} {calModel.selectedYearStr}"
                  font:
                    Font {
                      size: 24
                      style: FontStyle.BOLD
                    }
                }
            }
          right:
            FlowPanel {
              content: [
                Button {
                  text: ">"
                  action:
                    function():Void {
                      calModel.nextMonth();
                    }
                },
                Button {
                  text: ">>"
                  action:
                    function():Void {
                      calModel.nextYear();
                    }
                }
              ]
            }
          bottom:
            GridPanel {
              // TODO: Internationalize days of the week
              var days = ["Sun", "Mon", "Tue",
                          "Wed", "Thu", "Fri", "Sat"]
              rows: 1
              columns: 7
              cells:
                for (day in days) {
                  SimpleLabel {
                    text: day
                    font:
                      Font {
                        size: 18
                        style: FontStyle.BOLD
                      }
                    horizontalAlignment:
                      HorizontalAlignment.CENTER
                  }
                }
            }
        }
      center:
        GridPanel {
          vgap: 1
          hgap: 1
          rows: 6
          columns: 7
          cells:
            for (idx in [1..42]) {
              CalendarCell {
                calModel: calModel
                dayOfMonthStr: bind calModel.getDayInMonthStrForCell(idx as Integer);
                cellGregCal: bind calModel.getDateForCell(idx as Integer);
              }
            }
        }
    }
  onClose:
    function():Void {
      System.exit(0);
    } 
}


CalendarCell.fx

/*
*  CalendarCell.fx -
*  The UI for an individual cell (day) in the calendar
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.
*/

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class CalendarCell extends CompositeWidget {
  public attribute calModel:CalendarModel;
  attribute dayInMonthFmt = new SimpleDateFormat("d");
  public attribute cellGregCal:GregorianCalendar;
  public attribute dayOfMonthStr:String;

  public function composeWidget():Widget {
    BorderPanel {
      top:
        FlowPanel {
          background: Color.LIGHTBLUE
          alignment: Alignment.TRAILING
          content: [
            SimpleLabel {
              text: bind dayOfMonthStr
              font:
                Font {
                  size: 12
                  style: FontStyle.BOLD
                }
            }
          ]
        }
      center:
        Box {
          orientation: Orientation.VERTICAL
          content: bind
            for (calEntry in calModel.calEntries
              where cellGregCal.get(Calendar.YEAR) ==
                    calEntry.startTime.get(Calendar.YEAR) and
                    cellGregCal.get(Calendar.MONTH) ==
                    calEntry.startTime.get(Calendar.MONTH) and
                    cellGregCal.get(Calendar.DAY_OF_MONTH) ==
                    calEntry.startTime.get(Calendar.DAY_OF_MONTH)) {
              SimpleLabel {
                text: calEntry.title
              }
            }
        }
    }
  }
}


CalendarModel.fx

/*
*  CalendarModel.fx -
*  The model behind a compiled JavaFX calendar program
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.
*/

import javafx.xml.*;
import java.text.SimpleDateFormat;
import java.lang.System;
import java.util.Calendar;
import java.util.GregorianCalendar;

class CalendarModel {
  attribute calendarFeedURI =
    "http://www.google.com/calendar/feeds/james.l.weaver%40gmail.com/public/full";
  attribute docBuilder =
    DocumentBuilder {
      namespaceAware:true
      validating:true
      ignoringComments:false
    };
  attribute document =
    docBuilder.parseURI(calendarFeedURI);
  attribute calEntries:CalendarEntry[];
  attribute selectedGregCal:GregorianCalendar = new GregorianCalendar();
  attribute utilGregCal:GregorianCalendar = new GregorianCalendar();
  attribute selectedMonth:Integer;
  attribute selectedYear:Integer;
  attribute dayInMonthFmt = new SimpleDateFormat("d");
  attribute monthFmt = new SimpleDateFormat("MMMM");
  attribute yearFmt = new SimpleDateFormat("yyyy");
  attribute cellNumber:Integer;
  attribute selectedDayInMonthStr:String;
  attribute selectedMonthStr:String;
  attribute selectedYearStr:String;
 
  postinit {
    var calElements = document.getElementsByTagName("entry");
    calEntries = for (calElement in calElements)
      CalendarEntry {
        title: calElement.queryString("title")
        startTimeStr: calElement.queryString("when/@startTime")
        endTimeStr: calElement.queryString("when/@endTime")
        location: calElement.queryString("where/@valueString")
      };
    populateDateParts();
  }
 
  function prevMonth():Void {
    selectedGregCal.add(Calendar.MONTH, -1);
    selectedMonth = selectedGregCal.get(Calendar.MONTH);
    populateDateParts();
  }

  function nextMonth():Void {
    selectedGregCal.add(Calendar.MONTH, 1);
    selectedMonth = selectedGregCal.get(Calendar.MONTH);
    populateDateParts();
  }

  function prevYear():Void {
    selectedGregCal.add(Calendar.YEAR, -1);
    selectedYear = selectedGregCal.get(Calendar.YEAR);
    populateDateParts();
  }

  function nextYear():Void {
    selectedGregCal.add(Calendar.YEAR, 1);
    selectedYear = selectedGregCal.get(Calendar.YEAR);
    populateDateParts();
  }

  function populateDateParts():Void {
    var selDate = selectedGregCal.getTime();
    selectedDayInMonthStr = dayInMonthFmt.format(selDate);
    selectedMonthStr = monthFmt.format(selDate);
    selectedYearStr = yearFmt.format(selDate);
  }
 
  function getDateForCell(cellNumber:Integer):GregorianCalendar {
    utilGregCal.set(Calendar.YEAR, selectedYear);
    utilGregCal.set(Calendar.MONTH, selectedMonth);
    utilGregCal.set(Calendar.DAY_OF_MONTH, 1);
    var firstDayOffset = utilGregCal.get(Calendar.DAY_OF_WEEK);
    utilGregCal.add(Calendar.DAY_OF_MONTH, firstDayOffset * -1 + cellNumber);
    return utilGregCal;
  }

  function getDayInMonthStrForCell(cellNumber:Integer):String {
    utilGregCal.set(Calendar.YEAR, selectedYear);
    utilGregCal.set(Calendar.MONTH, selectedMonth);
    utilGregCal.set(Calendar.DAY_OF_MONTH, 1);
    var firstDayOffset = utilGregCal.get(Calendar.DAY_OF_WEEK);
    utilGregCal.add(Calendar.DAY_OF_MONTH, firstDayOffset * -1 + cellNumber);
    dayInMonthFmt.format(utilGregCal.getTime());
  }
}


CalendarEntry.fx

/*
*  CalendarEntry.fx -
*  A calendar entry, which is part of the model.
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.
*/

import java.lang.System;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Calendar;
import java.util.Date;

class CalendarEntry {
  private attribute sdf = new SimpleDateFormat
      ("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
  attribute title:String;
  attribute startTime:Calendar;
  attribute endTime:Calendar;
  attribute startTimeStr:String on replace {
    //TODO: Accommodate all-day events
    var d:Date = sdf.parse("{startTimeStr.substring(0, 26)}{startTimeStr.substring(27)}");
    var cal:Calendar = Calendar.getInstance();
    cal.setTime(d);
    startTime = cal;
  };
  attribute endTimeStr:String on replace {
    var d:Date = sdf.parse("{endTimeStr.substring(0, 26)}{endTimeStr.substring(27)}");
    var cal:Calendar = Calendar.getInstance();
    cal.setTime(d);
    endTime = cal;
  };
  attribute location:String;
}

Enjoy, and as always, please post a comment if you have any questions.

Regards,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

March 15, 2008

Me and You and a Dog Named Boo - Oh, Not *that* Lobo?

I suppose that I need to explain the title of this post.  Lobo is a singer/songwriter from the 70's that was well-known for several songs, including "Me and You and a Dog Named Boo".  Lobo also means "wolf" in Spanish.  Now, thanks to Jose Solorzano and crew, Lobo is an extensible, pure Java web browser that is capable of rendering links that contain JavaFX (and Java) source code files.  Jose contacted me a couple of days ago about using the code from one of my "Creating a Tetris Program in Compiled JavaFX Script" articles for demonstration purposes.  Here's a screenshot of the partially baked Tetris game running in the Lobo Browser:

Lobotetris

One nice thing about this approach is that the developer doesn't have to compile the JavaFX Script source files.  Rather, the Lobo browser recognizes them as JavaFX Script files, and immediately compiles and runs them.  The Lobo browser download comes with a recent version of the javafxrt.jar, javafxc.jar, and Scenario JAR files, which you may recognize from previous posts as necessary to compile and run compiled JavaFX Script files.  For more information on the Lobo browser, see Geertjan Wielenga's latest JavaLobby post on the subject, and check out the Lobo browser website.  After installing and running the Lobo browser, navigate to the http://www.lobobrowser.org/ext/jfx/TetrisMain.fx URL, and watch it automatically render the JavaFX Script code in that URL.  Nice work, Jose!

Regards,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

March 14, 2008

A Compiled JavaFX Script Playground - The Assortis Demo

Today I'd like to call your attention to a new compiled JavaFX Script demo, named JavaFX Assortis, from the JavaFX Script compiler team.  It's purpose is to enable you to experiment with and learn about various JavaFX Script UI features.  It is my understanding that Assortis means "sampler", or "assortment" in French, kind of like a box of chocolates (I'll spare you the Forrest Gump reference, by the way).  Anyway, here's a screenshot of JavaFX Assortis:

Assortisdemo

From the panes on the left side you can choose to work with Nodes (graphical objects like Circle), Widgets (UI objects like CheckBox), or come cool samples (like the animated Planetary System shown above).  You can then work with your selected objects in the right panes, altering the code to see the effects.

How can you get access to this demo, you ask?  Just download and build the OpenJFX Compiler, navigate to the demos/JavaFXAssortis directory, and run the following command from the command-line:

ant run

This will cause this demo to be built (if necessary) and executed.  It's a great way to get up to speed quickly on using some of the UI features of JavaFX Script.

Regards,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

February 22, 2008

Let's Create a Tetris Game in Compiled JavaFX Script

I thought it would be fun to spend a few posts creating a Tetris game in compiled JavaFX Script together.  Today I made a rough start on it, and if you promise not to laugh, I'll show you the humble beginnings.  Here's a screenshot of its current status:

Tetris_1

In Tetris, there are several types of tetrominoes, each having a letter that it resembles.  The four buttons on the left represent four of these shapes.  When you press one of these buttons, the corresponding tetromino appears at the top and begins moving down the screen.  When you click the Rotate button, the tetromino rotates to the right, and the Left/Right buttons move the tetromino left and right, respectively.

The code is contained in four FX program files, and needs some refactoring already. :-)  Before showing you the code in its current state, I'd like to point out a couple of helpful things: 

  • As explained in the Spinning Wheel post, the Key Frame animation syntax that you see here will become much less verbose as the JavaFX Script compiler team continues to address animation. 
  • JavaFX Script programs should always be designed with the UI binding to a model.  In this program, the model is represented in one class named TetrisModel, located in the FX file of the same name.  You may find it helpful to take a look the Creating a Compiled JavaFX Script Program with Multiple FX Source Files post to see a Hello World style program that has more than one FX file.  Please notice the package statments in this Tetris program, as that influences where you need to put the source files and how you build them.
  • You can obtain the JavaFX Compiler by following the instructions in the Obtaining the OpenJFX Script Compiler post.

The Source Code (so far)

Here's the main program, named TetrisMain.fx, that declaratively expresses the UI, and starts things up:

/*
*  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;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;
import tetris_model.*;

Frame {
  var model =
    TetrisModel {
      
    }
  var canvas:Canvas
  width: 480
  height: 500
  title: "TetrisJFX"
  background: Color.WHITE
  content:
    BorderPanel {
      center:
        canvas = Canvas {}
      bottom:
        FlowPanel {
          content: [
            Button {
              text: "I"
              action:
                function() {
                  canvas.content = [];
                  insert
                  TetrisShape {
                    model: model
                    shapeType: TetrisShapeType.I
                  }
                  into canvas.content; 
                  model.t.start();
                }
            },
            Button {
              text: "T"
              action:
                function() {
                  canvas.content = [];
                  insert
                  TetrisShape {
                    model: model
                    shapeType: TetrisShapeType.T
                  }
                  into canvas.content; 
                  model.t.start();
                }
            },
            Button {
              text: "L"
              action:
                function() {
                  canvas.content = [];
                  insert
                  TetrisShape {
                    model: model
                    shapeType: TetrisShapeType.L
                  }
                  into canvas.content; 
                  model.t.start();
                }
            },
            Button {
              text: "S"
              action:
                function() {
                  canvas.content = [];
                  insert
                  TetrisShape {
                    model: model
                    shapeType: TetrisShapeType.S
                  }
                  into canvas.content; 
                  model.t.start();
                }
            },
            Button {
              text: "Rotate"
              action:
                function() {
                  model.rotate90();
                }
            },
            Button {
              text: "Left"
              action:
                function() {
                  model.moveLeft();
                }
            },
            Button {
              text: "Right"
              action:
                function() {
                  model.moveRight();
                }
            }
          ]
        }
    }
  visible: true
  onClose:
    function():Void {
      System.exit(0);
    }
}

I made the TetrisShape class a custom graphical component.  Therefore, it is a subclass of the CompositeNode class, and overrides the composeNode function:

/*
*  TetrisShape.fx - A Tetris piece, configurable to the
*                   different shape types.  They are:
*                   I, J, L, O, S, T, and Z
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_ui;

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.awt.Point;
import java.lang.System;
import tetris_model.*;

class TetrisShape extends CompositeNode {
  private static attribute squareOutlineColor = Color.BLACK;
  private static attribute squareOutlineWidth = 2;
  private attribute squareColor;

  public attribute model:TetrisModel;
  public attribute shapeType:TetrisShapeType on replace {
    if (shapeType == TetrisShapeType.I) {
      squareLocs = [];
      insert new Point(0, model.SQUARE_SIZE * 1) into squareLocs;
      insert new Point(0, 0) into squareLocs;
      insert new Point(0, model.SQUARE_SIZE * 2) into squareLocs;
      insert new Point(0, model.SQUARE_SIZE * 3) into squareLocs;
      squareColor = Color.RED;
    }
    else if (shapeType == TetrisShapeType.T) {
      squareLocs = [];
      insert new Point(model.SQUARE_SIZE * 1, 0) into squareLocs;
      insert new Point(0, 0) into squareLocs;
      insert new Point(model.SQUARE_SIZE * 2, 0) into squareLocs;
      insert new Point(model.SQUARE_SIZE * 1, model.SQUARE_SIZE * 1) into squareLocs;
      squareColor = Color.GREEN;
    }
    else if (shapeType == TetrisShapeType.L) {
      squareLocs = [];
      insert new Point(0, model.SQUARE_SIZE * 1) into squareLocs;
      insert new Point(0, 0) into squareLocs;
      insert new Point(0, model.SQUARE_SIZE * 2) into squareLocs;
      insert new Point(model.SQUARE_SIZE * 1, model.SQUARE_SIZE * 2) into squareLocs;
      squareColor = Color.MAGENTA;
    }
    else if (shapeType == TetrisShapeType.S) {
      squareLocs = [];
      insert new Point(model.SQUARE_SIZE * 1, 0) into squareLocs;
      insert new Point(model.SQUARE_SIZE * 2, 0) into squareLocs;
      insert new Point(0, model.SQUARE_SIZE * 1) into squareLocs;
      insert new Point(model.SQUARE_SIZE * 1, model.SQUARE_SIZE * 1) into squareLocs;
      squareColor = Color.CYAN;
    }
  }
 
  private attribute squareLocs:Point[];
 
  public function composeNode():Node {
    return
      Group {
        transform: bind [
          Translate.translate(model.SQUARE_SIZE * model.tetrominoHorzPos,
                              (model.a / model.SQUARE_SIZE).intValue() * model.SQUARE_SIZE),
          Rotate.rotate(model.tetrominoAngle,
                        squareLocs[0].x + model.SQUARE_SIZE / 2,
                        squareLocs[0].y + model.SQUARE_SIZE / 2)
        ]
        content: [
          for (squareLoc in squareLocs) {
            Rect {
              x: bind squareLoc.x
              y: bind squareLoc.y
              width: bind model.SQUARE_SIZE
              height: bind model.SQUARE_SIZE
              fill: bind squareColor
              stroke: squareOutlineColor
              strokeWidth: squareOutlineWidth
            }
          }
        ]
      };
  }
}

The TetrisShapeType class defines the tetromino types:

/*
*  TetrisShapeType.fx - A Tetris shape type, which are
*                       I, J, L, O, S, T, and Z
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_ui;

import javafx.ui.*;

class TetrisShapeType {
  public attribute id: Integer;
  public attribute name: String;

  public static attribute O =
    TetrisShapeType {id: 0, name: "O"};
  public static attribute I =
    TetrisShapeType {id: 1, name: "I"};
  public static attribute T =
    TetrisShapeType {id: 2, name: "T"};
  public static attribute L =
    TetrisShapeType {id: 3, name: "L"};
  public static attribute S =
    TetrisShapeType {id: 4, name: "S"};
}

And finally, here's a model class, named TetrisModel:

/*
*  TetrisModel.fx - The model behind the Tetris UI
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a compiled JavaFX Script example.

*/
package tetris_model;

import javafx.ui.animation.*;
import java.lang.System;
import com.sun.javafx.runtime.PointerFactory;

public class TetrisModel {
  public static attribute SQUARE_SIZE = 20;
 
  public attribute a:Integer;
  private attribute pf = PointerFactory {};
  private attribute bpa = bind pf.make(a);
  private attribute pa = bpa.unwrap();
  private attribute interpolate = NumberValue.LINEAR;
  public attribute t =
    Timeline {
      keyFrames: [
        KeyFrame {
          keyTime: 0s;
          keyValues:
            NumberValue {
              target: pa;
              value: 0;
              interpolate: bind interpolate
            }
        },
        KeyFrame {
          keyTime: 20s;
          keyValues:
            NumberValue {
              target: pa;
              value: 370
              interpolate: bind interpolate
            }
        }
      ]
    };
  public attribute tetrominoAngle:Number;
  public attribute tetrominoHorzPos:Number = 10;
 
  public function rotate90():Void {
    (tetrominoAngle += 90) % 360;
  }

  public function moveLeft():Void {
    if (tetrominoHorzPos > 0) {
      tetrominoHorzPos--;
    }
  }
 
  public function moveRight():Void {
    if (tetrominoHorzPos < 20) { //TODO:Replace 10 with a calculated number
      tetrominoHorzPos++;
    }
  }

}

Compile and execute this example, and examine the code.  I'll get busy making it behave a little more like a Tetris game, and show you some progress in the next post.  Please feel free to get ahead of me, and make your own version!

JavaFX Script Boot Camp Registration Now Open!

Javafxpertbootcamp300x250bannerindy

I will be offering a 2.5 day JavaFX Script day "boot camp" on Wednesday, April 9 through Friday, April 11, 2008 (ending at noon) in Indianapolis, Indiana.  By the way, I'm scouting out the next location, so if you have suggestions for where I should offer a future BootCamp please let me know!

Regards,
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

February 04, 2008

Who's Zoomin' Who? Scaling in Compiled JavaFX Script

In the Spinning Wheel Got to Go 'Round post, I showed you some basic animation in the context of a wheel that can be used to select a prize winner.  Now I'd like to show you a modification that provides for zooming into a portion of the wheel, so that the audience can see the names more clearly as the wheel is spinning.  Here's a screenshot of the wheel with some of the top rock songs, including the artists that performed them:

Winnerwheeljfx_zoomable_2

Notice that there is a green square on the lower left side of the wheel.  Clicking on that square at any time (for example, while the wheel is spinning) causes the wheel to zoom in on a portion of the wheel as shown below.  Clicking on the square again causes the wheel to return to its original size.

Winnerwheeljfx_zoomed

Using the Scale Transform to Zoom into a Portion of a Graphic Node

The listing below shows the updated version of WinnerWheelJFX.fx which includes this zoom feature.  The WinnerWheelExample.fx file hasn't changed, and you can find that file in the post referenced above.

/*
*  WinnerWheelJFX.fx - A wheel with names on it that spins
*                      and lands on a random name.  This will
*                      be used initially by Java Users Groups
*                      to give a away door prizes.  It will be
*                      improved upon over time, serving as an
*                      example of compiled JavaFX 2D graphics
*                      and animation.
*
*                      Note: The compiled JavaFX Script Key-Frame
*                      implementation isn't complete, so the
*                      animation syntax is more verbose than it
*                      will be in the near future.
*
*  Initially developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*/
import javafx.ui.*;
import javafx.ui.animation.*;
import javafx.ui.canvas.*;
import java.io.BufferedReader;
import java.io.StringReader;
import java.lang.Math;
import java.lang.System;
import com.sun.javafx.runtime.PointerFactory;

public class WinnerWheelJFX extends CompositeNode {
  public attribute names:String[];
  public attribute chosenName:String;
  public attribute chosenIdx:Integer;
  public attribute running:Boolean = true;
 
  public attribute zoomed:Boolean;
 
  private static attribute maxNames = 60;
  private attribute dlg:Dialog;
  private attribute rand:Number;
  private attribute stopValue = 1100;
  private attribute a:Integer on replace (olda) {
    chosenIdx = (((a * rand) % 360) / 360.0 * sizeof names).intValue();
    chosenName = names[chosenIdx];
    if (a >= stopValue) {
      running = false;
      MessageDialog {
        title: "And the Winner Is..."
        visible: true
        message: chosenName
      }
    }
  }
  private attribute pf = PointerFactory {};
  private attribute bpa = bind pf.make(a);
  private attribute pa = bpa.unwrap();
  private attribute interpolate = NumberValue.LINEAR;
  private attribute t =
    Timeline {
      keyFrames: [
        KeyFrame {
          keyTime: 0s;
          keyValues:
            NumberValue {
              target: pa;
              value: 0;