Skip to content

Commit

Permalink
test: add SettlementEntityManagerTest for placeParcel
Browse files Browse the repository at this point in the history
  • Loading branch information
keturn committed Sep 27, 2021
1 parent c170011 commit 9cafd41
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 70 deletions.
2 changes: 1 addition & 1 deletion module.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
{
"id": "ModuleTestingEnvironment",
"minVersion": "0.2.0",
"minVersion": "0.3.2",
"optional": true
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.dynamicCities.districts;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.gestalt.assets.management.AssetManager;
import org.terasology.dynamicCities.utilities.Toolbox;
import org.terasology.engine.entitySystem.prefab.Prefab;
import org.terasology.engine.entitySystem.systems.BaseComponentSystem;
import org.terasology.engine.entitySystem.systems.RegisterMode;
import org.terasology.engine.entitySystem.systems.RegisterSystem;
import org.terasology.engine.registry.In;
import org.terasology.engine.registry.Share;
import org.terasology.gestalt.assets.management.AssetManager;

import java.util.ArrayList;
import java.util.HashSet;
Expand All @@ -52,12 +39,7 @@ public void postBegin() {
//Get building data
if (prefab.hasComponent(DistrictType.class)) {
DistrictType districtType = prefab.getComponent(DistrictType.class);
if (!districtType.zones.isEmpty()) {
Toolbox.stringsToLowerCase(districtType.zones);
districts.add(districtType);
} else {
logger.warn("Found district prefab with empty zone list");
}
addDistrict(districtType);
}
}

Expand All @@ -73,6 +55,15 @@ public void postBegin() {
logger.info("Finished loading districts: " + districts.size() + " district types found: " + districtNames);
}

public void addDistrict(DistrictType districtType) {
if (!districtType.zones.isEmpty()) {
Toolbox.stringsToLowerCase(districtType.zones);
districts.add(districtType);
} else {
logger.warn("Found district prefab with empty zone list");
}
}

public Optional<DistrictType> getDistrictFromName(String name) {
for (DistrictType districtType : districts) {
if (districtType.name.equals(name)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.terasology.nui.Color;
import org.terasology.reflection.MappedContainer;

import java.util.Arrays;
import java.util.List;

//TODO: give mixing factors for zones
Expand All @@ -20,7 +21,12 @@ public class DistrictType implements Component<DistrictType> {
public int color;
public List<String> zones = Lists.newArrayList();

public DistrictType ( ) { }
public DistrictType() { }

public DistrictType(String name, String... zones) {
this.name = name;
this.zones.addAll(Arrays.asList(zones));
};

public boolean isValidType(DynParcel parcel) {
String zone = parcel.getZone();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.dynamicCities.population;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.gestalt.assets.management.AssetManager;
import org.terasology.dynamicCities.utilities.Toolbox;
import org.terasology.engine.entitySystem.prefab.Prefab;
import org.terasology.engine.entitySystem.systems.BaseComponentSystem;
Expand All @@ -27,6 +13,7 @@
import org.terasology.engine.registry.In;
import org.terasology.engine.registry.Share;
import org.terasology.engine.utilities.random.MersenneRandom;
import org.terasology.gestalt.assets.management.AssetManager;

import java.util.HashSet;
import java.util.Set;
Expand All @@ -49,27 +36,7 @@ public void postBegin() {
//Get building data
if (prefab.hasComponent(CultureComponent.class)) {
CultureComponent cultureComponent = prefab.getComponent(CultureComponent.class);
if (cultureComponent.theme != null) {
cultureComponent.theme = cultureComponent.theme.toLowerCase();
} else {
logger.warn("No theme defined for culture " + cultureComponent.name);
}
if (!cultureComponent.buildingNeedPerZone.isEmpty()) {
cultureComponents.add(cultureComponent);
cultureComponent.buildingNeedPerZone = Toolbox.stringsToLowerCase(cultureComponent.buildingNeedPerZone);
} else {
logger.warn("Found culture prefab with empty buildingNeedPerZone list");
}
if (cultureComponent.availableBuildings != null) {
Toolbox.stringsToLowerCase(cultureComponent.availableBuildings);
} else {
logger.warn("No available Buildings defined for culture " + cultureComponent.name);
}
if (cultureComponent.residentialZones != null) {
Toolbox.stringsToLowerCase(cultureComponent.residentialZones);
} else {
logger.warn("No residential zones defined for culture " + cultureComponent.name);
}
addCulture(cultureComponent);
}
}

Expand All @@ -83,13 +50,37 @@ public void postBegin() {
rng = new MersenneRandom(assetManager.hashCode() * 5 + this.hashCode());
}

public void addCulture(CultureComponent cultureComponent) {
if (cultureComponent.theme != null) {
cultureComponent.theme = cultureComponent.theme.toLowerCase();
} else {
logger.warn("No theme defined for culture " + cultureComponent.name);
}
if (!cultureComponent.buildingNeedPerZone.isEmpty()) {
cultureComponents.add(cultureComponent);
cultureComponent.buildingNeedPerZone = Toolbox.stringsToLowerCase(cultureComponent.buildingNeedPerZone);
} else {
logger.warn("Found culture prefab with empty buildingNeedPerZone list");
}
if (cultureComponent.availableBuildings != null) {
Toolbox.stringsToLowerCase(cultureComponent.availableBuildings);
} else {
logger.warn("No available Buildings defined for culture " + cultureComponent.name);
}
if (cultureComponent.residentialZones != null) {
Toolbox.stringsToLowerCase(cultureComponent.residentialZones);
} else {
logger.warn("No residential zones defined for culture " + cultureComponent.name);
}
}

public CultureComponent getRandomCulture() {
if (!cultureComponents.isEmpty()) {
int max = cultureComponents.size();
int index = rng.nextInt(max);
return (CultureComponent) cultureComponents.toArray()[index];
}
logger.error("No culture found...barbarians..." );
logger.error("No culture found...barbarians...");
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.google.common.base.Preconditions.checkArgument;

public class DistrictFacetComponent implements Component<DistrictFacetComponent> {

/**
* TODO: Count the area for each district. Define consumption of zonearea for each district (evenly distributed atm).
* TODO: Assign districts similar to parcels (look up if needs are already fulfilled before placement)
*/
@Replicate
public BlockAreac relativeRegion =new BlockArea(BlockArea.INVALID);
public BlockAreac relativeRegion = new BlockArea(BlockArea.INVALID);
@Replicate
public BlockAreac worldRegion = new BlockArea(BlockArea.INVALID);
@Replicate
Expand Down Expand Up @@ -110,6 +112,7 @@ public DistrictFacetComponent(BlockRegion targetRegion, Border3D border, int gri


private void mapDistrictTypes(DistrictManager districtManager, CultureComponent cultureComponent) {
checkArgument(!districtManager.getDistrictTypes().isEmpty(), "There are no district types!");
Map<String, Float> zoneArea = new HashMap<>();
ProbabilityDistribution<DistrictType> probabilityDistribution = new ProbabilityDistribution<>(districtManager.hashCode() | 413357);
Map<String, Float> culturalNeedsPercentage = cultureComponent.getProcentualsForZone();
Expand All @@ -133,14 +136,14 @@ private void mapDistrictTypes(DistrictManager districtManager, CultureComponent
totalAssignedArea += districtSize.get(i);

//Calculate probabilities
Map<DistrictType, Float> probabilites = new HashMap<>(districtManager.getDistrictTypes().size());
Map<DistrictType, Float> probabilities = new HashMap<>(districtManager.getDistrictTypes().size());
float totalDiff = 0;

for (DistrictType districtType : districtManager.getDistrictTypes()) {
float diff = 0;
Map<String, Float> tempZoneArea = new HashMap<>(zoneArea);
for (String zone : districtType.zones) {
float area = districtSize.get(i) / districtType.zones.size();
float area = (float) districtSize.get(i) / districtType.zones.size();
tempZoneArea.put(zone, tempZoneArea.getOrDefault(zone, 0f) + area);
if (!culturalNeedsPercentage.containsKey(zone)) {
diff = Float.MAX_VALUE;
Expand All @@ -152,18 +155,18 @@ private void mapDistrictTypes(DistrictManager districtManager, CultureComponent
}

diff = (diff == 0) ? 0 : 1 / diff;
probabilites.put(districtType, diff);
probabilities.put(districtType, diff);
totalDiff += diff;
}
for (DistrictType districtType : districtManager.getDistrictTypes()) {
probabilites.put(districtType, probabilites.getOrDefault(districtType, 0f) / totalDiff);
probabilities.put(districtType, probabilities.getOrDefault(districtType, 0f) / totalDiff);
}

//Assign District
probabilityDistribution.initialise(probabilites);
probabilityDistribution.initialise(probabilities);
DistrictType nextDistrict = probabilityDistribution.get();
for (String zone : nextDistrict.zones) {
float area = districtSize.get(i) / nextDistrict.zones.size();
float area = (float) districtSize.get(i) / nextDistrict.zones.size();
zoneArea.put(zone, zoneArea.getOrDefault(zone, 0f) + area);
}
districtTypeMap.put(Integer.toString(districtCenters.indexOf(minCenter)), nextDistrict);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public String toString() {

@Override
public void copyFrom(SiteComponent other) {

this.coords.set(other.coords);
this.radius = other.radius;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

package org.terasology.dynamicCities.settlements;

import org.joml.RoundingMode;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import org.joml.Vector3fc;
import org.joml.Vector3i;
import org.joml.Vector3ic;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.terasology.dynamicCities.buildings.BuildingManager;
import org.terasology.dynamicCities.buildings.BuildingQueue;
import org.terasology.dynamicCities.districts.DistrictManager;
import org.terasology.dynamicCities.districts.DistrictType;
import org.terasology.dynamicCities.parcels.DynParcel;
import org.terasology.dynamicCities.parcels.ParcelList;
import org.terasology.dynamicCities.population.CultureComponent;
import org.terasology.dynamicCities.population.CultureManager;
import org.terasology.dynamicCities.settlements.components.DistrictFacetComponent;
import org.terasology.dynamicCities.sites.SiteComponent;
import org.terasology.engine.context.Context;
import org.terasology.engine.entitySystem.entity.EntityManager;
import org.terasology.engine.entitySystem.entity.EntityRef;
import org.terasology.engine.logic.location.LocationComponent;
import org.terasology.engine.registry.In;
import org.terasology.engine.registry.InjectionHelper;
import org.terasology.moduletestingenvironment.MTEExtension;
import org.terasology.moduletestingenvironment.extension.Dependencies;
import org.terasology.namegenerator.town.TownAssetTheme;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.google.common.truth.Truth8.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

@Tag("MteTest")
@ExtendWith(MTEExtension.class)
@Dependencies("DynamicCities")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SettlementEntityManagerTest {

static final int MAX_PLACEMENT_ATTEMPTS = 20;
static final String zone = "TestZone";
static final Vector2ic buildingSize = new Vector2i(9, 9);
static final Vector3ic siteLocation = new Vector3i(1234, 0, -5678);
static final int population = 3;

@In
Context context;

@BeforeAll
void initialiseZones() {
// FIXME: mockito kludge to give us a way to define buildings.
// Should have a way for this test to add a building prefab,
// or a way to add to BuildingManager without prefabs.
BuildingManager buildingManager = spy(context.get(BuildingManager.class));
when(buildingManager.getMinMaxSizePerZone()).thenReturn(Map.of(zone, List.of(buildingSize, buildingSize)));
context.put(BuildingManager.class, buildingManager);

// re-inject this so it has the mock version.
InjectionHelper.inject(context.get(SettlementEntityManager.class), context);
}

@BeforeAll
void initialiseCulture(CultureManager cultures) {
CultureComponent culture = new CultureComponent();
culture.name = "Testers";
culture.theme = TownAssetTheme.FANTASY.name();
culture.availableBuildings.add("Test Building");
culture.buildingNeedPerZone.put(zone, 1f);
culture.residentialZones.add(zone);

cultures.addCulture(culture);
}

@BeforeAll
void initialiseDistricts(DistrictManager districts) {
districts.addDistrict(new DistrictType("The District", zone));
}

EntityRef newSite() {
SiteComponent site = new SiteComponent(siteLocation.x(), siteLocation.z());
return context.get(EntityManager.class).create(
site,
new SettlementComponent(site, population),
new LocationComponent(siteLocation)
);
}

@Test
void placeParcel(SettlementEntityManager manager) {
ParcelList parcels = new ParcelList();
BuildingQueue buildingQueue = new BuildingQueue();

EntityRef site = newSite();
EntityRef settlement = manager.createSettlement(site);
Vector3fc center = settlement.getComponent(LocationComponent.class).getLocalPosition();

Optional<DynParcel> parcel = manager.placeParcel(
new Vector3i(center, RoundingMode.FLOOR), zone, parcels, buildingQueue,
settlement.getComponent(DistrictFacetComponent.class), MAX_PLACEMENT_ATTEMPTS
);
assertThat(parcel).isPresent();
}
}

0 comments on commit 9cafd41

Please sign in to comment.