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:
  1. 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.
  2. Add code to gpl.y so that you can create game objects that don't have any parameters (rectangle my_rectangle();) .
  3. 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).
  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)
  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).
  6. Implement forward declarations in gpl.y.
  7. Update expressions so that they can handle pointers to animation blocks (need this for #8).
  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).
  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.
  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).


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.