diff --git a/src/game/Puzzle.cpp b/src/game/Puzzle.cpp new file mode 100644 index 0000000..28a9a23 --- /dev/null +++ b/src/game/Puzzle.cpp @@ -0,0 +1,112 @@ +#include "Puzzle.h" +#include + +namespace +{ + static constexpr Puzzle::PuzPos Dirs[4]{ + {-1, 0}, + {0, -1}, + {0, 1}, + {1, 0}, + }; +} + +namespace Puzzle +{ + bool PuzzleNode::HasElement(PuzzleElementType search) const + { + for (int32_t i = 0; i < Config::MaxElementsPerTile; ++i) + { + if (PlacedTypes[i] == search) return true; + } + return false; + } + + bool PuzzleNode::IsEmpty() const + { + for (int32_t i = 0; i < Config::MaxElementsPerTile; ++i) + { + if (PlacedTypes[i] != PuzzleElementType::None) return false; + } + return true; + } + + uint8_t PuzzleCardStack::GetRemainingCount() + { + assert(MaxAvailableCount >= UsedCount); + return MaxAvailableCount - UsedCount; + } + + bool PuzzleSolver::IsPuzzleSolved(const PuzzleData& puzzle) + { + bool IsSolved = true; + for (uint32_t i = 0; i < puzzle.GoalPositionCount; ++i) + { + } + return IsSolved; + } + + bool PuzzleSolver::IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos) + { + const PuzzleNode& goalNode = puzzle.GetNodeAt(pos.Position); + PuzzleElementType goalType = puzzle.GetElementAt(pos); + + uint32_t currentPositionQueueIdx = 0; + uint32_t positionQueueCount = 0; + PuzPos positionQueue[Config::MaxTilesInPuzzle]; + + positionQueue[0] = pos.Position; + positionQueueCount++; + while (positionQueueCount > 0) + { + assert(currentPositionQueueIdx < Config::MaxTilesInPuzzle); + PuzPos currentPos = positionQueue[currentPositionQueueIdx]; + for (PuzPos dir : Dirs) + { + PuzPos nextPos = currentPos + dir; + if (IsValidGoalConnection(puzzle, nextPos, currentPos, goalType)) + { + const PuzzleNode& node = puzzle.GetNodeAt(nextPos); + for (PuzzleElementType e : node.PlacedTypes) + { + if (IsValidSource(e, goalType)) + { + return true; + } + } + positionQueue[positionQueueCount] = nextPos; + positionQueueCount++; + } + } + currentPositionQueueIdx++; + } + return false; + } + + bool PuzzleSolver::IsValidGoalConnection(const PuzzleData& puzzle, + PuzPos flowFrom, + PuzPos flowTo, + PuzzleElementType goalType) + { + const PuzzleNode& from = puzzle.GetNodeAt(flowFrom); + const PuzzleNode& to = puzzle.GetNodeAt(flowTo); + + if (goalType == PuzzleElementType::WaterGoal) + { + return from.HasElement(PuzzleElementType::WaterIn) || from.HasElement(PuzzleElementType::WaterChannel) || + from.HasElement(PuzzleElementType::WaterGoal); + } + else if (goalType == PuzzleElementType::ElectricGoal) + { + return from.HasElement(PuzzleElementType::ElectricIn) || from.HasElement(PuzzleElementType::ElectricGoal) || + from.IsEmpty(); + } + assert(false); + return false; + } + bool PuzzleSolver::IsValidSource(PuzzleElementType sourceType, PuzzleElementType goalType) + { + return (sourceType == PuzzleElementType::WaterIn && goalType == PuzzleElementType::WaterGoal) || + (sourceType == PuzzleElementType::ElectricIn && goalType == PuzzleElementType::ElectricGoal); + } +} // namespace Puzzle diff --git a/src/game/Puzzle.h b/src/game/Puzzle.h new file mode 100644 index 0000000..31e8087 --- /dev/null +++ b/src/game/Puzzle.h @@ -0,0 +1,120 @@ +#pragma once +#include + +namespace Puzzle +{ + struct Config + { + static constexpr uint32_t CardSize = 2; + static constexpr uint32_t NodesPerCard = CardSize * CardSize; + static constexpr uint32_t MaxElementsPerTile = 4; + static constexpr uint32_t MaxPuzzleSizeCards = 16; + static constexpr uint32_t MaxCardsInPuzzle = MaxPuzzleSizeCards * MaxPuzzleSizeCards; + static constexpr uint32_t MaxPuzzleSizeTiles = 16 * CardSize; + static constexpr uint32_t MaxTilesInPuzzle = MaxPuzzleSizeTiles * MaxPuzzleSizeTiles; + static constexpr uint32_t MaxAvailableStacks = 16; + static constexpr uint32_t MaxGoalPositions = 16; + }; + + struct PuzPos + { + int8_t X = 0; + int8_t Y = 0; + + PuzPos& operator+=(const PuzPos& rhs) + { + X += rhs.X; + Y += rhs.Y; + return *this; + } + + friend PuzPos operator+(PuzPos lhs, const PuzPos& rhs) + { + lhs += rhs; + return lhs; + } + }; + + struct ElemPos + { + PuzPos Position; + uint8_t ElemIdx = 0; + }; + + enum class PuzzleElementType : uint8_t + { + None, + WaterIn, + WaterGoal, + WaterChannel, + ElectricIn, + ElectricGoal, + Blocked, + Bridge, + }; + + struct PuzzleNode + { + PuzzleElementType PlacedTypes[Config::MaxElementsPerTile]{PuzzleElementType::None}; + + bool HasElement(PuzzleElementType search) const; + bool IsEmpty() const; + }; + + struct StaticPuzzleCard + { + PuzzleNode Nodes[Config::NodesPerCard]; + }; + + struct StaticPuzzleCardHandle + { + uint16_t Idx = UINT16_MAX; + }; + + struct PlacedPuzzleCard + { + StaticPuzzleCardHandle RefCard; + PuzPos Position; + uint8_t Rotation = 0; + bool IsLocked = false; + }; + + struct PuzzleCardStack + { + StaticPuzzleCardHandle RefCard; + uint8_t MaxAvailableCount = 0; + uint8_t UsedCount = 0; + + uint8_t GetRemainingCount(); + }; + + struct PuzzleData + { + uint32_t AvailableCardCount = 0; + PuzzleCardStack AvailableCards[Config::MaxAvailableStacks]; + uint32_t PlacedCardCount = 0; + PlacedPuzzleCard PlacedCards[Config::MaxCardsInPuzzle]; + + // Indexed by board position + PuzzleNode PlacedNodes[Config::MaxTilesInPuzzle]; + + uint32_t GoalPositionCount = 0; + ElemPos GoalPositions[Config::MaxGoalPositions]; + + const PuzzleNode& GetNodeAt(PuzPos pos) const; + PuzzleElementType GetElementAt(ElemPos pos) const; + }; + + struct PuzzleSolver + { + bool IsPuzzleSolved(const PuzzleData& puzzle); + bool IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos); + + // This assumes flowFrom is already verified to be connected. + bool IsValidGoalConnection(const PuzzleData& puzzle, + PuzPos flowFrom, + PuzPos flowTo, + PuzzleElementType goalType); + bool IsValidSource(PuzzleElementType sourceType, PuzzleElementType goalType); + }; +} // namespace Puzzle