Game Programming
Language
Phase 8: Animation Blocks
Overview:
If you have made it this far, you are pretty close to completing
the entire interpretor.
(1) Add animation blocks to the parser you created in program 7.
Since your program should already be able to parse a statement block,
all you have to do is:
(a) write the action for animation
blocks in gpl.y
(b) implement the game_object parameter for animation blocks
(2) Implement the touches and near operators. This will require
small additions to your expression class.
(3) Implement initialization blocks
(this is trivial).
Requirements:
By the end of p8 your program must do everything specified in
the gpl manual.
Your program should run the game you turned in for Phase
2.
Hints:
There are two tricky parts to this assignment
- Matching the Animation_block created in a
forward statement to the Animation_block in an animation declaration
- Implementing animation block parameters.
Animation block
Recall in p7 that when parsing a
statement block you created a new
Statement_block and pushed it onto the statement block stack when you
parsed the "{" and when you parsed the "}" you popped it off the stack. This way
each statement you parsed (while inside the block) could be inserted
into the Statement_block at
the top of the stack.
When parsing the forward statement you created an empty Animation_block
and
put in into the symbol table.
When you parse the "{" of an animation block, instead of creating a new
Statement_block, you need to get the current animation block's Animation_block
out of the symbol table and push it on top of the stack. At
the end of an animation block, you pop the Animation_block off the
stack.
Since Animation_block inherits Statement_block, there is no problem
pushing it onto the stack and using it when you are inserting
statements. In other words, you can code as if you were using a
Statement_block * instead of an Animation_block *.
The easiest way to do this is to add an action in the middle of the
rule for animation_block:
animation_block:
T_ANIMATION T_ID T_LPAREN check_animation_parameter T_RPAREN
T_LBRACE {put action here} statement_list T_RBRACE end_of_statement_block
In this action you will lookup the animation block in the symbol table
(its name is $2). Then you push it on the statement block
stack. Thus you will be filling in the animation block that was
created in the forward
statement instead of creating a new one.
Getting parameters to work
Building a mechanism that works
like parameter passing in C++ would be difficult, so I came up with a
hack. If you can come up with a cleaner way to implement this, I
would like to hear about it.
When an animation block is created by a forward statement, the formal parameter variable is
created and placed into the symbol table as if it were a global variable (except it is not drawn or animated).
When an animation block is executed, the actual parameter must replace the formal parameter.
Consider the following gpl code:
forward animation go(rectangle
cur_rect);
rectangle my_rect(animation_block = go);
animation go(rectangle cur_rect)
{
cur_rect.x += 10;
}
The formal parameter is cur_rect. The actual parameter is
my_rect. cur_rect is created and placed in the symbol table when
the forward statement is parsed. When the statements in the
animation block are parsed, they reference cur_rect. Since
cur_rect is in the symbol table, you can parse code that contains cur_rect just like the code in on blocks.
When the animation block go is executed for the rectangle my_rect,
cur_rect needs to be "replaced" with my_rect. Ok, here comes the
hack: Before executing the statement block, modify cur_rect's
symbol so that it points to my_rect's Game_object. It sounds like
a bad hack, but it is very easy to implement.
In order to implement this:
(1) Make sure you are creating the formal argument when you parse
the forward statement and passing it to Animation_block's constructor.
(2) Implement Animation_block::execute(Game_object *argument). This function should do the following:
(a) modify the symbol for the formal parameter so it points to argument
(b) call Statement_block::execute()
(c) undo the modification you did in step (a)
(3) Implement bool Statement_block::empty() which returns true if there
are no statements in the statement block and false otherwise.
Near and touches
near and touches are binary operators
that take two Game_objects and return an INT (0 or 1).
Game_object implements the actual functionality, all you have to do is
call near() or touches() when you evaluate a binary expression where
the operator is near or touches.
You will have to add actions in gpl.y to handle these operators.
Since the operands are always variables, there are minimal changes
(i.e. being able to detect the GAME_OBJECT type variable) to the
construction of your Expression. You will have to change
Expression::eval_int() to be able to handle these operators.
Initialization blocks
The window manager (window.cpp) makes the following call right before program execution starts:
event_manager->execute_handlers(Window::INITIALIZE);
This means that all you have to do to implement initialization blocks
is to insert the block into the Event_manager when you parse it (in the
action for initialization_block). Just like event blocks, there
can be many initialization blocks.
You should not need to change Event_manager::insert() because INITIALIZATION is a keystroke just like SPACE.
Turning in and Testing:
See
docs/turnin.html for a description of how to turn in assignments.
Testing works just like in p7 (see p7/p8 section of
docs/testing.html) however, you have to wait for the animations to stop before comparing your results to those in the test directory.
The animation blocks in the p8 tests run for a finite time. In other words, the animations don't go on forever.