And... Action!

Motivation

Cadet! To survive the struggle with objects in hostile world of Object-oriented programming, you need to get familiar with important feature which some objects use to mask themselves and to deceive the enemy with form of polymorphism. This feature is nothing more than inheritance!

Our tactical and strategic team prepared a training mission for you with codename Mission: Inheritance to enhance your abilities to stand in this battle. Several goals are prepared for you to help you understand objects that use this feature. After reaching the goals your chances for surviving in the world of Object-oriented programming will dramatically increase! Therefore proceed according to plan of this mission.

Good luck with achieving goals! From the operation center greets Manager.

Warning

Before you commence with work on today's lab, update the game library that you use in your project to version 2.1.0 by following this procedure.

Objectives

  • Inheritance
  • Overriding methods
  • Polymorphism
  • Using keyword super.
  • Using method references.

Postup

Step 1: Similarity (not) entirely coincidental

Cadet! Do not doubt that the origin of hammer Mjolnir is definitely extraterrestrial, and although it abounds with superhuman power, it has some common features, whether one likes it or not, with fire extinguisher. It can be used and breaks after some time.

Abstract class BreakableTool representing breakable tool.
Obr. 1: Abstract class BreakableTool representing breakable tool.

Task 1.1

Create abstract class BreakableTool which represents generalisation over any work tool that has limited amount of usages.

In the class declare instance variable remainingUses for amount of remaining uses, make it available outside the class only for reading and initialise it by using parametrized constructor. Create method use() for decreasing number of tool's remaining uses and its removal from game map once the number of uses reaches 0 (the tool wore away and broke).

Comment

Code which you already implemented in hammer and fire extinguisher may help you.

Task 1.2

Adjust classes Hammer and FireExtinguisher to be subclasses of class BreakableTool.

Do not forget to edit constructors of these classes and remove duplicate code.

Task 1.3

Create new package sk.tuke.kpi.oop.game.tools and move classes relating to tools into it.

The number of classes in the project is increasing. To maintain order among them and group related classes together, create new package tools in package sk.tuke.kpi.oop.game and move class BreakableTool, Hammer, Mjolnir and also FireExtinguisher in there.

Comment

In this task you can use feature called refactoring provided by the development environment. In this way you avoid manual rewriting of the package declaration at the beginning of source files and editing imports.

Step 2: Self-exploding reactor

Until now you could work with reactor only manually - you could turn it on and off, and increase and decrease its temperature by calling appropriate methods. In this step you will make some changes to make reactor work independently. We will use a system of actions for that which will represent activity or behaviour of actor in game scene.

Relationships between actor, scene, and action.
Obr. 2: Relationships between actor, scene, and action.

Comment

Action, represented by interface Action and its general implementation AbstractAction, serves in game library GameLib for encapsulating activities of actors into standalone objects. By scheduling execution of action on a scene into which actor belongs it will be (gradually) executed (realisation of design pattern Command). All actions scheduled on a scene, which have not yet been finished, are always executed before rendering next frame of scene on the screen (in case of standard rendering speed it is 60-times per second). With help of actions it is possible to implement actor's behaviour with regard to the flow of time, resp. reactions of actor to current events in the scene.

Comment

In general, each action has
  • state represented by two instance variables:
    • actor (getter getActor()) - actor with which action is executed (the reference is set by scene at the time of scheduling the action)
    • isDone (getter isDone()) - indicates whether the action has already finished (checked by the scene always before executing the action).
  • two significant methods:
    • execute(float deltaTime) - method implementing the logic of action, executed always before rendering new frame of scene (in case that action was scheduled and is not yet done). Parameter of this method is the time (in seconds) since last rendering of scene.
    • reset() - enables to reset action's state.

Task 2.1

in new package sk.tuke.kpi.oop.game.actions create class PerpetualReactorHeating which will represent action of perpetual incrementing of reactor's temperature. Enable to specify value of temperature's increment when creating instance of the class in constructor.

As you can see in class diagram above, new class of action PerpetualReactorHeating should inherit from class AbstractAction from package sk.tuke.kpi.gamelib.framework.actions.

Comment

Class AbstractAction uses type parameter which defines type of actor, with which action should be possible to execute. Since the action should increase temperature of reactor, use type Reactor as value of type parameter:
public class PerpetualReactorHeating extends AbstractAction<Reactor> {

}

Task 2.2

In class PerpetualReactorHeating override abstract method execute() in which you implement functionality of the action.

When implementing method execute(), obtain reference to actor with which the action is executing (reactor) and increase its temperature by value specified as parameter of action's constructor.

Task 2.3

Schedule execution of action PerpetualReactorHeating on reactor so that its temperature will increase by 1 degree.

Since actor needs to know which scene it belongs to when scheduling an action, the right place for scheduling the action can be method addedToScene() inherited from class AbstractActor which obtains reference to scene in parameter (type Scene) and is called right after the actor was added into the scene. Therefore, it is necessary to override method addedToScene(). Do not forget, however, that we still need to execute the original implementation of method addedToScene() from class AbstractActor, and so use keyword super to call the inherited implementation!

Scene defines method scheduleAction() which receives action and actor with which the action should be later executed. Scheduling execution of action in reactor's method can look as follows:

// in method addedToScene of class Reactor
scene.scheduleAction(new PerpetualReactorHeating(1), this);

Our tactical and strategic team, however, recommends to use a complementary method scheduleOn() available directly on object of action, which receives object of actor and provides the same functionality as method scheduleAction() on scene, but with somewhat clearer notation:

// in method addedToScene of class Reactor
new PerpetualReactorHeating(1).scheduleOn(this);

