diff --git a/default.puzzle b/default.puzzle index aabee79..9e2419c 100644 --- a/default.puzzle +++ b/default.puzzle @@ -11,20 +11,23 @@ R4599121212.................................. F449921....111222..11......111111.... # RushHour 1 -R6642..13..21..13..................ba..........12............21.............. - -# RushHour 2 R664231........13................12ba..........1221..12..12..21........31.... -# RushHour 3 +# RushHour 2 R66421231....1213..31........ba..121212..21................1221..........21.. -# RushHour 4 +# RushHour 3 R664231....12....1221......12..ba..1212..21..12....12......21......21..21.... -# RushHour 5 +# RushHour 4 R66421321....12....1212....13......ba....31....12........12..21..21....21.... +# RushHour + Walls 1 +R66421221..AA..........1221..12ba............1221..1321..........21..31...... + +# RushHour + Walls 2 +R664221....1212AA31..........12ba..12........12..21........21..12....21..AA.. + # Dad's Puzzler F4503bb..21......21..1111....121221......21.. diff --git a/include/config.hpp b/include/config.hpp index 2318ae6..56adeb3 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -58,5 +58,7 @@ constexpr Color BLOCK_COLOR = DARKGREEN; constexpr Color HL_BLOCK_COLOR = GREEN; constexpr Color TARGET_BLOCK_COLOR = RED; constexpr Color HL_TARGET_BLOCK_COLOR = ORANGE; +constexpr Color WALL_COLOR = BLACK; +constexpr Color HL_WALL_COLOR = DARKGRAY; #endif diff --git a/include/puzzle.hpp b/include/puzzle.hpp index d7902ef..c646e8a 100644 --- a/include/puzzle.hpp +++ b/include/puzzle.hpp @@ -21,8 +21,11 @@ enum Direction { // A block is represented as a 2-digit string "wh", where w is the block width // and h the block height. // The target block (to remove from the board) is represented as a 2-letter -// string "xy", where x is the block width and y the block height and +// lower-case string "xy", where x is the block width and y the block height and // width/height are represented by [abcdefghi] (~= [123456789]). +// Immovable blocks are represented as a 2-letter upper-case string "XY", where +// X is the block width and Y the block height and width/height are represented +// by [ABCDEFGHI] (~= [123456789]). class Block { public: int x; @@ -30,16 +33,21 @@ public: int width; int height; bool target; + bool immovable; public: - Block(int _x, int _y, int _width, int _height, bool _target) - : x(_x), y(_y), width(_width), height(_height), target(_target) { + Block(int _x, int _y, int _width, int _height, bool _target, bool _immovable) + : x(_x), y(_y), width(_width), height(_height), target(_target), + immovable(_immovable) { if (_x < 0 || _x + _width >= 10 || _y < 0 || _y + _height >= 10) { std::cerr << "Block must fit on a 9x9 board!" << std::endl; exit(1); } } + Block(int _x, int _y, int _width, int _height, bool _target) + : Block(_x, _y, _width, _height, _target, false) {} + Block(int _x, int _y, std::string block) : x(_x), y(_y) { if (block == "..") { this->x = 0; @@ -50,20 +58,34 @@ public: return; } - const std::array chars{'a', 'b', 'c', 'd', 'e', - 'f', 'g', 'h', 'i'}; + const std::array target_chars{'a', 'b', 'c', 'd', 'e', + 'f', 'g', 'h', 'i'}; target = false; - for (const char c : chars) { + for (const char c : target_chars) { if (block.contains(c)) { target = true; break; } } + const std::array immovable_chars{'A', 'B', 'C', 'D', 'E', + 'F', 'G', 'H', 'I'}; + + immovable = false; + for (const char c : immovable_chars) { + if (block.contains(c)) { + immovable = true; + break; + } + } + if (target) { width = static_cast(block.at(0)) - static_cast('a') + 1; height = static_cast(block.at(1)) - static_cast('a') + 1; + } else if (immovable) { + width = static_cast(block.at(0)) - static_cast('A') + 1; + height = static_cast(block.at(1)) - static_cast('A') + 1; } else { width = std::stoi(block.substr(0, 1)); height = std::stoi(block.substr(1, 1)); @@ -81,7 +103,7 @@ public: bool operator==(const Block &other) { return x == other.x && y == other.y && width && other.width && - target == other.target; + target == other.target && immovable == other.immovable; } bool operator!=(const Block &other) { return !(*this == other); } @@ -103,12 +125,13 @@ public: }; // A state is represented by a string "MWHXYblocks", where M is "R" -// (restricted) or "F" (free), W is the board width, H is the board height, X is -// the target block x goal, Y is the target block y goal and blocks is an -// enumeration of each of the board's cells, with each cell being a 2-letter or -// 2-digit block representation (a 3x3 board would have a string representation -// with length 5 + 3*3 * 2). The board's cells are enumerated from top-left to -// bottom-right with each block's pivot being its top-left corner. +// (restricted) or "F" (free), W is the board width, H is the board height, X +// is the target block x goal, Y is the target block y goal and blocks is an +// enumeration of each of the board's cells, with each cell being a 2-letter +// or 2-digit block representation (a 3x3 board would have a string +// representation with length 5 + 3*3 * 2). The board's cells are enumerated +// from top-left to bottom-right with each block's pivot being its top-left +// corner. class State { public: static constexpr int prefix = 5; @@ -250,6 +273,8 @@ public: auto ToggleTarget(int x, int y) -> bool; + auto ToggleWall(int x, int y) -> bool; + auto ToggleRestricted() -> void; auto MoveBlockAt(int x, int y, Direction dir) -> bool; diff --git a/src/input.cpp b/src/input.cpp index a207dd5..b9e6dc8 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -167,6 +167,10 @@ auto InputHandler::HandleKeys() -> void { state.current_state.ToggleTarget(sel_x, sel_y); state.ClearGraph(); state.edited = true; + } else if (IsKeyPressed(KEY_Y)) { + state.current_state.ToggleWall(sel_x, sel_y); + state.ClearGraph(); + state.edited = true; } else if (IsKeyPressed(KEY_LEFT) && state.current_state.width > 1) { state.current_state = state.current_state.RemoveColumn(); state.ClearGraph(); diff --git a/src/main.cpp b/src/main.cpp index 7f65bea..e1180b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,6 @@ #endif // TODO: Click states in the graph to display them in the board -// TODO: Add walls (unmoveable blocks) // NOTE: Tracy uses a huge amount of memory. For longer testing disable Tracy. diff --git a/src/puzzle.cpp b/src/puzzle.cpp index fec02ba..028e987 100644 --- a/src/puzzle.cpp +++ b/src/puzzle.cpp @@ -14,7 +14,7 @@ auto Block::Hash() const -> int { } auto Block::Invalid() -> Block { - Block block = Block(0, 0, 1, 1, false); + Block block = Block(0, 0, 1, 1, false, false); block.width = 0; block.height = 0; return block; @@ -27,12 +27,20 @@ auto Block::ToString() const -> std::string { return std::format("{}{}", static_cast(width + static_cast('a') - 1), static_cast(height + static_cast('a') - 1)); + } else if (immovable) { + return std::format("{}{}", + static_cast(width + static_cast('A') - 1), + static_cast(height + static_cast('A') - 1)); } else { 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) { @@ -194,7 +202,7 @@ auto State::RemoveBlock(int x, int y) -> bool { auto State::ToggleTarget(int x, int y) -> bool { Block block = GetBlock(x, y); - if (!block.IsValid()) { + if (!block.IsValid() || block.immovable) { return false; } @@ -217,6 +225,20 @@ auto State::ToggleTarget(int x, int y) -> bool { 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"); @@ -224,7 +246,7 @@ auto State::ToggleRestricted() -> void { auto State::MoveBlockAt(int x, int y, Direction dir) -> bool { Block block = GetBlock(x, y); - if (!block.IsValid()) { + if (!block.IsValid() || block.immovable) { return false; } @@ -285,6 +307,10 @@ auto State::GetNextStates() const -> std::vector { : Direction::NOR | Direction::EAS | Direction::SOU | Direction::WES; + if (b.immovable) { + continue; + } + if (dirs & Direction::NOR) { State north = *this; if (north.MoveBlockAt(b.x, b.y, Direction::NOR)) { diff --git a/src/renderer.cpp b/src/renderer.cpp index a33b705..2bd2b48 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -241,6 +241,12 @@ auto Renderer::DrawKlotski() -> void { } else { c = TARGET_BLOCK_COLOR; } + } else if (block.immovable) { + if (block.Covers(input.sel_x, input.sel_y)) { + c = HL_WALL_COLOR; + } else { + c = WALL_COLOR; + } } DrawRectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 + BLOCK_PADDING + block.x * block_size, @@ -360,7 +366,7 @@ auto Renderer::DrawMenu(const std::vector &masses) -> void { DARKGREEN); // Center column - draw_btn(1, 0, std::format("Select (LMB) / Move (W, A, S, D) / Target (T)"), + draw_btn(1, 0, std::format("Select (LMB) / Move (WASD) / Target/Wall (T/Y)"), DARKBLUE); draw_btn(1, 1, std::format("Add/Remove Col/Row (Arrow Keys)"), DARKBLUE); draw_btn(1, 2, std::format("Add/Remove Block (LMB/RMB) / Set Goal (MMB)"),