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:
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!
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
thank you for this article spanking long as I had not read an interesting article
Posted by: jeux casino en ligne | June 21, 2010 at 12:06 PM
When you click the Rotate button, the tetromino rotates to the right, and the Left/Right buttons move the tetromino left and right, respectively.
Posted by: ev089aa | June 11, 2010 at 11:49 PM
I'd like to do an online quiz where a picture is displayed and below
it a graphic that says "click to see answer" if you click on that
graphic, the answer is displayed. I'd like to have a page with 15 or
so quiz items, and people can just go one-by-one and click on each
answer button to see the answer.
Posted by: generic cialis | April 27, 2010 at 04:38 PM
many people like to travel at very high speeds without awareness of the harm they may cause.
Posted by: propecia online | April 26, 2010 at 05:04 PM