Use std::optional instead of sentinel values for absent data
This commit is contained in:
@@ -346,7 +346,7 @@ std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe
|
||||
// Placement
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
Rotation rotation, Tick currentTick)
|
||||
{
|
||||
const BuildingDef* def = findBuildingDef(type);
|
||||
@@ -356,7 +356,7 @@ BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
// Reject placements that fall outside the world (REQ-BLD-PLACE-VALID).
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
|
||||
@@ -43,13 +43,13 @@ public:
|
||||
std::mt19937& rng);
|
||||
|
||||
// -- Placement / demolish ------------------------------------------------
|
||||
// Returns the new entity id, or kInvalidBuildingId if the placement falls
|
||||
// outside the world bounds (vertical extent and asteroid left edge). Belt
|
||||
// and Splitter register with BeltSystem directly; other types enter the
|
||||
// construction queue. Terrain type (A vs S) is NOT checked here so that
|
||||
// tests can stage arbitrary layouts; the player-facing entry point
|
||||
// Returns the new entity id, or nullopt if the placement falls outside the
|
||||
// world bounds (vertical extent and asteroid left edge). Belt and Splitter
|
||||
// register with BeltSystem directly; other types enter the construction
|
||||
// queue. Terrain type (A vs S) is NOT checked here so that tests can stage
|
||||
// arbitrary layouts; the player-facing entry point
|
||||
// (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
|
||||
BuildingId place(BuildingType type, QPoint anchor, Rotation rotation,
|
||||
std::optional<BuildingId> place(BuildingType type, QPoint anchor, Rotation rotation,
|
||||
Tick currentTick);
|
||||
|
||||
// Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and
|
||||
|
||||
@@ -75,28 +75,28 @@ struct PlaceBuildingCommand : Command
|
||||
struct DemolishCommand : Command
|
||||
{
|
||||
DemolishCommand() : Command(CommandKind::Demolish) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::optional<BuildingId> id;
|
||||
};
|
||||
|
||||
struct RotateInPlaceCommand : Command
|
||||
{
|
||||
RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
Rotation newRotation = Rotation::East;
|
||||
std::optional<BuildingId> id;
|
||||
Rotation newRotation = Rotation::East;
|
||||
};
|
||||
|
||||
struct SetRecipeCommand : Command
|
||||
{
|
||||
SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::string recipeId;
|
||||
std::optional<BuildingId> id;
|
||||
std::string recipeId;
|
||||
};
|
||||
|
||||
struct SetShipLayoutCommand : Command
|
||||
{
|
||||
SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
ShipLayoutConfig layout;
|
||||
std::optional<BuildingId> id;
|
||||
ShipLayoutConfig layout;
|
||||
};
|
||||
|
||||
// Splitter filters for a queued / under-construction Splitter site (configured by
|
||||
@@ -104,8 +104,8 @@ struct SetShipLayoutCommand : Command
|
||||
struct SetSiteSplitterFiltersCommand : Command
|
||||
{
|
||||
SetSiteSplitterFiltersCommand() : Command(CommandKind::SetSiteSplitterFilters) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::vector<ItemType> filterA;
|
||||
std::optional<BuildingId> id;
|
||||
std::vector<ItemType> filterA;
|
||||
std::vector<ItemType> filterB;
|
||||
};
|
||||
|
||||
|
||||
@@ -129,24 +129,24 @@ std::string serializeCommand(const Command& command)
|
||||
break;
|
||||
}
|
||||
case CommandKind::Demolish:
|
||||
out << "demolish " << static_cast<const DemolishCommand&>(command).id;
|
||||
out << "demolish " << static_cast<const DemolishCommand&>(command).id.value();
|
||||
break;
|
||||
case CommandKind::RotateInPlace:
|
||||
{
|
||||
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
|
||||
out << "rotate " << c.id << ' ' << rotationToChar(c.newRotation);
|
||||
out << "rotate " << c.id.value() << ' ' << rotationToChar(c.newRotation);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetRecipe:
|
||||
{
|
||||
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
|
||||
out << "setrecipe " << c.id << ' ' << c.recipeId;
|
||||
out << "setrecipe " << c.id.value() << ' ' << c.recipeId;
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetShipLayout:
|
||||
{
|
||||
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
|
||||
out << "setlayout " << c.id << ' ';
|
||||
out << "setlayout " << c.id.value() << ' ';
|
||||
appendLayout(out, c.layout);
|
||||
break;
|
||||
}
|
||||
@@ -154,7 +154,7 @@ std::string serializeCommand(const Command& command)
|
||||
{
|
||||
const SetSiteSplitterFiltersCommand& c =
|
||||
static_cast<const SetSiteSplitterFiltersCommand&>(command);
|
||||
out << "sitefilters " << c.id << ' ';
|
||||
out << "sitefilters " << c.id.value() << ' ';
|
||||
appendFilters(out, c.filterA, c.filterB);
|
||||
break;
|
||||
}
|
||||
@@ -242,27 +242,35 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
|
||||
if (verb == "demolish")
|
||||
{
|
||||
std::shared_ptr<DemolishCommand> c = std::make_shared<DemolishCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
return c;
|
||||
}
|
||||
if (verb == "rotate")
|
||||
{
|
||||
std::shared_ptr<RotateInPlaceCommand> c = std::make_shared<RotateInPlaceCommand>();
|
||||
std::string rotToken;
|
||||
if (!(in >> c->id >> rotToken)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id >> rotToken)) { return nullptr; }
|
||||
c->id = id;
|
||||
c->newRotation = rotationFromString(rotToken);
|
||||
return c;
|
||||
}
|
||||
if (verb == "setrecipe")
|
||||
{
|
||||
std::shared_ptr<SetRecipeCommand> c = std::make_shared<SetRecipeCommand>();
|
||||
if (!(in >> c->id >> c->recipeId)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id >> c->recipeId)) { return nullptr; }
|
||||
c->id = id;
|
||||
return c;
|
||||
}
|
||||
if (verb == "setlayout")
|
||||
{
|
||||
std::shared_ptr<SetShipLayoutCommand> c = std::make_shared<SetShipLayoutCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
c->layout = parseLayout(in, ok);
|
||||
if (!ok) { return nullptr; }
|
||||
return c;
|
||||
@@ -271,7 +279,9 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
|
||||
{
|
||||
std::shared_ptr<SetSiteSplitterFiltersCommand> c =
|
||||
std::make_shared<SetSiteSplitterFiltersCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
parseFilters(in, c->filterA, c->filterB, ok);
|
||||
if (!ok) { return nullptr; }
|
||||
return c;
|
||||
|
||||
@@ -40,7 +40,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
, m_nextBuildingId(1)
|
||||
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
||||
, m_gameOver(false)
|
||||
, m_hqBuildingId(kInvalidBuildingId)
|
||||
, m_hqProxyEntity(entt::null)
|
||||
, m_playerStation1Entity(entt::null)
|
||||
, m_playerStation2Entity(entt::null)
|
||||
@@ -137,7 +136,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_gameOver = false;
|
||||
m_isWon = false;
|
||||
m_artifactCount = 0;
|
||||
m_hqBuildingId = kInvalidBuildingId;
|
||||
m_hqBuildingId = std::nullopt;
|
||||
m_hqProxyEntity = entt::null;
|
||||
m_playerStation1Entity = entt::null;
|
||||
m_playerStation2Entity = entt::null;
|
||||
@@ -217,11 +216,12 @@ void Simulation::apply(const Command& command)
|
||||
case CommandKind::PlaceBuilding:
|
||||
{
|
||||
const PlaceBuildingCommand& c = static_cast<const PlaceBuildingCommand&>(command);
|
||||
const BuildingId id = tryPlaceBuilding(c.type, c.anchor, c.rotation);
|
||||
if (id == kInvalidBuildingId)
|
||||
const std::optional<BuildingId> placed = tryPlaceBuilding(c.type, c.anchor, c.rotation);
|
||||
if (!placed.has_value())
|
||||
{
|
||||
break;
|
||||
}
|
||||
const BuildingId id = *placed;
|
||||
if (c.recipeId.has_value())
|
||||
{
|
||||
m_buildingSystem->setRecipe(id, *c.recipeId);
|
||||
@@ -237,31 +237,31 @@ void Simulation::apply(const Command& command)
|
||||
break;
|
||||
}
|
||||
case CommandKind::Demolish:
|
||||
demolish(static_cast<const DemolishCommand&>(command).id);
|
||||
demolish(*static_cast<const DemolishCommand&>(command).id);
|
||||
break;
|
||||
case CommandKind::RotateInPlace:
|
||||
{
|
||||
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
|
||||
m_buildingSystem->rotateInPlace(c.id, c.newRotation);
|
||||
m_buildingSystem->rotateInPlace(*c.id, c.newRotation);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetRecipe:
|
||||
{
|
||||
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
|
||||
m_buildingSystem->setRecipe(c.id, c.recipeId);
|
||||
m_buildingSystem->setRecipe(*c.id, c.recipeId);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetShipLayout:
|
||||
{
|
||||
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
|
||||
m_buildingSystem->setShipLayout(c.id, c.layout);
|
||||
m_buildingSystem->setShipLayout(*c.id, c.layout);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetSiteSplitterFilters:
|
||||
{
|
||||
const SetSiteSplitterFiltersCommand& c =
|
||||
static_cast<const SetSiteSplitterFiltersCommand&>(command);
|
||||
m_buildingSystem->setSiteSplitterFilters(c.id, c.filterA, c.filterB);
|
||||
m_buildingSystem->setSiteSplitterFilters(*c.id, c.filterA, c.filterB);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetSplitterFilters:
|
||||
@@ -1193,11 +1193,11 @@ bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const
|
||||
return it->second.unlocked;
|
||||
}
|
||||
|
||||
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||
std::optional<BuildingId> Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||
{
|
||||
if (!m_buildingSystem->isPlacementValid(type, anchor, rotation))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int cost = 0;
|
||||
@@ -1211,7 +1211,7 @@ BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotati
|
||||
}
|
||||
if (m_buildingBlocksStock < cost)
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
m_buildingBlocksStock -= cost;
|
||||
return m_buildingSystem->place(type, anchor, rotation, m_currentTick);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -131,8 +132,8 @@ private:
|
||||
// Reached during play exclusively via apply(); never called by UI/app code.
|
||||
|
||||
// Checks affordability, deducts building blocks, and places the building.
|
||||
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
// Returns the new entity id, or nullopt if blocks are insufficient.
|
||||
std::optional<BuildingId> tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
|
||||
// Demolishes the building with the given id and refunds building blocks.
|
||||
void demolish(BuildingId id);
|
||||
@@ -182,7 +183,7 @@ private:
|
||||
int m_artifactCount = 0;
|
||||
|
||||
// Pre-placed structure IDs.
|
||||
BuildingId m_hqBuildingId; // Building id (for belt integration)
|
||||
std::optional<BuildingId> m_hqBuildingId; // Building id (for belt integration)
|
||||
entt::entity m_hqProxyEntity; // ECS entity (HP, targeting)
|
||||
entt::entity m_playerStation1Entity;
|
||||
entt::entity m_playerStation2Entity;
|
||||
|
||||
Reference in New Issue
Block a user