Platinum Partner
java,ria,gui,javafx,game

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.  Note: There is a typo in line 62 of the TetrisShape.fx listing.  "returnreturn" should read "return".  It will be corrected ASAP.

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

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

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}