3. week

Task 2 - Implementing the game core

Objectives

  1. Create classes for the game core in the Java language according to design that you created in the previous week.
  2. Implement the core game logic based on designs that you made in the previous week.
  3. Create unit tests for testing the initialization of the game field and game logic methods.

Instructions

Step 1

Task 1.1

Create classes for the game core in the Java language according to design that you created in the previous week.

Pay attention to appropriate naming of classes, methods, variables, etc., follow the conventions of code indentation, and structure your code according to the principles of OOP.

Comment

When implementing the code, make use of design patterns, if possible. You could get familiar with them on study subject Object-oriented programming.

Organise your code into appropriate packages. For the game Minesweeper the main package could be e.g. sk.tuke.kpi.kp.minesweeper located in directory src.

Consider which data structure is suitable for representing the game field of your game. In case of game Minesweeper it seems that two-dimensional array of type Tile[][] will do well. Also consider using various classes, extending general class Tile, for various types of tiles in your game.

Comment

Before starting with your implementation you can discuss your game design with your lector.

Task 1.2

Generate conventional methods get, set, and constructors according to the rule of encapsulation in object-oriented programming.

In the development environment IntelliJ Idea it is possible to generate methods get and set based on declared attributes of the class, by selecting Refactor -> Encapsulate Fields ... from the context menu or main menu.

Fig. 1

In a displayed dialog window you can select methods which should be generated.

Fig. 2

Constructors can be generated in IntelliJ IDEA by selecting the option Generate... from context menu or Code -> Generate... from main menu, followed by selecting option Constructor from the menu displayed above the source code.

Fig. 3

In displayed dialog window it is possible to select attributes that should be initialized by generated constructor.

Fig. 4

Source code generation can be invoked anytime by keyboard shortcut Alt+Insert.

Step 2

Implement the core game logic based on designs that you made in the previous week. Just to remind you, it concerns mainly the following:

  • The means of generating game field, its initialisation.
  • The means of checking game states after each player's turn - whether the game is won, lost, or still being played.
  • The means of transition between states of tiles, player's moves.

When you succeed in the tasks, at the end of this module your game should be ready for the implementation of console user interface next week.

Step 3

In order to check if your implementation is correct, it is worth testing the functionality created.

Task 3.1

Create unit tests with JUnit 5 for testing the following:

  • correct initialisation of game field and states of the game or tiles in game field,
  • correct generation of game field,
  • correct functionality of methods providing core game logic - checking the states of the game and tiles, transitions between states, etc.

Comment: To create these tests you will need library JUnit. The development environment IntelliJ IDEA may help you download it. When labelling the test method by annotation @Test it will be (with high probability) highlighted in red. By clicking with the cursor on the annotation and using keyboard shortcut Alt+Enter you will invoke a context menu with actions, among which you will see also an option for adding required library JUnit into your project.

Comment: Place source codes of your testing classes into separate directory, named e.g. test (parallel to src) and also structure the code into appropriate packages (e.g. for game Minesweeper the main package could be named sk.tuke.kpi.kp.minesweeper).

Name classes containing the tests according to classes that are tested, while appending word Test to the name of the class. For example, a class that tests functionality of game field from class Field will be named FieldTest.

Annotate individual test methods that verify units of functionality by annotation @Test.

Testing class FieldTest containing several examples of test methods may look as follows:

package sk.tuke.kpi.kp.minesweeper;

import org.junit.jupiter.api.Test;
import sk.tuke.gamestudio.game.mines.myname.core.Field;
import sk.tuke.gamestudio.game.mines.myname.core.Mine;

import java.util.Random;

import static org.junit.jupiter.api.Assertions.*;

public class FieldTest {

    private Random randomGenerator = new Random();
    private Field field;
    private int rowCount;
    private int columnCount;
    private int minesCount;

    public FieldTest() {
        rowCount = randomGenerator.nextInt(10) + 5;
        columnCount = rowCount;
        minesCount = Math.max(1, randomGenerator.nextInt(rowCount * columnCount));
        field = new Field(rowCount, columnCount, minesCount);
    }

    @Test
    public void checkMinesCount() {
        int minesCounter = 0;
        for (int row = 0; row < rowCount; row++) {
            for (int column = 0; column < columnCount; column++) {
                if (field.getTile(row, column) instanceof Mine) {
                    minesCounter++;
                }
            }
        }

        assertEquals(minesCount, minesCounter, "Field was initialized incorrectly - " +
                "a different amount of mines was counted in the field than amount given in the constructor.");
    }

    @Test
    public void fieldWithTooManyMines() {
        Field fieldWithTooManyMines = null;
        int higherMineCount = rowCount * columnCount + randomGenerator.nextInt(10) + 1;
        try {
            fieldWithTooManyMines = new Field(rowCount, columnCount, higherMineCount);
        } catch (Exception e) {
            // field with more mines than tiles should not be created - it may fail on exception
        }
        assertTrue((fieldWithTooManyMines == null) || (fieldWithTooManyMines.getMineCount() <= (rowCount * columnCount)));
    }
    
    // ... more tests
}

Task 3.2

Check your implementation of game core by running unit tests that you implemented in the previous task.

In case some of your tests will fail, search for bugs in your implementation and correct them. Repeat the procedure until your code functions correctly.

Step 4

Task 4.1

Upload (commit and push) your implementation into your repository on GitLab. You can update the project as you gradually work on it. Before the next exercise, make sure you uploaded your latest work into your repository. Also, prepare questions that you would like to discuss on the next exercise.

Additional links

  1. JUnit 5
  2. Another example of JUnit test for Minesweeper.
  3. Documentation for creating JUnit tests in the development environment IntelliJ IDEA.
  4. JUnit 5 user guide