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, his 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

  • Learn to work with the project in development environment.
  • Learn to create your own classes, their constructors and methods.
  • Learn to overload implementation of a method for various types of parameters.
  • Learn to represent the state of an object by instance variables (encapsulation of data).
  • Learn to manage visibility of instance variables and methods.
  • Learn to create object instances from classes.

Postup

Step 1: Warming up

Cadet! For your training to start you first need to prepare your environment. You will download the project skeleton in which you will work.

Task 1.1

Download a package with the project, unpack it and open it in the IntelliJ IDEA development environment.

Comment

The prerequisite for working with the project is Java Development Kit (JDK) version 10 installed on your system. You can download it from this web site or use package manager of your operating system.

Comment

Using IntelliJ IDEA is not necessary to work with the project, as it will function in any IDE with support of Gradle build tool. However, in materials of this course we will expect usage of IntelliJ IDEA development environment.

When opening the project, use option Open from the Welcome screen of the environment or from the File menu. Choose the directory which contains the unpacked project that you downloaded (such directory contains file build.gradle.kts). Since we use Gradle build tool in our project, a window will show up in which creation of new IDEA project needs to be set up.

Setting up Gradle project import in _IntelliJ IDEA_ environment
Fig. 1: Setting up Gradle project import in IntelliJ IDEA environment

Check if your settings match those from the image given above. If Gradle JVM setting is missing entry for Java 10, click the button ... and choose the directory in which JDK 10 is installed.

After clicking OK button a project will open and configuration of Gradle tool and synchronisation of project dependencies (libraries used by the project) will start. This process may take several minutes since Gradle and all project dependencies need to be downloaded. The process will finish by indexing downloaded libraries, about which you will be informed in the bottom status bar.

Task 1.2

Check that your project is functioning properly by running it.

To run the project for the first time you need to open the tool window for Gradle, find the task run in group application (see image below) and run it (e.g. by double-click).

Comment

The tool window for Gradle has a button in upper right corner on vertical bar along the edge of environment's window. If you don't see this bar, turn it on in menu ViewTool Buttons.
Running the project for the first time
Fig. 2: Running the project for the first time

When everything is all right, after the project is compiled a starting map of basic level will appear, together with inspector window.

The starting form of game level
Fig. 3: The starting form of game level

Comment

The first manual execution of run action will create a so-called run configuration with name "project-ellen [run]" in IntelliJ IDEA environment, which you will find on the upper horizontal bar on the right. When this configuration is shown, another project executions are possible by using the green arrow button which is next to it or by using the keyboard shortcut Shift+F10.

Step 2: 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.
Obr. 4: Class diagram which presents the relationship between class Reactor and AbstractActor.

Task 2.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 2.2

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

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.

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.

You may know a little about inheritance so we will give you a hint. The notation that tells that class A is a subclass of class B is the following:

class A extends B {

}

Task 2.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!

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. 5: 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. 6: 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 2.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. 7: 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).

Task 2.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. 8: Fully-functioning reactor

Task 2.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 3: 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 3.1

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

To set its animation use image computer.png.

Animation computer.png (sprite dimensions: _80x48_, frame duration: _0.2_)
Fig. 9: Animation computer.png (sprite dimensions: 80x48, frame duration: 0.2)

Task 3.2

In class Computer create methods for basic arithmetic operations add() and sub() 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. 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 based on types of arguments provided in method call.

Verify your implementation by running the project and using inspector.

Krok 4: 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 4.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 after exceeding 6000 degrees. 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. 10: 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. 11: 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. 12: 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 4.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.png.

Task 4.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 4.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 4.5

Use inspector in the game, place reactor's object into the scene and verify correctness of your implementation.
Broken (overheated) reactor
Fig. 13: Broken (overheated) reactor

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.