Game Programming
Language
Phase 6: Game Objects & Forward Declarations
Max OS X disclaimer:
textboxes and pixmaps are different sizes on Macs. Thus you
will probably fail the stdout for all tests that have pixmaps and/or
textboxes.
Overview:
Add game objects and forward declarations to the parser you created in
program 5.
Your parser must handle:
Everything that your p5 parser
can handle
|
|
Forward declarations
|
forward animation bounce(circle
cur_circle);
|
game object declarations
|
rectangle my_rectangle();
circle my_circle(x = 100, y = 100, radius = 30);
|
expressions with animation blocks
|
circle my_circle(animation_block
= bounce);
|
expressions with member variables
|
int i = my_rectangle.x + 20;
|
When you print game objects that are in the symbol table, you should
print them
using the print
functions (either Game_object::print() or the overloaded
operator<<() that come with class Game_object. Since
you are using my
code, the output should automatically look like:
Rectangle
{
animation = 0,
blue = double(0),
green = double(0),
h = int(10),
red = double(1),
rotation = double(0),
visible = int(1),
w = int(10),
x = int(5),
y = int(6)
}
All the code you need is in src/p6 and src/p6/p6_src.tar.
Class
|
Description
|
Window
|
creates gpl window, only called
from gpl.cpp. You don't do anything with it in p6.
|
Game_object
|
Base class for all game
objects. You won't create a Game_object (you create objects that inherit it), but your program will deal
with Game_object pointers (Game_object *).
|
Rectangle
|
rectangle game object
|
Circle
|
circle game object
|
Textbox
|
textbox game object
|
Triangle
|
triangle game object
|
Pixmap
|
pixmap game object (uses
default_pixmap.h)
|
Statement_block
|
Skeleton class to act as the base class for class Animation_block (see below). Will eventually
hold statements for animation blocks and event blocks (i.e. will hold if statement, for statements, etc.).
|
| Animation_block |
Skeleton class so you can set
the animation_block parameter in p6. In p6 you will use pointers
to Animation_block and call its print functions. |
Event_manager
|
Skeleton class so that gpl.cpp
will compile. You don't have to do anything with it in p6.
|
The game objects and the window object use the OpenGL and GLUT
libraries. You will have to have these installed on your machine
before you can use these files. Check the class software
installation page if you don't have them installed.
A significant part of this assignment is figuring out how to use my
code. If you don't understand how something works, ask me.
Note: I won't be giving you anymore code. You are free to modify
my code, however, I may post updates to the posted code to fix errors.
Steps to follow:
- Update the symbol and symbol_table classes so that they can
handle game objects. class Game_object is the base class for all
games objects, thus you want to be able to put a Game_object * in a
symbol.
- Add code to gpl.y so that you can create game objects that don't
have any parameters (rectangle my_rectangle();) .
- Make sure you are compiling gpl.cpp with -DGRAPHICS in your
Makefile. When you run your program you should see a window with
a game object in the lower left and the game objects should print in
the symbol table (see t001.gpl, t001.out, and t001.jpg).
- Add code to gpl.y to parse game object initialization parameters
(e.g. rectangle my_rectangle(x = 100, y = 100);). Don't
handle animation_blocks yet. Your rectangle should be at a
different position in the window, and the print out of the rectangle
should show these x,y values (see t002.gpl,
t002.out, and t002.jpg)
- Update the symbol and symbol_table classes so that they can
handle animation blocks (i.e. update them so they can handle a pointer to
class Animation_block).
- Implement forward declarations in gpl.y.
- Update expressions so that they can handle pointers to animation
blocks (need this for #8).
- Add code to your game object parameters (#4 above) so that you
can initialize animation_blocks (they won't do anything yet, just show
up in the symbol table) (see t003.gpl, t003.out, and t003.jpg).
- Add member variables to class Variable and add code to gpl.y to
parse member variables (see t004.gpl,
t004.out, and t004.jpg). You should not have to
change Expression.
- Add array member variables to class Variable and add code to
gpl.y
to parse array member variables (see t005.gpl,
t005.out, and t005.jpg).
Step 1: Update the symbol and
symbol_table classes so that they can handle game
objects. class Game_object is the base class for all game
objects,
thus you want to be able to put a Game_object * in the symbol.
When a game object is declared:
rectangle my_rectangle();
it must be inserted into the symbol table (just like integers, doubles,
strings). Since all game objects (rectangle, circle, textbox,
etc) inherit from class Game_object, you can use a pointer to a game
object (Game_object *) to store them in the symbol table.
Add constructors and access functions (e.g. Game_object
*Symbol::get_game_object_value()) to class Symbol_table and class
Symbol.
There is a Gpl_type GAME_OBJECT which should be thought of as a
Game_object *.
You will have to update your Symbol::print() so that it can print game
objects. Use the overloaded operator<< functions for
Game_objects or use Game_object::print() (see game_object.h)
Step 2: There are two parts to
creating game objects (Part A) create the object and (Part B) set the
parameter values specified.
Step 2 is the Part A. After Step 2 you will be able to parse
programs that contain game objects but don't specify any parameters.
In Step 4 you will implement Part B.
In order to set the parameter values it is easiest if the object is
created before the parameter values are parsed. Consider the
following rule:
object_declaration:
object_type T_ID {here} T_LPAREN
parameter_list_or_empty T_RPAREN
bison allows you to specify an action anywhere on the right-hand-side
of a production. In the above rule the action (marked by {here}) is inserted in the middle of the right hand side. If you create the game object at the location marked by {here} then
when the parameter list is parsed the object will have already been
created. Consider the following action fragment (which goes in
the {here}):
if the object being created is a triangle
cur_object_under_construction = new Triangle();
cur_object_under_construction is a global variable that stores the
object that is currently being created. It can be used in Step 4
when the parameters are parsed. Consider the following
declaration:
rectangle rect(x = 42, y = 42);
The rectangle is constructed before the parameters are parsed. A
pointer to the object is placed in the global variable
cur_object_under_construction. When the parameters (for example:
x = 42) is parsed, the "x" field of cur_object_under_construction can
be set to 42.
There are two productions for creating
game objects. The first creates a single object, the second
creates an array of objects.
There is a second production for creating game objects. But since arrays of game objects
cannot be initialized, you don't have to worry about the
notion of a current object under construction for arrays of game objects.
object_declaration:
object_type T_ID {here} T_LPAREN
parameter_list_or_empty T_RPAREN
|
object_type T_ID
T_LBRACKET expression T_RBRACKET
object_type will match one of the
types of game objects (T_RECTANGLE, T_TRIANGLE, etc). It is
easiest to implement it so that it returns the token it matches .
For example, if object_type matches T_CIRCLE, return the integer
T_CIRCLE (recall that bison defines tokens like T_CIRCLE as an integer constant (search for T_CIRCLE in y.tab.h)).
If you do this, then in the code in
the {here} action you can
create the correct new
object:
switch ($1)
{
case
T_TRIANGLE:
cur_object_under_construction = new Triangle();
break;
....
}
The action for creating an array of
game objects will be very similar to the code for creating an array of
simple objects (int, double, string). You get the type of the
game object from $1, you evaluation the expression ($3->eval_int() )
to get the size of the array, and then you have a for loop to create
all the elements in the array. Inside the for loop you will have
a switch statement on $1 just like above.
Step 3: Make sure you are
compiling gpl.cpp with -DGRAPHICS in your Makefile.
When you run your program with t001.gpl you should see a window with a game object in
the lower left and the game objects should print in the symbol table
(see t001.gpl,
t001.out,
and t001.jpg).
gpl.cpp only creates the gpl window if it is compiled with the command
line argument -DGRAPHICS. That means you must edit your Makefile
so that lines compiling gpl.cpp with the -DGRAPHICS are uncommented and
all the others are commented.
In order to get graphics mode (i.e. when you compile gpl.cpp using
-DGRAPHICS) working you must provide the following functions in
Symbol_table
bool get(string name, int &value);
bool get(string name, double &value);
bool get(string name, string &value);
bool get_type(string name, Gpl_type &type);
bool set(string name, int value); // used for mouse_x, mouse_y
gpl.cpp uses these functions to look for reserved variables (e.g.
window_width, window_height, ...) in the symbol table. As an
example, here is my get for integers:
bool
Symbol_table::get(string name, int &value)
{
Symbol *cur = lookup(name);
if (!cur || !cur->is_int())
return false;
value = cur->get_int_value();
return true;
}
I think it is important that you get test t001 working before you go on.
Step 4: Add code to gpl.y to
parse game object initialization parameters (e.g.
rectangle my_rectangle(x = 100, y = 100);). Don't handle
animation_blocks yet. Your rectangle should be at a different
position
in the window, and the print out of the rectangle should show these x,y
values (see t002.gpl,
t002.out,
and t002.jpg)
This is conceptually the hardest part of the assignment. However,
once you get how it works, it is not hard to implement.
Consider the following rule:
object_declaration:
object_type T_ID {here} T_LPAREN
parameter_list_or_empty T_RPAREN
It is easiest if the object is created before
parameter_list_or_empty is matched by the parser. If you
create the object and put it in the symbol table in the {here} action (as is shown in step 2) then all you have to do is set a global variable in the {here}
action
to point to the game object you just created. Once you do this the parameter rules
can use the global variable to
insert parameters into the object.
The following rule will match all the parameters (one at a time):
parameter:
T_ID T_ASSIGN expression
The heart of the action for this rule is:
cur_object_under_construction->set_member_variable(<member
name>, <value>)
The global variable cur_object_under_construction was set in in the {here} action. The value
is retrieved from calling eval on the expression ($3). You
will need code to handle all the possible different types for the
values (INT, DOUBLE, STRING) [in step 8 you will add ANIMATION_BLOCK to
this list].
Game_object contains functions for getting and setting member
variables. These functions are overloaded so they can handle all
the possible types of member variables.
In addition to having a global variable for the current object under
construction, you will also need a global variable for the name
of the current object under construction for the following error
messages:
Error::UNKNOWN_CONSTRUCTOR_PARAMETER
Error::INCORRECT_CONSTRUCTOR_PARAMETER_TYPE
These errors are issued in the parameter rule and they require the name
of the game object being constructed. The only way you will be
able to get your hands on the name of the object being constructed is
to use a global variable initialized in the {here} action.
Step 5: Update the symbol and
symbol_table classes so that they can handle animation blocks (i.e. update them so they can handle a pointer to
class Animation_block).
The animation_block parameter of game objects is a pointer to an
Animation_block. In the final phase, this pointer will be used to
execute the animation blocks. Consider how the animation_block
parameter is set:
forward animation bounce(rectangle cur_rectangle);
rectangle my_rectangle(animation_block = bounce);
The variable name bounce needs to be associated with a pointer to an
animation block (Animation_block *). In order to do this we need
to add a new type to Symbol and Symbol_table. Add functions to
each so that they can hold a variable with type animation block.
There is a Gpl_type ANIMATION_BLOCK which should be thought of as a Animation_block *.
Step 6: Implement forward
declarations in gpl.y.
When you parse a forward statement two objects need to be put in the
symbol table: an Animation_block and a Game_object.
The Animation_block should be a pointer to an "empty" animation block
(i.e. new Animation_block(...) ). It should be inserted
into the symbol table using the given name:
forward animation bounce(rectangle cur_rectangle);
In the above example, the new animation block should be inserted into
the symbol table using the name "bounce"
The parameter in the above forward statement is a rectangle and has the
name "cur_rectangle" A new rectangle (i.e. new Rectangle()
) should be inserted into the symbol table using the name
"cur_rectangle"
You must also flag this new object as
a parameter object by calling the following functions:
new_object->never_animate();
new_object->never_draw();
In the last assignment you will parse animation blocks. At that
point you will look up the animation block in the symbol table and
insert statements into it. Consider this example:
animation bounce(rectangle cur_rectangle)
{
// this example is from p8
cur_rectangle.x = 42; // lookup the "bounce" Animation_block in the symbol table, insert an assignment statement into this block
}
When the statements in the forward block are parsed, the cur_rectangle
object will be used. That is why you put it in the symbol table
when parsing the forward statement.
Step 7: Update expressions and
variables so
that they can handle pointers to animation blocks (need this for #8).
The grammar rule setting a parameter is:
parameter:
T_ID T_ASSIGN expression
In order to handle the animation_block parameter the Expression
class must be able to handle a pointer to an Animation_block. Add
routines to
Expression so it can handle variables of type Animation_block.
Parameter declarations is the only
place in the language where an Expression contains an
Animation_block. And an Expression of type Animation_block
cannot be anything but a
single Expression node (technically a "tree" but only has one
node).
This node is always a variable node. So all you have to do is add
Expression::eval_animation_block(), make sure variable knows about type
ANIMATION_BLOCK, and make sure your Expression constructor that takes a
pointer to a Variable can handle ANIMATION_BLOCK type.
Step 8: Add code to your game
object parameters (#4 above) so that you can
initialize animation_blocks (they won't do anything yet, just show up
in the symbol table) (see t003.gpl,
t003.out,
and t003.jpg).
If you do step 7 and augment Expression so it handles
Animation_blocks, this step should be
trivial. Add code to the parameter action that can deal with type
ANIMATION_BLOCK. It will look just like the code that deals with
INT, DOUBLE, and STRING.
Step 9: Add member variables to
class Variable and add code to gpl.y to parse member variables
(see t004.gpl,
t004.out,
and t004.jpg).
You should not have to change Expression.
For this step you need to add an action to the the variable productions:
variable:
| T_ID T_PERIOD T_ID
This will require a Variable constructor (or you can use an existing
constructor if you use default parameter values).
The Variable get functions will also have to be modified to handle this
type of variable.
Step 10: Add array member
variables to class Variable and add code to gpl.y to parse array member
variables (see t005.gpl,
t005.out,
and t005.jpg).
This step is similar to step 9. You will need to an an action for
the production:
variable:
T_ID T_LBRACKET expression T_RBRACKET T_PERIOD T_ID
This step will also require a new constructor and for the Variable get
functions to be modified.
Turning in and Testing:
See
docs/turnin.html for a description of how to turn in assignments.
Testing p6 is different than the previous phases:
For tests that have graphics, your graphics output must match the posted output (e.g.
tests/t006.jpg and tests/t006.pixels).
See
docs/testing.html for a description of how to test your program.