Jim has posted 66 posts at DZone. You can read more from them at their website. View Full User Profile

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

02.24.2008
| 9193 views |
  • submit to reddit

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!

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

Published at DZone with permission of its author, Jim Weaver.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: