#include "Log.h" #include "Puzzle.h" #include "bx/string.h" #include "imgui.h" #include "rendering/Rendering.h" #include "bx/bx.h" #include namespace { static constexpr Generated::PuzPos Dirs[4]{ {-1, 0}, {0, -1}, {0, 1}, {1, 0}, }; Generated::StaticPuzzleData* StaticDataInstance = nullptr; } // namespace namespace Generated { void Setup(StaticPuzzleData& data) { StaticDataInstance = &data; LOG("Setting up static puzzle data"); for (int32_t i = 0; i < BX_COUNTOF(data.Cards); ++i) { data.Cards[i].ModelHandle = Game::GameRendering::Get().GetModelHandleFromPath("models/w straight.glb"); } } StaticPuzzleData& GetStaticPuzzleData() { assert(StaticDataInstance != nullptr); return *StaticDataInstance; } const StaticPuzzleCard& GetCard(const StaticPuzzleData& data, StaticPuzzleCardHandle H) { assert(IsValid(H)); return data.Cards[H.Idx]; } bool HasElement(const PuzzleNode& node, PuzzleElementType::Enum search) { for (int32_t i = 0; i < Puzzle::Config::MaxElementsPerTile; ++i) { if (node.PlacedTypes[i] == search) return true; } return false; } bool IsEmpty(const PuzzleNode& node) { for (int32_t i = 0; i < Puzzle::Config::MaxElementsPerTile; ++i) { if (node.PlacedTypes[i] != PuzzleElementType::None) return false; } return true; } bool IsValid(StaticPuzzleCardHandle h) { return h.Idx != UINT16_MAX; } uint8_t GetRemainingCount(const PuzzleCardStack& stack) { assert(stack.MaxAvailableCount >= stack.UsedCount); return stack.MaxAvailableCount - stack.UsedCount; } const PuzzleNode& GetNodeAt(const PuzzleData& puz, PuzPos pos) { assert(pos.X < Puzzle::Config::MaxPuzzleSizeCards && pos.Y < Puzzle::Config::MaxPuzzleSizeCards && pos.X >= 0 && pos.Y >= 0); return puz.PlacedNodes[pos.Y * Puzzle::Config::MaxPuzzleSizeCards + pos.X]; } PuzzleElementType::Enum GetElementAt(const PuzzleData& puz, ElemPos pos) { assert(pos.ElemIdx < Puzzle::Config::MaxElementsPerTile); const PuzzleNode& node = GetNodeAt(puz, pos.Position); return node.PlacedTypes[pos.ElemIdx]; } bool PuzzleSolver::IsPuzzleSolved(const PuzzleData& puzzle) { bool IsSolved = true; for (uint32_t i = 0; i < puzzle.GoalPositionCount; ++i) { if (!IsExitSatisfied(puzzle, puzzle.GoalPositions[i])) { IsSolved = false; break; } } return IsSolved; } bool PuzzleSolver::IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos) { const PuzzleNode& goalNode = GetNodeAt(puzzle, pos.Position); PuzzleElementType::Enum goalType = GetElementAt(puzzle, pos); uint32_t currentPositionQueueIdx = 0; uint32_t positionQueueCount = 0; PuzPos positionQueue[Puzzle::Config::MaxTilesInPuzzle]; positionQueue[0] = pos.Position; positionQueueCount++; while (positionQueueCount > 0) { assert(currentPositionQueueIdx < Puzzle::Config::MaxTilesInPuzzle); PuzPos currentPos = positionQueue[currentPositionQueueIdx]; for (PuzPos dir : Dirs) { PuzPos nextPos = currentPos + dir; if (IsValidGoalConnection(puzzle, nextPos, currentPos, goalType)) { const PuzzleNode& node = GetNodeAt(puzzle, nextPos); for (PuzzleElementType::Enum 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::Enum goalType) { const PuzzleNode& from = GetNodeAt(puzzle, flowFrom); const PuzzleNode& to = GetNodeAt(puzzle, flowTo); if (goalType == PuzzleElementType::WaterGoal) { return HasElement(from, PuzzleElementType::WaterIn) || HasElement(from, PuzzleElementType::WaterChannel) || HasElement(from, PuzzleElementType::WaterGoal); } else if (goalType == PuzzleElementType::ElectricGoal) { return HasElement(from, PuzzleElementType::ElectricIn) || HasElement(from, PuzzleElementType::ElectricGoal) || IsEmpty(from); } assert(false); return false; } bool PuzzleSolver::IsValidSource(PuzzleElementType::Enum sourceType, PuzzleElementType::Enum goalType) { return (sourceType == PuzzleElementType::WaterIn && goalType == PuzzleElementType::WaterGoal) || (sourceType == PuzzleElementType::ElectricIn && goalType == PuzzleElementType::ElectricGoal); } } // namespace Generated namespace Generated { bool RenderDebugUI(PuzzleData& obj) { bool dataChanged = false; bool isVisible = true; if (ImGui::Begin("Puzzle", &isVisible)) { ImGui::Text("%s", obj.PuzzleName); int32_t W = obj.WidthTiles; int32_t H = obj.HeightTiles; ImGui::PushID("width"); ImGui::SetNextItemWidth(40); if (ImGui::DragInt("", &W, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles)) { obj.WidthTiles = uint8_t(W); dataChanged = true; } ImGui::PopID(); ImGui::SameLine(); ImGui::Text("x"); ImGui::SameLine(); ImGui::SetNextItemWidth(40); ImGui::PushID("height"); if (ImGui::DragInt("", &H, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles)) { obj.HeightTiles = uint8_t(H); dataChanged = true; } ImGui::PopID(); } ImGui::End(); if (dataChanged) { char path[128]{0}; bx::snprintf(path, sizeof(path), "game/data/puzzles/%s.pzl", obj.PuzzleName); Serializer ser; ser.Init(path); if (Save(&obj, 1, ser)) { LOG("Saved to %s", path); } else { LOG_ERROR("Failed to save to %s", path); } ser.Finish(); } return isVisible; } PuzPos operator+=(PuzPos lhs, const PuzPos& rhs) { lhs.X += rhs.X; lhs.Y += rhs.Y; return lhs; } PuzPos operator+(PuzPos lhs, const PuzPos& rhs) { lhs += rhs; return lhs; } PuzPos operator-=(PuzPos lhs, const PuzPos& rhs) { lhs.X -= rhs.X; lhs.Y -= rhs.Y; return lhs; } PuzPos operator-(PuzPos lhs, const PuzPos& rhs) { lhs -= rhs; return lhs; } } // namespace Generated