The Game Programming
Language (GPL) v. 2.0
Tyson Henry
Revised 2/5/08 (revision history at
end of document)
Overview
The Game Programming Language (GPL) is
an object-based language for specifying simple computer games such as
those found arcade games of the late 1970's and early 1980's. It
was created as a semester long project for an undergraduate compiler
course. My goal was to create a project that demonstrated the
wide applicability of compiler construction tools. The language
is simple to learn and surprisingly expressive. A typical
computer
science undergraduate can learn it in about an hour and be creating
significant programs within several hours.
To keep the implementation simple enough to be completed in a single
semester several simplifying design decisions were made. For
example, all variables are global and there are no functions.
Running the gpl interpreter
The gpl interpreter is a stand alone
program. It does not have to be installed like many windows
programs. The only way to
run it is from the command line.
In linux: OpenGL and GLUT must be installed. OpenGL comes with
most flavors of linux. The GLUT
library is distributed with many flavors of linux but may not be
installed automatically. You run the gpl interpreter
from the command line.
In OSX: OpenGL and GLUT come with OSX. You run the gpl
interpreter from the command line.
In windows: the cygwin Unix emulator (or some other Unix
emulator), OpenGL, and GLUT
must be installed. You run the gpl interpreter from the cygwin
command prompt.
From the command line you run the gpl interpreter as follows
% gpl filename.gpl
Where filename.gpl is a text file that contains a gpl program. If
you don't specify a filename or the specified file cannot be opened,
an error
message is written to standard error and the program exits.
The command line argument -seed allows users to specify a seed for the
random number generator. If a seed is not given, the clock is
used. Specifying a seed makes it easier to debug programs that
use random numbers. When you specify a seed, every time you run
the program with that seed the same random numbers will be
generated. The -seed # must be the first command line argument:
% gpl -seed 42 filename.gpl
The Parts of a gpl Program
gpl programs have two distinct parts
that must be in the following order:
declarations
All declarations must be before the code blocks. The types of
declarations are:
variables
types are: int, double, string,
rectangle, triangle, circle, textbox, and pixmap
animation block forward declarations
animation blocks must be declared
before they can be referenced
forward block declarations provide that mechanism
Variables and block forward declarations can be in
any order as long as they are before all code blocks.
code blocks
initialization block (each gpl program
can have zero or many)
An initialization block is a series of
statements that is executed right before program control is passed to
the main event loop (i.e. after input has been parsed and after the
window has been created).
animation blocks (each gpl program can have zero or many)
Animation blocks are a series of
statements. They are like functions
that are automatically executed periodically.
The statements in an animation block can change global variables and
the properties of
rectangles, triangles,
circles, textboxes, and pixmaps. For example, by changing the x,y
of an
object, it will move around the window.
The frequency that animation blocks are executed is determined by the
reserved variable animation_speed (see next section).
event handler blocks (each gpl program
can have zero or many)
Event handlers are like callback
functions.
When a user event occurs (such as a left arrow being pressed) the
statements in the associated event handler are executed.
Variable
Declarations
All variables must be declared in the variable declaration
section. Variables cannot be declared in code blocks. In
other
words,
there are no "local" variables. All variables are global.
This make the implementation of the symbol table simple.
Basic Variable Types
Type
|
Example w/o
initialization
|
Example with
initialization
|
int
|
int a;
|
int a = 42;
|
double
|
double x;
|
double x = 1.42;
|
string
|
string s;
|
string s = "hello world";
|
Note: strings are delimited with double quotes, but the double quotes
are not part of the string. Strings can contain any printable
character (e.g. no line feed) except double quotes.
Reserved Variables
If any of the following variables are
declared, their values will be used to set up the window and the
animation speed. Note: If these variables are declared with a
different type then listed below, an error will be issued. The
value of these
variables
is only used at game start up time. Changes to these variables
during run time will
not affect the window.
If a reserved variable is not declared, the default value is used to
initialize the window.
Type
|
Name
|
Description
|
Default Value
|
int
|
window_x
|
X Placement of the game window
on the desktop (top left is x=0)
|
200
|
int
|
window_y
|
Y Placement of the game window
on the desktop (top left is y = 0)
|
200
|
int
|
window_width
|
Width of the game window
|
500
|
int
|
window_height
|
Height of the game window
|
500
|
string
|
window_title
|
Title of the game window
(appears in window's title bar)
|
gpl window
|
double
|
window_red
|
The red component of the
window's background color (0.0 is no red, 1.0 is full red)* |
1.0
|
double
|
window_green
|
The green component of the
window's background color. |
1.0
|
double
|
window_blue
|
The blue component of the
window's background color. |
1.0
|
int
|
animation_speed
|
Speed of the animation (1 is
slowest, 100 is fastest). Values are restricted to the range
1-100. Speed is not linear (e.g. 100 is many times faster
than 50). Speed is very machine dependent.
|
88
|
*See Colors section below for more about colors.
Game Objects: game objects are
the graphical components of games
Type
|
Example w/o
parameters
|
Example with
parameters
|
circle
|
circle c();
|
circle c(x = 10, y = 20, radius
= 50, animation = ball_animation);
|
rectangle
|
rectangle r();
|
rectangle r(x = 10, y = 10, h =
10, w = 10);
|
triangle
|
triangle t();
|
triangle t(x = 100, y = 100,
size = 50, animation = ship_animation);
|
textbox
|
textbox title();
|
textbox title(x = 10, y = 10,
text = "hello world", size = 0.1);
|
pixmap
|
pixmap photo();
|
pixmap photo(x = 10, y = 10,
filename = "mountains.bmp");
|
Game Object Attributes:
All game objects (circle, rectangle, triangle, textbox, pixmap) have
the
following attributes
Name
|
Description
|
Type
|
Default
|
x
|
x location of object in window
(in pixels)
|
integer
|
0
|
y
|
y location of object in window
(in pixels)
|
integer
|
0
|
w
|
width of the object (in
pixels) |
integer
|
10
|
h
|
height of the object (in pixels)
|
integer
|
10
|
animation_block
|
name of the animation block
associated with this game object
|
code block
|
none
|
| visible |
if visible == 1 object is drawn on screen, otherwise it is
not drawn (use 0 to indicate not-visible) |
integer |
1 |
red
|
the red component of the
object's color (0.0 is no red, 1.0 is full red)*
|
double
|
0.5
|
green
|
the green component of the
object's color (0.0 is no green, 1.0 is full green)
|
double
|
0.5
|
blue
|
the blue component of the
object's color (0.0 is no blue, 1.0 is full blue)
|
double
|
0.5
|
proximity
|
the distance used by near
operator to determine if object is "near" to this one
|
int
|
4
|
| drawing_order |
controls the oder the object is drawn, objects with high
drawing_order values are drawn on top of objects with a lower
drawing_order value |
int |
0 |
| user_int** |
an integer variable that can be used by the game program
(very helpful when using arrays of objects) |
int |
0 |
| user_double** |
a double variable that can be used by the game program |
double |
0.0 |
| user_string** |
a string variable that can be used by the game program |
string |
"" |
*See Colors section below for more about colors.
** There are 5 user variables for each type, e.g. user_int,
user_int2, user_int3, user_int4, user_int5.
Circles have the additional
integer
attribute radius:
Name
|
Description
|
Type
|
Default
|
radius
|
the radius in pixels of the
circle (overrides the h & w)
|
integer
|
10
|
Triangles have the additional
attributes of size, skew, and rotation:
Name
|
Description
|
Type
|
Default
|
size
|
size of the length of the base
of the triangle
|
integer
|
10
|
skew
|
the ratio: height/width.
If equal 1.0, triangle is equilateral, if > 1.0, triangle is taller,
if <1.0 shorter
|
double
|
1.0
|
rotation
|
degrees rotated
counter-clockwise around middle of triangle
|
double
|
0.0
|
Rectangles have one additional
attribute for rotation:
Name
|
Description
|
Type
|
Default
|
rotation
|
degrees rotated
counter-clockwise around middle of rectangle
|
double
|
0.0
|
Textboxes have three additional
attributes: text, size, and
space:
Name
|
Description
|
Type
|
Default
|
text
|
text to be displayed by the text
box
|
string
|
empty string
|
size
|
size of the text (1.0 is about
100 pixels high, 0.1 is about 10 pixels high)
|
double
|
0.1
|
space
|
number of pixels between letters
when size == 1.0
|
int
|
10
|
Pixmaps have one additional
attribute for the filename of the pixmap (in .bmp format)
Name
|
Description
|
Type
|
Default
|
filename
|
filename of the file containing
the pixmap (in .bmp format)
|
string
|
""
|
Pixmaps can only handle bitmaps with 24 bit color and 1 plane.
Attributes are named during game object initialization (e.g. text =
"hello") and can be specified in any order. Attributes can also
be changed in any code block (initialization block, animation blocks,
event handler blocks).
Variables and attributes can be initialized using variables and
expressions. For example:
int x_position = 40;
int y_position = x_position * 4 + 12;
circle c1(x = x_position);
circle c2(x = x_position + 40);
textbox(text = 42);
textbox(text = "hello world");
textbox(text = 4 + 12 * x_position);
Attributes can be accessed using the
C/C++ "." notation for members of a class. They can appear on
both sides of assignment operators, and in expressions.
c2.x = 42;
i = c2.x
if (c2.x == 42)
{
}
Drawing Order:
By default, game objects are drawn in the order they are created. For
example, consider the following code:
rectangle r1(x = 100, y = 100, w = 100,
h = 100);
rectangle r2(x = 100, y = 100, w = 200, h = 200);
Since r1 was declared before r2, every time the objects are drawn, r1
will be drawn before r2. This means that r2 will be on top of r1.
It is possible to change the drawing order of objects. All game
objects have a drawing_order field. This is an integer that
controls where the game object is placed in the drawing vector.
Game objects with high drawing_order values will be drawn on top
of objects with lower drawing_order values.
Limitations:
The collision detection (the near and
touches) assumes that
objects are not rotated. The bounding box of the original
rectangle or triangle is used for collision detection. If the
objects are rotated, collisions won't be detected accurately.
At some point I will fix this, but don't hold your breath.
Arrays:
Arrays of all types can be created:
int num = 10;
int numbers[42];
circle dots[num];
rectangle blocks[num * 2];
The size of the array can be any legal expression that evaluates to an
integer.
You cannot initialized arrays during initialization (this makes the
interpreter much more complicated).
Use the initialization block to initialize arrays.
Animation
Block Forward
Declarations
Animation blocks (see below) must be
declared before they can be referenced in a game object
declaration. For example:
circle earth(x = 100, y = 100, radius = 400,
animation_block = earth_animation);
earth_animation is the name of
the animation block associated with the circle object
earth.
Since all animation blocks must come after all declarations, a
forward statement is needed to tell the interpreter that a given
animation
block is defined below. The format is as follows:
forward
<block_name>(<parameter_type> <parameter_name>);
forward animation earth_animation(circle cur_rectangle);
All animation blocks that are used during variable initialization must
have a forward declaration.
Each animation block has a single game object parameter (e.g. circle,
triangle, etc.) that acts like a pass-by-reference function argument in
C/C++. The type of this parameter must match the type of the
object using the animation block (see next section).
It is an error to have an animation block forward without providing the animation block.
Initialization Blocks
A gpl program can have zero or more initialization blocks:
initialization
{
stmt;
stmt;
...
}
The statements in an initialization block are executed right before program control is passed to
the main event loop (i.e. after input has been parsed and after the
window has been created).
If there are more than one initialization block, then they are executed in the order they appear in the .gpl file.
Initialization blocks are most often used to initialize arrays of variables.
Animation
Blocks
Animation blocks are named blocks of
statements. They are executed at regular intervals for all
objects for which they are
associated.
For example, given the following
declarations:
circle c1(animation =
my_animation);
circle c2(animation = my_animation);
circle c3(animation = my_animation);
The animation block
my_animation
will be executed at regular intervals for
c1,
c2, and
c3 (when an animation block is
executed for a game object, that object is passed by reference to the
animation
block (see below)).
Animation blocks have the form:
animation my_animation(circle
cur_circle)
{
statement // statements are
explained below
statement
...
}
where my_animation is the name of the block (it can be any legal
identifier).
Animation blocks all have a single
typed parameter which is specified in the block's forward
statement and in the animation block header. The parameter can be
any game object type
(rectangle, circle, triangle, etc). The name of the parameter can
be
any legal identifier. The type of the parameter must match the
type of the object using this animation block.
For example:
forward animation my_animation(circle
cur_circle);
In the above example, the parameter
is of type circle, and the
parameter name is cur_circle.
When an animation block is specified for a game object:
circle earth(animation = earth_animation);
The type of the object (circle in this example) must match the type of
the animation block's
parameter specified in the forward statement.
The parameter behaves like a local variable inside of the animation
block.
For all game objects that have an animation block, the animation block
will be executed at regular intervals if the object's visible field is
true. The frequency of execution depends on the reserved variable
animation_speed.
Event Handler Blocks (also
called Event Handlers)
Event handlers are very similar to event callback functions in VB, C++,
X-windows, etc.
The basic idea is that you specify an event (something the user does,
like press a key) and associate a block of statements with it.
Event
handlers have the following form:
on leftarrow
{
statement // statements are
explained below
statement
...
}
The
on is a keyword.
All event handlers start with
on.
The
leftarrow is also a
keyword that describes a particular event, in this case, the pressing
of the left arrow key.
When the user causes an event (pressing the left arrow key in the above
example) the statements in the event block associated with that event
are executed.
The following events are supported by gpl Event Handlers:
space
|
user presses the space bar
|
leftarrow
|
user presses the left arrow key
|
rightarrow
|
user presses the right arrow key
|
uparrow
|
user presses the up arrow key
|
downarrow
|
user presses the down arrow key
|
f1
|
user pressed the f1 key
|
akey
|
user presses the 'A' or 'a' key
|
skey, dkey, fkey,
hkey,jkey,kkey, lkey,wkey
|
work just like akey
|
There can be any number of event handlers for each event. They
are executed in the order they are declared.
Statements
gpl supports five statements:
- if statement
- for statement
- print statement
- exit statement
- assignment statement
A statement is a single statement.
A statement_block is a series of statements
enclosed in { }.
A statement_or_statement_block is either a single statement or a
statement_block.
if statements have the
following two forms:
if (expression) statement_or_statement_block;
if (expression) statement_or_statement_block else
statement_or_statement_block;
the expression must be of type int
for statements have the
following form. These are not as general
as C/C++ for statements:
for (assignment_statement; expression;
assignment_statement) statement_block;
the expression must be of type int
print statements have the form:
print (expression);
Expression is evaluated to a string (which means it can be an int, double, or string)
Example: print("value = " + 42 + " x = " + x);
exit statements have the form:
exit (expression);
expression is evaluated to calculate the value passed to the system function exit(exit_status)
expression must be an int expression
assignment statement have the
form:
variable assignment_operator expression;
where assignment_operator can be: = += -=
Expressions
Expression
types:
gpl expressions are strongly
typed (int,double,string). However there are some implicit casts built into gpl.
If an arithmetic expression (e.g. "i + x") contains both
integers and doubles, the type of the expression will be double: integers are automatically cast to doubles (this is
called an implicit cast).
Integers and doubles are also automatically cast to strings. If
either an integer or a double is in an expression with a string, it is
converted to a string that represents its value. Consider the
following code:
int i = 21;
string s1 = "I am ";
string s2 = s1 + i;
The value of s2 will be the string "I
am 21"
However, strings are not cast to integers or doubles, and doubles are
not cast to integers.
There is a special form of expressions of type animation_block.
It can only consist of a variable--it can't contain any
operators. It is used when initializing or changing the member
variable animation_block:
rectangle my_rectangle(animation_block = move);
my_rectangle.animation_block = bounce;
move and bounce are expressions of type animation_block. They each contain a single variable.
Arithmetic
operators are similar to C/C++ expressions. They can
contain "(" and ")" just like C/C++ expressions.
Operator
|
Description
|
Legal input
types
|
Result type
|
*
|
multiplication
|
int, double
|
int or double
|
/
|
division
|
int, double
|
int or double
|
+
|
addition, string concatenation
|
int, double, string
|
int, double, or string
|
-
|
minus
|
int, double
|
int or double
|
%
|
modulus
|
int
|
int
|
-
|
unary minus
|
int, double
|
int or double
|
Logical
operators
Operator
|
Description
|
Legal Input
Types
|
Result Type
|
<
|
less than
|
int, double, string
|
int
|
>
|
greater than
|
int, double, string |
int
|
<=
|
less than or equal
|
int, double, string |
int
|
>=
|
greater than or equal
|
int, double, string |
int
|
==
|
equal
|
int, double, string |
int
|
!=
|
not equal
|
int, double, string |
int
|
!
|
not (unary operator)
|
int, double |
int
|
| && |
logical and |
int, double |
int |
| || |
logical or |
int, double |
int |
Math
Operators: There are several unary math operators.
While these are implemented as unary operators, they look like function
calls. The format is:
x = sin(y);
x = 2 * sin(y) + 3 * cos(z);
i = random(10);
Operator
|
Description
|
Legal Input
Types
|
Result Type
|
cos
|
cosine
|
int, double
|
double
|
sin
|
sine
|
int, double |
double |
tan
|
tangent
|
int, double |
double |
acos
|
inverse cosine (arccosine)
|
int, double |
double |
asin
|
inverse sine (arcsine)
|
int, double |
double |
atan
|
inverse tangent (arctangent)
|
int, double |
double |
sqrt
|
square root
|
int, double |
double |
abs
|
absolute value
|
int, double |
int,double
|
floor
|
floor of given value
|
int, double
|
int
|
random
|
random integer between 0 - (N-1)
(where N is the given number)
|
int, double (double is rounded
down)
|
int
|
The trig functions (cos, sin, tan) assume the input is specified in degrees.
Geometric
expression allow you
to compare the proximity of two game objects (rectangle, triangle,
circle, etc). The operators are
near
and
touches. They
have the following form:
if (c1 touches c2)
{
...
}
If the game object c1 is touching or overlapping game object c2, this
expression returns true. It returns false otherwise.
Specifically, if the bounding box of c1 overlaps the bounding box of
c2, c1 is "touching" c2.
if (c1 near c2)
{
...
}
If the game object c1 is near c2, this expression returns true.
It returns false otherwise. Specifically, the bounding box of c1
is increased in all directions by c1.proximity and the bounding box of
c2 is increased in all directions by c2.proximity. If the
expanded boxes overlap then c1 is "near" c2.
Identifiers
Identifiers are the same as in
C/C++. Must start with a-z, A-Z, _, but may also contain 0-9
after the first character.
Comments
Comments can appear anywhere in the
program. They are "//" to end of line. For example:
// this is a comment
int i; // this is another comment
Constants
There are four types of constants:
Type
|
Example
|
int
|
42
|
double
|
3.145
|
logical
|
true, false (note:
true and false are reserved words), they evaluated to the integers 1 (true) & 0 (false)**
|
string
|
"hello world"
|
** Just like C/C++, 0 is false and all other value is true
Quitting
the Program
The 'q' key exits a running gpl program (the window running the game
must be the currently selected window).
Colors
The values range for the red, green,
and blue attributes are from
0.0 (none) to 1.0 (full). For example, (1.0, 0.0, 0.0) means that
the
color has full red, no green, no blue. This is the color red.
You can create colors by experimentation (guessing values for the RGB)
or you can look them up somewhere. If you search the web for "RGB
values" you will find countless charts giving the RGB values of colors.
An alternative way to specify RGB values is to use 0-255 instead of 0.0
- 1.0. You can convert one of those numbers by dividing each
element
by 255:
Lemon: red = 255, green = 231, blue = 109
In gpl the values would be:
Lemon: red =
255/255.0, green = 231/255.0, blue = 109/255.0
Make sure you use the ".0" or the expressions will be evaluated as
integer expressions and will always have the value 0 or 1.
Examples:
circle earth(x = 100, y = 100, radius = 100, red =
0.0, blue = 1.0, green = .1);
circle sun(x = 1, y = 1, radius = 300 , red =
255/255.0, blue = 231/255.0, green = 109/255.0);
Revision
History
2/8/06: added && and || to the
table of logical operators, added documentation for the new user
variables fields (e.g. user_int2, user_int3, ...)
1/18/07: added exit statement, changed print to take an expression,
changed initialization block to indicate there can now be multiple
blocks and that initialization blocks are now executed after window is
created, added drawing_order member variable and description of how it
works, added some description of implicit cast
4/4/07: added clarification for true and false constants
2/5/08: added section on initialization blocks, added bits/plane
restrictions for pixmaps, added mention that trig functions expect the
input to be degrees
2/7/08: added wkey
Copyright (c) Tyson R. Henry 2006, 2007, 2008.
This document may be freely copied and distributed for not-for-profit educational
purpose only.