Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AuctionTextPlayback.cpp
Go to the documentation of this file.
2
3#include "AuctionPlayback.hpp"
4#include "FontRuntime.hpp"
6#include "MoneyFormat.hpp"
9
10#include <algorithm>
11#include <memory>
12#include <string_view>
13#include <utility>
14
15namespace monopoly::auctionui
16{
17 namespace
18 {
19 constexpr std::uint32_t CurrentBidMessageId = 2000;
20 constexpr std::uint32_t White = 0x00FFFFFFU;
21 constexpr std::uint32_t Black = 0x00000000U;
22 constexpr std::uint16_t TextPriorityOffset = 2;
23 constexpr std::int32_t NameTextWidth = BackdropSmallWidth;
24 constexpr std::int32_t NameTextHeight = 30;
25 constexpr std::int32_t NameTextY = BackdropY + 10;
26 constexpr std::int32_t CashTextWidth = BackdropSmallWidth;
27 constexpr std::int32_t CashTextHeight = 30;
28 constexpr std::int32_t CashTextY = BackdropY + 55;
29 constexpr std::int32_t CurrentBidWidth = 200;
30 constexpr std::int32_t CurrentBidHeight = 50;
31 constexpr std::int32_t CurrentBidX = 20;
32 constexpr std::int32_t CurrentBidY = 280;
33
34 [[nodiscard]] data::LegacyBitmapRGBA8 blankImage(
35 std::uint32_t width, std::uint32_t height)
36 {
37 data::LegacyBitmapRGBA8 result{width, height, {}};
38 result.pixels.assign(static_cast<std::size_t>(width) * height * 4U, 0U);
39 return result;
40 }
41
42 [[nodiscard]] std::expected<void, std::string> drawText(
43 data::LegacyBitmapRGBA8& destination,
44 fonts::Runtime& fontRuntime,
45 std::string_view text,
46 std::uint32_t colour,
47 int x, int y)
48 {
49 if (text.empty()) return {};
50 const auto blitted = fontRuntime.blitText(
52 if (!blitted) return std::unexpected(blitted.error().detail);
53 return {};
54 }
55
56 [[nodiscard]] std::expected<void, std::string> drawCentered(
57 data::LegacyBitmapRGBA8& destination,
58 fonts::Runtime& fontRuntime,
59 std::string_view text,
60 std::uint32_t colour,
61 int y)
62 {
63 if (text.empty()) return {};
64 const auto metrics = fontRuntime.measure(text);
65 if (!metrics) return std::unexpected(metrics.error().detail);
66 const int x = (static_cast<int>(destination.width) - metrics->width) / 2;
68 }
69
70 [[nodiscard]] std::expected<std::u16string, std::string> languageText(
71 const engine::SequencePlayback& playback, std::uint32_t messageId)
72 {
73 const auto resources = playback.resources();
74 if (!resources) return std::unexpected("auction text has no resource snapshot");
75 const auto language = resources->language();
76 if (!language || !language->catalog)
77 return std::unexpected("auction text has no language catalog");
78 const auto text = language->catalog->message(messageId);
79 if (!text) return std::unexpected(text.error().detail);
80 return **text;
81 }
82 }
83
84 std::expected<void, std::string> TextPlayback::ensureSurfaces(
85 engine::SequencePlayback& playback)
86 {
87 auto ensure = [&](std::optional<data::DataId>& target,
88 std::uint32_t width, std::uint32_t height)
89 -> std::expected<void, std::string>
90 {
91 if (target) return {};
92 const auto created = playback.runtimeBitmaps().create(width, height, true);
93 if (!created) return std::unexpected(created.error());
94 target = *created;
95 return {};
96 };
97
98 if (auto result = ensure(currentBidSurface_, CurrentBidWidth, CurrentBidHeight); !result)
99 return result;
100 for (std::size_t player = 0; player < rules::MaxPlayers; ++player)
101 {
102 if (auto result = ensure(nameSurfaces_[player], NameTextWidth, NameTextHeight); !result)
103 return result;
104 if (auto result = ensure(bidSurfaces_[player], AuctionBidTextWidth, AuctionBidTextHeight); !result)
105 return result;
106 if (auto result = ensure(cashSurfaces_[player], CashTextWidth, CashTextHeight); !result)
107 return result;
108 }
109 return {};
110 }
111
112 std::expected<void, std::string> TextPlayback::sync(
113 const State& state,
116 int monetarySystem,
119 {
120 std::vector<Published> desired;
121 std::vector<data::DataId> changedSurfaces;
122 std::vector<std::pair<data::DataId, data::LegacyBitmapRGBA8>> pendingImages;
123 auto nextCurrentBidCache = currentBidCache_;
124 auto nextNameCache = nameCache_;
125 auto nextBidCache = bidCache_;
126 auto nextCashCache = cashCache_;
128 {
129 if (currentObjects_.empty()) return {};
130 }
131 else
132 {
133 if (gameState.numberOfPlayers > rules::MaxPlayers)
134 return std::unexpected("auction text player count exceeds retail maximum");
135 if (fontRuntime == nullptr || !fontRuntime->ready())
136 return std::unexpected("auction text font runtime is unavailable");
137 if (const auto ready = ensureSurfaces(playback); !ready)
138 return ready;
139
140 const auto resources = playback.resources();
141 if (!resources) return std::unexpected("auction text has no resources");
142 const auto edition = resources->context().board;
143
144 if (!currentBidLabel_)
145 {
147 if (!label) return std::unexpected(label.error());
148 currentBidLabel_ = *label;
149 }
150 const auto amount = money::format(
151 state.highestBid, monetarySystem, true, edition);
152 if (!amount) return std::unexpected(amount.error());
153 const auto currentBidLabelUtf8 =
154 fonts::transcodeUtf8(std::u16string_view(*currentBidLabel_));
156 return std::unexpected(currentBidLabelUtf8.error().detail);
157 const std::string currentKey = *currentBidLabelUtf8 + '\n' + *amount;
158 if (!currentBidCache_ || *currentBidCache_ != currentKey)
159 {
161 if (auto drawn = drawText(image, *fontRuntime,
162 *currentBidLabelUtf8, White, 10, 5); !drawn)
163 return drawn;
164 if (auto drawn = drawText(image, *fontRuntime,
165 *amount, White, 10, 30); !drawn)
166 return drawn;
167 pendingImages.emplace_back(*currentBidSurface_, std::move(image));
169 changedSurfaces.push_back(*currentBidSurface_);
170 }
171 desired.push_back({*currentBidSurface_,
172 static_cast<std::uint16_t>(AuctionPropertyPriority + 1),
174
175 const auto count = std::min<rules::PlayerNumber>(
176 gameState.numberOfPlayers, rules::MaxPlayers);
177 for (rules::PlayerNumber player = 0; player < count; ++player)
178 {
179 if (!playerPanelWanted(state, count, desiredView, player)) continue;
180 const auto index = static_cast<std::size_t>(player);
181 const auto priority = static_cast<std::uint16_t>(
183 const int center = state.backdropCenterX[index];
184
186 std::wstring_view(gameState.players[index].name));
187 if (!encodedName) return std::unexpected(encodedName.error().detail);
188 const auto& name = *encodedName;
189 if (!nameCache_[index] || *nameCache_[index] != name)
190 {
192 if (auto drawn = drawCentered(image, *fontRuntime, name, Black, 0); !drawn)
193 return drawn;
194 pendingImages.emplace_back(*nameSurfaces_[index], std::move(image));
196 changedSurfaces.push_back(*nameSurfaces_[index]);
197 }
198
199 const auto bid = money::format(
200 state.bids[index], monetarySystem, false, edition);
201 if (!bid) return std::unexpected(bid.error());
202 if (!bidCache_[index] || *bidCache_[index] != *bid)
203 {
205 if (auto drawn = drawCentered(image, *fontRuntime, *bid, White, 5); !drawn)
206 return drawn;
207 pendingImages.emplace_back(*bidSurfaces_[index], std::move(image));
209 changedSurfaces.push_back(*bidSurfaces_[index]);
210 }
211
212 const auto cash = money::format(
213 gameState.players[index].cash - state.bids[index],
214 monetarySystem, true, edition);
215 if (!cash) return std::unexpected(cash.error());
216 if (!cashCache_[index] || *cashCache_[index] != *cash)
217 {
219 if (auto drawn = drawCentered(image, *fontRuntime, *cash, Black, 5); !drawn)
220 return drawn;
221 pendingImages.emplace_back(*cashSurfaces_[index], std::move(image));
222 nextCashCache[index] = *cash;
223 changedSurfaces.push_back(*cashSurfaces_[index]);
224 }
225
226 desired.push_back({*nameSurfaces_[index], priority,
227 center - NameTextWidth / 2, NameTextY});
228 desired.push_back({*bidSurfaces_[index], priority,
229 center - AuctionBidTextWidth / 2, AuctionBidTextY});
230 desired.push_back({*cashSurfaces_[index], priority,
231 center - CashTextWidth / 2, CashTextY});
232 }
233 }
234
235 const std::size_t requiredCommands = desired == currentObjects_
236 ? changedSurfaces.size() : currentObjects_.size() + desired.size();
238 return std::unexpected("sequence command queue cannot fit auction text update");
239 for (auto& [id, image] : pendingImages)
240 if (auto updated = playback.runtimeBitmaps().update(id, std::move(image)); !updated)
241 return updated;
242 currentBidCache_ = std::move(nextCurrentBidCache);
243 nameCache_ = std::move(nextNameCache);
244 bidCache_ = std::move(nextBidCache);
245 cashCache_ = std::move(nextCashCache);
246
247 if (desired == currentObjects_)
248 {
249 for (const auto id : changedSurfaces)
250 {
251 const auto object = std::find_if(desired.begin(), desired.end(),
252 [id](const auto& value) { return value.id == id; });
253 if (object == desired.end()) continue;
254 const auto forced = playback.forceRedraw(id, object->priority);
255 if (!forced) return forced;
256 }
257 return {};
258 }
259
260 std::vector<std::shared_ptr<const sequence::SequenceProgram>> programs;
261 programs.reserve(desired.size());
262 for (const auto& object : desired)
263 {
266 if (!program) return std::unexpected(program.error().detail);
267 programs.push_back(std::move(*program));
268 }
269
270 const std::size_t required = currentObjects_.size() + desired.size();
272 playback.commands().pendingCount())
273 return std::unexpected("sequence command queue cannot fit auction text transition");
274
275 for (const auto& object : currentObjects_)
276 {
277 if (!playback.commands().enqueue(sequence::StopSequenceCommand{
278 object.id, object.priority, false}))
279 return std::unexpected("validated auction text stop rejected");
280 }
281 for (std::size_t index = 0; index < desired.size(); ++index)
282 {
283 const auto& object = desired[index];
284 if (!playback.commands().enqueue(sequence::StartSequenceCommand{
285 programs[index], object.priority, {},
286 sequence::moveXYTransform(object.x, object.y)}))
287 return std::unexpected("validated auction text start rejected");
288 }
289 currentObjects_ = std::move(desired);
290 return {};
291 }
292
293 void TextPlayback::reset() noexcept
294 {
295 currentBidSurface_.reset();
296 nameSurfaces_.fill(std::nullopt);
297 bidSurfaces_.fill(std::nullopt);
298 cashSurfaces_.fill(std::nullopt);
299 currentObjects_.clear();
300 currentBidCache_.reset();
301 nameCache_.fill(std::nullopt);
302 bidCache_.fill(std::nullopt);
303 cashCache_.fill(std::nullopt);
304 currentBidLabel_.reset();
305 }
306}
std::size_t count
const char * name
std::string_view text
LegacyBitmapRGBA8 image
std::uint16_t priority
fonts::Metrics metrics
std::uint32_t height
std::uint32_t width
unsigned state
unsigned index
std::string_view label
int colour
std::uint16_t value
Definition Timers.cpp:16
std::expected< void, std::string > sync(const State &state, const rules::GameState &gameState, display::Screen2D desiredView, int monetarySystem, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback)
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
constexpr std::uint16_t AuctionPropertyPriority
constexpr int BackdropSmallWidth
Definition AuctionUI.hpp:20
constexpr std::uint16_t AuctionBasePriority
constexpr int BackdropY
Definition AuctionUI.hpp:27
constexpr std::int32_t AuctionBidTextWidth
bool playerPanelWanted(const State &state, rules::PlayerNumber numberOfPlayers, display::Screen2D desiredView, rules::PlayerNumber player) noexcept
constexpr std::int32_t AuctionBidTextHeight
constexpr std::int32_t AuctionBidTextY
std::expected< std::string, Error > transcodeUtf8(std::u16string_view text)
std::expected< std::string, std::string > format(std::int64_t defaultValue, int monetarySystem, bool printSymbol, data::BoardEdition edition)
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept