/******************************************************************************
Online C++ Compiler.
Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.
*******************************************************************************/
#include <iostream>
#include "GridSpace.h"
int main()
{
OpenXcom::GridSpace<8, 8> grid1 = OpenXcom::GridSpace<8, 8>::fromRectangle({2, 2}, {3, 5});
OpenXcom::GridSpace<8, 8> grid2 = OpenXcom::GridSpace<8, 8>::fromRectangle({0, 0}, {1, 8});
OpenXcom::GridSpace<8, 8> grid3 = (grid1 | grid2).getConnectedSpace({2, 2});
auto rectangle = OpenXcom::GridRectangle({2, 2}, {3, 5}).getBounds();
auto size = OpenXcom::GridSize{3, 5};
std::cout<< std::format("{}", grid3) << '\n';
//std::cout<< std::format("{}", OpenXcom::GridSpace<8, 8>::MAX_BOUNDS.inBounds(rectangle));
return 0;
}
#include <bitset>
#include <cstdint>
#include <cstdlib>
#include <format>
#include <numeric>
#include <queue>
#include <ranges>
#include <vector>
namespace OpenXcom
{
template <typename T>
concept arithmetic = std::integral<T> || std::floating_point<T>;
// Represents a point on the grid.
template<arithmetic N>
struct Point
{
N x, y;
[[nodiscard]] constexpr Point operator+(Point other) const { return Point{ x + other.x, y + other.y }; }
[[nodiscard]] constexpr Point operator-(Point other) const { return Point{ x - other.x, y - other.y }; }
[[nodiscard]] constexpr bool operator==(const Point& other) const = default;
[[nodiscard]] constexpr bool operator!=(const Point& other) const = default;
static const Point CARDINAL_DIRECTIONS[4];
};
template<arithmetic N>
constexpr Point<N> Point<N>::CARDINAL_DIRECTIONS[4] = { { -1, 0 }, { +1, 0 }, { 0, -1 }, { 0, +1 } };
// represents a width/height size
template<arithmetic N>
struct Size
{
N w, h;
[[nodiscard]] constexpr bool operator==(const Size& other) const = default;
[[nodiscard]] constexpr bool operator!=(const Size& other) const = default;
static const Size EMPTY;
};
template<arithmetic N>
constexpr Size<N> Size<N>::EMPTY = { 0 };
template<arithmetic N>
[[nodiscard]] constexpr Point<N> operator+(Point<N> point, Size<N> size) { return Point{ point.x + size.w - 1, point.y + size.h - 1 }; }
template<arithmetic N>
[[nodiscard]] constexpr Point<N> operator+(Size<N> size, Point<N> point) { return point + size; }
// represents a rectangular area on the grid
template<arithmetic P, arithmetic S>
struct Rectangle;
// represents a min/max bounding box on a grid
template<arithmetic N>
struct Bounds
{
Point<N> min, max;
[[nodiscard]] constexpr bool operator==(const Bounds<N>& other) const = default;
[[nodiscard]] constexpr bool operator!=(const Bounds<N>& other) const = default;
// gets the size for the area defined by this GridBounds
[[nodiscard]] constexpr Size<N> size() const
{
return (min.x <= max.x && min.y <= max.y)
? Size<N>{ .w = max.x - min.x + 1, .h = max.y - min.y + 1 }
: Size<N>{ .w = 0, .h = 0 };
}
// checks if a point is in bounds or not
[[nodiscard]] constexpr bool inBounds(const Point<N> point) const
{
return (point.x >= min.x && point.x <= max.x && point.y >= min.y && point.y <= max.y);
}
// checks if a bounds is fully inside the bounds
[[nodiscard]] constexpr bool inBounds(const Bounds<N> bounds) const
{
return inBounds(bounds.min) && inBounds(bounds.max);
}
// checks if a rectangle is fully inside the bounds
template<arithmetic S>
[[nodiscard]] constexpr bool inBounds(const Rectangle<N, S> rectangle) const
{
return inBounds(rectangle.point) && inBounds(rectangle.point + rectangle.size);
}
template <typename T>
constexpr void validateInBounds(const T& shape) const {
if (!inBounds(shape)) {
throw std::out_of_range(std::format("Argument {} out of bounds {}", shape, *this));
}
}
static const Bounds EMPTY;
};
template<arithmetic N>
constexpr Bounds<N> Bounds<N>::EMPTY = { 0 };
// represents a rectangular area on the grid
template<arithmetic P, arithmetic S>
struct Rectangle
{
Point<P> point;
Size<S> size;
[[nodiscard]] constexpr bool operator==(const Rectangle& other) const = default;
[[nodiscard]] constexpr bool operator!=(const Rectangle& other) const = default;
[[nodiscard]] constexpr Bounds<P> getBounds() const { return { point, point + size }; }
};
template<arithmetic P, arithmetic S>
requires std::is_integral<P>::value&& std::is_integral<S>::value
struct IntRectangle : Rectangle<P, S>
{
using Rectangle<P, S>::point;
using Rectangle<P, S>::size;
[[nodiscard]] std::ranges::range auto getPoints() const
{
return std::views::iota(0, size.w * size.h) | std::views::transform([this](int index) {
std::div_t result = std::div(index, size.w);
return Point<P>{ point.x + result.rem, point.y + result.quot };
});
}
};
using GridPoint = Point<int>;
using GridSize = Size<int>;
using GridBounds = Bounds<int>;
using GridRectangle = IntRectangle<int, int>;
// Represents an occupied space on a grid. Immutable by design.
template<size_t WIDTH, size_t HEIGHT>
class GridSpace
{
public:
static constexpr std::size_t AREA = WIDTH * HEIGHT; // Area of the grid space
static constexpr GridSize GRID_SIZE = { WIDTH, HEIGHT }; // The size of ths grid.
static constexpr GridBounds MAX_BOUNDS = { // Maximum bounds of the grid area.
.min = GridPoint{.x = 0, .y = 0 },
.max = GridPoint{.x = WIDTH - 1, .y = HEIGHT - 1},
};
static const GridSpace EMPTY;
private:
std::bitset<AREA> _spaces;
// Gets a GridPoint from an index
[[nodiscard]] constexpr static GridPoint indexToPoint(size_t index)
{
std::div_t result = std::div(static_cast<int>(index), WIDTH);
return GridPoint{ result.rem , result.quot };
}
[[nodiscard]] constexpr static size_t pointToIndex(GridPoint point)
{
return static_cast<size_t>(point.y * WIDTH + point.x);
}
// Gets a GridSpace using prevalidated points.
[[nodiscard]] constexpr static GridSpace fromValididPoints(std::ranges::input_range auto&& points)
{
GridSpace result = GridSpace::EMPTY;
SetAdaptor adaptor = SetAdaptor{result};
std::ranges::copy(points, std::back_inserter(adaptor));
return result;
}
struct SetAdaptor
{
using value_type = GridPoint;
void push_back(const GridPoint point) { _gridSpace.set(point, true); }
GridSpace& _gridSpace;
};
constexpr void setUnvalidated(GridPoint point, bool value) { _spaces[GridSpace::pointToIndex(point)] = value; }
public:
// Creates a new gridspace from an unsigned long.
constexpr GridSpace(uint64_t space = 0) : _spaces(space) { }
// Creates a new gridspace from a bitset.
constexpr GridSpace(std::bitset<AREA> space) : _spaces(space) { }
// Creates a new gridspace from a vector of coordiantes
[[nodiscard]] constexpr static GridSpace fromPoints(std::ranges::input_range auto&& points)
{
GridSpace gridSpace = GridSpace::EMPTY;
for (const GridPoint point : points)
{
MAX_BOUNDS.validateInBounds(point);
gridSpace._spaces.set(static_cast<size_t>(point.y * WIDTH + point.x));
}
return gridSpace;
}
// Creates a new gridspace from a rectangle.
[[nodiscard]] constexpr static GridSpace fromRectangle(GridPoint point, GridSize size)
{
return GridSpace::fromRectangle(GridRectangle{ point, size });
}
[[nodiscard]] constexpr static GridSpace fromRectangle(GridRectangle rectangle)
{
return MAX_BOUNDS.inBounds(rectangle) && rectangle.size != GridSize::EMPTY
? GridSpace::fromValididPoints(rectangle.getPoints())
: throw std::invalid_argument(std::format("Argument {} out of bounds {}", rectangle, MAX_BOUNDS));
}
// gets the value of the space at a point.
[[nodiscard]] constexpr bool operator[](GridPoint point) const
{
MAX_BOUNDS.validateInBounds(point);
return _spaces[GridSpace::pointToIndex(point)];
}
// sets the value of the space at a point.
constexpr void set(GridPoint point, bool value)
{
MAX_BOUNDS.validateInBounds(point);
_spaces[GridSpace::pointToIndex(point)] = value;
}
[[nodiscard]] constexpr const std::bitset<AREA>& getSpaces() const { return _spaces; }
// gets if any grid space is occupied.
[[nodiscard]] constexpr bool any() const { return _spaces.any(); }
// Gets the minimum GridBounds that can contains all occupied spaces in this grid.
[[nodiscard]] constexpr GridBounds getOccupiedSpaceBounds() const
{
if (_spaces.none()) { return GridBounds::EMPTY; }
auto occupiedPoints = getOccupiedPoints();
return std::accumulate(occupiedPoints.begin(), occupiedPoints.end(), GridSpace::MAX_BOUNDS,
[](GridBounds bounds, GridPoint point) {
bounds.min.x = std::min(bounds.min.x, point.x);
bounds.min.y = std::min(bounds.min.y, point.y);
bounds.max.x = std::max(bounds.max.x, point.x);
bounds.max.y = std::max(bounds.max.y, point.y);
return bounds;
});
}
// Gets a range of the occupied grid coordinates.
[[nodiscard]] constexpr std::ranges::range auto getOccupiedPoints() const
{
return std::views::iota(size_t{ 0 }, AREA)
| std::views::filter([&](size_t index) { return _spaces[index]; })
| std::views::transform(indexToPoint);
}
/**
* @brief Gets the area that is connected to point in a cardinal direction.
* @param point the point to check for connectivity with.
* @return A GridSpace that represents the connected area.
*/
[[nodiscard]] constexpr GridSpace getConnectedSpace(GridPoint point) const
{
GridSpace visitedSpaces(0);
auto isInside = [&](const GridPoint point) { return MAX_BOUNDS.inBounds(point) && (*this)[point] && !visitedSpaces[point]; };
if (!isInside(point)) { return visitedSpaces; } // early return allows the implementation logic to not worry about the out of bounds case.
// standard floodfill algo
std::vector<GridPoint> gridStack;
gridStack.push_back(point);
do {
GridPoint backPoint = gridStack.back();
gridStack.pop_back();
visitedSpaces = visitedSpaces | backPoint;
// filtering before we push the point saves on stack size
auto addDirection = [&backPoint](const GridPoint direction) { return backPoint + direction; };
auto newPoints = GridPoint::CARDINAL_DIRECTIONS | std::views::transform(addDirection) | std::views::filter(isInside);
std::ranges::copy(newPoints, std::back_inserter(gridStack));
} while (!gridStack.empty());
return visitedSpaces;
}
// Ands two GridSpaces together
[[nodiscard]] constexpr GridSpace operator&(const GridSpace& other) const { return GridSpace(_spaces & other._spaces); }
// Ors two GridSpaces together
[[nodiscard]] constexpr GridSpace operator|(const GridSpace& other) const { return GridSpace(_spaces | other._spaces); }
// Xors two GridSpaces together
[[nodiscard]] constexpr GridSpace operator^(const GridSpace& other) const { return GridSpace(_spaces ^ other._spaces); }
// flattens the given grid spaces via an OR merge
[[nodiscard]] constexpr static GridSpace flatten(std::ranges::view auto& gridSpaces)
{
return std::accumulate(gridSpaces.begin(), gridSpaces.end(), GridSpace(0), std::bit_or<>());
}
// Ors a GridSpace with a point.
[[nodiscard]] constexpr GridSpace operator|(const GridPoint point) const
{
MAX_BOUNDS.validateInBounds(point);
GridSpace result = *this;
result._spaces.set(static_cast<size_t>(point.y * WIDTH + point.x));
return result;
}
};
template<size_t WIDTH, size_t HEIGHT>
constexpr GridSpace<WIDTH, HEIGHT> GridSpace<WIDTH, HEIGHT>::EMPTY = { 0 };
using BaseGrid = GridSpace<6, 6>;
}
template <>
struct std::formatter<OpenXcom::GridPoint> : std::formatter<std::string_view>
{
auto format(const OpenXcom::GridPoint point, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "(x:{}, y:{})", point.x, point.y);
}
};
template <>
struct std::formatter<OpenXcom::GridSize> : std::formatter<std::string_view>
{
auto format(const OpenXcom::GridSize size, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "(w:{}, h:{})", size.w, size.h);
}
};
template <>
struct std::formatter<OpenXcom::GridBounds> : std::formatter<std::string_view>
{
auto format(const OpenXcom::GridBounds bounds, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "(min:{}, max:{})", bounds.min, bounds.max);
}
};
template <>
struct std::formatter<OpenXcom::GridRectangle> : std::formatter<std::string_view>
{
auto format(const OpenXcom::GridRectangle rectangle, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "(point:{}, size:{})", rectangle.point, rectangle.size);
}
};
template <size_t WIDTH, size_t HEIGHT>
struct std::formatter<OpenXcom::GridSpace<WIDTH, HEIGHT>> : std::formatter<string_view>
{
auto format(const OpenXcom::GridSpace<WIDTH, HEIGHT>& gridSpace, std::format_context& ctx) const
{
std::format_to(ctx.out(), "GridSpace:{}\n", OpenXcom::GridSpace<WIDTH, HEIGHT>::GRID_SIZE);
const auto& bitset = gridSpace.getSpaces();
for (size_t i = 0; i < bitset.size(); ++i)
{
std::format_to(ctx.out(), "{}", bitset[i] ? '1' : '0');
if ((i + 1) % WIDTH == 0) { std::format_to(ctx.out(), "\n"); }
}
return ctx.out();
}
};