Usable Items

Motivation

Colonists from planet Acheron (LV-426) used many instruments and special devices when colonising the planet. It is possible to assume that many of them will still be fully or at least partially functioning. That is why managing them could help you to achieve success in your mission when you land the planet.

Technical experts from our strategic and tactical team will teach you today how to manage these instruments and devices and thus help Ripley to use them in appropriate manner.

From the operation center greets Manager.

Warning

Before you commence work on this lab, update the game library that you use in the project to version 2.4.0 by following procedure at the end of page about game library.

This version contains also changes that will cause warnings during compilation of existing code: Actions Invoke and Wait have type parameter that will enable to compose them better with other actions into sequence. In majority of cases, the type argument will be automatically inferred by compiler, however, all calls like new Wait(...) and new Invoke(...) is necessary to rewrite to new Wait<>(...) and new Invoke<>(...).

A quick way to fix all such warning is as follows:
  1. Update the game library and import changes through Gradle.
  2. In IDE, from menu Analyze run action Inspect code on the whole project.
  3. In the output (a panel will open at the bottom of window) solve problems from section Java / Compiler issues.

Objectives

  • Use marker interface for distinguishing subgroup of objects
  • Practice generic programming
  • Learn to use design pattern Iterator
  • Learn to raise and handle exceptions

Postup

Step 1: Useful Rubbish

According to image from several functioning cameras, heavy battles took place in the area of colony after which multitude of items remained in mess inside rooms. Such first aid kits and ammunition magazines could come in handy...

Classes Energy a Ammo implementing interface Usable.
Obr. 1: Classes Energy a Ammo implementing interface Usable.

Task 1.1

By using refactoring, rename package sk.tuke.kpi.oop.game.tools to sk.tuke.kpi.oop.game.items.

In this step we will start to add more items which may be useful for Ripley into the project. Since some of these items will be tools at the same time, we will generalise package tools to items.

Task 1.2

In package sk.tuke.kpi.oop.game.items create class Energy which will be child class of class AbstractActor and will implement interface Usable.

Class Energy will represent a first aid kit, which will always lift Ripley's spirit and refills her energy to 100%. However, you will implement this functionality a bit later.

For animation representing first aid kit use file energy.png.

Animation energy.png (sprite dimensions: _16x16_).
Fig. 2: Animation energy.png (sprite dimensions: 16x16).

Task 1.3

Modify class Ripley to have get and set methods available for current state of energy.

Ripley's energy will be represented by integer. We will use range of values 0 - 100 as amount of percent of remaining energy. Initial value of energy should be 100.

Task 1.4

In method useWith() of class Energy implement refilling Ripley's energy to 100%.

Ensure that after the change of Ripley's state the first aid kit should be removed from scene. If Ripley has full energy, "using" the first aid kit will not happen.

Task 1.5

In package for actions create new action Use that should extend class AbstractAction.

Action Use will have constructor with single parameter which will be of Usable type of actor, usable with some actor. Actor for which certain Use action will be scheduled, will, of course, be available from method getActor() (set by calling method setActor() by game scene).

Comment

For example, when intending to use first aid kit with Ripley (to refill her energy), which would be represented by calling energy.useWith(ripley) in execute() method of action, we would be using the action Use like this:
new Use(energy).scheduleOn(ripley);
Using hammer for repairing reactor (calling hammer.useWith(reactor) in execute() method of action) would look like this:
new Use(hammer).scheduleOn(reactor);

Energy and other Usable actors restrict with which type of actor they can work by type argument which is passed to Usable. Therefore, when using action Use, only such combination of constructor's argument and actor in method scheduleOn() should be possible, which would preserve this restriction (meaning that we would call method useWith() with the right type of argument). We will achieve this by using type parameter also in action Use by which we determine the type of cooperating actor.

Comment

Class AbstractAction also has type parameter, so do not forget to properly specify it: it has to be actor of such type, which we want to be acceptable in method scheduleOn() and as return value of getActor().

Comment

In method execute() do not forget to invoke usage of usable item with help of actor performing the action, and then call method setDone(true) to signalise finishing the action.

Task 1.6

In scenario plan an action that will ensure automatic usage of first aid kit in case it comes into contact with Ripley.

Compose new action Use with appropriate existing action.

Comment

To be able to verify whether Ripley's amount of energy really changed, add displaying state of her energy into game window. You can realise it by calling method drawText() on graphic layer Overlay over rendered game which you obtain from scene like this:
scene.getGame().getOverlay();
To horizontally align the text of energy with the text FPS, use y-coordinate (yTextPos) in method drawText() calculated as follows:
int windowHeight = scene.getGame().getWindowSetup().getHeight();
int topOffset = GameApplication.STATUS_LINE_OFFSET;
int yTextPos = windowHeight - topOffset;
Set value of x-coordinate as needed.
Text displayed by method drawText() is shown only for 1 frame. Since energy should be displayed always, we need to secure repeating call for displaying (drawing) its state. You can achieve that either by scheduling appropriate actions or by implementing drawing of the text in the class of scenario into (overridden) method sceneUpdating() which is called by scene always before rendering next frame.

Task 1.7

Verify correctness of your implementation.

Modify initial value of Ripley's energy for a while to verify behaviour of first aid kit. It should disappear from scene only if Ripley has not full energy when passing through it.

_Ripley_ and first aid kit.
Fig. 3: Ripley and first aid kit.

Task 1.8

Similarly to Energy in package sk.tuke.kpi.oop.game.items create class Ammo and verify correctness of your implementation.

Class Ammo will represent ammunition magazine (ammo), which always lifts up Ripley's mood by increasing amount of bullets in her weapon.

Implement the following behaviour:

  • Only instance of class Ripley will be able to take ammo.
  • Amount of bullets will be increased by 50.
  • Maximal amount of bullets that Ripley can currently hold in her pockets is 500.
  • After Ripley "uses" the ammo, remove it from scene.
  • When Ripley's amount of bullets is at its maximum, no increasing or removing the ammo from scene will happen.

As sprite for representing the ammo use file named ammo.png.

Sprite ammo.png (sprite dimensions: _16x16_).
Fig. 4: Sprite ammo.png (sprite dimensions: 16x16).

Comment

Remember that for correct functioning of the class it is necessary to also adjust implementation of class Ripley. Therefore, in class Ripley create instance variable for storing the amount of bullets and respective getter and setter for providing access to it.

Step 2: !Samsonite Backpack

Your task now is to create a backpack, with respect to instructions provided by our tactical and strategic team, with which Ripley will be equipped. This backpack will allow her to have various items at hand which may be needed during the mission. The instructions prepared for you also show a way how to teach Ripley to distinguish items that can fit into her backpack.

To have something to build on, our technological team also prepared a universal interface ActorContainer which represents LIFO collection of specific type of _actor_s. The backpack created by you should implement this interface.

Class Backpack implementing interface ActorContainer, and interface  Keeper representing actors with a container.
Obr. 5: Class Backpack implementing interface ActorContainer, and interface Keeper representing actors with a container.

Task 2.1

In package sk.tuke.kpi.oop.game.items create interface Collectible which will represent items that can be stored into a backpack, as shown in class diagram.

This interface will extend interface Actor and will not define any additional methods. Therefore, it will serve as marker interface which in the scope of type system defines new type for distinguishing group of objects from others - in this case for distinguishing actors that can be stored into backpack.

Task 2.2

In package sk.tuke.kpi.oop.game.items create class Backpack which will implement interface ActorContainer.

Comment

Interface ActorContainer is already in the game library.

When implementing Backpack you need to specify type parameter of interface ActorContainer. Since we want Ripley to be able to hold only such items in her backpack which are suitable for it, use type Collectible in there.

Backpack will behave as stack (LIFO). You can use one of collections provided by Java language for storing elements, e.g. ArrayList.

Constructor of backpack will have the following signature:

public Backpack(String name, int capacity)
  • name represents name of the backpack,
  • capacity defines maximal capacity (i.e. number of items that can be stored in backpack at the same time)

Task 2.3

In class Backpack implement methods getCapacity(), getContent(), getName() and getSize() from interface ActorContainer.
  • getCapacity() will represent getter for capacity of backpack.
  • getContent() will return copy of list of items in backpack. It is important that the changes made to this list will not influence content of backpack!
  • getName() will return name of container (backpack) which will be displayed in game together with its content.
  • getSize() will return current amount of items in backpack.

