Game Features 2 assesses Linked Lists and Inheritance. You will continue to build functionality for the Game Engine in this component of the task. This will be done on top of the code written for the previous tasks; you should not reclone or remove said code for this task or any future Game Features tasks.
There are three sections listed in the handout for convenience. Future Game Features tasks may expect some or all of these sections to be fully completed.
We have provided some tests for the PhysicsEngineWithGravity and LevelParser classes.
They can be accessed at this link: TestTask2.java. You can open this link in a
new tab to copy and paste the content into your project, or right click and select "save link as", then download the
file to the correct location within the project. The correct location is in the same directory as the previous two given test files,
the test/java/tests/ package.
For this portion, you will be writing code in the app.gameengine.LinearGame class, which already exists.
Many of the methods in this task will make use of the LinkedListNode class, found at
app.gameengine.model.datastructures. You are required to use this class. It is identical to the
LinkedListNode class used in the Problem Set.
For this portion of the task, you will implement the following functionality:
LinearGame class. This class represents a Game
with a linear sequence of levels that are typically only traveled through in order from start to finish. For
example, the MarioGame class extends the LinearGame class, and uses these methods.
getLevelList
getLevelList which takes no parameters and returns a
LinkedListNode of Levels. It should return the head of the game's list
of levels.
null.
setLevelList
setLevelList which takes a LinkedListNode of
Levels as an argument and returns void. It should replace this game's
list of
levels with the input value.
addLevel
addLevel which takes a Level object as an
argument and returns void. This method should append the input Level
to the end of
this game's list of levels.
getLevelList, setLevelList, and addLevel are worth 10 points.advanceLevel (10 points)
advanceLevel which takes no parameters and returns
void. When
this method is called, it should find the current level within the list of levels, and call
this.loadLevel on the next level in the list.
getCurrentLevel method to access the current level. If the
current level is not in the level list, including when the list is empty, this method should
not do anything.
getName method on each level object to determine if it is the
current level (ie. if the names are equal). This method may assume that there will not be
multiple levels with the same name in the list.
loadLevel with an argument of null.
removeLevelByName (10 points)
removeLevelByName which takes a String and
returns void. It should remove the first level in the list which has the same name
as the input
String.
Levels in the list exist with this name, only the first should be
removed.
getName method on each level object to determine if its name is
the same as the input String.
resetGame
reset method on it.
this.currentLevel directly, and set it to the desired value. Note that this
instance variable is of type Level, not LinkedListNode.
reset called on it.
Otherwise, the player will start at the wrong location.
You are not given any tests for this portion. You are encouraged to write your own and test in game. Once you've completed the PhysicsEngineWithGravity portion of this assignment, you'll be able to verify if some of these methods are functioning properly by playing Mario.
Implement the methods from the specification. You are encouraged to write tests before you begin your implementation and run them throughout your implementation.
Autolab will display the results of several features for this portion, and the number of points earned for each feature. Some methods may be split into multiple features; these methods have partial credit. Successful completion of this portion will earn you 30 points.
For this portion, you will be writing code in the following classes which already exist:
PhysicsEngineWithGravity, located in the app.gameengine.model.physics package.
LevelParser, located in the app.gameengine package.
You should read through all the following sections before you begin to implement the methods from the specification. You must at least create every class/method from this specification to receive feedback on this section. You can "stub out" these methods by having them always return a fixed value, but they must exist so the grading code, and your tests, can compile and run.
Many of the methods in this task will make use of the LinkedListNode class, found at
app.gameengine.model.datastructures. You are required to use this class. It is identical to the
LinkedListNode class used in the Problem Set.
For this portion of the Game Features task, you will implement the following functionality:
PhysicsEngineWithGravity class. Notice that this class
extends the PhysicsEngine class, and as such has access to all of the public methods from that
class, including those that you wrote in the previous task.
double as a parameter and represents the amount of gravity.
You should create an instance variable to store the gravity, and assign it in the constructor.
getGravity
getGravity that takes no parameters and returns a
double. This method should return the gravity of this physics engine. Note that
this is different from the static variable "DEFAULT_GRAVITY". You should return the instance
variable that was described above.
setGravity
setGravity that takes a double and returns
void.
This method should set the gravity instance variable to the input value.
updateObject
updateObject which takes a double and a
DynamicGameObject and returns void. This method should override the
method of the
same name in the PhysicsEngine class.
DynamicGameObject's
y-velocity (note that change in velocity is equal to [acceleration from] gravity times the
change in time) (note also that the positive y direction is down).
updateObject method and pass in the parameters. Be sure to
call this after applying gravity, to make sure that velocity and position are
appropriately adjusted.
isOnGround method on the DynamciGameObject from the parameter,
which returns true if the object is on the ground, and false otherwise.
isPlayer
method on the DynamicGameObject from the parameter, which returns true if the
object is the player, and false otherwise.
PhysicsEngineWithGravity object instead of a basic PhysicsEngine will
now have gravity applied automatically. You can play Mario, as described above, to see the effects (as
long as you have also made the required changes to the LevelParser).
LevelParser class. These changes will allow the game engine
to parse levels which contain objects for Mario.
LevelParser are worth 5 points all together with no partial credit.readDynamicObject
Goomba objects. If the string being checked is exactly "Goomba", a
new Goomba object should be returned. The Goomba constructor takes
only two doubles, which should be the variables x and y
which already exist in this method.
Koopa objects. If the string being checked is exactly "Koopa", a new
Koopa object should be returned. The Koopa constructor takes only two
doubles, which should be the variables x and y
which already exist in this method.
readStaticObject
readDynamicObject, this method uses a switch-case statement.
Block objects. If the string being checked is "Block", "Bricks", or
"Ground", a new Block object should be returned. The Block constructor
takes two doubles and a String. The doubles should be the
variables x and y which already exist in this method. The
String should be the same as the String being checked. For example, if
the String is "Bricks", "Bricks" should be passed into the constructor.
QuestionBlock, HiddenBlock,
PipeEnd, PipeStem. For each of these objects, the String
should exactly match the class name (eg. "QuestionBlock" or "PipeStem"), and the only
constructor parameters are two doubles, which should be the variables
x and y which already exist in this method.
Flag objects. If the string being checked is exactly "Flag", a new
Flag object should be returned. The Flag constructor takes two
doubles and a Game, which should be the variables x,
y, and game which already exist in this method.
parseLevel
TopDownLevel or a MarioLevel
depending on the content of the csv file being read.
TopDownLevel object. If that field is "MarioLevel", it should be a
MarioLevel object instead.
LevelParser should still be capable of reading and parsing all previously assessed
levels. However, now it can also create levels for Mario with the appropriate objects.
You are given tests for this portion. See the "Overview" section for more details on how to get these tests.
Implement the methods from the specification. You are encouraged to run the provided tests and game throughout your implementation.
Autolab will display the results of several features for this portion, and the number of points earned for each feature. Some methods may be split into multiple features; these methods have partial credit. Successful completion of this portion will earn you 30 points.
For this portion of this task, you will be implementing functionality in the
Agent class (from the app.gameengine.model.gameobjects package) and the
PathfindingUtils class (that you should create in the app.gameengine.utils package) such
that enemies have somewhat functional pathfinding. This will be most obvious in the Sample Game, which you can
access by changing the GAME constant in app.Configuration to
"Sample Game".
Each of the methods you implement will have an associated point count that you will earn upon passing our tests for that method. Partially implemented methods may result in some partial credit being earned, depending on the method.
findPath (20 points)
app.gameengine.utils package named PathfindingUtils. In
this class, implement the following static method:
PathfindingUtils class, write a static method named
findPath that takes in two Vector2D objects as parameters and returns a
LinkedListNode of Vector2Ds.
Vector2D object to the tile containing the second
Vector2D object.
Vector2Ds themselves should not be modified. You should instead
create copies of them to modify. There are several ways to do this, such as: create a
new Vector2D using the constructor, with the desired x and y components;
use the Vector2D.copy method, and modify the returned vector; use the
static Vector2D.floor method, which returns a new vector with
floored components.
Vector2D object after the head of the list is one tile above,
below, to the left, or to the right of the previous one AND if each Vector2D object in the list is
aligned to a tile (i.e. no decimals). Considered the following paths attempting to travel from eg. (3.9,
4.6) to (5.2, 5.999)
followPath (20 points)
Agent class, there exists a method named followPath that takes a
double and returns void. The input double represents the change in time since
the last
time this method was called. Complete this method so that the agent along its path if it has one.
path, as well as a getter and setter for that instance variable, named getPath
and setPath respectively. You will need to use these to complete this method.
Agent's path instance variable is null, their
velocity in the x and y directions should be set to 0, and nothing else should be done.
Agent is directly on the tile
at the path's head, the Agent's position is manually set to the location of that
tile. The head of the list is then removed from the list so the agent will travel to the next
node the next time this method is called.
this.getMovementSpeed, or with the
movementSpeed instance variable, both of which already exist in this class.
The change in time is the parameter dt.
√((x1-x2)2 + (y1-y2)2).
You may use the Vector2D.euclideanDistance method, which calculates this
for you.
Agent's velocity should be set to move towards the tile at the head of the
Agent's path.
Agent's velocity must have a magnitude (speed) equal to its movement
speed. The movement speed can be accessed by calling this.getMovementSpeed,
or with the movementSpeed instance variable, both of which already exist in
this class.
Agent's orientation should be set in the same direction as its
velocity, but always with a magnitude of 1.0.
Agent can only move in four directions, only one component (x or
y) of a moving Agent's velocity or orientation will be non-zero at any
point. This means there are only 4 possible values for the Agent's
orientation, (1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), or (0.0, -1.0). Velocity is much the
same, except that the magnitude will depend on the movement speed.
Agent is located at (2.5, 2), has a movement speed of 4,
and its path is [(3, 2), (4, 2), (4, 3)], it must first target the point (3, 2). To
reach that point, it must travel in the positive x direction, so its velocity would be
set to (4, 0), and its orientation would be set to (1, 0).
Agent.update method, add code that checks if the path instance variable is null. If
so, call the findPath method, where the start is the location of the Agent,
and the end is the location of the Player (obtained with
level.getPlayer().getLocation()), and call setPath with the returned path.
Agent.update method, if the path is not null, call the
followPath method, passing in dt as the change in time.
Agent.update method in a different way.
Those changes should replace these ones.
You are not given tests for these methods. You are encouraged to use one of the previously discussed ways to show paths in-game, as well as writing tests and running the debugger, to verify the correctness of your solution.
Autolab will display the results of several features for this portion, and the number of points earned for each feature. Some methods may be split into multiple features; these methods have partial credit. Successful completion of this portion will earn you 40 points.