|
|
@ -4,171 +4,212 @@ |
|
|
|
#include "spatialmap.hpp" |
|
|
|
#include "spatialmap.hpp" |
|
|
|
#include "dinkyecs.hpp" |
|
|
|
#include "dinkyecs.hpp" |
|
|
|
#include "rand.hpp" |
|
|
|
#include "rand.hpp" |
|
|
|
|
|
|
|
#include <limits> |
|
|
|
|
|
|
|
|
|
|
|
using DinkyECS::Entity; |
|
|
|
using DinkyECS::Entity; |
|
|
|
using namespace fmt; |
|
|
|
using namespace fmt; |
|
|
|
|
|
|
|
|
|
|
|
EntityList require_found(const SpatialMap& collider, Point at, bool diag, size_t expect_size) { |
|
|
|
|
|
|
|
println("TEST require_found at={},{}", at.x, at.y); |
|
|
|
|
|
|
|
auto [found, nearby] = collider.neighbors(at, diag); |
|
|
|
|
|
|
|
REQUIRE(found == true); |
|
|
|
|
|
|
|
REQUIRE(nearby.size() == expect_size); |
|
|
|
|
|
|
|
return nearby; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("confirm basic collision operations", "[collision]") { |
|
|
|
TEST_CASE("SpatialMap::insert", "[spatialmap]") { |
|
|
|
DinkyECS::World world; |
|
|
|
DinkyECS::World world; |
|
|
|
Entity player = world.entity(); |
|
|
|
SpatialMap map; |
|
|
|
Entity enemy = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SpatialMap collider; |
|
|
|
auto player = world.entity(); |
|
|
|
collider.insert({11,11}, player, true); |
|
|
|
auto item = world.entity(); |
|
|
|
collider.insert({21,21}, enemy, true); |
|
|
|
auto potion = world.entity(); |
|
|
|
|
|
|
|
auto enemy = world.entity(); |
|
|
|
{ // not found
|
|
|
|
Point at{10,10}; |
|
|
|
auto [found, nearby] = collider.neighbors({1,1}); |
|
|
|
Point enemy_at{11,11}; |
|
|
|
REQUIRE(!found); |
|
|
|
|
|
|
|
REQUIRE(nearby.empty()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// found
|
|
|
|
map.insert(at, item, false); |
|
|
|
EntityList nearby = require_found(collider, {10,10}, true, 1); |
|
|
|
map.insert(at, potion, false); |
|
|
|
REQUIRE(nearby[0] == player); |
|
|
|
REQUIRE(!map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
{ // removed
|
|
|
|
map.insert(at, player, true); |
|
|
|
collider.remove({11,11}, player); |
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
auto [found, nearby] = collider.neighbors({10,10}, true); |
|
|
|
|
|
|
|
REQUIRE(!found); |
|
|
|
|
|
|
|
REQUIRE(nearby.empty()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
collider.insert({11,11}, player, true); // setup for the move test
|
|
|
|
|
|
|
|
{ // moving, not found
|
|
|
|
|
|
|
|
collider.move({11,11}, {12, 12}, player); |
|
|
|
|
|
|
|
auto [found, nearby] = collider.neighbors({10,10}, true); |
|
|
|
|
|
|
|
REQUIRE(!found); |
|
|
|
|
|
|
|
REQUIRE(nearby.empty()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nearby = require_found(collider, {11,11}, true, 1); |
|
|
|
REQUIRE_THROWS(map.insert(at, enemy, true)); |
|
|
|
REQUIRE(nearby[0] == player); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
map.insert(enemy_at, enemy, true); |
|
|
|
// confirm occupied works
|
|
|
|
REQUIRE(map.occupied(enemy_at)); |
|
|
|
REQUIRE(collider.occupied({12,12})); |
|
|
|
|
|
|
|
REQUIRE(collider.occupied({21,21})); |
|
|
|
|
|
|
|
REQUIRE(!collider.occupied({1,10})); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRE(collider.get({12,12}) == player); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("SpatialMap::remove", "[spatialmap]") { |
|
|
|
TEST_CASE("confirm multiple entities moving", "[collision]") { |
|
|
|
|
|
|
|
DinkyECS::World world; |
|
|
|
DinkyECS::World world; |
|
|
|
Entity player = world.entity(); |
|
|
|
SpatialMap map; |
|
|
|
Entity e1 = world.entity(); |
|
|
|
|
|
|
|
Entity e2 = world.entity(); |
|
|
|
|
|
|
|
Entity e3 = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SpatialMap collider; |
|
|
|
|
|
|
|
collider.insert({11,11}, player, true); |
|
|
|
|
|
|
|
collider.insert({10,10}, e2, true); |
|
|
|
|
|
|
|
collider.insert({11,10}, e3, true); |
|
|
|
|
|
|
|
collider.insert({21,21}, e1, true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EntityList nearby = require_found(collider, {11,11}, false, 1); |
|
|
|
|
|
|
|
REQUIRE(nearby[0] == e3); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nearby = require_found(collider, {11,11}, true, 2); |
|
|
|
|
|
|
|
REQUIRE(nearby[0] == e3); |
|
|
|
|
|
|
|
REQUIRE(nearby[1] == e2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
collider.move({11,11}, {20,20}, player); |
|
|
|
|
|
|
|
nearby = require_found(collider, {20,20}, true, 1); |
|
|
|
|
|
|
|
REQUIRE(nearby[0] == e1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("test edge cases that might crash", "[collision]") { |
|
|
|
auto player = world.entity(); |
|
|
|
DinkyECS::World world; |
|
|
|
auto item = world.entity(); |
|
|
|
Entity player = world.entity(); |
|
|
|
Point at{120, 120}; |
|
|
|
Entity enemy = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SpatialMap collider; |
|
|
|
// confirm that things can be in any order
|
|
|
|
collider.insert({0,0}, player, true); |
|
|
|
map.insert(at, player, true); |
|
|
|
|
|
|
|
map.insert(at, item, false); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
Point enemy_at = {1, 0}; |
|
|
|
auto data = map.remove(at, player); |
|
|
|
collider.insert(enemy_at, enemy, true); |
|
|
|
REQUIRE(!map.occupied(at)); |
|
|
|
|
|
|
|
REQUIRE(data.entity == player); |
|
|
|
|
|
|
|
REQUIRE(data.collision == true); |
|
|
|
|
|
|
|
|
|
|
|
EntityList nearby = require_found(collider, {0,0}, true, 1); |
|
|
|
REQUIRE_THROWS(map.remove(at, player)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
collider.move({1,0}, {1,1}, enemy); |
|
|
|
|
|
|
|
nearby = require_found(collider, {0,0}, true, 1); |
|
|
|
|
|
|
|
REQUIRE(nearby[0] == enemy); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
collider.move({1,1}, {0,1}, enemy); |
|
|
|
TEST_CASE("SpatialMap::move", "[spatialmap]") { |
|
|
|
nearby = require_found(collider, {0,0}, true, 1); |
|
|
|
DinkyECS::World world; |
|
|
|
REQUIRE(nearby[0] == enemy); |
|
|
|
SpatialMap map; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto player = world.entity(); |
|
|
|
|
|
|
|
auto item = world.entity(); |
|
|
|
|
|
|
|
Point at{10, 320}; |
|
|
|
|
|
|
|
map.insert(at, player, true); |
|
|
|
|
|
|
|
map.insert(at, item, false); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto enemy = world.entity(); |
|
|
|
|
|
|
|
auto potion = world.entity(); |
|
|
|
|
|
|
|
Point enemy_at{11, 320}; |
|
|
|
|
|
|
|
map.insert(enemy_at, enemy, true); |
|
|
|
|
|
|
|
map.insert(enemy_at, potion, false); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(enemy_at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point target{at.x + 1, at.y}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try bad move with a slot that's empty
|
|
|
|
|
|
|
|
REQUIRE_THROWS(map.move({0,0}, target, player)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try move into an occupied spot also fails
|
|
|
|
|
|
|
|
REQUIRE_THROWS(map.move(at, target, player)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// now move to a new spot, need to add them back
|
|
|
|
|
|
|
|
map.insert(at, player, true); |
|
|
|
|
|
|
|
target.x++; // just move farther
|
|
|
|
|
|
|
|
map.move(at, target, player); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRE(map.occupied(target)); |
|
|
|
|
|
|
|
auto data = map.remove(target, player); |
|
|
|
|
|
|
|
REQUIRE(data.entity == player); |
|
|
|
|
|
|
|
REQUIRE(data.collision == true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("check all diagonal works", "[collision]") { |
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("SpatialMap::occupied/something_there", "[spatialmap]") { |
|
|
|
DinkyECS::World world; |
|
|
|
DinkyECS::World world; |
|
|
|
Entity player = world.entity(); |
|
|
|
SpatialMap map; |
|
|
|
Entity enemy = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
auto player = world.entity(); |
|
|
|
|
|
|
|
auto item = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point at{1000, 20}; |
|
|
|
|
|
|
|
// first test empty locations
|
|
|
|
|
|
|
|
REQUIRE(!map.something_there(at)); |
|
|
|
|
|
|
|
REQUIRE(!map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// then when there's something without collision
|
|
|
|
|
|
|
|
map.insert(at, item, false); |
|
|
|
|
|
|
|
REQUIRE(map.something_there(at)); |
|
|
|
|
|
|
|
REQUIRE(!map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// finally with collision and an item there
|
|
|
|
|
|
|
|
map.insert(at, player, true); |
|
|
|
|
|
|
|
REQUIRE(map.something_there(at)); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// then remove the item and still have collision
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
map.remove(at, item); |
|
|
|
|
|
|
|
REQUIRE(map.something_there(at)); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// remove player and back to no collision
|
|
|
|
|
|
|
|
map.remove(at, player); |
|
|
|
|
|
|
|
REQUIRE(!map.something_there(at)); |
|
|
|
|
|
|
|
REQUIRE(!map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// last thing, put just the player in at a new spot
|
|
|
|
|
|
|
|
Point target{at.x+1, at.y+10}; |
|
|
|
|
|
|
|
map.insert(target, player, true); |
|
|
|
|
|
|
|
REQUIRE(map.something_there(target)); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(target)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
SpatialMap collider; |
|
|
|
|
|
|
|
Point player_at = {1,1}; |
|
|
|
|
|
|
|
collider.insert(player_at, player, true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point enemy_at = {1, 0}; |
|
|
|
TEST_CASE("SpatialMap::get", "[spatialmap]") { |
|
|
|
collider.insert(enemy_at, enemy, true); |
|
|
|
DinkyECS::World world; |
|
|
|
|
|
|
|
SpatialMap map; |
|
|
|
|
|
|
|
|
|
|
|
for(size_t x = 0; x <= 2; x++) { |
|
|
|
auto player = world.entity(); |
|
|
|
for(size_t y = 0; y <= 2; y++) { |
|
|
|
auto item = world.entity(); |
|
|
|
if(enemy_at.x == player_at.x && enemy_at.y == player_at.y) continue; // skip player spot
|
|
|
|
Point at{101, 31}; |
|
|
|
EntityList nearby = require_found(collider, player_at, true, 1); |
|
|
|
|
|
|
|
REQUIRE(nearby[0] == enemy); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// move the enemy to a new spot around the player
|
|
|
|
// finally with collision and an item there
|
|
|
|
Point move_to = {enemy_at.x + x, enemy_at.y + y}; |
|
|
|
map.insert(at, player, true); |
|
|
|
|
|
|
|
REQUIRE(map.occupied(at)); |
|
|
|
|
|
|
|
|
|
|
|
if(!collider.occupied(move_to)) { |
|
|
|
auto entity = map.get(at); |
|
|
|
collider.move(enemy_at, move_to, enemy); |
|
|
|
REQUIRE(player == entity); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enemy_at = move_to; |
|
|
|
// This probably doesn't work so need to
|
|
|
|
} |
|
|
|
// rethink how get works.
|
|
|
|
} |
|
|
|
map.insert(at, item, false); |
|
|
|
|
|
|
|
entity = map.get(at); |
|
|
|
|
|
|
|
REQUIRE(entity == item); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE("confirm can iterate through all", "[collision]") { |
|
|
|
TEST_CASE("SpatialMap::neighbors", "[spatialmap]") { |
|
|
|
DinkyECS::World world; |
|
|
|
DinkyECS::World world; |
|
|
|
SpatialMap collider; |
|
|
|
SpatialMap map; |
|
|
|
Point player{10,10}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++) { |
|
|
|
auto player = world.entity(); |
|
|
|
size_t max = Random::uniform<size_t>(2,30); |
|
|
|
auto enemy1 = world.entity(); |
|
|
|
|
|
|
|
auto enemy2 = world.entity(); |
|
|
|
|
|
|
|
//auto item1 = world.entity();
|
|
|
|
|
|
|
|
//auto item2 = world.entity();
|
|
|
|
|
|
|
|
Point at{101, 31}; |
|
|
|
|
|
|
|
|
|
|
|
for(size_t i = 0; i < max; i++) { |
|
|
|
map.insert(at, player, true); |
|
|
|
size_t x = Random::uniform<size_t>(0, 213); |
|
|
|
map.insert({at.x+1, at.y}, enemy1, true); |
|
|
|
size_t y = Random::uniform<size_t>(0, 251); |
|
|
|
map.insert({at.x-1, at.y+1}, enemy2, true); |
|
|
|
|
|
|
|
|
|
|
|
Entity ent = world.entity(); |
|
|
|
auto result = map.neighbors(at, true); |
|
|
|
if(!collider.occupied({x, y})) { |
|
|
|
REQUIRE(result.found); |
|
|
|
collider.insert({x,y}, ent, true); |
|
|
|
REQUIRE(result.nearby.size() > 0); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
bool maybe = result.nearby[0] == enemy1 || result.nearby[1] == enemy1; |
|
|
|
|
|
|
|
REQUIRE(maybe); |
|
|
|
|
|
|
|
|
|
|
|
auto sprite_distance = collider.distance_sorted(player, 1000); |
|
|
|
maybe = result.nearby[0] == enemy2 || result.nearby[1] == enemy2; |
|
|
|
|
|
|
|
REQUIRE(maybe); |
|
|
|
|
|
|
|
|
|
|
|
// sometimes there's nothing near us
|
|
|
|
result = map.neighbors(at, false); |
|
|
|
if(sprite_distance.size() == 0) continue; |
|
|
|
REQUIRE(result.found); |
|
|
|
|
|
|
|
REQUIRE(result.nearby.size() == 1); |
|
|
|
|
|
|
|
REQUIRE(result.nearby[0] == enemy1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int prev_dist = sprite_distance[0].first; |
|
|
|
TEST_CASE("SpatialMap::distance_sorted", "[spatialmap]") { |
|
|
|
|
|
|
|
|
|
|
|
for(auto dist : sprite_distance) { |
|
|
|
DinkyECS::World world; |
|
|
|
REQUIRE(prev_dist >= dist.first); |
|
|
|
SpatialMap map; |
|
|
|
prev_dist = dist.first; |
|
|
|
|
|
|
|
} |
|
|
|
auto player = world.entity(); |
|
|
|
|
|
|
|
auto enemy1 = world.entity(); |
|
|
|
|
|
|
|
auto item = world.entity(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
map.insert({1,1}, player, true); |
|
|
|
|
|
|
|
map.insert({4,4}, enemy1, true); |
|
|
|
|
|
|
|
map.insert({3, 3}, item, false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto result = map.distance_sorted({1, 1}, 100); |
|
|
|
|
|
|
|
REQUIRE(result.size() == 3); |
|
|
|
|
|
|
|
REQUIRE(result[0].second == enemy1); |
|
|
|
|
|
|
|
REQUIRE(result[1].second == item); |
|
|
|
|
|
|
|
REQUIRE(result[2].second == player); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int prev_dist = std::numeric_limits<int>::max(); |
|
|
|
|
|
|
|
for(auto [dist, entity] : result) { |
|
|
|
|
|
|
|
REQUIRE(dist < prev_dist); |
|
|
|
|
|
|
|
prev_dist = dist; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|