Files
PuzGame/src/game/Puzzle.cpp
2025-03-11 01:19:13 +01:00

246 lines
7.3 KiB
C++

#include "Log.h"
#include "Puzzle.h"
#include "bx/string.h"
#include "imgui.h"
#include "rendering/Rendering.h"
#include "bx/bx.h"
#include <cassert>
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