Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ A `Grid` stores its locations by value in a `std::vector<Location>`. `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 `<gridId>-<x>-<y>` (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 `<gridId>-<x>-<y>` (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`.

Expand All @@ -59,14 +59,16 @@ A `Grid` stores its locations by value in a `std::vector<Location>`. `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.

`Environment::removeEntity` and `Grid::removeEntity` do nothing when the entity is not present. Calling either on an entity that is not in that environment or grid — including one placed in a different environment — leaves its environment id, grid id, location id, and actual placement unchanged. `Location::removeEntity` likewise leaves the location id of an entity that is not in that location unchanged.

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
Expand Down
144 changes: 144 additions & 0 deletions src/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -1072,5 +1207,14 @@ int main() {
testPlacingAndRemovingEntityResetsAllIds();
testEntitiesSharingAnIdAreMatchedAsTheSameEntity();
testAddingEntityTwiceStoresItTwice();
testPrintingConsoleRepresentationRowByRow();
testPrintingConsoleRepresentationMisalignsAfterSettingGridSize();
testRetrievingFirstEntityFollowsLocationOrder();
testRemovingLocationLeavesEntityEnvironmentIdUnchanged();
testAddingLocationToGridStoresACopy();
testMovingEntityInSingleLocationGridAlwaysReturnsFalse();
testRemovingEntityAddedTwiceToOneLocationLeavesOnePointer();
testRemovingEntityAddedToTwoLocationsRemovesBoth();
testRemovingEntityViaSameIdTwinLeavesOriginalIdsStale();
return 0;
}
Loading