Skip to content

Resource Generation

rjg1 edited this page Sep 12, 2022 · 14 revisions

Introduction

As resources are a core aspect of the game, given they resemble both the main gameplay loop of upgrading militia and the game's win condition, it is essential that they can be added to the game easily (See article: Adding a resource), and consistently. The ResourceGenerator class implemented in Sprint 2, allows for customizable Resource addition and balancing within the game, through the addition of a resources.json config file. The ResourceGenerator will read this file, and add all resources defined in it into the game, based on a few key constraints: Minimum number to add, maximum number to add, preferred distance from city centre, tiles wide and tiles high. This is relevant to the Studio's direction, as future sprints call for the necessity of more resources to be added (especially to win the game), and they will be added in unknown quantities and sizes. This feature solves these problems in advance, by allowing the developer to have agency over the qualities of the resource, and add in a variable or fixed number of them to the game.

Algorithm

Master Algorithm

The algorithm to procedurally place each Resource defined in /assets/configs/resources.json is as follows:

  1. Read resources from resources.json into a list of ResourceSpecification, which will automatically choose a number to generate between minAmount/maxAmount on construction
  2. Load in all island tiles from the MapGenerator's map into a list of Coordinate (used to speed up iteration)
  3. Sort the ResourceSpecification list such that the largest resources are at the top -> these are placed first, to maximize the chance of them finding space in the map before it fills up with other resources
  4. For each ResourceSpecification on the list, run the placement algorithm to allocate them a tile on the island that is unencumbered, and will fit the resource

Placement algorithm

  1. While this ResourceSpecification has not been placed the desired amount of times, continue placing resources
  2. Determine the places on the map where this resource may be placed, based on its size, existing resource placements and a constant buffer variable which may be set to allow space between resource placements (Is currently set to 1, to ensure resources don't block off the map)
  3. If this resource has no valid locations, the map is full, and the resource generation has failed. In this case, the MapGenerator will re-create a new map with the same initial specifications, and the ResourceGenerator will run again. The ResourceGenerator will attempt to re-make the map to fill the desired amount of resources up to 5 times before throwing an exception, to avoid infinite loops
  4. Assuming now that the resource can be placed, a list of Coordinate is formed, representing valid locations for the resource to be placed
  5. A weighted tree map is formed from the valid placements list, with each entry's key referring to its weight - which is determined by the preferred distance of the resource (i.e. a Coordinate closer to the preferred distance will have a higher weight)
  6. A single placement coordinate is chosen randomly from this tree map, implying that desired distances are significantly more likely to be chosen
  7. The placement of the resource is added to its ResourceSpecification's list of placements, and the internal char[][] map is updated to reflect that these tiles are now occupied

UML Diagram of how Map Generation classes interact

Note that a few fields and functions of MapGenerator were omitted in this summary for conciseness, as they are primarily used for unit testing, and not relevant to the ResourceGeneration of the map.

UML Summary

  1. A MapGenerator is created with a set of static constants defined in AtlantisTerrainFactory, which calls generateMap() to create the game map
  2. A ResourceGenerator is created, composed of the MapGenerator, and allocates space for a number of resources on the map, based on a list of ResourceSpecification that is read from a .json config file
  3. Each entry of this list of ResourceSpecification contains a list of coordinate, representing places on the map to put this resource, which is passed back to MapGenerator and output to the game using its getResourcePlacements() function

Test Plan

The approach to test this feature was 2-pronged: Experimental testing was conducted to visually verify that the code was performing as expected, while Unit testing was run to algorithmically ensure that the Resources could consistently be added within their rule set.

Experimental Testing

As the ResourceGenerator was developed, a function writeCurrentResources() was developed, to output the contents of the map as resources were added, to visually verify that they were being placed correctly (I.e. not in the ocean, not on top of other resources, not inside the city, not outside the map bounds). This provided insight into the algorithmic process as it executed placing each resource to ensure the code performed as intended. A sample output of a generation run is provided below, generating 2x2 stone resources and 1x1 tree resources:

Two .txt files are created, after_Stone.txt and after_Tree.txt, which show how the resource map looks after each resource has been fully placed. after_Stone.txt:

after_Tree.txt

Upon visual examination of these two outputs, the following observations can be made: Stone is placed first, which is correct functionality given it is the larger of the two resources, both resources are placed within the island bounds, neither resource is placed in the city, neither resource is overlapping.

These visual, experimental evaluations were carried out dozens of times over the development process of ResourceGenerator, to ensure its functionality was consistent.

Unit testing

Experimental evaluation wasn't deemed as enough, as edge cases exist where the ResourceGenerator could fail - for example a resource being generated in a large range (e.g {1, 1000} resources added to the map) which may succeed in 90% of runs, but fail when a large number is chosen for the resource multiple times (even despite the concession the algorithm makes to re-generate the map when a resource can't fit up to 5 times). As such, cases like these were tested in the game unit tests (found in src/test/com/deco2800/game/areas/MapGenerator/ResourceGeneratorTest.java)

The core issues tested for include:

  1. A resource being generated in an invalid location (as defined above)
  2. A set of ResourceSpecification data defined in resources.json that will sometimes be unable to be correctly placed on the map, possibly due to map size being too small, resource size being too large, or resource quantity being too large
  3. The inability to correctly parse the resources.json config file

These insulate the project from a developer incorrectly modifying resources.json, leading to a chance of a runtime crash if unlucky values were chosen for the number of resources to add to the map.

Table of Contents

Home

Game

Game Home

Design Influences

Gameplay Features

Style

Story

Friendly Units
Map
City
Buildings
Unit Selections

Spell

Game User Testing: Theme of Unit Selection & Spell System

UI User Testing

Tutorial

Resource Stats Display

Loading Screen Bar

Health Bars
In Game menu
  • Feature
  • User Testing:In Game Menu

Landscape Tile Design

Landscape Tile Design Feedback

Weather Design

Weather Design Feedback

Camera Movement

Enemy design

Enemy Units

Enemy AI

How Animation Works

Map Flooding

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Troubleshooting

MacOS Setup Guide

Clone this wiki locally