End Game Statistics Programming Guide - UQcsse3200/2024-studio-2 GitHub Wiki

Introduction

The game has a Statistics Tracking System, which works off of the events. It utilises the GameState and SaveHandler class to maintain stats through play sessions. Upon starting a new game, or dying all stats are wiped

The system is made up of the StatManager, StatDisplay and Stat classes. The Stat class handles stat initialisation and incrementation methods. The StatManager class handles subscribing to listeners and firing triggers for incrementing stats. The StatDisplay creates the stat logbook, displaying end game stats to the player.

Each stat has a statName that is used as the string for events listeners and a description that is used to display the stat information to the player. A stat currently holds one value, a type and an optional max value and provides a handler to update the value on an event being triggered.

Usage

Stat has the following constructor

    public Stat(String statName, String description, int current, Integer statMax, Boolean hasMax, StatType type) {
        this.statName = statName;
        this.statDescription = description;
        this.current = current;
        this.statMax = statMax;
        this.hasMax = hasMax;
        this.type = type;
    }

Stats can be instantiated by adding them to assets/defaultsaves/stats.json file :

[
  {
    "statName": "AppleCollected",
    "statDescription": "Apples collected by player",
    "statCurrent": 0,
    "statHasMax": false,
    "statMax": null,
    "type": "ITEM"
  },
]

Stats are added using the StatManager component in the player factory

    player.addComponent((new StatManager(player)));

Reading and Writing

Stats are written to and read from json using the StatSave class within GameState.

        public void write(Json json) {
        json.writeArrayStart("stats");
        for(Stat element : stats) {
            json.writeObjectStart();
            json.writeType(element.getClass());
            json.writeValue("statName", element.getStatName());
            json.writeValue("statDescription", element.getStatDescription());
            json.writeValue("statCurrent", element.getCurrent());
            json.writeValue("statHasMax", element.getStatMax()!=-1);
            json.writeValue("statMax", element.getStatMax());
            json.writeValue("type", element.getType());
            json.writeObjectEnd();
        }
        json.writeArrayEnd();
    }

    public void read(Json json, JsonValue jsonData) {
        JsonValue.JsonIterator iterator = jsonData.child.iterator();
        while(iterator.hasNext()) {
            JsonValue value = iterator.next();
            Stat newStat = new Stat(
                    value.get("statName").asString(),
                    value.get("statDescription").asString(),
                    value.get("statCurrent").asInt(),
                    value.get("statMax").asInt(),
                    value.get("statHasMax").asBoolean(),
                    Stat.StatType.valueOf(value.get("type").asString())
            );
            stats.add(newStat);
        }
    }

Saving and Loading

Stats are Saved through the SaveHandler, and can be loaded in the same way. Currently stats are saved once a stat is incremented and when the player exits the game.

    SaveHandler.getInstance().save(GameState.class, "saves", FileLoader.Location.LOCAL);

They are saved to assets/saves/stats. These stats are used for tracking until the file is manually reset.

[
{
	class: com.csse3200.game.components.stats.Stat
	statName: AppleCollected
	statDescription: Apples collected by player
	statCurrent: 5
	statMax: null
	statHasMax: false
	type: ITEM
}
]

Event Handling

Event Service Integration

The StatManager integrates with the EventService by registering event listeners for each stat using its statName. When an event with the matching name is triggered, incrementStat is invoked, modifying the corresponding stat based on the provided operation.

Stats are managed within the StatManager. Firstly they are setup and a listener (example using item collection)

    void setupStats() {
        // Event for defeating an enemy
        player.getEvents().addListener("addItem", this::handleCollection);
        for (Stat stat : stats) {
            subscribeToStatEvents(stat);
        }
    }

Then a listener is added for the stat name which is made up of 'item.getName()' + "Collected"

    private void subscribeToStatEvents(Stat  stat) {
        player.getEvents().addListener(stat.getStatName(), () -> this.incrementStat(stat.getStatName(), "add", 1));
    }

If an "addItem" event is triggered handle the collection of an item by triggering the subscribeToStatEvents listener

    void handleCollection(AbstractItem item){
        player.getEvents().trigger(item.getName() + "Collected");
    }

Stat Display

Stats are displayed at the end of the game, following the defeat of the final Kangaroo Boss. This is achieved within the StatDisplay class using the GDXLib table ui system.

Logging

StatManager uses logging to provide information when updating stats. This is for debugging and tracking operations during gameplay:

logger.info("Updating {} with {} to {}", stat.getStatName(), operation, value);

If a stat does not exist, a log entry is generated:

logger.info("stat not found in stats");

Complete Methods for Accessing Stats

package: com.csse3200.game.components

  • Seeing getStatName(): returns name of the Stat.
  • void getStatDescription(): returns the description of the stat.
  • int getCurrent(): returns the current stat value.
  • Integer getMax(): if it exists, returns the max stat value.
  • boolean hasMax(): returns true if the stat has a max value.
  • void setCurrent(int value): sets the current value of the stat by a value.
  • void addValue(int experience): adds a value to the current stat.
  • void subtractValue(int value): subtract a value from the current stat.
  • void update(String operation, int value): update the Stat based on the specified operation and value.
  • Stat.StatType getType(): get the type of stat.

Stat JSON read and write methods

  • public void write(JSON json): write values into json
  • public void read(JSON json): read values from json

Note: No stat can go below a value of 0.

Testing Plan

Unit Testing and Coverage Summary

  • Stat Class:
    • The Stat class is thoroughly unit tested, with 100% method coverage and 94% line coverage.
    • The following functionalities are covered by the tests:
      • Constructor validation, ensuring the correct instantiation of stat attributes (statName, statDescription, current, max, hasMax, and type).
      • getCurrent(), getMax(), hasMax() methods for retrieving stat values and max limits.
      • The setCurrent(), addValue(), and subtractValue() methods to ensure stat values are correctly updated, including boundary cases like exceeding max values and reducing below zero.
      • update() method to handle different operations (set, add, subtract) and confirm stat updates.
      • JSON serialisation and deserialisation (write(Json) and read(Json, JsonValue)), ensuring that stats are correctly saved and loaded.
      • The toString() method to ensure a correct string representation of the stat object.

Code Duplication:

  • There is no significant code duplication observed in the Stat class, as the focus remains on ensuring each method functions independently and covers its intended functionality.

Integration Testing

  • Stat Manager and Stat Display Integration:
    • Integration testing for the StatManager and StatDisplay components was primarily done through playtesting and visual validation.

    • Focus: Ensuring the following:

      • StatManager properly tracks and updates statistics during gameplay based on events (e.g., item collection or combat events).
      • StatDisplay correctly renders the statistics at the end of the game, after defeating the final boss, showing the right values for collected items, defeated enemies, and player stats.
    • Approach: Integration testing was performed by simulating real gameplay, where item collection events and combat events trigger stat updates. The final boss defeat scenario tested the correct display of all accumulated statistics.

Play Testing

  • Objective: Play testing was performed to ensure the StatManager, StatDisplay, and StatSaveManager work cohesively during a full playthrough, especially during combat and after defeating the final boss.
  • Areas of Focus:
    • Stat Updates: Ensuring stats like "items collected", "enemies defeated", and "player actions" were updated correctly during gameplay and reflected in the UI.
    • Stat Display: Verifying that the end-game display shows accurate and readable statistics after defeating the final boss.
    • Saving and Loading: Ensuring stats were accurately saved to and loaded from the save file during different game sessions.

https://github.com/user-attachments/assets/2d26581b-e366-43e6-91b5-74003eaf66ef

UML

STAT_UML