Task 2.4

Verify correctness of your implementation by starting a reactor.

If you proceeded correctly, a reactor that is turned on should gradually increase its temperature, what you can also verify by using inspector.

Step 3: How cool is cool enough?

Now when the first step towards autonomous reactor is behind us, it surely has not escaped your attention that only for calling one method of reactor a whole new class was created. We will try to solve this shortcoming of initial implementation one step after the other.

The adjustment made to reactor in previous step has a little catch though - by letting reactor work independently it will systematically overheat. And overheating, as we know since year 1986, will inevitably lead to its permanent damage. To avoid such situation, instead of hammers you will create a cooler which will cool reactor down systematically.

Relationship of classes Reactor and Cooler.
Obr. 3: Relationship of classes Reactor and Cooler.

Task 3.1

In package sk.tuke.kpi.oop.game create class Cooler for cooling device which will ensure sufficient cooling of reactor connected to it.

Behaviour of cooler and its relationship with reactor is described by the following class diagram:

Class Cooler.
Obr. 4: Class Cooler.

Use file fan.png as animation for cooler. When cooler is turned off, pause its animation. Otherwise when cooler is turned on, play its animation. You will find appropriate methods on object of animation to control it.

Animation fan.png (sprite dimensions: _32x32_, frame duration: _0.2_).
Fig. 5: Animation fan.png (sprite dimensions: 32x32, frame duration: 0.2).

Task 3.2

Add private method into class Cooler in which you implement activity of cooler.

You can name this method e.g. coolReactor(). Every time it is called (when cooler is turned on) temperature of connected reactor should decrease by one degree.

Task 3.3

Override method addedToScene() in which you schedule action for cooler's behaviour.

For calling a method within action it is not necessary to create whole new class of action which will then serve only one very specific purpose. Now we will use action Invoke (available from GameLib library) with which we can define activity of action (method execute()) also by reference to method which implements the requested behaviour.

Comment

Reference to method with name method of specific object object has the notation of object::method in Java language, hence the operator of double colon is used.

We can implement the action and its scheduling as follows:

// in method addedToScene of class Cooler
new Invoke(this::coolReactor).scheduleOn(this);

This action, however, will not meet our expectations: method coolReactor() will be called only once and after that it is considered finished (isDone() will return true). We can achieve repeated execution of action by using action Loop (available in GameLib), which will accept another action as parameter of its constructor and is always considered not completed (isDone() of action Loop always returns false):

// in method addedToScene of class Cooler
new Loop<>(new Invoke(this::coolReactor)).scheduleOn(this);

Comment

Action Loop implements design pattern Decorator.

Task 3.4

Verify correctness of your implementation by starting a reactor and connecting at first only cooler to it, and then two coolers at the same time.

The more coolers you connect with reactor, the sooner will reactor be cooled down. Beware, however, that reactor cannot be cooled to negative temperature.

Reactor's operating temperature is secured by two coolers working at the same time.
Fig. 6: Reactor's operating temperature is secured by two coolers working at the same time.

Step 4: Defective Light

Our analytic team defined defective light as light which is defective. Since we can assume that defective light was all right at first, we can derive its behaviour from the well-functioning light. In this case, the defectiveness will be manifested by blinking of light at irregular intervals.

Hereditary line of class DefectiveLight.
Obr. 7: Hereditary line of class DefectiveLight.

Task 4.1

In package sk.tuke.kpi.oop.game create class DefectiveLight which will be descendant of class Light.

Task 4.2

In class DefectiveLight create method which will define behaviour of defective light.

To achieve the effect of defective light you can use any approach you like. One of them could be e.g. generating random number in range from 0 to 20 and if the generated number is 1 the state of light will change from off to on and vice-versa.

Comment

In Java you can obtain random number by using instance of class Random or simply by calling static method Math.random().

Task 4.3

Schedule behaviour of defective light by using actions Loop and Invoke.

Task 4.4

Verify correctness of your implementation by creating defective light and connecting it to reactor.

Remember that type of parameter in method addLight() in class Reactor needs no change. The principle of polymorphism is used right here: DefectiveLight as descendant of class Light can be used wherever type Light is accepted.

You can recognise defective light from well-functioning light by its behaviour.
Fig. 8: You can recognise defective light from well-functioning light by its behaviour.

Additional tasks

Task A.1

Create class SmartCooler which will represent upgraded Cooler that will automatically turn on and off depending on temperature of reactor.

By using smart cooler ensure that operational temperature of reactor will be maintained in range from 1500 to 2500 degrees. This means that cooler will cool down connected reactor only when its temperature rises above 2500 degrees. However, if its temperature drops below 1500 degrees, cooler will stop cooling down the reactor.

In this case it will be interesting to observe how cooler turns on and off on its own depending on its activity.

Comment

Remember that part of smart cooler's functionality is already implemented in class Cooler. Therefore, avoid duplicating the code and take the opportunity to call method of superclass.

Task A.2

Create class Helicopter for combat helicopter that will follow the player and attack him when it reaches him.

In the class implement public method searchAndDestroy() which will initiate pursuit of player. The movement of helicopter towards the player when helicopter chases him should be implemented in method created for action Invoke. When helicopter meets player, ensure that player's energy will decrease by 1. For animation of helicopter use image heli.png (download it into project's directory src/main/resources/sprites).

Comment

In this task do not override method addedToScene() in class Helicopter! Think about more appropriate place for scheduling the action.

Comment

For obtaining reference to object of player, have a look at methods which are available on object of game scene. Player is of type Player and has name Player. To check if helicopter actually touches (collides with) player, use suitable method which is accessible on object of actor.

Additional links