载入中...
搜索中...
未找到
TacticsTypes.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <array>
5#include <limits>
6#include <utility>
7
8namespace eve::tactics {
9 // namespace
10
12 if (state.moveCost <= 0)
13 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics cell movement cost must be positive", "cell.moveCost"));
14 std::sort(state.tags.begin(), state.tags.end());
15 state.tags.erase(std::unique(state.tags.begin(), state.tags.end()), state.tags.end());
16 if (cells_.contains(cellValue))
17 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics board cell already exists", "cell"));
18 cells_.emplace(cellValue, std::move(state));
20}
21
22bool BoardState::contains(Cell cellValue) const noexcept { return cells_.contains(cellValue); }
23
25 const auto found = cells_.find(cellValue);
26 if (found == cells_.end())
28 Diagnostic::error(DiagnosticCode::NotFound, "tactics board cell does not exist", "cell"));
29 return Result<CellState>::success(found->second);
30}
31
33 if (!subject.isValid())
34 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics placement requires a valid subject", "subject"));
35 const auto target = cells_.find(cellValue);
36 if (target == cells_.end())
37 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics placement cell does not exist", "cell"));
38 if (!target->second.passable)
39 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics placement cell is not passable", "cell"));
40 if (occupantByCell_.contains(cellValue))
41 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics placement cell is occupied", "cell"));
42 const std::string key = subjectKey(subject);
43 if (cellBySubject_.contains(key))
44 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics subject is already placed", "subject"));
45 occupantByCell_.emplace(cellValue, subject);
46 cellBySubject_.emplace(key, cellValue);
48}
49
51 if (!subject.isValid())
52 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics movement requires a valid subject", "subject"));
53 const std::string key = subjectKey(subject);
54 const auto source = cellBySubject_.find(key);
55 if (source == cellBySubject_.end())
56 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics movement subject is not placed", "subject"));
57 const auto target = cells_.find(destination);
58 if (target == cells_.end())
59 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics movement destination does not exist", "destination"));
60 if (!target->second.passable)
61 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics movement destination is not passable", "destination"));
62 if (occupantByCell_.contains(destination))
63 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics movement destination is occupied", "destination"));
64 const Cell oldCell = source->second;
65 occupantByCell_.erase(oldCell);
66 occupantByCell_.emplace(destination, subject);
67 source->second = destination;
69}
70
72 if (!subject.isValid())
73 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics removal requires a valid subject", "subject"));
74 const auto found = cellBySubject_.find(subjectKey(subject));
75 if (found == cellBySubject_.end())
76 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics subject is not placed", "subject"));
77 occupantByCell_.erase(found->second);
78 cellBySubject_.erase(found);
80}
81
82std::optional<SubjectRef> BoardState::occupant(Cell cellValue) const {
83 const auto found = occupantByCell_.find(cellValue);
84 return found == occupantByCell_.end() ? std::nullopt : std::optional<SubjectRef>(found->second);
85}
86
87std::optional<Cell> BoardState::position(SubjectRef subject) const {
88 if (!subject.isValid()) return std::nullopt;
89 const auto found = cellBySubject_.find(subjectKey(subject));
90 return found == cellBySubject_.end() ? std::nullopt : std::optional<Cell>(found->second);
91}
92
93std::vector<Cell> BoardState::cells() const {
94 std::vector<Cell> result;
95 result.reserve(cells_.size());
96 for (const auto& [coordinate, state] : cells_) {
97 (void)state;
98 result.push_back(coordinate);
99 }
100 return result;
101}
102
103std::vector<BoardCellRecord> BoardState::records() const {
104 std::vector<BoardCellRecord> result;
105 result.reserve(cells_.size());
106 for (const auto& [cellValue, state] : cells_) result.push_back({cellValue, state, occupant(cellValue)});
107 return result;
108}
109
110std::vector<Cell> BoardState::neighbours(Cell origin) const {
111 static constexpr std::array<Cell, 4> square4{{{1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, {0, -1, 0}}};
112 static constexpr std::array<Cell, 8> square8{{{1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {-1, 1, 0},
113 {-1, 0, 0}, {-1, -1, 0}, {0, -1, 0}, {1, -1, 0}}};
114 static constexpr std::array<Cell, 6> hex{{{1, 0, 0}, {1, -1, 0}, {0, -1, 0}, {-1, 0, 0}, {-1, 1, 0}, {0, 1, 0}}};
115 std::vector<Cell> result;
116 const auto append = [&](const auto& offsets) {
117 for (const Cell offset : offsets) {
118 const Cell candidate{origin.x + offset.x, origin.y + offset.y, origin.layer};
119 if (cells_.contains(candidate)) result.push_back(candidate);
120 }
121 };
122 switch (topology_) {
123 case BoardTopology::Square4: append(square4); break;
124 case BoardTopology::Square8: append(square8); break;
125 case BoardTopology::HexAxial: append(hex); break;
126 case BoardTopology::ExplicitGraph: {
127 constexpr int minimum = std::numeric_limits<int>::min();
128 auto it = edges_.lower_bound({origin, Cell{minimum, minimum, minimum}});
129 for (; it != edges_.end() && it->first.first == origin; ++it) result.push_back(it->first.second);
130 break;
131 }
132 }
133 return result;
134}
135
136Result<void> BoardState::addEdge(Cell from, Cell to, EdgeState state) {
137 if (!cells_.contains(from))
138 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics edge source cell does not exist", "board.edge.from"));
139 if (!cells_.contains(to))
140 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics edge destination cell does not exist", "board.edge.to"));
141 const std::vector<Cell> adjacent =
142 topology_ == BoardTopology::ExplicitGraph ? std::vector<Cell>{to} : neighbours(from);
143 if (from == to || std::find(adjacent.begin(), adjacent.end(), to) == adjacent.end())
144 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics edge endpoints are not adjacent", "board.edge"));
145 if (state.extraCost < 0)
146 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics edge extra cost must be non-negative", "board.edge.extraCost"));
147 const std::pair<Cell, Cell> key{from, to};
148 if (edges_.contains(key))
149 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics edge is already declared", "board.edge"));
150 edges_.emplace(key, std::move(state));
152}
153
154Result<EdgeState> BoardState::edge(Cell from, Cell to) const {
155 const auto found = edges_.find(std::pair<Cell, Cell>{from, to});
156 if (found == edges_.end())
158 Diagnostic::error(DiagnosticCode::NotFound, "tactics edge is not declared", "board.edge"));
159 return Result<EdgeState>::success(found->second);
160}
161
162std::optional<EdgeState> BoardState::tryEdge(Cell from, Cell to) const {
163 const auto found = edges_.find(std::pair<Cell, Cell>{from, to});
164 if (found == edges_.end()) return std::nullopt;
165 return found->second;
166}
167
168std::vector<BoardEdgeRecord> BoardState::edgeRecords() const {
169 std::vector<BoardEdgeRecord> result;
170 result.reserve(edges_.size());
171 for (const auto& [key, state] : edges_) result.push_back({key.first, key.second, state});
172 return result;
173}
174
175Result<void> BoardState::validateInvariants() const {
176 for (const auto& [key, state] : edges_) {
177 if (!cells_.contains(key.first) || !cells_.contains(key.second))
178 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvariantViolation, "tactics edge references a missing cell", "board.edge"));
179 }
180 if (occupantByCell_.size() != cellBySubject_.size())
181 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvariantViolation, "tactics occupancy indexes differ in size", "board"));
182 for (const auto& [cellValue, subject] : occupantByCell_) {
183 if (!cells_.contains(cellValue))
184 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvariantViolation, "tactics occupancy references a missing cell", "board.occupancy"));
185 const auto reverse = cellBySubject_.find(subjectKey(subject));
186 if (reverse == cellBySubject_.end() || reverse->second != cellValue)
187 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvariantViolation, "tactics occupancy reverse index is inconsistent", "board.occupancy"));
188 }
190}
191
192std::string BoardState::subjectKey(SubjectRef subject) { return subject.format(); }
193
194Result<void> TurnResourceSpec::validate() const {
195 if (actionPoints < 0 || movePoints < 0 || reactionPoints < 0)
196 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics consumable turn resources must be non-negative", "turnResources"));
198}
199
200std::string_view phaseName(BattlePhase phase) noexcept {
201 switch (phase) {
202 case BattlePhase::Setup: return "setup";
203 case BattlePhase::BattleStart: return "battle_start";
204 case BattlePhase::RoundStart: return "round_start";
205 case BattlePhase::TurnStart: return "turn_start";
206 case BattlePhase::Acting: return "acting";
207 case BattlePhase::Reaction: return "reaction";
208 case BattlePhase::TurnEnd: return "turn_end";
209 case BattlePhase::RoundEnd: return "round_end";
210 case BattlePhase::BattleEnd: return "battle_end";
211 }
212 return "unknown";
213}
214
215std::string_view statusName(BattleStatus status) noexcept {
216 switch (status) {
217 case BattleStatus::Setup: return "setup";
218 case BattleStatus::Running: return "running";
219 case BattleStatus::Ended: return "ended";
220 }
221 return "unknown";
222}
223
224std::string_view commandKindName(BattleCommandKind kind) noexcept {
225 switch (kind) {
226 case BattleCommandKind::Start: return "start";
227 case BattleCommandKind::Advance: return "advance";
228 case BattleCommandKind::Move: return "move";
229 case BattleCommandKind::Face: return "face";
230 case BattleCommandKind::Wait: return "wait";
231 case BattleCommandKind::EndTurn: return "end_turn";
232 case BattleCommandKind::Finish: return "finish";
233 case BattleCommandKind::OpenReaction: return "open_reaction";
234 case BattleCommandKind::AcceptReaction: return "accept_reaction";
235 case BattleCommandKind::DeclineReaction: return "decline_reaction";
236 case BattleCommandKind::DefeatUnit: return "defeat_unit";
237 case BattleCommandKind::RollRandom: return "roll_random";
238 case BattleCommandKind::UseAbility: return "use_ability";
239 }
240 return "unknown";
241}
242
243} // namespace eve::tactics
LogicalId target
int subject
Definition AnimSmr.cpp:163
std::string from
float phase
Definition CaveMesh.cpp:58
float minimum[3]
std::uint32_t key
wgpu::PopErrorScopeStatus status
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
TokenKind kind
size_t offset
V3 origin
Definition RoadBake.cpp:138
bool found
Deterministic board values and short-root ECS entities for tactics.
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Strong, domain-neutral reference to a runtime subject.
Definition SubjectRef.h:26
Result< void > remove(SubjectRef subject)
Remove a placed subject from the board.
std::vector< Cell > cells() const
Return cells in deterministic coordinate order.
bool contains(Cell cell) const noexcept
Return whether the logical cell exists.
Result< void > addCell(Cell cell, CellState state={})
Add a cell with validated positive movement cost.
Result< CellState > cell(Cell cell) const
Return an owning copy of a cell fact, or NotFound.
std::vector< BoardCellRecord > records() const
Return all cell facts and occupants in deterministic coordinate order.
Result< void > move(SubjectRef subject, Cell destination)
Move a placed subject to an existing passable empty cell atomically.
Result< void > place(SubjectRef subject, Cell cell)
Place an unplaced subject on an existing passable empty cell.
std::optional< SubjectRef > occupant(Cell cell) const
Return the occupying subject, or empty when the cell is unoccupied.
std::optional< Cell > position(SubjectRef subject) const
Return the subject cell, or empty when it is not placed.
BattleStatus
Tactical battle lifecycle.
std::string_view statusName(BattleStatus status) noexcept
Return the stable protocol spelling of a battle status.
BattleCommandKind
Accepted deterministic command kinds recorded for replay.
std::string_view commandKindName(BattleCommandKind kind) noexcept
Return the stable protocol spelling of an accepted command kind.
BattlePhase
Explicit, externally observable battle phase.
std::string_view phaseName(BattlePhase phase) noexcept
Return the stable protocol spelling of a battle phase.
One authoritative board cell fact.
Logical board coordinate independent of rendering projection.
One authoritative directed edge fact between two adjacent cells.