Comment

For creating copy of list you can use appropriate factory method that is available on interface List.

Task 2.4

Create method add() of interface ActorContainer which will allow you to insert new item into backpack.

Adhere to the following specification of behaviour while implementing methods:

  • Items will be stored in collection in the same order in which they were inserted into backpack.
  • Backpack will hold at most as many items as specified by argument capacity at the time of its creation.
  • If backpack is full and method for adding another item will be called nevertheless, raise your own runtime exception IllegalStateException in which constructor specify message "<backpack name> is full" where <backpack name> should be substituted with backpack's name.

Comment

An exception can be raised in Java by statement throw:
throw new IllegalStateException();

Task 2.5

Create method remove() of interface ActorContainer which will allow to remove item from backpack.

Task 2.6

Create method iterator() of interface ActorContainer which will return reference on iterator of backpack.

Interface ActorContainer extends interface Iterable<E> which defines method iterator(). By implementing this method we will gain the possibility to iterate through backpack's contents by so-called enhanced for loop, as the following code snippet illustrates:

for (Collectible item : backpack) {
    // using item from backpack
}

To implement the method, just returning iterator available on collection storing contents of backpack will suffice.

In this way we realise design pattern Iterator.

Task 2.7

In class Backpack create method peek() which will return reference on item that is at the top.

Backpack is represented as stack. It is important to be able to obtain reference on last inserted item at any time, since this will be the item which which various operations will be performed (e.g. using it or dropping on the ground).

Task 2.8

Create method shift() of interface ActorContainer which will move last inserted item to the bottom of backpack.

In container you can work only with actor which is on top. Because of that, when you will need to work with another actor which is deeper in the container, you will have to rearrange the content. Instead of taking all actors out and inserting them back in desired order, you can rearrange them directly in container with this method shift().

An example of usage can be the following: if container holds actors in the order D, C, B, A (from last inserted to first inserted), then after calling method shift() the order of actors in container will be C, B, A, D.

Comment

For changing the order of items in backpack you can use suitable static method from utility class Collections.

Task 2.9

In package sk.tuke.kpi.oop.game create interface Keeper which will extend interface Actor and will represent actors owning some ActorContainer.

The interface will have type parameter bound to type Actor which will determine type of actors in container.

The interface will add the following method to actor:

ActorContainer<A> getContainer();

where A represents type parameter mentioned above.

Task 2.10

Modify class Ripley so that it implements interface Keeper and equip her with created backpack represented by class Backpack.

Set backpack's capacity to 10 items and you can name it for example "Ripley's backpack".

Task 2.11

Modify classes Hammer, Wrench, FireExtinguisher to implement interface Collectible.

After this adjustment, objects of mentioned classes will be possible to insert into backpack.

Task 2.12

Display Ripley's backpack, fill it with at least 3 different items and test your implementation.

It is possible to graphically represent contents of ActorContainers in the game (see picture below). To do that, in scenario use method pushActorContainer() on object of type Game (which you obtain from scene).

In scenario, fill backpack with at least 3 items and check if last added item is on the left in backpack displayed in the game. Try calling method shift() to verify reordering of items.

_Ripley_ equipped with backpack.
Fig. 6: Ripley equipped with backpack.

Step 3: man backpack

In this step we will teach Ripley to use backpack that you created. Therefore, you will create several actions thanks to which Ripley will manage it with no problem. You will remember the principle of polymorphism, according to which these actions should not be limited only for work with Ripley's backpack, but to work with any Keeper owning any type of ActionContainer.

Actions should be adapted to any type of actors in container. Because of that, we will create them with type parameter which will enable to specify type of actors in classes of created actions.

Classes of actions for work with containers of actors.
Obr. 7: Classes of actions for work with containers of actors.

Comment

Create all actions in package for actions, and as subclasses of AbstractAction.

Task 3.1

Create action Take which will enable to add into container compatible item which is in scene and collides with Keeper actor that performs the action.

