diff --git a/README.md b/README.md index a67a7ad..0b1a2e8 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ A `Grid` stores its locations by value in a `std::vector`. `Grid::getL ## Grid Layout and Movement -`Environment(id, name, size)` creates a square grid of `size` × `size` locations. The grid is given the environment's own `id`, and each location's id has the form `--` (for example `0-2-1`). Locations are generated row by row, with `x` and `y` each running from `0` to `size - 1`; `Grid::getLocations()` and `Environment::printConsoleRepresentation()` follow that order. +`Environment(id, name, size)` creates a square grid of `size` × `size` locations. The grid is given the environment's own `id`, and each location's id has the form `--` (for example `0-2-1`). Locations are generated row by row, with `x` and `y` each running from `0` to `size - 1`; `Grid::getLocations()` and `Environment::printConsoleRepresentation()` follow that order, and `Environment::getFirstEntity` returns the first entity of the first occupied location in that order, regardless of the order in which entities were added. `printConsoleRepresentation()` prints each location as `[x] ` when it is occupied or `[ ] ` when it is empty. `Environment::addEntity` (and `Grid::addEntity`) places the entity in a location chosen by `Grid::getRandomLocation()`, not in the first location. To place an entity at a specific location, use `addEntityToLocation`. @@ -59,7 +59,7 @@ A `Grid` stores its locations by value in a `std::vector`. `Grid::getL ## Entity Identity -Entities are matched by `Entity::getId()`, not by address. `Location::isEntityPresent`, `Location::removeEntity`, and `Environment::getEntity` all compare ids, so two distinct `Entity` objects that share an id are treated as the same entity. Entity ids should therefore be unique within an environment. +Entities are matched by `Entity::getId()`, not by address. `Location::isEntityPresent`, `Location::removeEntity`, and `Environment::getEntity` all compare ids, so two distinct `Entity` objects that share an id are treated as the same entity. Removing through such a twin takes the stored entity out of its location but resets only the twin's ids, so the stored entity keeps ids describing a placement it no longer has. Entity ids should therefore be unique within an environment. A newly constructed entity has an environment id and grid id of `-1` and a location id of `"N/S"`. Placing it through an `Environment` sets all three; `Environment::removeEntity` resets them to those same values. @@ -67,6 +67,8 @@ A newly constructed entity has an environment id and grid id of `-1` and a locat Adding is likewise unchecked. `addEntity` and `addEntityToLocation` do not test whether the entity is already present, so adding it a second time stores a second pointer to it (in another location, or in the same one), its location id names only the most recent location, and `getNumEntities` counts it twice. To relocate an entity that is already placed, use `moveEntityToNewLocation` or one of the directional move methods, which remove it before adding it again. +Removal takes out at most one pointer per location. An entity added to two different locations is removed from both, but one added twice to the same location keeps a pointer there after `removeEntity`: it is still present and still counted, while its ids have already been reset, so a directional move then throws `std::runtime_error` because its location id `"N/S"` names no location. + `Grid::removeLocation` also detaches any entities in the removed location: it resets their location id to `"N/S"` and their grid id to `-1`. It leaves their environment id unchanged, since the grid has no knowledge of its environment. ## Errors diff --git a/src/tests.cpp b/src/tests.cpp index b861946..8d1ca2d 100644 --- a/src/tests.cpp +++ b/src/tests.cpp @@ -1004,6 +1004,141 @@ void testAddingEntityTwiceStoresItTwice() { std::cout << " --- " << "Success" << std::endl; } +std::string captureConsoleRepresentation(Environment& environment) { + std::ostringstream buffer; + std::streambuf* previousBuffer = std::cout.rdbuf(buffer.rdbuf()); + environment.printConsoleRepresentation(); + std::cout.rdbuf(previousBuffer); + return buffer.str(); +} + +void testPrintingConsoleRepresentationRowByRow() { + std::cout << "Test 60 - Printing the console representation row by row"; + Environment environment(0, "Earth", 2); + assert(captureConsoleRepresentation(environment) == "[ ] [ ] \n[ ] [ ] \n\n"); + Entity entity(0, "Daniel"); + environment.addEntityToLocation(entity, environment.getGrid()->getLocationByCoordinates(1, 0)); + assert(captureConsoleRepresentation(environment) == "[ ] [x] \n[ ] [ ] \n\n"); + environment.moveEntityDown(0); + assert(captureConsoleRepresentation(environment) == "[ ] [ ] \n[ ] [x] \n\n"); + std::cout << " --- " << "Success" << std::endl; +} + +void testPrintingConsoleRepresentationMisalignsAfterSettingGridSize() { + std::cout << "Test 61 - Printing the console representation misaligns after setting the grid size"; + Environment environment(0, "Earth", 2); + environment.getGrid()->setSize(3); + assert(captureConsoleRepresentation(environment) == "[ ] [ ] [ ] \n[ ] \n"); + std::cout << " --- " << "Success" << std::endl; +} + +void testRetrievingFirstEntityFollowsLocationOrder() { + std::cout << "Test 62 - Retrieving the first entity follows location order, not insertion order"; + Environment environment(0, "Earth", 2); + Entity addedFirst(1, "Daniel"); + Entity addedSecond(2, "Bob"); + environment.addEntityToLocation(addedFirst, environment.getGrid()->getLocationByCoordinates(0, 1)); + environment.addEntityToLocation(addedSecond, environment.getGrid()->getLocationByCoordinates(1, 0)); + assert(&environment.getFirstEntity() == &addedSecond); + std::cout << " --- " << "Success" << std::endl; +} + +void testRemovingLocationLeavesEntityEnvironmentIdUnchanged() { + std::cout << "Test 63 - Removing a location leaves the environment id of its entities unchanged"; + Environment environment(5, "Earth", 2); + Entity entity(0, "Daniel"); + Location& location = environment.getGrid()->getLocationByCoordinates(0, 0); + environment.addEntityToLocation(entity, location); + environment.getGrid()->removeLocation(location); + assert(entity.getEnvironmentId() == 5); + assert(entity.getGridId() == -1); + assert(entity.getLocationId() == "N/S"); + assert(environment.isEntityPresent(entity) == false); + assert(environment.getNumEntities() == 0); + std::cout << " --- " << "Success" << std::endl; +} + +void testAddingLocationToGridStoresACopy() { + std::cout << "Test 64 - Adding a location to a grid stores a copy of it"; + Grid grid(0, 2); + Location original("extra-location", 10, 10); + grid.addLocation(original); + Entity entity(0, "Daniel"); + original.addEntity(&entity); + assert(original.getNumEntities() == 1); + assert(grid.getLocation("extra-location").getNumEntities() == 0); + assert(grid.isEntityPresent(entity) == false); + std::cout << " --- " << "Success" << std::endl; +} + +void testMovingEntityInSingleLocationGridAlwaysReturnsFalse() { + std::cout << "Test 65 - Moving an entity in a single-location grid always returns false"; + Environment environment(0, "Earth", 1); + Entity entity(0, "Daniel"); + environment.addEntity(entity); + assert(entity.getLocationId() == "0-0-0"); + assert(environment.moveEntityUp(0) == false); + assert(environment.moveEntityRight(0) == false); + assert(environment.moveEntityDown(0) == false); + assert(environment.moveEntityLeft(0) == false); + for (size_t i = 0; i < 20; i++) { + assert(environment.moveEntityToRandomAdjacentLocation(0) == false); + } + assert(entity.getLocationId() == "0-0-0"); + assert(environment.getNumEntities() == 1); + std::cout << " --- " << "Success" << std::endl; +} + +void testRemovingEntityAddedTwiceToOneLocationLeavesOnePointer() { + std::cout << "Test 66 - Removing an entity added twice to one location leaves one pointer behind"; + Environment environment(0, "Earth", 2); + Entity entity(0, "Daniel"); + Location& location = environment.getGrid()->getLocationByCoordinates(0, 0); + environment.addEntityToLocation(entity, location); + environment.addEntityToLocation(entity, location); + environment.removeEntity(entity); + assert(location.getNumEntities() == 1); + assert(environment.isEntityPresent(entity) == true); + assert(entity.getEnvironmentId() == -1); + assert(entity.getGridId() == -1); + assert(entity.getLocationId() == "N/S"); + bool threw = false; + try { + environment.moveEntityUp(0); + } catch (const std::runtime_error& e) { + threw = true; + } + assert(threw == true); + std::cout << " --- " << "Success" << std::endl; +} + +void testRemovingEntityAddedToTwoLocationsRemovesBoth() { + std::cout << "Test 67 - Removing an entity added to two locations removes it from both"; + Environment environment(0, "Earth", 2); + Entity entity(0, "Daniel"); + environment.addEntityToLocation(entity, environment.getGrid()->getLocationByCoordinates(0, 0)); + environment.addEntityToLocation(entity, environment.getGrid()->getLocationByCoordinates(1, 1)); + environment.removeEntity(entity); + assert(environment.isEntityPresent(entity) == false); + assert(environment.getNumEntities() == 0); + std::cout << " --- " << "Success" << std::endl; +} + +void testRemovingEntityViaSameIdTwinLeavesOriginalIdsStale() { + std::cout << "Test 68 - Removing an entity via a same-id twin leaves the original's ids stale"; + Environment environment(0, "Earth", 2); + Entity original(5, "Daniel"); + Entity twin(5, "Bob"); + environment.addEntityToLocation(original, environment.getGrid()->getLocationByCoordinates(1, 0)); + environment.removeEntity(twin); + assert(environment.isEntityPresent(original) == false); + assert(environment.getNumEntities() == 0); + assert(original.getEnvironmentId() == 0); + assert(original.getGridId() == 0); + assert(original.getLocationId() == "0-1-0"); + std::cout << " --- " << "Success" << std::endl; +} + void seedRandomNumberGenerator() { srand (time (NULL)); } @@ -1072,5 +1207,14 @@ int main() { testPlacingAndRemovingEntityResetsAllIds(); testEntitiesSharingAnIdAreMatchedAsTheSameEntity(); testAddingEntityTwiceStoresItTwice(); + testPrintingConsoleRepresentationRowByRow(); + testPrintingConsoleRepresentationMisalignsAfterSettingGridSize(); + testRetrievingFirstEntityFollowsLocationOrder(); + testRemovingLocationLeavesEntityEnvironmentIdUnchanged(); + testAddingLocationToGridStoresACopy(); + testMovingEntityInSingleLocationGridAlwaysReturnsFalse(); + testRemovingEntityAddedTwiceToOneLocationLeavesOnePointer(); + testRemovingEntityAddedToTwoLocationsRemovesBoth(); + testRemovingEntityViaSameIdTwinLeavesOriginalIdsStale(); return 0; } \ No newline at end of file