Writing Flash Games Using Only Free Tools
In this tutorial, I will cover how to make a game playable in the Adobe
Flash Player, using only
free software
[fsf.org] available for the GNU/Linux operating system.
The example game will be the "Hello World!" of game developers, namely a Tetris clone. We will also include an online high-score
list, since it it an important aspect of online gaming.
Since I've previously written a (partial) Flash player, I'll try give you some insight in how things work behind the scenes. If you've learned all you know from a GUI Flash editor, you have likely got the wrong impression.
Although this is not a tutorial in ActionScript, if you are a C, C++ or Java programmer, you won't need much more than the sample code provided here to get going yourself.
The Tools you Need
- MTASC - Motion-Twin ActionScript 2 Compiler
apt-get install mtasc - swfmill - An image/video/font/sound bundler
apt-get install swfmill
... and of course your favorite text editor, image editor and sound synthesizer. Ideally, you will also have what most of the Flash game developing community (including me) lack: ability to perform original game design. By reading this tutorial, you agree to not make any catapult games.
Getting Started
First the Makefile, so you can see what is going on:
Makefile
all: tetris.swf
tetris.swf: tetris.as projectile.as tetris.xml field.png start.png
mtasc -swf tetris-code.swf -header 800:600:20 tetris.as projectile.as
swfmill simple tetris.xml tetris.swf
As you can see, we first use mtasc to produce an intermediate .swf-file containing only the ActionScript bytecode. We then run swfmill to merge this file with our other resources, producing the final output.
tetris.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<movie width="800" height="600" framerate="20" version="7">
<frame>
<clip import="tetris-code.swf" />
<library>
<font id="DejaVuSansMono" import="/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono.ttf"
glyphs="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .-_'!?"/>
<clip id="field" import="field.png" />
<clip id="start" import="start.png" />
</library>
<Export>
<symbols>
<Symbol objectID="1" name="djv"/>
</symbols>
</Export>
<place id="field" name="background" x="0" y="0" depth="1" />
<call object="Tetris" method="main" />
</frame>
</movie>
This file is pretty self-explanatory. You are going to want to import your fonts, since no system fonts will look consistent across all platforms. The reason why I specified a list of glyphs, is to avoid including all possible characters from the font -- this would greatly increase the size of the final output.
Images are imported like field.png and start.png, and I decided to place the background (field.png) into the canvas using XML instead of ActionScript code, since it's always visible anyway. However, it really doesn't matter whether anything is part of the Flash animation itself or placed using ActionScript, since both are bytecode, and would be interpreted in pretty much the same way by any sane interpreter.
Other than the images, the rest of the work is pure code. \o/ However, I won't proceed until I've told you about the technicalities. :(
Flash
Here is a quick description of the Flash file format: There's a tiny header which defines the version, the file size, the coordinate system for the file, as well as the framerate. Then comes the tags. The tags are executed in the order they appear, and a frame is displayed whenever the showframe tag is encountered (with a delay, derived from the framerate). Tags can conditionally jump to other tags, making the tag stream look like the bytecode output of a scripting language compiler.
The first few tags will typically define resources like images, sounds and shapes (vector graphics). Since the Adobe Flash player starts playback as soon as it starts receiving data from the server, you can easily make a loading progress indicator -- you simply jump backwards in the animation if the file hasn't been completely loaded yet.
What we will be making is a one frame long looping animation. Our code will be embedded in a tag somewhere in this frame, in effect building a typical scenario for a game developer, where a function is called once per frame. Due to the flexibility of Flash, there are many other ways to make a game, but we will be doing it the coder-centric way.
ActionScript
One of the tags is special: the doaction tag. This tag includes the bytecode compiled from the ActionScript code, and schedules its execution at the end of the current frame, no matter where in the stream it occurs. Additionally, there's a button tag, which contains reference to a shape, a bitmask and some ActionScript code. The bitmask and the shape are used as conditions to execute the attached ActionScript code, for example when the mouse is entering or leaving, or when a key is pressed.
The bytecode looks nothing like the code you will be writing -- it is stack based (no registers), and features heavy use of push and pop.
ActionScript reference at Adobe.com
tetris.as
Here follows a heavily commented version of tetris.as. I don't comment this much in real life, but this is meant to be educational.
class Tetris
{
/* Reference to the only instance of this class */
static var app : Tetris;
/* Field size. Must be 300x600 to fit in my artwork */
static var FIELD_WIDTH : Number = 10;
static var FIELD_HEIGHT : Number = 20;
static var BLOCK_SIZE : Number = 30;
/* Delay before first key repeat */
static var REPEAT_DELAY : Number = 0.2;
/* Delay between subseqeuent key repeats */
static var REPEAT_INTERDELAY : Number = 0.1;
/* Number of seconds elapsed in this round */
static var t : Number = 0;
/* Last time the current piece was moved down */
static var last_update : Number = 0;
/* The index of the current piece layout */
static var piece : Number = 0;
/* The index of the next piece layout. This is displayed in a box outside
* the field */
static var nextPiece : Number = 0;
/* Rotation of the current piece */
static var pieceRot : Number = 0;
/* X and Y position of the current piece */
static var pieceX : Number = 0;
static var pieceY : Number = 0;
/* The contents of the current field. One Number per cell */
static var field : Array = new Array();
/* The colors of the difference pieces */
static var palette : Array = new Array();
/* The shape of the difference pieces, in all their orientations */
static var shapes : Array = new Array();
/* Number of lines removed. Used to determine game speed */
static var lines : Number = 0;
/* Number of points scored */
static var score : Number = 0;
/* Number of seconds between each time the current piece is moved down */
static var delay : Number = 0.5;
/* Are we currently dead? */
static var dead : Boolean = true;
/* How much of the area has been filled with boxes after death? */
static var dead_fill : Number = 0;
/* The player's name, as entered by the player */
static var name : String = "";
/* Font information */
static var number_format : TextFormat = new TextFormat();
static var text_format : TextFormat = new TextFormat();
/* When a line is removed, all the boxes on the line are converted into
* projectiles. This array contains those projectiles. */
static var projectiles : Array = new Array();
/* What keys are currently pressed? */
static var leftDown : Boolean;
static var rightDown : Boolean;
static var downDown : Boolean;
static var upDown : Boolean;
static var spaceDown : Boolean;
/* The time each key was previously removed. Flash does not provide any clue
* about what keyboard events are repeat events, so we assume that a keypress
* that quickly follows a key release is a repeat, and can be ignored. */
static var left_released : Number;
static var right_released : Number;
static var down_released : Number;
static var up_released : Number;
static var space_released : Number;
/* The time of our next key repeat event, as generated by ourselves (not the
* operating system). */
static var next_repeat : Number;
/**
* The class constructor. Called once on startup, by the main() function.
*/
function Tetris()
{
var x : Number = 0;
/* Asynchronously fetch variables into the _root. scope from the specified
* URL. In this particular case, the sergver will return a snippet that
* assigns some text to the _root.score_info variable. Very nifty!
* Go ahead and fetch this URL if you want to see what it looks like. */
_root.loadVariables("http://www.junoplay.com/tetris-submit");
/* Set up callbacks for all the relevant events. */
_root.onEnterFrame = on_enter_frame;
_root.onMouseUp = on_mouse_up;
_root.onKeyDown = on_key_down;
_root.onKeyUp = on_key_up;
/* We want to receive key events regardless of focus */
Key.addListener(_root);
/* The colors of the 7 different tetrominoes */
palette = [ 0xffcf7f, 0xff7fcf, 0x7fffcf, 0xcfff7f, 0x7fcfff, 0xcf7fff, 0xcfcfcf ];
### The shapes go here ###
/* This is the layer we will be painting to in our frame callback function.
* I have given it a depth of 10, so it will be above most other things */
_root.createEmptyMovieClip("lines", 10 /* Depth */);
/* Place the text fields */
_root.createTextField("score_field", 2, 340, 33, 425, 40);
_root.createTextField("high_score", 3, 340, 265, 425, 280);
/* The start button. It has a depth of 11, one more than the depth of the
* painting layer */
_root.attachMovie("start", "start", 11);
_root.start._x = 118;
_root.start._y = 268;
/* Font used for high score list */
text_format.font = "DejaVuSansMono";
text_format.color = 0xffffff;
text_format.size = 20;
text_format.align = "left";
/* Font used for score view */
number_format.font = "DejaVuSansMono";
number_format.color = 0xffffff;
number_format.size = 30;
number_format.align = "right";
/* Assign the fonts to the text fields */
_root.score_field.embedFonts = true;
_root.score_field.setTextFormat(number_format);
_root.score_field.setNewTextFormat(number_format);
_root.high_score.embedFonts = true;
_root.high_score.setTextFormat(text_format);
_root.high_score.setNewTextFormat(text_format);
/* Fill the entire field with boxes */
for(x = 0; x < FIELD_WIDTH * FIELD_HEIGHT; ++x)
field[x] = 7;
}
/**
* Start a new game. Reset all game variables to their initial state.
*/
static function reset_field() : Void
{
var x : Number = 0;
for(x = 0; x < FIELD_WIDTH * FIELD_HEIGHT; ++x)
field[x] = 0;
pieceX = FIELD_WIDTH / 2 - 2;
pieceY = 0;
piece = random(7);
nextPiece = random(7);
t = 0;
last_update = 0;
lines = 0;
score = 0;
delay = 0.5;
dead = false;
leftDown = false;
rightDown = false;
downDown = false;
upDown = false;
spaceDown = false;
left_released = 0;
right_released = 0;
down_released = 0;
up_released = 0;
space_released = 0;
}
/**
* Check whether any lines have been completely filled. Convert
* any filled line into projectiles.
*/
static function check_lines() : Void
{
var x : Number = 0;
var y : Number = 0;
var hit : Boolean = false;
var power : Number = 1000.0;
var y2 : Number;
var linesNow : Number = 0;
/* Iterate over all lines ... */
for(y = 0; y < FIELD_HEIGHT; ++y)
{
for(x = 0; x < FIELD_WIDTH; ++x)
if(field[y * FIELD_WIDTH + x] == 0)
break;
if(x != FIELD_WIDTH)
continue;
/* If we got here, this line is full */
hit = true;
for(x = 0; x < FIELD_WIDTH; ++x)
{
var p : Projectile = new Projectile();
var len : Number;
/* Create a projectile headed in a random direction */
p.x = x * BLOCK_SIZE;
p.y = y * BLOCK_SIZE;
p.vx = random(17) / 16.0 - 0.5;
p.vy = -random(17) / 16.0 - 0.1;
p.color = field[y * FIELD_WIDTH + x] - 1;
len = 1.0 / Math.sqrt((p.vx * p.vx) + (p.vy * p.vy));
len *= power;
p.vx *= len;
p.vy *= len;
projectiles.push(p);
}
/* The projectils for the 2nd, 3rd and 4th lines removed
* will be moving faster */
power *= 1.3;
++linesNow;
/* Move the top of the field down */
for(y2 = y; y2 > 0; --y2)
for(x = 0; x < FIELD_WIDTH; ++x)
field[y2 * FIELD_WIDTH + x] = field[(y2 - 1) * FIELD_WIDTH + x];
++lines;
/* Calculate new game speed */
delay = 0.1 + (0.4 / (lines * 0.03 + 1));
for(x = 0; x < FIELD_WIDTH; ++x)
field[x] = 0;
}
score += linesNow * linesNow * 357;
}
/**
* Make a new piece appear at the top, or die if there's no room for it.
*/
static function spawn() : Void
{
var x : Number = 0;
var y : Number = 0;
for(y = 0; y < 4; ++y)
for(x = 0; x < 4; ++x)
if(shapes[piece][pieceRot][y][x])
field[(pieceY + y) * FIELD_WIDTH + pieceX + x] = piece + 1;
/* Remove any full lines */
check_lines();
piece = nextPiece;
nextPiece = random(7);
pieceX = FIELD_WIDTH / 2 - 2;
pieceY = 0;
pieceRot = 0;
/* If a new piece cannot be placed, we are dead */
if(will_crash(piece, pieceX, pieceY, pieceRot))
{
dead = true;
dead_fill = 0;
_root.attachMovie("start", "start", 11);
_root.start._x = 118;
_root.start._y = 268;
if(score > 0)
{
/* Ask the player for his name */
_root.score_info = "";
_root.score = 0;
_root.high_score.text = "Your name:\n" + name + "_\n\n(Press Enter when finished)";
}
}
}
/**
* Move the current piece one unit down.
*/
static function down() : Boolean
{
last_update = t;
if(will_crash(piece, pieceX, pieceY + 1, pieceRot))
{
spawn();
return true;
}
else
++pieceY;
return false;
}
/**
* Test whether a piece will crash in a new position/orientation. This is
* useful for determining when a piece has fallen to the bottom, whether a
* piece is impossible to rotate, and whether a new piece cannot be spawned.
*/
static function will_crash(piece : Number, px : Number, py : Number, pr : Number) : Boolean
{
var x : Number = 0;
var y : Number = 0;
for(y = 0; y < 4; ++y)
{
for(x = 0; x < 4; ++x)
{
if(shapes[piece][pr][y][x]
&& (px + x < 0 || px + x > FIELD_WIDTH
|| py + y < 0 || py + y > FIELD_HEIGHT
|| field[(py + y) * FIELD_WIDTH + px + x]))
{
return true;
}
}
}
return false;
}
/**
* Draw a loose shape. Used to draw the current piece and the next piece.
*/
static function draw_shape(piece : Number, px : Number, py : Number, pr : Number)
{
var x : Number = 0;
var y : Number = 0;
var alphas : Array = [ 100, 100 ];
var ratios : Array = [ 0, 0xFF ];
var colors, left, top, matrix, color;
for(y = 0; y < 4; ++y)
{
for(x = 0; x < 4; ++x)
{
if(shapes[piece][pr][y][x] != 0)
{
left = px + x * BLOCK_SIZE;
top = py + y * BLOCK_SIZE;
color = palette[piece];
colors = [ color, ((color >> 1) & 0x7f7f7f) ];
matrix =
{
matrixType:"box", x:left, y:top, w:BLOCK_SIZE, h:BLOCK_SIZE, r:(45/180) * Math.PI
};
_root.lines.moveTo(left, top);
_root.lines.lineStyle(2, (color >> 1) & 0x7f7f7f, 100);
_root.lines.beginGradientFill("linear", colors, alphas, ratios, matrix );
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top);
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top);
_root.lines.endFill();
}
}
}
}
/**
* The function called by the frame loop.
*/
function on_enter_frame() : Void
{
var x : Number = 0;
var y : Number = 0;
t += 0.05;
/* Update the score display */
_root.score_field.text = String(score);
if(!dead)
{
/* Move the current piece down a bit if it's time */
if(t - last_update > delay)
down();
/* Handle key repeat */
if(t > next_repeat)
{
if(leftDown)
{
if(!will_crash(piece, pieceX - 1, pieceY, pieceRot))
--pieceX;
}
if(rightDown)
{
if(!will_crash(piece, pieceX + 1, pieceY, pieceRot))
++pieceX;
}
if(upDown)
{
if(!will_crash(piece, pieceX, pieceY, (pieceRot + 1) % 4))
pieceRot = (pieceRot + 1) % 4;
}
if(downDown)
down();
next_repeat = t + REPEAT_INTERDELAY;
}
}
else
{
/* Crude death animation filling up the field from below */
if(dead_fill < FIELD_HEIGHT)
{
++dead_fill;
for(x = 0; x < FIELD_WIDTH; ++x)
field[(FIELD_HEIGHT - dead_fill) * FIELD_WIDTH + x] = 7;
}
}
/* Update the score table if we've received it from the server */
if(_root.score_info.length)
_root.high_score.text = _root.score_info;
/* Clear our canvas */
_root.lines.clear();
/* Paint the field */
var alphas : Array = [ 100, 100 ];
var ratios : Array = [ 0, 0xFF ];
var pc, colors, left, top, matrix, color;
for(y = 0; y < FIELD_HEIGHT; ++y)
{
for(x = 0; x < FIELD_WIDTH; ++x)
{
pc = field[y * FIELD_WIDTH + x];
if(pc > 0)
{
left = x * BLOCK_SIZE;
top = y * BLOCK_SIZE;
color = palette[pc - 1];
colors = [ color, ((color >> 1) & 0x7f7f7f) ];
matrix =
{
matrixType:"box", x:left, y:top, w:BLOCK_SIZE, h:BLOCK_SIZE, r:(45/180) * Math.PI
};
_root.lines.moveTo(left, top);
_root.lines.lineStyle(2, (color >> 1) & 0x7f7f7f, 100);
_root.lines.beginGradientFill("linear", colors, alphas, ratios, matrix );
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top);
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top);
_root.lines.endFill();
}
}
}
/* Paint the projectiles from removed lines */
x = 0;
while(x < projectiles.length)
{
var p : Projectile = projectiles[x];
p.x += p.vx * 0.05;
p.y += p.vy * 0.05;
if(p.y > 600)
{
projectiles.splice(x, 1);
continue;
}
p.vy += 250.0;
left = p.x;
top = p.y;
color = palette[p.color];
colors = [ color, ((color >> 1) & 0x7f7f7f) ];
matrix =
{
matrixType:"box", x:left, y:top, w:BLOCK_SIZE, h:BLOCK_SIZE, r:(45/180) * Math.PI
};
_root.lines.moveTo(left, top);
_root.lines.lineStyle(2, (color >> 1) & 0x7f7f7f, 100);
_root.lines.beginGradientFill("linear", colors, alphas, ratios, matrix);
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top);
_root.lines.lineTo(left + (BLOCK_SIZE - 1), top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top + (BLOCK_SIZE - 1));
_root.lines.lineTo(left, top);
_root.lines.endFill();
++x;
}
if(!dead)
{
/* Draw the current and the next piece */
draw_shape(piece, pieceX * BLOCK_SIZE, pieceY * BLOCK_SIZE, pieceRot);
draw_shape(nextPiece, 330, 105, 0);
}
}
/**
* The player pressed a key on his keyboard.
*/
function on_key_down() : Void
{
var k : Number = Key.getCode();
if(dead)
{
if(score > 0 && !_root.score)
{
switch(k)
{
case Key.BACKSPACE:
if(name.length > 0)
name = name.slice(0, name.length - 1);
break;
case Key.SPACE:
if(name.length > 0)
name = name + " ";
break;
case Key.ENTER:
/* If the current score has not been submitted, and a
* name has been entered, submit the score */
if(!_root.score && name.length)
{
_root.name = name;
_root.score = score;
_root.loadVariables("http://www.junoplay.com/tetris-submit", "POST");
_root.high_score.text = "Score submitted";
}
break;
case Key.TAB:
break;
default:
if(Key.getAscii() && name.length < 16)
name = name + String.fromCharCode(Key.getAscii());
}
/* Update the view with the current entered name, if
* the score hasn't been submitted yet */
if(!_root.score)
_root.high_score.text = "Your name:\n" + name + "_\n\n(Press Enter when finished)";
}
return;
}
switch(k)
{
case Key.RIGHT:
if(!rightDown)
{
/* Move right */
rightDown = true;
if(t - right_released > 0.01)
{
if(!will_crash(piece, pieceX + 1, pieceY, pieceRot))
++pieceX;
next_repeat = t + REPEAT_DELAY;
}
}
break;
case Key.LEFT:
if(!leftDown)
{
/* Move left */
leftDown = true;
if(t - left_released > 0.01)
{
if(!will_crash(piece, pieceX - 1, pieceY, pieceRot))
--pieceX;
next_repeat = t + REPEAT_DELAY;
}
}
break;
case Key.UP:
if(!upDown)
{
/* Rotate! */
upDown = true;
if(t - up_released > 0.01)
{
if(!will_crash(piece, pieceX, pieceY, (pieceRot + 1) % 4))
pieceRot = (pieceRot + 1) % 4;
next_repeat = t + REPEAT_DELAY;
}
}
break;
case Key.SPACE:
if(!spaceDown)
{
/* Drop! */
spaceDown = true;
if(t - space_released > 0.01)
{
while(!down())
score += 7;
}
}
break;
case Key.DOWN:
if(!downDown)
{
/* Move one line down */
downDown = true;
if(t - down_released > 0.01)
{
down();
next_repeat = t + REPEAT_DELAY;
}
}
break;
}
}
/**
* The player released a key on his keyboard.
*/
function on_key_up() : Void
{
var k : Number = Key.getCode();
switch(k)
{
case Key.RIGHT:
right_released = t;
rightDown = false;
break;
case Key.LEFT:
left_released = t;
leftDown = false;
case Key.UP:
up_released = t;
upDown = false;
break;
case Key.SPACE:
space_released = t;
spaceDown = false;
break;
case Key.DOWN:
down_released = t;
downDown = false;
break;
}
}
/**
* A mouse button was released.
*/
function on_mouse_up() : Void
{
if(_root.start.hitTest(_root._xmouse, _root._ymouse, false))
{
reset_field();
_root.start.removeMovieClip();
if(!_root.score_info.length)
_root.loadVariables("http://www.junoplay.com/tetris-submit");
}
}
/**
* The entry point, as defined in tetris.xml:
*
* <call object="Tetris" method="main" />
*/
static function main() : Void
{
app = new Tetris();
}
}
You may have noticed the part saying ### The shapes go here ###. This should be replaced with the following piece of code (which I took out of the program listing because it mostl looks like noise). You can easily make a different game simply by replacing the shapes here.
shapes =
[
[ [ [ 0,0,1,0 ], /* The I tetromino */
[ 0,0,1,0 ],
[ 0,0,1,0 ],
[ 0,0,1,0 ] ],
[ [ 0,0,0,0 ],
[ 1,1,1,1 ],
[ 0,0,0,0 ],
[ 0,0,0,0 ] ],
[ [ 0,0,1,0 ],
[ 0,0,1,0 ],
[ 0,0,1,0 ],
[ 0,0,1,0 ] ],
[ [ 0,0,0,0 ],
[ 1,1,1,1 ],
[ 0,0,0,0 ],
[ 0,0,0,0 ] ] ],
[ [ [ 0,0,0,0 ], /* The O tetromino */
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,1,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,1,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,1,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,1,1,0 ] ] ],
[ [ [ 0,0,0,0 ], /* The T tetromino */
[ 0,0,0,0 ],
[ 0,1,1,1 ],
[ 0,0,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,1,1,0 ],
[ 0,0,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,1,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,0,1,1 ],
[ 0,0,1,0 ] ] ],
[ [ [ 0,0,0,0 ] /* The L tetromino */,
[ 0,0,0,0 ],
[ 0,1,1,1 ],
[ 0,1,0,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,1 ],
[ 0,0,0,1 ],
[ 0,0,0,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,0,0,1 ],
[ 0,1,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,0,1,0 ],
[ 0,0,1,1 ] ] ],
[ [ [ 0,0,0,0 ] /* The J tetromino */,
[ 0,0,0,0 ],
[ 0,1,1,1 ],
[ 0,0,0,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,1 ],
[ 0,0,0,1 ],
[ 0,0,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,1,0,0 ],
[ 0,1,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,1 ],
[ 0,0,1,0 ],
[ 0,0,1,0 ] ] ],
[ [ [ 0,0,0,0 ] /* The S tetromino */,
[ 0,0,0,0 ],
[ 0,0,1,1 ],
[ 0,1,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,0,1,1 ],
[ 0,0,0,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,0,1,1 ],
[ 0,1,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,1,0 ],
[ 0,0,1,1 ],
[ 0,0,0,1 ] ] ],
[ [ [ 0,0,0,0 ] /* The Z tetromino */,
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,0,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,1 ],
[ 0,0,1,1 ],
[ 0,0,1,0 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,0 ],
[ 0,1,1,0 ],
[ 0,0,1,1 ] ],
[ [ 0,0,0,0 ],
[ 0,0,0,1 ],
[ 0,0,1,1 ],
[ 0,0,1,0 ] ] ]
];
projectile.as
MTASC demands, for no reason, that each class be defined in its own separate file. Here's the Projectile class used in tetris.as.
class Projectile
{
public var x : Number;
public var y : Number;
public var vx : Number;
public var vy : Number;
public var color : Number;
}
Complete Source Code
The code on this page and in the packages below can be redistributed under the terms of the GNU General Public License version 3, as distributed by the Free Software Foundation. Snippets of just a few lines can be copied at will, since they are not copyrightable.
- flash-tetris-1.0.tar.gz (for Unix people)
- flash-tetris-1.0.zip (for Windows people)
A running version of the game can be tested at http://www.junoplay.com/tetris.
Copyright © 2008 Junoplay Ltd - Contact information