« Let's Create a Tetris Game in Compiled JavaFX Script | Main | Part Three of Creating a Tetris Program in Compiled JavaFX Script »

February 24, 2008

Creating a Tetris Program (Part Two) in Compiled JavaFX Script

This is the second part of a mini-series in which we're creating a Tetris game in compiled JavaFX Script together.  If you haven't already, please take a look at the Let's Create a Tetris Game in Compiled JavaFX Script post to get up to speed.

Here's a screen shot of the executing program in its current state:

Tetris_2

As you can see, I've made a few changes to the UI, making it look a little more like a Tetris game.  I also refactored some of the code, introduced a new class to represent the playing field, moved the TetrisShapeType class to the tetris_model package, and added some functionality.

Compile and run the code that you see below, and click the Play button when the screen shown above appears.  This causes one of the seven tetromino types to begin falling down the playing field.  Clicking the Rotate button rotates the tetromino clockwise, clicking the Left button moves it to left, and clicking the Right button moves it to the right.  When the tetromino arrives at the bottom, it disappears and a new randomly chosen tetromino shape begins falling.  To exit the program, close the window.

In its current state, the program doesn't have all of the functionality required in a Tetris game (for example, the tetrominoes don't stack up).  We'll get there, and hopefully we'll all learn more about developing JavaFX Script applications in the process.  Here's the current source code:

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.LIGHTGREY
  content:
    BorderPanel {
      center:
        FlowPanel {
          content: [
            Canvas {
              content:
                TetrisPlayingField {
                  model: model
                }
            }
          ]
        }
      bottom:
        FlowPanel {
          content: [
            Button {
              text: "Play"
              action:
                function() {
                  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);
    }
}

The TetrisShape custom graphical component.  By the way, I refactored the big ugly if/else statement from this class:

/*
*  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 = 1;
  private attribute squareColor;

  public attribute model:TetrisModel;
 
  public attribute shapeType:TetrisShapeType on replace {
    squareLocs =
      for (block in shapeType.squarePositions) {
          new Point(block.x * model.SQUARE_SIZE,
                    block.y * model.SQUARE_SIZE)
      };
      squareColor = shapeType.squareColor;
  };

  private attribute squareLocs:Point[];
 
  public function composeNode():Node {
    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: bind [
        for (squareLoc in squareLocs) {
          Rect {
            x: squareLoc.x
            y: squareLoc.y
            width: bind model.SQUARE_SIZE
            height: bind model.SQUARE_SIZE
            fill: bind squareColor
            stroke: squareOutlineColor
            strokeWidth: squareOutlineWidth
          }
        }
      ]
    };
  }
}

The newly introduced TetrisPlayingField custom graphical component that manages the area in which the tetrominoes fall:

/*
*  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 {
  public attribute model:TetrisModel;

  public function composeNode():Node {
      Group {
        content: [
          Rect {
            x: 0
            y: 0
            width: model.NUM_COLS * model.SQUARE_SIZE
            height: model.NUM_ROWS * model.SQUARE_SIZE
            strokeWidth: 1
            stroke: Color.BLACK
            fill: Color.WHITE
          },
          TetrisShape {
            model: model
            shapeType: bind model.activeShapeType
          }
        ]
      }
  }
}

The TetrisShapeType class defines the tetromino types, and I decided that it is really part of the model, so I moved it to the tetris_model package:

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

/**
* 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;
 
  public attribute squareColor:Color;

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

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

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

  /** 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(1, 1),
        new Point(1, 0),
        new Point(1, 2),
        new Point(0, 2)
      ]
      squareColor: Color.YELLOW
    };

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

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

  /** 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.BLUE //TODO:Research official color
      allowRotate: false
    };
   
  /**
   * 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]
  }
}

And finally, here's the TetrisModel class.  I added several attributes to this class, mainly to serve as model variables for the new TetrisPlayingField class.

/*
*  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;
import java.lang.Double;

public class TetrisModel {
  /** Size of each tetromino in pixels */
  public static attribute SQUARE_SIZE = 20;
 
  /** Number of rows in the playing field */
  public static attribute NUM_ROWS = 20;
 
  /** Number of columns in the playing field */
  public static attribute NUM_COLS = 10;

  /**
   * Sequence of objects that represent the
   * type of shapes in each playing field cell
   */
  public attribute fieldCells:TetrisShapeType[];
 
  /**
   * The active tetromino shape type.
   */
  public attribute activeShapeType:TetrisShapeType;
 
  public attribute running:Boolean = true;
  public attribute a:Integer on replace {
    if (a >= stopValue) {
      activeShapeType = TetrisShapeType.randomShapeType();
      tetrominoAngle = 0;
    }
  };
  private attribute pf = PointerFactory {};
  private attribute bpa = bind pf.make(a);
  private attribute pa = bpa.unwrap();
  private attribute interpolate = NumberValue.LINEAR;
  private attribute stopValue = 370;
  public attribute t =
    Timeline {
      keyFrames: [
        KeyFrame {
          keyTime: 0s;
          keyValues:
            NumberValue {
              target: pa;
              value: 0;
              interpolate: bind interpolate
            }
        },
        KeyFrame {
          keyTime: 10s;
          keyValues:
            NumberValue {
              target: pa;
              value: stopValue
              interpolate: bind interpolate
            }
        }
      ]
      repeatCount: Double.POSITIVE_INFINITY
    };
  public attribute tetrominoAngle:Number;
  public attribute tetrominoHorzPos:Number = (NUM_COLS / 2) as Integer;

  public function rotate90():Void {
    if (activeShapeType.allowRotate) {
      (tetrominoAngle += 90) % 360;
    }
  }

  public function moveLeft():Void {
    if (tetrominoHorzPos > 0) {
      tetrominoHorzPos--;
    }
  }
 
  public function moveRight():Void {
    //TODO: Need to allow pieces to bump up against the
    //      right side.  For now, hardcode 3.
    if (tetrominoHorzPos < NUM_COLS - 3) {
      tetrominoHorzPos++;
    }
  }
}

Some Remaining Functionality

There is still much to do in order to make this a fully functioning Tetris game.  For example, the model needs to:

  • Keep track of which cells in the playing field are empty, and which hold tetromino debris.
  • Know when a tetromino has landed on the bottom (or on tetromino debris), so that it can stop falling.
  • Delete the tetromino debris from a filled up row, and allow the tetromino debris lying above to collapse into the vacated row.
  • Use the rotation state of the currently falling tetromino to allow it to accurately bump up against the left and right sides.

Also, functionality needs to be added that displays the score, shows the next tetromino shape to fall, etc.  Have fun running, and examining, this code!

A JavaFXpert BootCamp Question:

I'm considering offering the JavaFXpert BootCamp directly after JavaOne (all day Saturday, Sunday afternoon, and all day Monday, in the San Jose/San Fransisco area).  The dates would be May 10,11 and 12.  The plan would be to allow up to 50 students in the BootCamp, which would allow me to reduce the price for that particular class.  For details on the content of the JavaFXpert BootCamp, please see http://bootcamp.JavaFXpert.com
Please send me an email to jim.weaver at lat-inc.com if you'd consider attending this, so I can decide whether to offer this post-JavaOne edition of the BootCamp.

Javafxpertbootcamp300x250bannerindy

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

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00e54f133d69883400e5506dc3718833

Listed below are links to weblogs that reference Creating a Tetris Program (Part Two) in Compiled JavaFX Script:

Comments

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been posted. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

My Photo

Upcoming Speaking Engagements:


  • Stephen Chin and Jim Weaver speaking about JavaFX Platform

  • Speaking on JavaFX and Java at Øredev in Malmö, Sweden on 2-6 November, 2009

Upcoming JavaFX Training:


  • Developing Secure, Rich Internet Applications Hosted on a Variety of Clients Using JavaFX Technology

Enter your email address:

Delivered by FeedBurner

Available now as early access eBook


  • Click book image above to obtain eBook

Twitter Updates

    follow me on Twitter

    Affiliations:

    DZone Links:


    July 2009

    Sun Mon Tue Wed Thu Fri Sat
          1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31  

    Disclaimer:

    • By reading this site, you are agreeing that under no circumstances will Veriana Networks, Inc. or its affiliates be responsible for (1) any information contained on or omitted from the site, (2) any person's reliance on any such information, whether or not the information is correct, current or complete, (3) the consequences of any action you or any other person takes or fails to take, whether or not based on information provided by or as a result of the use of the sites.