Compatible item means item of such type which is possible to insert into container owned by Keeper actor. Class of action Take will again use type parameter which will represent type of compatible actors. Based on this parameter, type of Keeper actor, which will be able to execute the action (and will be passed as type parameter for AbstractAction), will have to be defined in suitable manner.

Declaration of action's constructor will look as follows:

public Take(Class<T> takableActorsClass)

where:

  • T corresponds with type parameter described above,
  • takableActorsClass of type Class<T> is reference to runtime representation of class of actors which can be stored in backpack.

In method execute() find first actor in scene which collides with (intersects) Keeper actor executing the action, and also is of compatible type. If you find such actor, add it into container of Keeper actor and remove it from scene.

Comment

To implement the method you will need to check types of which actors in scene are. Because of limitation of JVM platform, type parameters cannot be used with instanceof. However, runtime representation of actor's class compatible with container (constructor's parameter) has the following methods that you can use:
  • isInstance() instead of instanceof
  • cast() instead of basic casting.

Comment

Searching for compatible actor that intersects Keeper actor is possible to solve by using stream API.

Comment

Remember to always set isDone of action's state to true in method execute() since action Take is a one-off.

Task 3.2

In action Take, deal with potential occurrence of exception IllegalStateException in method execute().

Since action Take adds actors into container, calling method add() can cause exception IllegalStateException to be thrown. Instead of letting the application crash we want to display exception's message on the screen to the player. Therefore, the exception needs to be handled and properly presented.

Comment

In Java, exceptions can be caught inside block try, and specific types of exceptions can be handled in following block catch:
try {
  // code which can cause exception
} catch (Exception ex) {
  // handling the exception of type Exception
}

Comment

In case of exception caused by full container, do not remove the intersecting actor from scene.

Comment

You can obtain the message from exception by method getMessage() and you can display it similarly as state of Ripley's energy - into layer Overlay. However, to show the message, let's say, for 2 seconds, you can use method showFor() of object OverlayDrawing which you can obtain from method call drawText():
overlay.drawText(exception.getMessage(), x, y).showFor(2);

Task 3.3

Create action Drop which will enable to lay actor from top of container into the scene where Keeper actor is, executing the action.

Signature of class will be similar to action Take. Since in this action there will be no need for instanceof check on type parameter, constructor of this action will be parameterless.

When implementing method execute(), take the topmost actor from container and place it into scene so that its center position matches the center position of Keeper actor which executes the action. Do not forget to end the action by properly setting isDone.

Comment

When using this action, specifying type parameter for type of actors in container will be required, because the type system will not be able to infer the type.

Task 3.4

Create action Shift which will enable to reorder items in container.

Action Shift just reorders elements in container by already existing parameterless method, so it does not need to work directly with actors stored in container, and it also does not need to know their specific type. As a consequence, class of this action does not need type parameter and container's type of actors in type argument AbstractAction can be replaced with wildcard "?".

Constructor of action will be again parameterless and method execute() again needs to properly change action's state to done.

Task 3.5

In new package sk.tuke.kpi.oop.game.controllers create class CollectorController implementing interface KeyboardListener. This controller will serve to schedule actions Take, Drop and Shift on Keeper<Collectible> actor by using keyboard.

Obtain reference to controlled actor by parametric constructor. Then override method keyPressed() and implement scheduling of actions for work with container as follows:

  • Enter - take item into backpack
  • Backspace - drop item which is on top of backpack (last inserted item)
  • S - shift items in backpack, which will change which item is on top of backpack

Task 3.6

Verify correctness of your implementation.

For new CollectorController to work with Ripley's backpack by using keyboard, do not forget to create instance of this class in scenario and set it as another listener for handling the input.

Thoroughly check if actions work properly by pressing corresponding keys. In addition to basic functionality of keys and actions for working with backpack, check also unusual situations, for example

  • pressing a key to add another item into backpack even if its capacity is full,
  • pressing a key to drop item from backpack even when backpack is empty,
  • pressing a key to shift items in backpack when it is empty and then when its capacity is full.

Additional tasks

Task A.1

Rename KeyboardController created in previous lab to MovableController and move it into package sk.tuke.kpi.oop.game.controllers.

The reason for renaming is that this controller will serve only to control movement and so MovableController will be more accurate name for it.

Additional links