2. week

Red Alert: Reactor Overheat!

A reactor which temperature rises out of control can wreak great havoc.

Motivation

Welcome in the training center, cadet, in which during 12 weeks of training we will make you a proper man (in other words object-oriented programmer)! The form and looks of your training will be taken care of by our tactical and strategic team which has long years of experience with training cadets of your calibre and certainly will not let you take a break. Your training is, in fact, its priority.

Today it will be just a warm-up round in which we will see who you are, what's in you and what to expect from you. Even though it is just a warm-up, many cadets break the team and stay on today's mission for few more weeks. We believe you will not be one of them!

From the operation center greets Manager.

Objectives

  1. Learn to create your own classes, their constructors and methods.
  2. Learn to overload implementation of a method for various types of parameters.
  3. Learn to represent the state of an object by instance variables (encapsulation of data).
  4. Learn to manage visibility of instance variables and methods.
  5. Learn to create object instances from classes.

Instructions

Step 1: Reactor Class

Your task is to create a class which will represent a reactor in Alien Breed game. Its state will be represented by actual temperature of the core, total damage and animation which visualises reactor's current condition.

Class diagram which presents the relationship between class Reactor and AbstractActor.
Fig. 1: Class diagram which presents the relationship between class Reactor and AbstractActor.

Task 1.1

Create a package sk.tuke.kpi.oop.game in your project.

Since the project uses build tool Gradle, it is necessary to comply with its convention of Java source files placement and therefore you need to create this package in directory src/main/java.

Task 1.2

In package sk.tuke.kpi.oop.game create class Reactor which will be a subclass of AbstractActor.

In Java, the notation that tells that class A is a subclass of class B is the following:

class A extends B {

}

Class AbstractActor which you will use as a parent class of new class Reactor is part of library GameLib which is defined among dependencies of your project. Because of that there is no need to create the class again! You only need to import it into the source file of Reactor class. The development environment will facilitate the work for you, after you type in a class name that compiler do not recognizes without an import statement.

Comment

Command import which is auto-completed by the environment serves to shorten the full name of the class which is sk.tuke.kpi.gamelib.framework.AbstractActor. After providing the import you can use just the short name AbstractActor in the file.

Comment

When you need to import some class from provided library and the environment shows several options with the same short class names, you can identify the right choice by package prefix sk.tuke.kpi.gamelib.

Task 1.3

Add instance variables (also called fields) into class Reactor which will represent reactor's state: current temperature, damage, and animation visually representing the reactor.

They are the following instance variables:

  • temperature - current temperature of reactor represented by whole number, with minimal value 0 degrees,
  • damage - damage of reactor as percentage, represented by whole numbers in range from 0 (reactor is intact) to 100 (reactor is broken),
  • normalAnimation - object of animation which visually represents reactor in its intact state. We will use type Animation represented by class Animation from package sk.tuke.kpi.gamelib.graphics.

When writing instance variables, do not forget that they shouldn't be visible outside the class! Use adequate visibility modifier.

Comment

In computer graphics (mainly in relation to 2D games) a term sprite is used very often. Sprite is a small two-dimensional picture (or animation) which is integrated into larger scene. The effect of animation is created by sequential rendering of several pictures (frames) on the same position.

Frames of sprite representing the main protagonist in game Jet Set Willy walking to the right
Fig. 2: Frames of sprite representing the main protagonist in game Jet Set Willy walking to the right

Resulting animation created by sequential rendering of frames of animation combined with horizontal movement
Fig. 3: Resulting animation created by sequential rendering of frames of animation combined with horizontal movement

In game library that we use we will work with objects of type Animation which can be used equally for simple single images and also images containing several frames of sprite's animation.

Task 1.4

Create a no-argument constructor of class Reactor in which you initialize instance variables representing state of newly formed object.

