载入中...
搜索中...
未找到
CardPlay.cpp
浏览该文件的文档.
1#include "card/CardPlay.h"
2
3#include <algorithm>
4#include <exception>
5#include <string>
6#include <utility>
7#include <vector>
8
9namespace eve::card {
10namespace {
11
12std::optional<eve::Value> cardValue(const CardData* card, const CardDefinition& definition, std::string_view key) {
13 if (!card) return std::nullopt;
14 const auto state = const_cast<CardData*>(card)->state();
15 if (key == "card.id") return eve::Value(const_cast<CardData*>(card)->identity()->id);
16 if (key == "card.definition") return eve::Value(definition.id);
17 if (key == "card.kind") return eve::Value(definition.kind);
18 if (key == "card.cost") return eve::Value(definition.cost);
19 if (key == "card.attack") return eve::Value(definition.attack);
20 if (key == "card.health") return eve::Value(definition.health);
21 if (key == "card.phase") return eve::Value(cardStateName(state->phase));
22 return std::nullopt;
23}
24
25} // namespace
26
29 : card_(card), definition_(&definition), queries_(std::move(queries)) {}
30
31std::optional<eve::Value> CardPlayConditionContext::value(std::string_view key) const {
32 return cardValue(card_, *definition_, key);
33}
34
35std::optional<bool> CardPlayConditionContext::hasTag(std::string_view tag) const {
36 if (!card_) return std::nullopt;
37 return std::find(definition_->tags.begin(), definition_->tags.end(), tag) != definition_->tags.end();
38}
39
40std::optional<eve::Value> CardPlayConditionContext::attribute(std::string_view key) const {
41 if (!card_) return std::nullopt;
42 if (key == "cost" || key == "card.cost") return eve::Value(definition_->cost);
43 if (key == "attack" || key == "card.attack") return eve::Value(definition_->attack);
44 if (key == "health" || key == "card.health") return eve::Value(definition_->health);
45 return std::nullopt;
46}
47
48std::optional<eve::Value> CardPlayConditionContext::resource(std::string_view key) const {
49 if (!queries_.resource) return std::nullopt;
50 return queries_.resource(key);
51}
52
53std::optional<eve::Value> CardPlayConditionContext::state(std::string_view key) const {
54 return cardValue(card_, *definition_, key);
55}
56
57std::optional<bool> CardPlayConditionContext::authority(std::string_view scope) const {
58 if (!queries_.authority) return std::nullopt;
59 return queries_.authority(scope);
60}
61
62std::optional<decision::ConditionResult> CardPlayConditionContext::policy(std::string_view name,
63 const eve::Value& arguments) const {
64 if (!queries_.policy) return std::nullopt;
65 return queries_.policy(name, arguments);
66}
67
71 CardPlayConditionContext context(card, definition, std::move(queries));
72 return condition.evaluate(context);
73}
74
75namespace {
76
77eve::Result<void> cardLifecycleConflict(std::string message) {
81}
82
83class CardTransferParticipant final : public eve::transaction::ITransactionParticipant {
84public:
85 CardTransferParticipant(CardData& card, eve::container::IContainer& source, eve::container::IContainer& destination,
86 std::optional<eve::container::SlotIndex> sourceSlot,
87 std::optional<eve::container::SlotIndex> destinationSlot)
88 : card_(card),
89 source_(source),
90 destination_(destination),
91 sourceSlot_(sourceSlot),
92 destinationSlot_(destinationSlot) {}
93
94 [[nodiscard]] std::string_view name() const noexcept override { return "card-container-transfer"; }
95
96 [[nodiscard]] eve::Result<void> prepare(const eve::transaction::TransactionContext&) override {
97 if (prepared_ || committed_) return cardLifecycleConflict("card transfer participant is already in flight");
98 if (&source_ == &destination_)
100 eve::DiagnosticCode::Conflict, "card play source and destination must differ", {}));
101
102 auto sourceResult = source_.snapshot();
103 if (!sourceResult) return eve::Result<void>::failure(sourceResult.status());
104 auto destinationResult = destination_.snapshot();
105 if (!destinationResult) return eve::Result<void>::failure(destinationResult.status());
106 beforeSource_ = std::move(sourceResult).takeValue();
107 beforeDestination_ = std::move(destinationResult).takeValue();
108
109 const eve::container::MembershipId object(card_.identity()->id);
110 const auto sourceIt = std::find_if(beforeSource_.entries.begin(), beforeSource_.entries.end(),
111 [&object](const auto& entry) { return entry.membership.object == object; });
112 if (sourceIt == beforeSource_.entries.end())
114 eve::DiagnosticCode::NotFound, "card is not present in the source container", "source"));
115 if (sourceSlot_ && *sourceSlot_ != sourceIt->membership.slot)
117 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "card source slot is stale", "sourceSlot"));
118
119 auto accepted = destination_.validateInsert(sourceIt->object, destinationSlot_, std::nullopt);
120 if (!accepted) return accepted;
121 if (destinationSlot_ && (destinationSlot_->value() < 0 || static_cast<std::size_t>(destinationSlot_->value()) >
122 beforeDestination_.entries.size()))
124 eve::DiagnosticCode::InvalidArgument, "card destination slot is out of range", "destinationSlot"));
125
126 afterSource_ = beforeSource_;
127 afterSource_.entries.erase(afterSource_.entries.begin() + (sourceIt - beforeSource_.entries.begin()));
128 renumber(afterSource_);
129 afterSource_.revision = nextRevision(beforeSource_.revision);
130 if (afterSource_.revision == beforeSource_.revision)
132 "source container revision is exhausted", {}));
133
134 afterDestination_ = beforeDestination_;
135 auto moved = *sourceIt;
136 const std::size_t insertion =
137 destinationSlot_ ? static_cast<std::size_t>(destinationSlot_->value()) : afterDestination_.entries.size();
138 if (insertion > afterDestination_.entries.size())
140 eve::DiagnosticCode::InvalidArgument, "card destination slot is out of range", "destinationSlot"));
141 afterDestination_.entries.insert(afterDestination_.entries.begin() + insertion, std::move(moved));
142 renumber(afterDestination_);
143 afterDestination_.revision = nextRevision(beforeDestination_.revision);
144 if (afterDestination_.revision == beforeDestination_.revision)
146 eve::DiagnosticCode::InvariantViolation, "destination container revision is exhausted", {}));
147
148 auto sourcePrepared = source_.prepare(beforeSource_, afterSource_);
149 if (!sourcePrepared) return eve::Result<void>::failure(sourcePrepared.status());
150 sourceStage_ = std::move(sourcePrepared).takeValue();
151 if (!sourceStage_)
153 "source container returned an empty stage", {}));
154 auto destinationPrepared = destination_.prepare(beforeDestination_, afterDestination_);
155 if (!destinationPrepared) {
156 sourceStage_->rollback();
157 sourceStage_.reset();
158 return eve::Result<void>::failure(destinationPrepared.status());
159 }
160 destinationStage_ = std::move(destinationPrepared).takeValue();
161 if (!destinationStage_) {
162 sourceStage_->rollback();
163 sourceStage_.reset();
165 eve::DiagnosticCode::InvariantViolation, "destination container returned an empty stage", {}));
166 }
167 prepared_ = true;
169 }
170
171 [[nodiscard]] eve::Result<void> commit(const eve::transaction::TransactionContext&) override {
172 if (!prepared_ || committed_) return cardLifecycleConflict("card transfer has no prepared stage");
173 destinationStage_->commit();
174 sourceStage_->commit();
175 committed_ = true;
177 }
178
179 [[nodiscard]] eve::Result<void> rollback(const eve::transaction::TransactionContext&) override {
180 if (committed_) return cardLifecycleConflict("committed card transfer requires compensation");
181 if (destinationStage_) destinationStage_->rollback();
182 if (sourceStage_) sourceStage_->rollback();
183 sourceStage_.reset();
184 destinationStage_.reset();
185 prepared_ = false;
187 }
188
189 [[nodiscard]] eve::Result<void> compensate(const eve::transaction::TransactionContext&) override {
190 if (!committed_) return cardLifecycleConflict("card transfer has no committed state to compensate");
191
192 auto currentSource = source_.snapshot();
193 if (!currentSource) return eve::Result<void>::failure(currentSource.status());
194 auto currentDestination = destination_.snapshot();
195 if (!currentDestination) return eve::Result<void>::failure(currentDestination.status());
196 if (currentSource.value().revision != afterSource_.revision ||
197 currentDestination.value().revision != afterDestination_.revision)
199 eve::DiagnosticCode::StaleHandle, "card containers changed before compensation", {}));
200 auto sourceCandidate = beforeSource_;
201 auto destinationCandidate = beforeDestination_;
202 sourceCandidate.revision = nextRevision(currentSource.value().revision);
203 destinationCandidate.revision = nextRevision(currentDestination.value().revision);
204 if (sourceCandidate.revision == currentSource.value().revision ||
205 destinationCandidate.revision == currentDestination.value().revision)
207 eve::DiagnosticCode::InvariantViolation, "card transfer compensation revision is exhausted", {}));
208
209 auto sourceRestore = source_.prepare(currentSource.value(), sourceCandidate);
210 if (!sourceRestore) return eve::Result<void>::failure(sourceRestore.status());
211 auto sourceStage = std::move(sourceRestore).takeValue();
212 if (!sourceStage)
214 eve::DiagnosticCode::InvariantViolation, "source compensation returned an empty stage", {}));
215 auto destinationRestore = destination_.prepare(currentDestination.value(), destinationCandidate);
216 if (!destinationRestore) {
217 sourceStage->rollback();
218 return eve::Result<void>::failure(destinationRestore.status());
219 }
220 auto destinationStage = std::move(destinationRestore).takeValue();
221 if (!destinationStage) {
222 sourceStage->rollback();
224 eve::DiagnosticCode::InvariantViolation, "destination compensation returned an empty stage", {}));
225 }
226 destinationStage->commit();
227 sourceStage->commit();
228 sourceStage_.reset();
229 destinationStage_.reset();
230 prepared_ = false;
231 committed_ = false;
233 }
234
235private:
236 static eve::Revision nextRevision(eve::Revision revision) noexcept {
237 const auto next = revision.incremented();
238 return next ? *next : revision;
239 }
240
241 static void renumber(eve::container::ContainerSnapshot& snapshot) noexcept {
242 for (std::size_t index = 0; index < snapshot.entries.size(); ++index)
243 snapshot.entries[index].membership.slot = eve::container::SlotIndex(static_cast<std::int32_t>(index));
244 }
245
246 CardData& card_;
248 eve::container::IContainer& destination_;
249 std::optional<eve::container::SlotIndex> sourceSlot_;
250 std::optional<eve::container::SlotIndex> destinationSlot_;
252 eve::container::ContainerSnapshot beforeDestination_;
254 eve::container::ContainerSnapshot afterDestination_;
255 std::unique_ptr<eve::container::IContainer::PreparedState> sourceStage_;
256 std::unique_ptr<eve::container::IContainer::PreparedState> destinationStage_;
257 bool prepared_ = false;
258 bool committed_ = false;
259};
260
261} // namespace
262
264
266 (void)context;
267 if (prepared_ || committed_) return cardLifecycleConflict("card play participant is already in flight or terminal");
268 auto state = card_.state();
269 if (state->phase == CardState::Played || state->phase == CardState::Discarded ||
270 state->phase == CardState::Disabled)
271 return cardLifecycleConflict("card is not playable in its current state");
272 previousState_ = state->phase;
273 prepared_ = true;
275}
276
278 (void)context;
279 if (!prepared_ || committed_) return cardLifecycleConflict("card play participant has no uncommitted stage");
280 auto state = card_.state();
281 // A preceding container-transfer participant deliberately projects the
282 // card's destination phase during the same synchronous transaction. The
283 // play participant is the final authority and publishes Played once all
284 // preceding participants have committed successfully.
285 state->phase = CardState::Played;
286 committed_ = true;
288}
289
291 (void)context;
292 if (!prepared_ || committed_)
293 return cardLifecycleConflict("card play participant has no prepared stage to roll back");
294 prepared_ = false;
296}
297
299 (void)context;
300 if (!committed_) return cardLifecycleConflict("card play participant has no committed effect to compensate");
301 auto state = card_.state();
302 state->phase = previousState_;
303 committed_ = false;
304 prepared_ = false;
306}
307
309 if (definition.id.empty())
311 eve::DiagnosticCode::InvalidArgument, "card definition id must not be empty", "definition.id"));
312 if (definition.cost < 0)
314 eve::DiagnosticCode::InvalidArgument, "card mana cost must not be negative", "definition.cost"));
315 if (definition.cost == 0) return eve::Result<std::optional<eve::resource::CostSpec>>::success(std::nullopt);
316 auto cost = eve::resource::CostSpec::single("mana", definition.cost);
317 if (!cost) return eve::Result<std::optional<eve::resource::CostSpec>>::failure(cost.status());
318 return eve::Result<std::optional<eve::resource::CostSpec>>::success(std::move(cost).takeValue());
319}
320
322 CardData& card, const CardDefinition& definition, eve::resource::IResourceAccount& playerAccount,
323 std::string transactionId) {
325 request.card = &card;
326 request.definition = &definition;
327 request.playerAccount = &playerAccount;
328 request.transactionId = std::move(transactionId);
329 return play(std::move(request));
330}
331
333 if (request.card == nullptr || request.definition == nullptr)
335 eve::DiagnosticCode::InvalidArgument, "card play requires a card and definition", "request"));
336 auto& card = *request.card;
337 const auto& definition = *request.definition;
338 if ((request.composition.source == nullptr) != (request.composition.destination == nullptr))
341 "card play requires both source and destination containers", "composition"));
342
343 const auto condition = CardPlayConditionAdapter::evaluate(&card, definition, definition.playCondition,
344 request.composition.conditionQueries);
345 if (!condition.passed()) {
346 DiagnosticDetails details;
347 details.emplace_back("reason", decision::conditionReasonCodeName(condition.reasonCode()));
349 eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "card play condition was rejected",
350 "condition", details));
351 }
352
353 auto cost = manaCost(definition);
355 if (cost.value() && request.playerAccount == nullptr)
358 "a mana-bearing card play requires a player account", "playerAccount"));
359 if (request.transactionId.empty()) request.transactionId = "card.play." + definition.id;
360
362 std::unique_ptr<CardTransferParticipant> transfer;
363 std::vector<eve::transaction::ITransactionParticipant*> participants;
364 participants.reserve(request.composition.effect ? 3u : 2u);
365 if (request.composition.source != nullptr) {
366 transfer = std::make_unique<CardTransferParticipant>(
367 card, *request.composition.source, *request.composition.destination, request.composition.sourceSlot,
368 request.composition.destinationSlot);
369 participants.push_back(transfer.get());
370 }
371 participants.push_back(&state);
372 if (request.composition.effect != nullptr) participants.push_back(request.composition.effect);
373
374 eve::transaction::TransactionContext context(std::move(request.transactionId));
375 if (cost.value())
377 context, *request.playerAccount, *cost.value(),
378 std::span<eve::transaction::ITransactionParticipant*>(participants));
380 context, std::span<eve::transaction::ITransactionParticipant*>(participants));
381}
382
383} // namespace eve::card
std::unordered_map< std::string, QuestRuntime > entries
Card Play adapter for the renderer-independent decision protocol.
eve::resource::CostSpec cost
eve::Value condition
std::string message
const GltfImportRequest & request
std::uint32_t key
JobScope scope
std::string name
const std::string * tag
std::uint64_t revision
Json object
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
const VegetationPresetContext & context
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 failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
单张卡牌:ECS 实体,数据拆成 Identity / Stats / Visual / Layout / State。
Definition CardTypes.h:94
static decision::ConditionResult evaluate(const CardData *card, const CardDefinition &definition, const decision::Condition &condition, CardPlayConditionQueries queries={})
Evaluate a card definition's play condition against one instance.
Definition CardPlay.cpp:68
Read-only EvaluationContext backed by one card instance and definition.
Definition CardPlay.h:47
CardPlayConditionContext(const CardData *card, const CardDefinition &definition, CardPlayConditionQueries queries={})
Bind a card instance and definition for one evaluation.
Definition CardPlay.cpp:27
std::optional< bool > hasTag(std::string_view tag) const override
Query tag membership; nullopt means the tag source is unavailable.
Definition CardPlay.cpp:35
std::optional< eve::Value > resource(std::string_view key) const override
Query a resource amount or resource marker.
Definition CardPlay.cpp:48
std::optional< bool > authority(std::string_view scope) const override
Query authority without changing grants, leases, or state.
Definition CardPlay.cpp:57
std::optional< eve::Value > value(std::string_view key) const override
Query a named scalar or structured value for Compare nodes.
Definition CardPlay.cpp:31
std::optional< decision::ConditionResult > policy(std::string_view name, const eve::Value &arguments) const override
Evaluate a named read-only policy with owned arguments.
Definition CardPlay.cpp:62
std::optional< eve::Value > state(std::string_view key) const override
Query a state value for StateEquals nodes.
Definition CardPlay.cpp:53
std::optional< eve::Value > attribute(std::string_view key) const override
Query an attribute value; nullopt means the attribute is absent.
Definition CardPlay.cpp:40
Transaction participant that publishes one CardData play.
Definition CardPlay.h:111
CardPlayParticipant(CardData &card)
Bind one card effect whose state transition is played.
Definition CardPlay.cpp:263
eve::Result< void > rollback(const eve::transaction::TransactionContext &context) override
Discard a prepared but not committed stage.
Definition CardPlay.cpp:290
eve::Result< void > commit(const eve::transaction::TransactionContext &context) override
Publish this participant's staged changes.
Definition CardPlay.cpp:277
eve::Result< void > prepare(const eve::transaction::TransactionContext &context) override
Validate and stage local changes for the transaction.
Definition CardPlay.cpp:265
eve::Result< void > compensate(const eve::transaction::TransactionContext &context) override
Undo an already committed external effect by compensation.
Definition CardPlay.cpp:298
static eve::Result< std::optional< eve::resource::CostSpec > > manaCost(const CardDefinition &definition)
Convert a non-negative card mana value into a canonical cost.
Definition CardPlay.cpp:308
static eve::Result< eve::transaction::TransactionReceipt > play(CardPlayRequest request)
Execute condition, movement, card effect and Mana as one transaction.
Definition CardPlay.cpp:332
Borrowed container implementation owned by a domain adapter.
Definition Container.h:287
virtual Result< std::unique_ptr< PreparedState > > prepare(const ContainerSnapshot &expected, const ContainerSnapshot &candidate)=0
Prepare an exact replacement from expected to candidate.
virtual Result< void > validateInsert(const ContainerObject &object, std::optional< SlotIndex > destination, std::optional< MembershipId > ignoredObject=std::nullopt) const =0
Validate insertion while optionally ignoring one moved object.
virtual Result< ContainerSnapshot > snapshot() const =0
Return current lossless state without retaining adapter pointers.
Strong identity of an object which can have one container membership.
Definition Container.h:68
Explicit slot number; it never implicitly converts to an integer.
Definition Container.h:117
Explain one condition evaluation in a UI- and log-friendly form.
Definition Condition.h:129
Immutable condition AST node.
Definition Condition.h:195
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
static eve::Result< CostSpec > single(std::string_view resource, std::int64_t amount)
Build a one-resource cost.
Provider-neutral resource account contract.
static eve::Result< TransactionReceipt > execute(const TransactionContext &context, std::span< ITransactionParticipant * > participants)
Execute already prepared domain participants as one transaction.
Synchronous participant contract for one coordinator transaction.
Immutable context shared by every participant of one transaction.
Definition Transaction.h:30
const char * cardStateName(CardState state)
状态枚举的字符串名(用于脚本/调试)。
Definition CardTypes.cpp:12
const char * conditionReasonCodeName(ConditionReasonCode code) noexcept
Return the stable lowercase spelling of a condition reason code. @ownership Borrowed process-static t...
Definition Condition.cpp:82
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
WidgetDesc card(std::vector< WidgetDesc > children, std::string id)
Bordered surface container with editor-friendly padding.
Definition Widget.cpp:502
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
卡牌类型定义(registerCardsFromJson 注册)。
Definition CardTypes.h:79
std::vector< std::string > tags
Definition tags available to HasTag nodes.
Definition CardTypes.h:90
Read-only extension points used while evaluating a card play.
Definition CardPlay.h:29
Complete caller-owned input for one atomic card play.
Definition CardPlay.h:163
Lossless snapshot of one container at one revision.
Definition Container.h:190
std::vector< MembershipEntry > entries
Definition Container.h:193