Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
EuropeanDeed.cpp
Go to the documentation of this file.
1#include "EuropeanDeed.hpp"
2
3#include "BoardRules.hpp"
4#include "MoneyFormat.hpp"
6
7#include <algorithm>
8#include <array>
9#include <optional>
10#include <string_view>
11#include <utility>
12
14{
15 namespace
16 {
17#include "EuropeanDeedText.inc"
18
19 constexpr std::array<int, 28> Squares{
20 1, 3, 6, 8, 9, 11, 13, 14, 16, 18, 19, 21, 23, 24,
21 26, 27, 29, 31, 32, 34, 37, 39, 5, 15, 25, 35, 12, 28};
22
23 constexpr std::uint32_t rgb(unsigned r, unsigned g, unsigned b)
24 { return r | (g << 8U) | (b << 16U); }
25
26 std::uint32_t propertyColor(int square, int language)
27 {
28 // UDPENNY_ReturnColorForPropertySet uses display language, not board.
29 constexpr std::array ordinary{
30 rgb(134,103,86), rgb(35,158,206), rgb(221,3,109),
31 rgb(253,128,61), rgb(223,0,0), rgb(255,255,0),
32 rgb(37,154,57), rgb(45,65,144)};
33 constexpr std::array french{
34 rgb(217,91,187), rgb(35,158,206), rgb(134,0,157),
35 rgb(237,181,0), rgb(223,0,0), rgb(255,255,0),
36 rgb(0,128,52), rgb(45,65,144)};
37 constexpr std::array limits{3,9,14,19,24,29,34,39};
38 const auto group = static_cast<std::size_t>(
39 std::lower_bound(limits.begin(), limits.end(), square) - limits.begin());
40 return (language == 3 ? french : ordinary)[group];
41 }
42
43 struct FontRestore
44 {
45 fonts::Runtime& runtime;
46 fonts::Settings saved;
48 {
49 (void)runtime.setSize(saved.size);
50 runtime.setWeight(saved.weight);
51 runtime.setItalic(saved.italic);
52 runtime.setUnderline(saved.underline);
53 runtime.setStrikeOut(saved.strikeOut);
54 }
55 };
56
57 struct Line
58 {
59 std::string_view text;
60 fonts::Metrics metrics;
61 };
62
63 // Source's deed wrapper differs from CHAT_WordWrap: an unbreakable word
64 // never gets split; its entire block must shrink until the word fits.
65 std::expected<std::optional<std::vector<Line>>, std::string> wrap(
66 std::string_view text, fonts::Runtime& font)
67 {
68 std::vector<Line> lines;
69 while (!text.empty())
70 {
71 const auto full = font.measure(text);
72 if (!full) return std::unexpected(full.error().detail);
73 std::size_t end = text.size();
74 while (true)
75 {
76 const auto suffix = font.measure(text.substr(end));
77 if (!suffix) return std::unexpected(suffix.error().detail);
78 if (full->width - suffix->width <= Width - 42) break;
79 if (end == 0) return std::optional<std::vector<Line>>{};
80 end = text.rfind(' ', end - 1);
81 if (end == std::string_view::npos || end == 0)
82 return std::optional<std::vector<Line>>{};
83 }
84 const auto part = text.substr(0, end);
85 const auto measured = font.measure(part);
86 if (!measured) return std::unexpected(measured.error().detail);
87 lines.push_back({part, *measured});
88 if (end == text.size()) break;
89 text.remove_prefix(end + 1);
90 }
91 return std::optional{std::move(lines)};
92 }
93
94 std::expected<void, std::string> print(
95 data::LegacyBitmapRGBA8& image, const TextRegion& region,
96 fonts::Runtime& font)
97 {
98 font.resetCharacteristics();
99 font.setWeight(region.bold ? 1000 : 400);
100 font.setItalic(region.italic);
101 for (int size = region.fontSize; size >= 1; --size)
102 {
103 const auto changed = font.setSize(size);
104 if (!changed) return std::unexpected(changed.error().detail);
105 auto wrapped = wrap(region.text, font);
106 if (!wrapped) return std::unexpected(wrapped.error());
107 if (!*wrapped) continue;
108 int height = 0;
109 for (const auto& line : **wrapped) height += line.metrics.height;
110 if (height > region.height + region.verticalLeeway && size > 1)
111 continue;
112 // Preserve the original slight-overflow leeway direction.
113 const int overflow = std::max(0, height - region.height);
114 int y = region.y + (overflow <= region.verticalLeeway ? overflow : 0);
115 if (region.verticalCenter && (**wrapped).size() == 1)
116 y += (region.height - height) / 2;
117 for (const auto& line : **wrapped)
118 {
119 const int x = region.justification == 0 ? 21 :
120 region.justification == 1 ? (Width - line.metrics.width) / 2 :
121 Width - 21 - line.metrics.width;
122 const auto result = font.blitText(image, line.text, x, y, region.color);
123 if (!result) return std::unexpected(result.error().detail);
124 y += line.metrics.height;
125 }
126 return {};
127 }
128 return std::unexpected("European deed text has no fitting word boundary");
129 }
130 }
131
132 int propertyIndex(int square) noexcept
133 {
134 const auto found = std::find(Squares.begin(), Squares.end(), square);
135 return found == Squares.end() ? -1 : static_cast<int>(found - Squares.begin());
136 }
137
138 data::DataId templateId(const Request& request) noexcept
139 {
140 const int property = propertyIndex(request.square);
141 if (property < 0) return data::EmptyDataId;
142 const data::DataTag tag = !request.front ? 0x009D :
143 property == 26 ? 0x00D3 : property == 27 ? 0x00D4 :
144 property >= 22 ? 0x00D2 : 0x00D1;
146 }
147
148 std::expected<Plan, std::string> plan(const Request& request)
149 {
150 const int property = propertyIndex(request.square);
151 if (property < 0) return std::unexpected("European deed square is not ownable");
152 if (request.usaPresentation &&
153 (request.languageId != 1 || request.board != 0 || request.monetarySystem != 13))
154 return std::unexpected("USA deed presentation requires USA English, classic board and US dollars");
155 if (request.languageId < (request.usaPresentation ? 1 : 2) || request.languageId > 10)
156 return std::unexpected("European deed language must be UK..Norwegian (2..10)");
157 const int board = request.board == -1 ? request.languageId - 2 : request.board;
159 return std::unexpected("European deed board must be 0..11 or custom (-1)");
160 if (request.monetarySystem < 0 || request.monetarySystem > 13)
161 return std::unexpected("European deed currency must be 0..13");
162 if (request.housesPerHotel != 4 && request.housesPerHotel != 5)
163 return std::unexpected("European deed housesPerHotel must be 4 or 5");
164 // The source mutable rent table swaps slots 4/5 for a short game and
165 // CreateDeed compensates that swap, so printed values are invariant.
166 // Read the canonical table directly: player selection and remote peers
167 // can request deeds before the local rules table has been initialized.
168 const auto& square = rules::board::originalDefinition(
169 static_cast<rules::board::SquareType>(request.square));
170 const int language = request.languageId - 1;
171 const char* name = TRANS_PROP[property][request.usaPresentation ? 0 : board + 1];
172 Plan result;
173 result.background = templateId(request);
174 std::optional<std::string> moneyError;
175 const auto amount = [&](std::int64_t value, bool symbol = true)
176 {
177 const auto formatted = money::format(value, request.monetarySystem,
179 if (!formatted)
180 {
181 moneyError = formatted.error();
182 return std::string{};
183 }
184 return *formatted;
185 };
186 const auto text = [&](std::string value, int y, int height,
187 int justification, int size, bool bold = false,
188 bool italic = false, bool centered = false, int leeway = 0)
189 {
190 result.text.push_back({std::move(value), y, height, justification,
191 leeway, size, 0, bold, italic, centered});
192 };
193 const auto pair = [&](const char* label, std::int64_t value,
194 int y, int height, bool symbol = true)
195 {
196 text(label, y, height, 0, height);
197 text(amount(value, symbol), y, height, 2, height);
198 };
199 if (!request.front)
200 {
201 text(name, 27, 30, 1, 10, true, false, false, 2);
202 text(TRANS[12][language], 92, 15, 1, 15, false, false, false, 2);
203 std::string mortgage = amount(square.mortgageCost);
204 if (request.languageId == 8) mortgage += ":STA";
205 text(std::move(mortgage), 109, 15, 1, 15, false, false, false, 2);
206 text(TRANS_MORT[language], 153, 30, 1, 15, false, true, false, 2);
207 }
208 else if (property >= 26)
209 {
210 text(name, 72, 12, 1, 12);
211 constexpr std::array factors{0,3,2,3,3,2,2,2,2,2,1,0,0,0};
212 const int factor = factors[request.monetarySystem];
213 const char* one = property == 27 && request.languageId == 4
214 ? TRANS_UTIL1g[factor] : property == 27 && request.languageId == 10
215 ? TRANS_UTIL1n[factor] : TRANS_UTIL1[factor][language];
216 text(one, 92, 54, 1, 10);
217 text(TRANS_UTIL2[factor][language], 146, 54, 1, 10);
218 pair(TRANS[7][language], square.mortgageCost, 200, 12);
219 // Utility copyright is commented out in the source.
220 }
221 else if (property >= 22)
222 {
223 text(name, 72, 12, 1, 12);
224 pair(request.languageId == 8 ? "VUOKRA ASEMASTA" : TRANS[1][language],
225 square.rent[1], 92, 14);
226 for (int i = 2; i <= 4; ++i)
227 pair(TRANS[11 + i][language], square.rent[i], 110 + (i - 2) * 18,
228 14, false);
229 pair(TRANS[7][language], square.mortgageCost, 164, 14);
230 text(TRANS_COPYWRITE[language], 205, 4, 0, 4, true);
231 }
232 else
233 {
234 result.fills.push_back({19,16,162,36,propertyColor(request.square, request.languageId)});
235 // The TITLE DEED heading is commented out in the source.
236 text(name, 18, 32, 1, 10, true, false, true);
237 text(std::string(TRANS[1][language]) + " " + amount(square.rent[0]),
238 56, 12, 1, 12, true);
239 for (int i = 1; i <= 5; ++i)
240 {
241 pair(TRANS[i + 1][language], square.rent[i],
242 68 + (i - 1) * 12, 12, i == 1);
243 }
244 pair(TRANS[7][language], square.mortgageCost, 130, 12);
245 pair(TRANS[8][language], square.housePurchaseCost, 142, 12);
246 pair(TRANS[9][language], square.housePurchaseCost, 154, 12);
247 text(TRANS[10][language], 166, 12, 2, 12);
248 text(TRANS_SIMPLE_MONO[language], 180, 24, 1, 24, false, true);
249 text(TRANS_COPYWRITE[language], 205, 4, 0, 4, true);
250 }
251 if (moneyError) return std::unexpected(*moneyError);
252 return result;
253 }
254
255 std::expected<data::LegacyBitmapRGBA8, std::string> render(
256 const Request& request, fonts::Runtime& fonts, const TemplateResolver& resolveTemplate)
257 {
258 const auto layout = plan(request);
259 if (!layout) return std::unexpected(layout.error());
260 if (!fonts.ready()) return std::unexpected("European deed requires configured retail Arial");
261 if (!resolveTemplate) return std::unexpected("European deed template resolver is missing");
262 const auto background = resolveTemplate(layout->background);
263 if (!background) return std::unexpected(background.error());
264 if (!*background) return std::unexpected("European deed template is empty");
265 if ((*background)->image.width != Width || (*background)->image.height != Height)
266 return std::unexpected("European deed template must be 199x227");
268 std::vector<std::uint8_t>(Width * Height * 4, 255)};
269 const auto blitted = data::blitStraightRGBA8(image, (*background)->image,
271 if (!blitted) return std::unexpected(blitted.error());
272 for (const auto& fill : layout->fills)
273 {
274 for (int y = fill.y; y < fill.y + fill.height; ++y)
275 for (int x = fill.x; x < fill.x + fill.width; ++x)
276 {
277 const auto pixel = static_cast<std::size_t>((y * Width + x) * 4);
278 image.pixels[pixel] = static_cast<std::uint8_t>(fill.color);
279 image.pixels[pixel + 1] = static_cast<std::uint8_t>(fill.color >> 8U);
280 image.pixels[pixel + 2] = static_cast<std::uint8_t>(fill.color >> 16U);
281 image.pixels[pixel + 3] = 255;
282 }
283 }
284 const FontRestore restore{fonts, fonts.settings()};
285 for (const auto& region : layout->text)
286 {
287 const auto printed = print(image, region, fonts);
288 if (!printed) return std::unexpected(printed.error());
289 }
290 return image;
291 }
292}
decision::EconomicActionPlan plan
const char * name
std::uint16_t group
std::string_view text
LegacyBitmapRGBA8 image
fonts::Runtime & font
fonts::Settings saved
fonts::Metrics metrics
fonts::Runtime & runtime
std::uint32_t height
std::uint32_t width
unsigned index
std::string_view label
std::uint16_t value
Definition Timers.cpp:16
const char * suffix
const Settings & settings() const noexcept
bool ready() const noexcept
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataId EmptyDataId
Definition DataBanks.hpp:13
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< void, std::string > blitStraightRGBA8(LegacyBitmapRGBA8 &destination, const LegacyBitmapRGBA8 &source, std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
std::expected< data::LegacyBitmapRGBA8, std::string > render(const Request &request, fonts::Runtime &fonts, const TemplateResolver &resolveTemplate)
constexpr int Height
data::DataId templateId(const Request &request) noexcept
std::function< std::expected< std::shared_ptr< const data::BitmapRuntimeAsset >, std::string >(data::DataId)> TemplateResolver
int propertyIndex(int square) noexcept
constexpr int Width
std::expected< std::string, std::string > format(std::int64_t defaultValue, int monetarySystem, bool printSymbol, data::BoardEdition edition)
const SquareDefinition & originalDefinition(SquareType square)
std::size_t size()
std::vector< TextRegion > text
std::vector< Fill > fills