In initialization of the object set:

  • animation which will represent the reactor, by using the following image

    Animation reactor_on.png (sprite dimensions: 80x80, frame duration: 0.1 second)
    Fig. 4: Animation reactor_on.png (sprite dimensions: 80x80, frame duration: 0.1 second)

  • initial temperature of reactor's core to value 0, and

  • initial percentage of reactor's damage to value 0.

Animations are located in the project in directory src/main/resources/sprites/. When setting path to animation file use a relative path - the root directory is src/main/resources/. As a result, path to reactor's animation will be sprites/reactor_on.png.

Comment

When writing path to images with animation you can also use keyboard shortcut Ctrl+Space to auto-complete names of files and directories.

To set the animation use the following fragment of code in constructor of Reactor class:

// create animation object
normalAnimation = new Animation("sprites/reactor_on.png", 80, 80, 0.1f, Animation.PlayMode.LOOP_PINGPONG);
// set actor's animation to just created Animation object
setAnimation(normalAnimation);

Comment

A documentation of classes, their constructors and methods can be shown directly in the development environment by placing cursor on the name of desired element and using keyboard shortcut Ctrl+Q. This shortcut can be used also when going through the list of code auto-complete options (Ctrl+Space). Documentation is also availablr at a separate page.

Task 1.5

Verify correctness of your implementation by creating an instance of reactor and placing it into the game scene.

If you have proceeded correctly, after you run the project (Shift+F10) you will be able to use inspector to create instance of class Reactor and place it into the game.

Fully-functioning reactor
Fig. 5: Fully-functioning reactor

Task 1.6

Create methods getTemperature() and getDamage() which will allow you to obtain current value of reactor's core temperature and damage.

These methods provide access for reading values of temperature and damage. Naturally, their visibility should be set to public. After that you can check their functionality with inspector.

Comment

Notice that method for obtaining value of instance variable temperature is called getTemperature() and for damage it is called getDamage(). This naming of methods for obtaining values of variables by adding prefix get to the name of the variable is conventional and we will use it very often. The resulting method is also colloquially called getter.

Step 2: Intermezzo - Method Overloading

Now when you know how to create class and its methods we will show you one of several properties of polymorphism (we will return to it several times later). The property is method overloading.

Task 2.1

In package sk.tuke.kpi.oop.game create class Computer as subclass of AbstractActor.

To set its animation use image computer.

Animation computer.png (sprite dimensions: 80x48, frame duration: 0.2)
Fig. 6: Animation computer.png (sprite dimensions: 80x48, frame duration: 0.2)

Task 2.2

In class Computer create methods for basic arithmetic operations add() and sub(), both of them for numeric data types int and float.

Each of these methods will have 2 parameters of the same type for operands of given arithmetic operation. In one case parameters will be of type int, in other case float. As a result, 4 methods need to be implemented: add() for int, add() for float, sub() for int and sub() for float type. By implementing methods in described way they will become overloaded.

Comment

Which of the two implemented methods with name add (the same for method sub) will be called is determined statically during compilation based on types of arguments provided in method call.

Verify your implementation by running the project and using inspector.

Step 3: Reactor (Over)Heating

Back to reactor! You already know how to get its state and now we will take a look at how to change the state.

Task 3.1

Create method increaseTemperature() by using which increasing current temperature of reactor's core will be possible.

This method will return no value and will have one whole-numbered parameter (named e.g. increment) which will represent value by which actual temperature of reactor should increase.

