small refactor
This commit is contained in:
843
src/puzzle.cpp
843
src/puzzle.cpp
@ -1,396 +1,509 @@
|
||||
#include "puzzle.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef TRACY
|
||||
#include "tracy.hpp"
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#endif
|
||||
|
||||
auto Block::Hash() const -> std::size_t {
|
||||
std::string s = std::format("{},{},{},{}", x, y, width, height);
|
||||
return std::hash<std::string>{}(s);
|
||||
auto puzzle::block::hash() const -> size_t
|
||||
{
|
||||
const std::string s = std::format("{},{},{},{}", x, y, width, height);
|
||||
return std::hash<std::string>{}(s);
|
||||
}
|
||||
|
||||
auto Block::Invalid() -> Block {
|
||||
Block block = Block(0, 0, 1, 1, false, false);
|
||||
block.width = 0;
|
||||
block.height = 0;
|
||||
return block;
|
||||
auto puzzle::block::valid() const -> bool
|
||||
{
|
||||
return width > 0 && height > 0 && x >= 0 && x + width <= 9 && y >= 0 && y + height <= 9;
|
||||
}
|
||||
|
||||
auto Block::IsValid() const -> bool { return width != 0 && height != 0; }
|
||||
|
||||
auto Block::ToString() const -> std::string {
|
||||
if (target) {
|
||||
return std::format("{}{}",
|
||||
static_cast<char>(width + static_cast<int>('a') - 1),
|
||||
static_cast<char>(height + static_cast<int>('a') - 1));
|
||||
} else if (immovable) {
|
||||
return std::format("{}{}",
|
||||
static_cast<char>(width + static_cast<int>('A') - 1),
|
||||
static_cast<char>(height + static_cast<int>('A') - 1));
|
||||
} else {
|
||||
auto puzzle::block::string() const -> std::string
|
||||
{
|
||||
if (target) {
|
||||
return std::format("{}{}", static_cast<char>(width + static_cast<int>('a') - 1),
|
||||
static_cast<char>(height + static_cast<int>('a') - 1));
|
||||
}
|
||||
if (immovable) {
|
||||
return std::format("{}{}", static_cast<char>(width + static_cast<int>('A') - 1),
|
||||
static_cast<char>(height + static_cast<int>('A') - 1));
|
||||
}
|
||||
return std::format("{}{}", width, height);
|
||||
}
|
||||
}
|
||||
|
||||
auto Block::GetPrincipalDirs() const -> int {
|
||||
if (immovable) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (width > height) {
|
||||
return Direction::EAS | Direction::WES;
|
||||
} else if (height > width) {
|
||||
return Direction::NOR | Direction::SOU;
|
||||
} else {
|
||||
return Direction::NOR | Direction::EAS | Direction::SOU | Direction::WES;
|
||||
}
|
||||
}
|
||||
|
||||
auto Block::Covers(int xx, int yy) const -> bool {
|
||||
return xx >= x && xx < x + width && yy >= y && yy < y + height;
|
||||
}
|
||||
|
||||
auto Block::Collides(const Block &other) const -> bool {
|
||||
return x < other.x + other.width && x + width > other.x &&
|
||||
y < other.y + other.height && y + height > other.y;
|
||||
}
|
||||
|
||||
auto State::Hash() const -> std::size_t {
|
||||
return std::hash<std::string>{}(state);
|
||||
}
|
||||
|
||||
auto State::HasWinCondition() const -> bool {
|
||||
return target_x != 9 && target_y != 9;
|
||||
}
|
||||
|
||||
auto State::IsWon() const -> bool {
|
||||
if (!HasWinCondition()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &block : *this) {
|
||||
if (block.target) {
|
||||
return block.x == target_x && block.y == target_y;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto State::SetGoal(int x, int y) -> bool {
|
||||
Block target_block = GetTargetBlock();
|
||||
if (!target_block.IsValid() || x < 0 || x + target_block.width > width ||
|
||||
y < 0 || y + target_block.height > height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target_x == x && target_y == y) {
|
||||
target_x = 9;
|
||||
target_y = 9;
|
||||
} else {
|
||||
target_x = x;
|
||||
target_y = y;
|
||||
}
|
||||
|
||||
state.replace(3, 1, std::format("{}", target_x));
|
||||
state.replace(4, 1, std::format("{}", target_y));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::ClearGoal() -> void {
|
||||
target_x = 9;
|
||||
target_y = 9;
|
||||
state.replace(3, 2, "99");
|
||||
}
|
||||
|
||||
auto State::AddColumn() const -> State {
|
||||
State newstate = State(width + 1, height, restricted);
|
||||
|
||||
for (const auto &block : *this) {
|
||||
newstate.AddBlock(block);
|
||||
}
|
||||
|
||||
return newstate;
|
||||
}
|
||||
|
||||
auto State::RemoveColumn() const -> State {
|
||||
State newstate = State(width - 1, height, restricted);
|
||||
|
||||
for (const auto &block : *this) {
|
||||
newstate.AddBlock(block);
|
||||
}
|
||||
|
||||
return newstate;
|
||||
}
|
||||
|
||||
auto State::AddRow() const -> State {
|
||||
State newstate = State(width, height + 1, restricted);
|
||||
|
||||
for (const auto &block : *this) {
|
||||
newstate.AddBlock(block);
|
||||
}
|
||||
|
||||
return newstate;
|
||||
}
|
||||
|
||||
auto State::RemoveRow() const -> State {
|
||||
State newstate = State(width, height - 1, restricted);
|
||||
|
||||
for (const auto &block : *this) {
|
||||
newstate.AddBlock(block);
|
||||
}
|
||||
|
||||
return newstate;
|
||||
}
|
||||
|
||||
auto State::AddBlock(const Block &block) -> bool {
|
||||
if (block.x + block.width > width || block.y + block.height > height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Block b : *this) {
|
||||
if (b.Collides(block)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int index = GetIndex(block.x, block.y);
|
||||
state.replace(index, 2, block.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::GetBlock(int x, int y) const -> Block {
|
||||
if (x >= width || y >= height) {
|
||||
return Block::Invalid();
|
||||
}
|
||||
|
||||
for (Block b : *this) {
|
||||
if (b.Covers(x, y)) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
return Block::Invalid();
|
||||
}
|
||||
|
||||
auto State::GetBlockAt(int x, int y) const -> std::string {
|
||||
return state.substr(GetIndex(x, y), 2);
|
||||
}
|
||||
|
||||
auto State::GetTargetBlock() const -> Block {
|
||||
for (Block b : *this) {
|
||||
if (b.target) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
return Block::Invalid();
|
||||
}
|
||||
|
||||
auto State::GetIndex(int x, int y) const -> int {
|
||||
return prefix + (y * width + x) * 2;
|
||||
}
|
||||
|
||||
auto State::RemoveBlock(int x, int y) -> bool {
|
||||
Block block = GetBlock(x, y);
|
||||
if (!block.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = GetIndex(block.x, block.y);
|
||||
state.replace(index, 2, "..");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::ToggleTarget(int x, int y) -> bool {
|
||||
Block block = GetBlock(x, y);
|
||||
if (!block.IsValid() || block.immovable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove the current target
|
||||
int index;
|
||||
for (const auto &b : *this) {
|
||||
if (b.target) {
|
||||
index = GetIndex(b.x, b.y);
|
||||
state.replace(index, 2,
|
||||
Block(b.x, b.y, b.width, b.height, false).ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new target
|
||||
block.target = !block.target;
|
||||
index = GetIndex(block.x, block.y);
|
||||
state.replace(index, 2, block.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::ToggleWall(int x, int y) -> bool {
|
||||
Block block = GetBlock(x, y);
|
||||
if (!block.IsValid() || block.target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the new target
|
||||
block.immovable = !block.immovable;
|
||||
int index = GetIndex(block.x, block.y);
|
||||
state.replace(index, 2, block.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::ToggleRestricted() -> void {
|
||||
restricted = !restricted;
|
||||
state.replace(0, 1, restricted ? "R" : "F");
|
||||
}
|
||||
|
||||
auto State::MoveBlockAt(int x, int y, Direction dir) -> bool {
|
||||
Block block = GetBlock(x, y);
|
||||
if (!block.IsValid() || block.immovable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int dirs = restricted ? block.GetPrincipalDirs()
|
||||
: Direction::NOR | Direction::EAS | Direction::SOU |
|
||||
Direction::WES;
|
||||
|
||||
// Get target block
|
||||
int _target_x = block.x;
|
||||
int _target_y = block.y;
|
||||
switch (dir) {
|
||||
case Direction::NOR:
|
||||
if (!(dirs & Direction::NOR) || _target_y < 1) {
|
||||
return false;
|
||||
}
|
||||
_target_y--;
|
||||
break;
|
||||
case Direction::EAS:
|
||||
if (!(dirs & Direction::EAS) || _target_x + block.width >= width) {
|
||||
return false;
|
||||
}
|
||||
_target_x++;
|
||||
break;
|
||||
case Direction::SOU:
|
||||
if (!(dirs & Direction::SOU) || _target_y + block.height >= height) {
|
||||
return false;
|
||||
}
|
||||
_target_y++;
|
||||
break;
|
||||
case Direction::WES:
|
||||
if (!(dirs & Direction::WES) || _target_x < 1) {
|
||||
return false;
|
||||
}
|
||||
_target_x--;
|
||||
break;
|
||||
}
|
||||
Block target =
|
||||
Block(_target_x, _target_y, block.width, block.height, block.target);
|
||||
|
||||
// Check collisions
|
||||
for (Block b : *this) {
|
||||
if (b != block && b.Collides(target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveBlock(x, y);
|
||||
AddBlock(target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto State::GetNextStates() const -> std::vector<State> {
|
||||
std::vector<State> new_states;
|
||||
|
||||
for (const Block &b : *this) {
|
||||
int dirs = restricted ? b.GetPrincipalDirs()
|
||||
: Direction::NOR | Direction::EAS | Direction::SOU |
|
||||
Direction::WES;
|
||||
|
||||
if (b.immovable) {
|
||||
continue;
|
||||
auto puzzle::block::principal_dirs() const -> int
|
||||
{
|
||||
if (immovable) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dirs & Direction::NOR) {
|
||||
State north = *this;
|
||||
if (north.MoveBlockAt(b.x, b.y, Direction::NOR)) {
|
||||
new_states.push_back(north);
|
||||
}
|
||||
if (width > height) {
|
||||
return eas | wes;
|
||||
}
|
||||
|
||||
if (dirs & Direction::EAS) {
|
||||
State east = *this;
|
||||
if (east.MoveBlockAt(b.x, b.y, Direction::EAS)) {
|
||||
new_states.push_back(east);
|
||||
}
|
||||
if (height > width) {
|
||||
return nor | sou;
|
||||
}
|
||||
|
||||
if (dirs & Direction::SOU) {
|
||||
State south = *this;
|
||||
if (south.MoveBlockAt(b.x, b.y, Direction::SOU)) {
|
||||
new_states.push_back(south);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirs & Direction::WES) {
|
||||
State west = *this;
|
||||
if (west.MoveBlockAt(b.x, b.y, Direction::WES)) {
|
||||
new_states.push_back(west);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new_states;
|
||||
return nor | eas | sou | wes;
|
||||
}
|
||||
|
||||
auto State::Closure() const
|
||||
-> std::pair<std::vector<State>,
|
||||
std::vector<std::pair<std::size_t, std::size_t>>> {
|
||||
auto puzzle::block::covers(const int _x, const int _y) const -> bool
|
||||
{
|
||||
return _x >= x && _x < x + width && _y >= y && _y < y + height;
|
||||
}
|
||||
|
||||
auto puzzle::block::collides(const block& b) const -> bool
|
||||
{
|
||||
return x < b.x + b.width && x + width > b.x && y < b.y + b.height && y + height > b.y;
|
||||
}
|
||||
|
||||
auto puzzle::get_index(const int x, const int y) const -> int
|
||||
{
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
errln("Trying to calculating index of invalid board coordinates ({}, {})", x, y);
|
||||
exit(1);
|
||||
}
|
||||
return PREFIX + (y * width + x) * 2;
|
||||
}
|
||||
|
||||
auto puzzle::hash() const -> size_t
|
||||
{
|
||||
return std::hash<std::string>{}(state);
|
||||
}
|
||||
|
||||
auto puzzle::has_win_condition() const -> bool
|
||||
{
|
||||
return target_x != MAX_WIDTH && target_y != MAX_HEIGHT;
|
||||
}
|
||||
|
||||
auto puzzle::won() const -> bool
|
||||
{
|
||||
const std::optional<block>& b = try_get_target_block();
|
||||
return has_win_condition() && b && b->x == target_x && b->y == target_y;
|
||||
}
|
||||
|
||||
auto puzzle::valid() const -> bool
|
||||
{
|
||||
return width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT;
|
||||
}
|
||||
|
||||
auto puzzle::valid_thorough() const -> bool
|
||||
{
|
||||
if (has_win_condition() && !try_get_target_block()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
infoln("Validating puzzle {}", state);
|
||||
|
||||
if (static_cast<int>(state.length()) != width * height * 2 + PREFIX) {
|
||||
infoln("Puzzle invalid: Representation length {} doesn't match {}x{} board", state.length(), width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check prefix
|
||||
if (!std::string("FR").contains(state[0])) {
|
||||
infoln("Puzzle invalid: Representation[0] {} doesn't match [FR]", state[0]);
|
||||
return false;
|
||||
}
|
||||
if (restricted && state[0] != 'R') {
|
||||
infoln("Puzzle invalid: Representation[0] {} doesn't match restricted={}", state[0], restricted);
|
||||
return false;
|
||||
}
|
||||
if (!std::string("3456789").contains(state[1]) || !std::string("3456789").contains(state[2])) {
|
||||
infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match [3-9]", state[1], state[2]);
|
||||
return false;
|
||||
}
|
||||
if (std::stoi(state.substr(1, 1)) != width || std::stoi(state.substr(2, 1)) != height) {
|
||||
infoln("Puzzle invalid: Representation[1/2] {}/{} doesn't match {}x{} board", state[1], state[2], width,
|
||||
height);
|
||||
return false;
|
||||
}
|
||||
if (!std::string("012345678").contains(state[3]) || !std::string("012345678").contains(state[4])) {
|
||||
infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match [1-9]", state[1], state[2]);
|
||||
return false;
|
||||
}
|
||||
if (std::stoi(state.substr(3, 1)) != target_x || std::stoi(state.substr(4, 1)) != target_y) {
|
||||
infoln("Puzzle invalid: Representation[3/4] {}/{} doesn't match target ({}, {})", state[1], state[2], target_x,
|
||||
target_y);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check blocks
|
||||
const std::string allowed_chars = ".123456789abcdefghiABCDEFGHI";
|
||||
for (const char c : state.substr(PREFIX, state.length() - PREFIX)) {
|
||||
if (!allowed_chars.contains(c)) {
|
||||
infoln("Puzzle invalid: Block {} has invalid character", c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool success = width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT;
|
||||
|
||||
if (!success) {
|
||||
infoln("Puzzle invalid: Board size {}x{} not in range [3-9]", width, height);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
auto puzzle::try_get_block(const int x, const int y) const -> std::optional<block>
|
||||
{
|
||||
if (!covers(x, y)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const block& b : *this) {
|
||||
if (b.covers(x, y)) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto puzzle::try_get_target_block() const -> std::optional<block>
|
||||
{
|
||||
for (const block b : *this) {
|
||||
if (b.target) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto puzzle::covers(const int x, const int y, const int w, const int h) const -> bool
|
||||
{
|
||||
return x >= 0 && x + w <= width && y >= 0 && y + h <= height;
|
||||
}
|
||||
|
||||
auto puzzle::covers(const int x, const int y) const -> bool
|
||||
{
|
||||
return covers(x, y, 1, 1);
|
||||
}
|
||||
|
||||
auto puzzle::covers(const block& b) const -> bool
|
||||
{
|
||||
return covers(b.x, b.y, b.width, b.height);
|
||||
}
|
||||
|
||||
auto puzzle::try_toggle_restricted() const -> std::optional<puzzle>
|
||||
{
|
||||
puzzle p = *this;
|
||||
p.restricted = !restricted;
|
||||
p.state.replace(0, 1, p.restricted ? "R" : "F");
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_set_goal(const int x, const int y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_target_block();
|
||||
if (!b || !covers(x, y, b->width, b->height)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = *this;
|
||||
if (target_x == x && target_y == y) {
|
||||
p.target_x = MAX_WIDTH;
|
||||
p.target_y = MAX_HEIGHT;
|
||||
} else {
|
||||
p.target_x = x;
|
||||
p.target_y = y;
|
||||
}
|
||||
|
||||
p.state.replace(3, 1, std::format("{}", target_x));
|
||||
p.state.replace(4, 1, std::format("{}", target_y));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_clear_goal() const -> std::optional<puzzle>
|
||||
{
|
||||
puzzle p = *this;
|
||||
p.target_x = MAX_WIDTH;
|
||||
p.target_y = MAX_HEIGHT;
|
||||
p.state.replace(3, 2, "99");
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_add_column() const -> std::optional<puzzle>
|
||||
{
|
||||
if (width >= MAX_WIDTH) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = {width + 1, height, restricted};
|
||||
|
||||
// Non-fitting blocks won't be added
|
||||
for (const block& b : *this) {
|
||||
if (const std::optional<puzzle>& _p = p.try_add_block(b)) {
|
||||
p = *_p;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_remove_column() const -> std::optional<puzzle>
|
||||
{
|
||||
if (width <= MIN_WIDTH) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = {width - 1, height, restricted};
|
||||
|
||||
// Non-fitting blocks won't be added
|
||||
for (const block& b : *this) {
|
||||
if (const std::optional<puzzle>& _p = p.try_add_block(b)) {
|
||||
p = *_p;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_add_row() const -> std::optional<puzzle>
|
||||
{
|
||||
if (height >= 9) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = puzzle(width, height + 1, restricted);
|
||||
|
||||
for (const block& b : *this) {
|
||||
if (const std::optional<puzzle>& _p = p.try_add_block(b)) {
|
||||
p = *_p;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_remove_row() const -> std::optional<puzzle>
|
||||
{
|
||||
if (height == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = puzzle(width, height - 1, restricted);
|
||||
|
||||
for (const block& b : *this) {
|
||||
if (const std::optional<puzzle>& _p = p.try_add_block(b)) {
|
||||
p = *_p;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_add_block(const block& b) const -> std::optional<puzzle>
|
||||
{
|
||||
if (!covers(b)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (block _b : *this) {
|
||||
if (_b.collides(b)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
puzzle p = *this;
|
||||
const int index = get_index(b.x, b.y);
|
||||
p.state.replace(index, 2, b.string());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_remove_block(const int x, const int y) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_block(x, y);
|
||||
if (!b) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
puzzle p = *this;
|
||||
const int index = get_index(b->x, b->y);
|
||||
p.state.replace(index, 2, "..");
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_toggle_target(const int x, const int y) const -> std::optional<puzzle>
|
||||
{
|
||||
std::optional<block> b = try_get_block(x, y);
|
||||
if (!b || b->immovable) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Remove the current target if it exists
|
||||
puzzle p = *this;
|
||||
if (const std::optional<block>& _b = try_get_target_block()) {
|
||||
const int index = get_index(_b->x, _b->y);
|
||||
p.state.replace(index, 2, block(_b->x, _b->y, _b->width, _b->height, false).string());
|
||||
}
|
||||
|
||||
// Add the new target
|
||||
b->target = !b->target;
|
||||
const int index = get_index(b->x, b->y);
|
||||
p.state.replace(index, 2, b->string());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_toggle_wall(const int x, const int y) const -> std::optional<puzzle>
|
||||
{
|
||||
std::optional<block> b = try_get_block(x, y);
|
||||
if (!b || b->target) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Add the new target
|
||||
puzzle p = *this;
|
||||
b->immovable = !b->immovable;
|
||||
const int index = get_index(b->x, b->y);
|
||||
p.state.replace(index, 2, b->string());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::try_move_block_at(const int x, const int y, const direction dir) const -> std::optional<puzzle>
|
||||
{
|
||||
const std::optional<block>& b = try_get_block(x, y);
|
||||
if (!b || b->immovable) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const int dirs = restricted ? b->principal_dirs() : nor | eas | sou | wes;
|
||||
|
||||
// Get target block
|
||||
int _target_x = b->x;
|
||||
int _target_y = b->y;
|
||||
switch (dir) {
|
||||
case nor:
|
||||
if (!(dirs & nor) || _target_y < 1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
_target_y--;
|
||||
break;
|
||||
case eas:
|
||||
if (!(dirs & eas) || _target_x + b->width >= width) {
|
||||
return std::nullopt;
|
||||
}
|
||||
_target_x++;
|
||||
break;
|
||||
case sou:
|
||||
if (!(dirs & sou) || _target_y + b->height >= height) {
|
||||
return std::nullopt;
|
||||
}
|
||||
_target_y++;
|
||||
break;
|
||||
case wes:
|
||||
if (!(dirs & wes) || _target_x < 1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
_target_x--;
|
||||
break;
|
||||
}
|
||||
const block moved_b = block(_target_x, _target_y, b->width, b->height, b->target);
|
||||
|
||||
// Check collisions
|
||||
for (const block& _b : *this) {
|
||||
if (_b != b && _b.collides(moved_b)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<puzzle> p = try_remove_block(x, y);
|
||||
if (!p) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
p = p->try_add_block(moved_b);
|
||||
if (!p) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
auto puzzle::find_adjacent_puzzles() const -> std::vector<puzzle>
|
||||
{
|
||||
std::vector<puzzle> puzzles;
|
||||
|
||||
for (const block& b : *this) {
|
||||
if (b.immovable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int dirs = restricted ? b.principal_dirs() : nor | eas | sou | wes;
|
||||
|
||||
if (dirs & nor) {
|
||||
if (const std::optional<puzzle>& north = try_move_block_at(b.x, b.y, nor)) {
|
||||
puzzles.push_back(*north);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirs & eas) {
|
||||
if (const std::optional<puzzle>& east = try_move_block_at(b.x, b.y, eas)) {
|
||||
puzzles.push_back(*east);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirs & sou) {
|
||||
if (const std::optional<puzzle>& south = try_move_block_at(b.x, b.y, sou)) {
|
||||
puzzles.push_back(*south);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirs & wes) {
|
||||
if (const std::optional<puzzle>& west = try_move_block_at(b.x, b.y, wes)) {
|
||||
puzzles.push_back(*west);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for (const puzzle& p : puzzles) {
|
||||
// println("Adjacent puzzle: {}", p.state);
|
||||
// }
|
||||
|
||||
return puzzles;
|
||||
}
|
||||
|
||||
auto puzzle::explore_state_space() const -> std::pair<std::vector<puzzle>, std::vector<std::pair<size_t, size_t>>>
|
||||
{
|
||||
#ifdef TRACY
|
||||
ZoneScoped;
|
||||
ZoneScoped;
|
||||
#endif
|
||||
|
||||
std::vector<State> states;
|
||||
std::vector<std::pair<std::size_t, std::size_t>> links;
|
||||
infoln("Exploring state space, this might take a while...");
|
||||
|
||||
// Helper to construct the links vector
|
||||
std::unordered_map<State, std::size_t> state_indices;
|
||||
std::vector<puzzle> state_pool;
|
||||
std::unordered_map<puzzle, std::size_t> state_indices; // Helper to construct the links vector
|
||||
std::vector<std::pair<size_t, std::size_t>> links;
|
||||
|
||||
// Buffer for all states we want to call GetNextStates() on
|
||||
std::unordered_set<State> remaining_states;
|
||||
remaining_states.insert(*this);
|
||||
// Buffer for all states we want to call GetNextStates() on
|
||||
std::unordered_set<puzzle> remaining_states;
|
||||
remaining_states.insert(*this);
|
||||
|
||||
do {
|
||||
const State current = *remaining_states.begin();
|
||||
remaining_states.erase(current);
|
||||
do {
|
||||
const puzzle current = *remaining_states.begin();
|
||||
remaining_states.erase(current);
|
||||
|
||||
if (!state_indices.contains(current)) {
|
||||
state_indices.emplace(current, states.size());
|
||||
states.push_back(current);
|
||||
}
|
||||
if (!state_indices.contains(current)) {
|
||||
state_indices.emplace(current, state_pool.size());
|
||||
state_pool.push_back(current);
|
||||
}
|
||||
|
||||
for (const State &s : current.GetNextStates()) {
|
||||
if (!state_indices.contains(s)) {
|
||||
remaining_states.insert(s);
|
||||
state_indices.emplace(s, states.size());
|
||||
states.push_back(s);
|
||||
}
|
||||
links.emplace_back(state_indices.at(current), state_indices.at(s));
|
||||
}
|
||||
} while (remaining_states.size() > 0);
|
||||
for (const puzzle& s : current.find_adjacent_puzzles()) {
|
||||
if (!state_indices.contains(s)) {
|
||||
remaining_states.insert(s);
|
||||
state_indices.emplace(s, state_pool.size());
|
||||
state_pool.push_back(s);
|
||||
}
|
||||
links.emplace_back(state_indices.at(current), state_indices.at(s));
|
||||
}
|
||||
} while (!remaining_states.empty());
|
||||
|
||||
std::cout << std::format("State space has size {} with {} transitions.",
|
||||
states.size(), links.size())
|
||||
<< std::endl;
|
||||
infoln("State space has size {} with {} transitions.", state_pool.size(), links.size());
|
||||
|
||||
return std::make_pair(states, links);
|
||||
return std::make_pair(state_pool, links);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user