When implementing the method, consider the following conditions:

  • With increasing reactor's temperature linearly increase its damage. Reactor starts to be damaged after exceeding temperature 2000 degrees and completely breaks when reaching 6000 degrees and more. Every following attempt to cool down the reactor will not affect its damage that was caused by high temperature (it means that after calling this method reactor's damage cannot be lower than before the method call). When calculating damage, round the decimal part of the result down.

    Correlation between increasing reactor's damage and its temperature
    Fig. 7: Correlation between increasing reactor's damage and its temperature

    Comment

    Decreasing reactor's damage will be possible only by repairing the reactor, which will be your task later.

  • If the temperature after increasing exceeds value 4000 degrees, from that moment reactor's appearance will be represented by the following animation:

    Animation reactor_hot.png (sprite dimensions: 80x80, frame duration: 0.05)
    Fig. 8: Animation reactor_hot.png (sprite dimensions: 80x80, frame duration: 0.05)

  • If reactor's damage is in the interval <33%, 66%>, temperature rises 1.5-fold; if it exceeds value 66%, temperature rises twofold. In both cases the change of temperature's increase is based on the initial value of parameter increment. After calculating new value of temperature always round the decimal part of the result up to the nearest integer.

  • If temperature after increasing reaches (or exceeds) value 6000 degrees, reactor will be broken (its damage reaches value 100%). Broken reactor will be represented by the following animation:

    Animation reactor_broken.png (sprite dimensions: 80x80, frame duration: 0.1)
    Fig. 9: Animation reactor_broken.png (sprite dimensions: 80x80, frame duration: 0.1)

  • Once reactor's damage reached value 100%, any further increase of temperature will have no effect.

Comment

It's convenient to create objects of animations when initializing the reactor object and storing references to them into another instance variables. Since reactor can be in several states (specifically in 3 states - state ok, overheated, and broken), animation representing reactor's state will change. If reactor changes state during gameplay e.g. 50 times, it means that by each change of animation you need to load the other animation over - in summary 50 loadings of animation files. However, if you load all three animations when initializing the object of reactor, you will perform only 3 animation loadings and the animation objects can be used as needed.

Task 3.2

Create method decreaseTemperature() which will allow to decrease actual temperature of reactor's core.

This method will return no value and will have one whole-numbered parameter (named e.g. decrement) which will represent value by which actual temperature of reactor should decrease. Beware, decreasing temperature will not decrease the damage which has already been made by high temperature!

When implementing the method, consider the following conditions:

  • If damage of reactor's core is at least 50%, actual decreasing of temperature will be halved compared to the value of parameter decrement.
  • If damage of reactor's core is at value 100%, decreasing temperature has no effect at all.
  • If resulting temperature of reactor's core drops to 4000 degrees or less, change the animation representing state of reactor to reactor_on.

Task 3.3

Create method updateAnimation() which will set proper animation of reactor according to its current temperature.

When you look carefully at methods increaseTemperature() and decreaseTemperature() you will notice that both methods address setting proper animation depending on its temperature. Such duplicity is useless and complicates future changes of functionality. Because of that, refactor your code to separate the common functionality into another method updateAnimation().

Task 3.4

Adjust your implementation of methods increaseTemperature() and decreaseTemperature() so that they use method updateAnimation() to properly change current animation of reactor.

After refactoring from previous task do not forget to change also original methods from which the code was extracted so that they will use the new method.

Comment

Method updateAnimation() represents only an implementation detail and it is not necessary (neither even desirable) to make it accessible to users of reactor's object. Therefore, hide this method by using proper access modifier.

Task 3.5

Use inspector in the game, place reactor's object into the scene and verify correctness of your implementation.

Broken (overheated) reactor
Fig. 10: Broken (overheated) reactor

Step 4: Repository

Task 4.1

Upload (commit and push) your source code to your GitLab repository. You should do it before the following exercise. Also, prepare questions that you want to discuss on the following exercise.

Upload your implementation even if it's not yet complete. We recommend to comment out parts of the code that could cause compilation errors.

Additional Tasks

Task A.1

Handle case when methods increaseTemperature() and decreaseTemperature() are called with negative value as argument.

If such method call occurs do not change reactor's temperature.

Task A.2

Modify reactor's implementation so that the speed of pulsing created by its animation will depend on reactor's damage.

When reactor's damage increases, duration of one frame of animation should decrease to create the effect of faster pulsation of reactor.

Additional links