Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AITradeUtility.cpp
Go to the documentation of this file.
1#include "AITradeUtility.hpp"
2
3#include <algorithm>
4#include <cmath>
5
7{
8 namespace
9 {
10 inline constexpr std::array<std::array<double, 21>, 8> DarzinskisImportance{{
11 {{-13.4, -13.5, -13.7, -14.0, -14.5, -14.9, -15.4, -16.1, -16.9, -17.8,
12 -19.0, -20.0, -21.0, -22.0, -23.0, -24.0, -25.0, -26.0, -27.0, -28.0, -29.0}},
13 {{10.0, 8.0, 6.0, 4.0, 2.0, 0.6, 0.0, -0.6, -1.0, -1.5,
14 -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, -12.0}},
15 {{0.0, 0.05, 0.1, 0.15, 0.3, 0.9, 1.5, 0.5, 0.1, -0.5,
16 -1.0, -1.5, -2.0, -2.8, -3.6, -4.4, -5.2, -6.0, -6.8, -7.6, -8.4}},
17 {{-1.0, -0.5, -0.2, 0.1, 0.4, 1.1, 1.7, 1.3, 0.9, 0.8,
18 0.7, 0.665, 0.63, 0.595, 0.560, 0.525, 0.490, 0.455, 0.42, 0.385, 0.35}},
19 {{-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, -0.1, 0.3, 0.5, 0.6,
20 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.4, 0.4, 0.4, 0.5}},
21 {{-3.3, -2.8, -2.3, -1.8, -1.3, -0.8, -0.3, 0.1, 0.3, 0.35,
22 0.39, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.35, 0.35, 0.38, 0.45}},
23 {{-5.0, -4.5, -4.0, -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5,
24 0.0, 0.15, 0.22, 0.29, 0.4, 0.45, 0.52, 0.6, 0.70, 0.78, 0.85}},
25 {{-2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.4, 0.48, 0.50, 0.49,
26 0.49, 0.49, 0.49, 0.49, 0.49, 0.49, 0.48, 0.39, 0.39, 0.37, 0.36}}
27 }};
28 [[nodiscard]] std::size_t darzinskisIndex(std::int64_t assets) noexcept
29 {
30 if (assets <= 0)
31 return 0;
32 return static_cast<std::size_t>(std::min<std::int64_t>(assets, 5000) / 250);
33 }
34
36 std::size_t group, std::size_t index, double offset, std::uint8_t aiLevel) noexcept
37 {
38 if (aiLevel != 3 || group >= DarzinskisImportance.size())
39 return 1.0;
40 const double power = std::exp(DarzinskisImportance[group][index] * std::log(2.0));
41 return (power + offset) / (power + offset + 1.0);
42 }
43
44 [[nodiscard]] std::array<std::size_t, 8> monopolyImportanceOrder(
45 std::int64_t assets, bool descending = true)
46 {
47 std::array<std::size_t, 8> order{};
48 for (std::size_t index = 0; index < order.size(); ++index)
49 order[index] = index;
50 const auto chart = darzinskisIndex(assets);
51 std::stable_sort(order.begin(), order.end(), [&](std::size_t lhs, std::size_t rhs) {
52 return descending
53 ? DarzinskisImportance[lhs][chart] > DarzinskisImportance[rhs][chart]
54 : DarzinskisImportance[lhs][chart] < DarzinskisImportance[rhs][chart];
55 });
56 return order;
57 }
58
59 [[nodiscard]] PropertyImportanceResult findPlayerPropertyImportanceAtChart(
60 const rules::GameState& state,
63 const PropertyImportanceConfig& config,
64 std::size_t chart,
65 std::int64_t moneyOwed) noexcept
66 {
67 PropertyImportanceResult result{};
68 const auto liquid = ai::liquidAssets(state, player, false, false, moneyOwed);
69 const auto aiLevel = state.players[strategyPlayer].aiPlayerLevel;
70 for (std::size_t group = 0; group < ai::ExpensiveMonopolySquares.size(); ++group)
71 {
72 const auto square = ai::ExpensiveMonopolySquares[group];
73 if (ai::isMonopoly(state, square) &&
74 state.squares[static_cast<std::size_t>(square)].owner == player)
75 {
76 ++result.monopolies;
77 const double change = group == 0
78 ? config.propertyAllowTradeImportance
79 : config.monopolyReceivedImportance * monopolyAssetFactor(group, chart, 3.0, aiLevel);
80 result.importance += change;
81 result.monopolyImportance += change;
82 }
83
84 const int unowned = possibleMonopoly(state, player, square);
85 if (unowned == -1)
86 continue;
87 const double factor = monopolyAssetFactor(group, chart, 2.0, aiLevel);
88 if (unowned == 1)
89 {
94 result.importance += (twoLotEdge ? config.propertyTwoUnownedImportance
95 : config.propertyOneUnownedImportance) * factor;
96 }
97 else if (unowned == 2)
98 result.importance += config.propertyTwoUnownedImportance * factor;
99 }
100
101 PropertySets properties{};
102 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
103 std::size_t candidateCount{};
104 for (rules::PlayerNumber current = 0; current < state.numberOfPlayers; ++current)
105 {
106 properties[current] = ai::propertiesOwnedByPlayer(state, current);
107 if (current == player || ai::playerOwnsMonopoly(state, current, false) ||
108 state.players[current].currentSquare ==
109 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard))
110 continue;
112 }
114 state, player, rules::board::SquareGroup::Railroad, false, properties[player]);
115 if (railroads > 0 && static_cast<std::size_t>(railroads) <= config.railroadImportance.size())
116 result.importance += config.railroadImportance[static_cast<std::size_t>(railroads - 1)];
118 state, player, rules::board::SquareGroup::Utility, false, properties[player]);
119 if (utilities > 0 && static_cast<std::size_t>(utilities) <= config.utilityImportance.size())
120 result.importance += config.utilityImportance[static_cast<std::size_t>(utilities - 1)];
121
122 const auto order = monopolyImportanceOrder(liquid);
123 bool firstTradeProperty = true;
124 for (std::size_t rank = 0; rank + 1 < order.size(); ++rank)
125 {
126 const auto group = order[rank];
127 const auto trade = findSmallestMonopolyTrade(
129 std::span<const rules::PlayerNumber>(candidates.data(), candidateCount), properties);
130 if (trade.count == 0)
131 continue;
132 const double factor = monopolyAssetFactor(group, chart, 1.0, aiLevel);
133 result.importance += (firstTradeProperty ? config.propertyAllowTradeImportance
134 : config.propertyAllowMoreTradeImportance) * factor;
135 firstTradeProperty = false;
136 }
137 const int vetoes = vetoMonopolies(properties[player]);
138 if (vetoes >= 0 && static_cast<std::size_t>(vetoes) < config.monopolyVetoImportance.size())
139 result.importance += config.monopolyVetoImportance[static_cast<std::size_t>(vetoes)];
140 return result;
141 }
142
143 [[nodiscard]] MonopolyTradeGroup findRecursiveFromBase(
146 std::array<rules::PlayerNumber, rules::MaxPlayers> list,
147 std::size_t count,
148 const PropertySets& properties) noexcept
149 {
150 if (count == 0)
151 return {};
152
154 for (std::size_t index = 0; index < count; ++index)
155 combined |= properties[list[index]];
156
157 if ((combined & monopoly) != monopoly)
158 return {};
159
160 MonopolyTradeGroup best{};
162 static_cast<int>(count + 1))
163 {
164 best.count = count;
165 std::copy_n(list.begin(), count, best.players.begin());
166 }
167
168 for (std::size_t index = 0; index < count; ++index)
169 {
170 std::swap(list[count - 1], list[index]);
171 const auto current = findRecursiveFromBase(
172 baseProperties, monopoly, list, count - 1, properties);
173 if (current.count != 0 &&
174 (best.count == 0 || current.count < best.count))
175 {
176 best = current;
177 }
178 std::swap(list[index], list[count - 1]);
179 }
180
181 return best;
182 }
183
184 [[nodiscard]] MonopolyTradeGroup findRecursive(
185 rules::PlayerNumber player,
187 std::array<rules::PlayerNumber, rules::MaxPlayers> list,
188 std::size_t count,
189 const PropertySets& properties) noexcept
190 {
192 properties[player], monopoly, list, count, properties);
193 }
194
196 const rules::GameState& state,
198 std::span<const rules::PlayerNumber> players) noexcept
199 {
200 if (players.empty())
201 return false;
202 const int before = ai::monopoliesBetweenPlayers(
204 const int after = property == rules::board::SquareType::Go
205 ? before
207 if (before < static_cast<int>(players.size()) &&
208 after >= static_cast<int>(players.size()))
209 return true;
210 if (property == rules::board::SquareType::Go &&
211 before >= static_cast<int>(players.size()))
212 return true;
213 return players.size() == 1 && before != after;
214 }
215
216 [[nodiscard]] MonopolyTradeGroup findNthImportantGroup(
217 const rules::GameState& state,
219 std::span<const rules::PlayerNumber> players,
221 std::size_t nth) noexcept
222 {
223 MonopolyTradeGroup result{};
224 if (players.empty() || players.size() > rules::MaxPlayers || nth == 0)
225 return result;
226 std::size_t bestSize = rules::MaxPlayers + 1;
227 std::size_t found{};
228 const std::uint32_t combinations = 1u << players.size();
229 for (std::uint32_t mask = 1; mask < combinations; ++mask)
230 {
231 std::array<rules::PlayerNumber, rules::MaxPlayers> subset{};
232 std::size_t count{};
234 for (std::size_t index = 0; index < players.size(); ++index)
235 if ((mask & (1u << index)) != 0)
236 {
239 }
240 if (!hasRequired || count > bestSize ||
241 !importantGroupMatches(state, property,
242 std::span<const rules::PlayerNumber>(subset.data(), count)))
243 continue;
244 if (count < bestSize)
245 {
246 bestSize = count;
247 found = 0;
248 }
249 if (count == bestSize && ++found == nth)
250 {
251 result.count = count;
252 std::copy_n(subset.begin(), count, result.players.begin());
253 return result;
254 }
255 }
256 return {};
257 }
258 }
259
260 std::array<rules::board::SquareGroup, 8> orderMonopolyImportance(
261 std::int64_t liquidAssets,
262 MonopolySortOrder order,
263 std::span<const std::uint32_t> randomKeys) noexcept
264 {
265 std::array<std::size_t, 8> indices{};
266 for (std::size_t index = 0; index < indices.size(); ++index)
267 indices[index] = index;
268
269 if (order == MonopolySortOrder::Random)
270 {
271 if (randomKeys.size() >= indices.size())
272 {
273 std::stable_sort(indices.begin(), indices.end(),
274 [&](std::size_t lhs, std::size_t rhs) {
275 return randomKeys[lhs] < randomKeys[rhs];
276 });
277 }
278 }
279 else
280 {
281 const auto chart = darzinskisIndex(liquidAssets);
282 const bool descending = order == MonopolySortOrder::Descending;
283 std::stable_sort(indices.begin(), indices.end(),
284 [&](std::size_t lhs, std::size_t rhs) {
285 return descending
286 ? DarzinskisImportance[lhs][chart] > DarzinskisImportance[rhs][chart]
287 : DarzinskisImportance[lhs][chart] < DarzinskisImportance[rhs][chart];
288 });
289 }
290
291 std::array<rules::board::SquareGroup, 8> result{};
292 for (std::size_t index = 0; index < result.size(); ++index)
293 result[index] = static_cast<rules::board::SquareGroup>(indices[index]);
294 return result;
295 }
296
298 rules::PlayerNumber player,
300 std::span<const rules::PlayerNumber> candidates,
301 const PropertySets& properties) noexcept
302 {
303 if (player >= rules::MaxPlayers ||
305 return {};
306
307 std::array<rules::PlayerNumber, rules::MaxPlayers> list{};
308 for (std::size_t index = 0; index < candidates.size(); ++index)
309 {
311 return {};
313 }
314
315 return findRecursive(
316 player,
317 monopoly,
318 list,
319 candidates.size(),
320 properties);
321 }
322
323 std::int64_t transferTax(
324 const rules::GameState& state,
325 rules::board::PropertySet properties) noexcept
326 {
327 std::int64_t tax{};
328 for (std::size_t index = 0;
330 ++index)
331 {
332 const auto square = static_cast<rules::board::SquareType>(index);
333 const auto bit = rules::board::propertyBit(square);
334 if (bit == 0 || (properties & bit) == 0 ||
335 !state.squares[index].mortgaged)
336 continue;
337 tax += static_cast<std::int64_t>(
339 state.options.taxRate / 100;
340 }
341 return tax;
342 }
343
345 {
346 int count{};
347 for (std::size_t group = 1;
349 {
351 properties) != 0)
352 ++count;
353 }
354 return count;
355 }
356
358 const rules::GameState& state,
360 rules::board::SquareType square) noexcept
361 {
362 if (owner >= rules::MaxPlayers)
363 return -1;
364 const auto group = rules::board::definition(square).group;
365 if (static_cast<std::size_t>(group) >=
367 return -1;
368 const auto range = ai::groupRange(group);
369 int unowned{};
370 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
371 {
372 const auto candidate = static_cast<rules::board::SquareType>(index);
374 continue;
375 const auto candidateOwner = state.squares[index].owner;
376 if (candidateOwner == owner)
377 continue;
379 return -1;
380 ++unowned;
381 }
382 return unowned;
383 }
384
386 const rules::GameState& state,
387 rules::PlayerNumber player,
390 std::int64_t moneyOwed) noexcept
391 {
392 if (state.numberOfPlayers > rules::MaxPlayers ||
393 player >= state.numberOfPlayers || strategyPlayer >= state.numberOfPlayers)
394 return {};
396 state, player, strategyPlayer, config,
397 darzinskisIndex(ai::liquidAssets(state, player, false, false, moneyOwed)), moneyOwed);
398 }
399
401 const rules::GameState& before,
402 const rules::GameState& after,
403 rules::PlayerNumber player,
408 std::span<const std::int64_t> moneyOwed) noexcept
409 {
410 if (before.numberOfPlayers == 0 || before.numberOfPlayers > rules::MaxPlayers ||
411 after.numberOfPlayers != before.numberOfPlayers || player >= before.numberOfPlayers ||
412 strategyPlayer >= before.numberOfPlayers)
413 return 0.0;
414 const auto debtFor = [&](rules::PlayerNumber current) {
415 return current < moneyOwed.size() ? moneyOwed[current] : 0;
416 };
417 std::array<std::size_t, rules::MaxPlayers> charts{};
418 for (rules::PlayerNumber current = 0; current < before.numberOfPlayers; ++current)
420 after, current, false, false, debtFor(current)));
421
422 auto filtered = proposals;
423 const auto keep = ~proposals[player].propertiesGiven;
424 filtered[player].propertiesReceived = 0;
425 for (rules::PlayerNumber current = 0; current < before.numberOfPlayers; ++current)
426 filtered[current].propertiesReceived &= keep;
427 auto withoutOurProperties = before;
429
431 before, player, strategyPlayer, config, charts[player], debtFor(player));
433 after, player, strategyPlayer, config, charts[player], debtFor(player));
434 double importance = playerAfter.importance - playerBefore.importance;
435 const int playerMonopolyChange = playerAfter.monopolies - playerBefore.monopolies;
437 int involvedOthers{};
438
439 for (rules::PlayerNumber current = 0; current < before.numberOfPlayers; ++current)
440 {
441 if (current == player || !playerInvolvedInTrade(proposals[current]))
442 continue;
446 charts[current], debtFor(current));
448 after, current, strategyPlayer, config, charts[current], debtFor(current));
449 const int originalMonopolies = static_cast<int>(
450 ai::monopoliesOwned(before, current, false).count);
451 const int monopolyChange = otherAfter.monopolies - originalMonopolies;
453 otherAfter.monopolyImportance - otherBefore.monopolyImportance;
454 double importanceChange = otherAfter.importance - otherBefore.importance -
456 if (importanceChange < 0.0)
457 importanceChange *= config.negativePropertyImportanceChangeMultiplier;
458 importance -= importanceChange;
460 {
461 const double weight = givingMonopolyAway
462 ? config.givingMonopolyImportance
463 : config.monopolyReceivedImportance;
464 const double change = static_cast<double>(monopolyChange - playerMonopolyChange) *
465 weight * 0.875;
466 importance -= change;
469 else
471 }
473 }
474
475 // Retail assumes at least one other participant. Keep the intended
476 // arithmetic while avoiding a divide-by-zero for malformed proposals.
477 if (involvedOthers > 0)
478 importance -= totalMonopolyImportance / static_cast<double>(involvedOthers);
479 return importance;
480 }
481
483 const rules::GameState& state,
484 rules::PlayerNumber player) noexcept
485 {
486 if (state.numberOfPlayers > rules::MaxPlayers ||
487 player >= state.numberOfPlayers)
489
491 {
492 return ai::playerOwnsMonopoly(state, player, false)
495 }
496
499
500 std::array<double, rules::MaxPlayers> averageIncome{};
501 double largestIncome{};
503 for (rules::PlayerNumber current = 0;
504 current < state.numberOfPlayers; ++current)
505 {
506 const auto owned = ai::propertiesOwnedByPlayer(state, current);
508 state, current, 0, false, 1.0, owned) *
509 static_cast<double>(state.numberOfPlayers - 1);
510 averageIncome[current] -= static_cast<double>(
511 ai::averageRentPaid(state, current, 0, true, 1.0));
512 averageIncome[current] +=
513 static_cast<double>(state.options.passingGoAmount);
514
515 if (averageIncome[current] >= largestIncome)
516 {
517 largestIncome = averageIncome[current];
518 largestPlayer = current;
519 }
520 }
521
523 {
524 const auto playerIncome = averageIncome[player];
525 const auto gap = largestIncome - playerIncome;
526 if ((playerIncome == 0.0 && gap > 0.0) ||
527 (playerIncome != 0.0 && gap / playerIncome > 0.2))
529 }
530
532 }
533
535 const rules::GameState& state,
536 rules::PlayerNumber player,
537 std::span<const std::int64_t> moneyOwed) noexcept
538 {
539 if (state.numberOfPlayers > rules::MaxPlayers || player >= state.numberOfPlayers)
540 return rules::NobodyPlayer;
541
542 std::int64_t highestAssets{};
544 for (rules::PlayerNumber current = 0; current < state.numberOfPlayers; ++current)
545 {
546 if (current == player ||
547 state.players[current].currentSquare ==
548 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard) ||
549 ai::playerOwnsMonopoly(state, current, false))
550 continue;
551
552 const auto debt = current < moneyOwed.size() ? moneyOwed[current] : 0;
553 const auto assets = ai::liquidAssets(
554 state, current, false, false, debt);
555 if (assets > highestAssets)
556 {
558 highestPlayer = current;
559 }
560 }
561 return highestPlayer;
562 }
563
565 const rules::GameState& state,
566 rules::PlayerNumber player) noexcept
567 {
568 if (state.numberOfPlayers > rules::MaxPlayers ||
569 player >= state.numberOfPlayers)
570 return false;
571
572 bool playerIsOnlyOwner{};
573 for (rules::PlayerNumber current = 0;
574 current < state.numberOfPlayers; ++current)
575 {
576 if (ai::playerOwnsMonopoly(state, current, false))
577 {
578 if (current == player)
579 playerIsOnlyOwner = true;
580 else
581 return false;
582 }
583 else if (current == player)
584 {
585 return false;
586 }
587 }
588
590 return false;
591
592 PropertySets properties{};
593 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
594 std::size_t candidateCount{};
595 for (rules::PlayerNumber current = 0;
596 current < state.numberOfPlayers; ++current)
597 {
598 properties[current] = ai::propertiesOwnedByPlayer(state, current);
599 if (current == player ||
600 state.players[current].currentSquare ==
601 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard))
602 continue;
603 candidates[candidateCount++] = current;
604 }
605
606 // Retail calls AI_Find_Smallest_Monopoly_Trade with RULE_MAX_PLAYERS
607 // as a synthetic player whose property set is empty. Preserve that quirk:
608 // N real opponents therefore need at least N+1 monopolies between them.
610 {
611 const auto possible = findRecursiveFromBase(
613 if (possible.count != 0)
614 return false;
615 }
616 return true;
617 }
618
620 const rules::GameState& state,
621 rules::PlayerNumber player,
622 rules::PlayerNumber excludedPlayer) noexcept
623 {
624 if (state.numberOfPlayers > rules::MaxPlayers ||
625 player >= state.numberOfPlayers)
626 return false;
627
628 PropertySets properties{};
629 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
630 std::size_t count{};
631 for (rules::PlayerNumber current = 0; current < state.numberOfPlayers; ++current)
632 {
633 properties[current] = ai::propertiesOwnedByPlayer(state, current);
634 if (current == player || current == excludedPlayer ||
635 state.players[current].currentSquare ==
636 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard))
637 continue;
638 candidates[count++] = current;
639 }
640 if (count == 0)
641 return false;
642
644 {
647 std::span<const rules::PlayerNumber>(candidates.data(), count),
648 properties).count != 0)
649 return true;
650 }
651 return false;
652 }
653
657 rules::PlayerNumber purchasingPlayer,
658 rules::board::SquareType purchasingProperty) noexcept
659 {
662 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
663 property >= SquareType::InJail)
664 return Importance::NotImportant;
665
666 auto simulated = state;
667 if (purchasingPlayer < simulated.numberOfPlayers &&
668 purchasingProperty < SquareType::InJail && purchasingProperty != property)
669 simulated.squares[static_cast<std::size_t>(purchasingProperty)].owner = purchasingPlayer;
670 if (ai::isCashCow(property))
671 return Importance::CashCow;
672
673 const auto directFor = [&](rules::PlayerNumber candidate) {
674 if (candidate >= simulated.numberOfPlayers) return false;
675 const std::array<rules::PlayerNumber, 1> one{candidate};
676 return findNthImportantGroup(simulated, property, one,
677 rules::NobodyPlayer, 1).count == 1;
678 };
680 {
682 return Importance::GivesDirectMonopoly;
683 }
684 else
685 {
686 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
687 if (current != excludedPlayer && directFor(current))
688 return Importance::GivesDirectMonopoly;
689 }
690
691 std::array<rules::PlayerNumber, rules::MaxPlayers> original{};
692 std::size_t originalCount{};
693 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
694 if (current != excludedPlayer && !ai::playerOwnsMonopoly(simulated, current, false))
695 original[originalCount++] = current;
696
697 for (std::size_t tradeNumber = 1; tradeNumber <= (1u << rules::MaxPlayers); ++tradeNumber)
698 {
699 auto players = original;
700 std::size_t playerCount = originalCount;
701 bool removedAny{};
702 while (playerCount != 0)
703 {
704 const auto existing = findNthImportantGroup(
705 simulated, SquareType::Go,
706 std::span<const rules::PlayerNumber>(players.data(), playerCount),
708 if (existing.count == 0) break;
709 playerCount = ai::removePlayersFromList(
710 std::span<rules::PlayerNumber>(players.data(), players.size()), playerCount,
711 std::span<const rules::PlayerNumber>(existing.players.data(), existing.count));
712 removedAny = true;
713 }
714 if (removedAny && playerCount != 0 && playerCount != originalCount)
715 {
716 const auto enabled = findNthImportantGroup(
717 simulated, property,
718 std::span<const rules::PlayerNumber>(players.data(), playerCount),
719 includedPlayer, 1);
720 if (enabled.count != 0) return Importance::AllowsTrade;
721 }
722 if (!removedAny) break;
723 }
724 return Importance::NotImportant;
725 }
726
728 std::span<const std::int64_t> timeLastTrade,
729 std::size_t maxTrades) noexcept
730 {
731 if (maxTrades == 0 || maxTrades > timeLastTrade.size())
732 return -1;
733 for (std::size_t index = 0; index < maxTrades; ++index)
734 {
735 if (timeLastTrade[index] == 0)
736 return static_cast<int>(index);
737 }
738 return -1;
739 }
740
742 const rules::GameState& state,
743 rules::PlayerNumber player,
744 std::uint8_t importance,
745 std::span<const std::int64_t> timeLastTrade,
746 std::size_t maxTrades,
747 const TradeCadenceInputs& inputs) noexcept
748 {
749 if (state.numberOfPlayers > rules::MaxPlayers ||
750 player >= state.numberOfPlayers)
751 return false;
752
753 if (inputs.playerSendingTrade || state.tradeInProgress ||
754 (inputs.buySellMortgagePlayer != player &&
755 inputs.buySellMortgagePlayer != rules::NobodyPlayer))
756 return false;
757
758 if (findFreeTradeSpot(timeLastTrade, maxTrades) < 0)
759 return false;
760
761 if ((importance & TradeSomewhatImportant) != 0)
762 return true;
763
764 if (inputs.shouldGiveAwayMonopoly &&
765 inputs.giveAwayRoll <= inputs.giveAwayProbability)
766 return true;
767
768 const auto monopolyDecision = shouldTradeForMonopoly(state, player);
770 ? inputs.monopolyProbability
771 : inputs.proposalProbability;
772 return inputs.proposalRoll <= probability;
773 }
774
776 const rules::GameState& state,
777 rules::PlayerNumber toPlayer,
778 rules::board::PropertySet& properties,
781 std::int64_t moneyOwed) noexcept
782 {
783 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
784 toPlayer >= state.numberOfPlayers ||
786 return false;
787
788 const auto assets = ai::liquidAssets(state, toPlayer, false, false, moneyOwed);
789 const auto order = monopolyImportanceOrder(
791 for (const auto group : order)
792 {
795 if ((properties & groupSet) != groupSet)
796 continue;
797
798 auto next = proposals;
799 for (rules::PlayerNumber owner = 0; owner < state.numberOfPlayers; ++owner)
800 {
801 const auto owned = ai::propertiesOwnedByPlayer(state, owner) & groupSet;
802 if (owner == toPlayer || owned == 0)
803 continue;
804 next[toPlayer].propertiesReceived |= owned;
805 next[owner].propertiesGiven |= owned;
806 }
807 proposals = next;
808 properties &= ~groupSet;
809 return true;
810 }
811 return false;
812 }
813
815 const rules::GameState& state,
816 rules::PlayerNumber player,
818 std::span<const double> playerAttitudes) noexcept
819 {
821 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
822 player >= state.numberOfPlayers || playerAttitudes.size() < state.numberOfPlayers)
823 return result;
824
825 for (rules::PlayerNumber target = 0; target < state.numberOfPlayers; ++target)
826 {
827 const double attitude = playerAttitudes[target];
828 int index = attitude <= -1.0 ? 0 : static_cast<int>((attitude + 1.0) * 10.0);
829 if (index >= static_cast<int>(WhatToTradeEntries))
830 index = static_cast<int>(WhatToTradeEntries) - 1;
831 result.playerIndex[target] = index;
832 result.playerProperties[target] = ai::propertiesOwnedByPlayer(state, target);
833 if (target == player || !playerInvolvedInTrade(proposals[target]))
834 continue;
835 result.tradePlayers[result.tradePlayerCount++] = target;
836 result.totalAttitude += attitude;
837 }
838 return result;
839 }
840
842 const rules::GameState& state,
843 rules::PlayerNumber player,
847 const CashMultiplierTable& multipliers) noexcept
848 {
849 TradeImportanceList result{};
850 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
851 player >= state.numberOfPlayers || strategyPlayer >= state.numberOfPlayers)
852 return result;
853 const double factor = player == strategyPlayer
855 const auto owned = ai::propertiesOwnedByPlayer(state, player);
860 if (rails == 4) --rails;
861 if (utils == 2) --utils;
862
863 result = {{
864 {TradeImportanceItem::Monopoly, config.monopolyReceivedImportance * 0.75 * factor},
865 {TradeImportanceItem::Trade, config.propertyAllowTradeImportance * factor},
866 {TradeImportanceItem::TwoUnowned, config.propertyTwoUnownedImportance * factor},
867 {TradeImportanceItem::OneUnowned, config.propertyOneUnownedImportance * factor},
869 (config.railroadImportance[rails] -
870 (rails ? config.railroadImportance[rails - 1] : 0.0)) * factor},
872 (config.utilityImportance[utils] -
873 (utils ? config.railroadImportance[utils - 1] : 0.0)) * factor}
874 }};
875
876 if (strategyPlayer != player)
877 {
883 if (rails)
884 {
885 --rails;
886 result[4].importance += (config.railroadImportance[rails] -
887 (rails ? config.railroadImportance[rails - 1] : 0.0)) * factor;
888 }
889 if (utils)
890 {
891 --utils;
892 result[5].importance += (config.utilityImportance[utils] -
893 (utils ? config.railroadImportance[utils - 1] : 0.0)) * factor;
894 }
895 }
896
897 for (std::size_t head = 0; head < result.size(); ++head)
898 {
899 for (std::size_t tail = head + 1; tail < result.size(); ++tail)
900 {
901 if (result[head].importance < result[tail].importance)
902 std::swap(result[head], result[tail]);
903 }
904 }
905 return result;
906 }
907
909 const rules::GameState& before,
910 const rules::GameState& after,
911 rules::PlayerNumber player,
916 const PropertySets& properties,
917 bool giveDescending,
918 const WhatToTradeTable& whatToTrade,
920 std::int64_t playerMoneyOwed) noexcept
921 {
922 if (before.numberOfPlayers == 0 || before.numberOfPlayers > rules::MaxPlayers ||
923 after.numberOfPlayers != before.numberOfPlayers ||
924 player >= before.numberOfPlayers || strategyPlayer >= before.numberOfPlayers)
925 return false;
926
928 for (rules::PlayerNumber current = 0; current < before.numberOfPlayers; ++current)
929 combinedTraded |= proposals[current].propertiesGiven |
930 proposals[current].propertiesReceived;
931
932 std::uint16_t monopoliesTraded{};
933 for (std::size_t group = 0; group < ai::ExpensiveMonopolySquares.size(); ++group)
934 {
936 monopoliesTraded |= static_cast<std::uint16_t>(1u << group);
937 }
938
939 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
940 std::size_t candidateCount{};
941 for (rules::PlayerNumber current = 0; current < after.numberOfPlayers; ++current)
942 {
943 if (current == player ||
944 after.players[current].currentSquare ==
945 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard) ||
946 ai::playerOwnsMonopoly(after, current, false))
947 continue;
948 candidates[candidateCount++] = current;
949 }
950
951 const auto order = monopolyImportanceOrder(
952 ai::liquidAssets(after, player, false, false, playerMoneyOwed),
954 rules::board::PropertySet selected{};
955
957 {
958 if (player == strategyPlayer)
959 return addTradeMonopoly(before, player, combinedProperties,
961
963 ? 0 : static_cast<int>((strategyAttitudeTowardPlayer + 1.0) * 10.0);
964 generosityIndex = std::clamp(generosityIndex, 0,
965 static_cast<int>(WhatToTradeEntries) - 1);
966 const auto& generosity = whatToTrade[static_cast<std::size_t>(generosityIndex)];
967 if (generosity.giveMonopoly == PropertyClassification::Best ||
969 {
970 if (!addTradeMonopoly(before, player, combinedProperties,
971 generosity.giveMonopoly, proposals, playerMoneyOwed))
972 return false;
973 }
974
975 int counter = generosity.giveGroupTrades;
976 for (; counter > 0; --counter)
977 {
978 if (!addTypeProperty(before, after, player, strategyPlayer,
980 properties, giveDescending, whatToTrade,
982 !addTypeProperty(before, after, player, strategyPlayer,
984 properties, giveDescending, whatToTrade,
986 !addTypeProperty(before, after, player, strategyPlayer,
988 properties, giveDescending, whatToTrade,
990 break;
991 }
992 counter += generosity.giveCashCows;
993 for (; counter > 0; --counter)
994 {
995 if (!addTypeProperty(before, after, player, strategyPlayer,
997 properties, giveDescending, whatToTrade,
999 !addTypeProperty(before, after, player, strategyPlayer,
1001 properties, giveDescending, whatToTrade,
1003 break;
1004 }
1005 counter += generosity.giveJunk;
1006 for (; counter > 0; --counter)
1007 {
1008 if (!addTypeProperty(before, after, player, strategyPlayer,
1010 properties, giveDescending, whatToTrade,
1012 break;
1013 }
1014 return true;
1015 }
1016
1017 if (item == TradeImportanceItem::Railroad ||
1019 {
1024 if ((groupSet & proposals[player].propertiesGiven) != 0)
1025 return false;
1026 auto available = combinedProperties & groupSet & ~properties[player];
1027 if (available == 0)
1028 return false;
1029 selected = available & (0u - available);
1030 }
1031 else if (item == TradeImportanceItem::Junk)
1032 {
1033 // Retail scans for junk but then returns FALSE unconditionally.
1034 return false;
1035 }
1036 else if (item == TradeImportanceItem::Trade ||
1039 {
1041 ? 99
1042 : static_cast<int>(item) -
1043 (static_cast<int>(TradeImportanceItem::OneUnowned) + 1);
1044 bool found{};
1045 for (const auto group : order)
1046 {
1047 if (player == strategyPlayer && group == 0)
1048 continue;
1049 if ((monopoliesTraded & static_cast<std::uint16_t>(1u << group)) != 0)
1050 continue;
1054 continue;
1055 if (availableInGroup == (properties[player] & groupSet))
1056 continue;
1057 const auto external = availableInGroup & ~properties[player];
1058 if (external == 0)
1059 continue;
1060
1061 if (item == TradeImportanceItem::Trade)
1062 {
1063 auto simulatedProperties = properties;
1064 for (rules::PlayerNumber current = 0;
1065 current < before.numberOfPlayers; ++current)
1066 simulatedProperties[current] &= ~external;
1067 simulatedProperties[player] |= external;
1068 const auto trade = findSmallestMonopolyTrade(
1069 player, groupSet,
1070 std::span<const rules::PlayerNumber>(candidates.data(), candidateCount),
1072 if (trade.count == 0)
1073 continue;
1074 }
1075 else if (possibleMonopoly(after, player,
1077 {
1078 continue;
1079 }
1080 selected = external;
1081 found = true;
1082 break;
1083 }
1084 if (!found)
1085 return false;
1086 }
1087 else
1088 {
1089 return false;
1090 }
1091
1092 proposals[player].propertiesReceived |= selected;
1093 for (rules::PlayerNumber current = 0; current < before.numberOfPlayers; ++current)
1094 {
1095 const auto given = selected & properties[current];
1096 if (given == 0)
1097 continue;
1098 proposals[current].propertiesGiven |= given;
1100 }
1101 return true;
1102 }
1103
1106 rules::PlayerNumber proposedPlayer,
1107 const TradeProposalRecord& proposal) noexcept
1108 {
1109 if (proposedPlayer >= rules::MaxPlayers)
1110 return;
1111 const auto traded = proposal.propertiesReceived | proposal.propertiesGiven;
1112 auto& bit1 = memory.bit1[proposedPlayer];
1113 auto& bit2 = memory.bit2[proposedPlayer];
1114 const auto mask = ~bit1;
1115 const auto mask2 = bit1 & traded & ~bit2;
1116 bit1 |= mask & traded;
1117 bit2 |= mask2;
1118 bit1 &= ~mask2;
1119 }
1120
1123 rules::PlayerNumber proposedPlayer,
1124 const TradeProposalRecord& proposal,
1125 int dangerLevel) noexcept
1126 {
1127 if (proposedPlayer >= rules::MaxPlayers || dangerLevel < 1 || dangerLevel > 3)
1128 return 0;
1129 const auto traded = proposal.propertiesReceived | proposal.propertiesGiven;
1130 const auto first = traded & memory.bit1[proposedPlayer];
1131 const auto second = traded & memory.bit2[proposedPlayer];
1132 if (dangerLevel == 1)
1133 return first | second;
1134 if (dangerLevel == 2)
1135 return second;
1136 return first & second;
1137 }
1138
1140 {
1141 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
1142 {
1143 auto& bit1 = memory.bit1[player];
1144 auto& bit2 = memory.bit2[player];
1145 const auto mask = ~bit1;
1146 const auto mask2 = bit2 & mask;
1147 bit2 ^= mask2;
1148 bit1 = mask2;
1149 }
1150 }
1151
1153 double attitude,
1154 const CashMultiplierTable& multipliers) noexcept
1155 {
1156 if (attitude <= -1.0)
1157 attitude = -0.999;
1158 if (attitude >= 1.0)
1159 attitude = 0.999;
1160
1161 auto index = static_cast<std::size_t>((attitude + 1.0) * 10.0);
1164
1165 // Preserve retail's interpolation quirk: it uses the fractional part
1166 // of attitude itself, not the fractional position within the 0.1 bin.
1167 attitude -= std::floor(attitude);
1168 if (index == 0)
1169 return multipliers[0];
1170 return multipliers[index] * attitude +
1171 multipliers[index - 1] * (1.0 - attitude);
1172 }
1173
1175 const TradeProposalRecord& proposal) noexcept
1176 {
1177 if (proposal.propertiesGiven != 0 || proposal.propertiesReceived != 0 ||
1178 proposal.cashReceived != 0 || proposal.cashGiven != 0)
1179 return true;
1180 for (std::size_t index = 0; index < DeckCount; ++index)
1181 {
1182 if (proposal.jailCardGiven[index] || proposal.jailCardReceived[index])
1183 return true;
1184 }
1185 return false;
1186 }
1187
1189 const TradeProposalList& proposals) noexcept
1190 {
1191 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
1192 {
1193 if (proposals[player].cashReceived != 0)
1194 return player;
1195 }
1196 return rules::NobodyPlayer;
1197 }
1198
1200 const rules::GameState& state,
1202 std::span<const FutureImmunityRecord> immunities) noexcept
1203 {
1204 if (state.numberOfPlayers > rules::MaxPlayers)
1205 return false;
1206
1207 bool someoneInTrade{};
1208 for (rules::PlayerNumber player = 0;
1209 player < state.numberOfPlayers; ++player)
1210 {
1211 const auto& proposal = proposals[player];
1212 if (!playerInvolvedInTrade(proposal))
1213 continue;
1214
1215 someoneInTrade = true;
1216 bool given = proposal.propertiesGiven != 0 || proposal.cashGiven != 0;
1217 bool received =
1218 proposal.propertiesReceived != 0 || proposal.cashReceived != 0;
1219 for (std::size_t index = 0; index < DeckCount; ++index)
1220 {
1221 received = received || proposal.jailCardReceived[index];
1222 given = given || proposal.jailCardGiven[index];
1223 }
1224 for (const auto& immunity : immunities)
1225 {
1226 if (immunity.count == 0)
1227 continue;
1228 if (immunity.fromPlayer == player)
1229 given = true;
1230 if (immunity.toPlayer == player)
1231 received = true;
1232 }
1233 if (!given || !received)
1234 return false;
1235 }
1236 return someoneInTrade;
1237 }
1238
1240 const rules::GameState& state,
1242 std::span<const FutureImmunityRecord> immunities) noexcept
1243 {
1244 if (state.numberOfPlayers > rules::MaxPlayers)
1245 return;
1246
1248 int firstPlayerNeed{};
1249 for (rules::PlayerNumber player = 0;
1250 player < state.numberOfPlayers; ++player)
1251 {
1252 auto& proposal = proposals[player];
1253 if (!playerInvolvedInTrade(proposal))
1254 continue;
1255
1256 bool given = proposal.propertiesGiven != 0 || proposal.cashGiven != 0;
1257 bool received = proposal.propertiesReceived != 0 || proposal.cashReceived != 0;
1258 for (std::size_t deck = 0; deck < DeckCount; ++deck)
1259 {
1260 given = given || proposal.jailCardGiven[deck];
1261 received = received || proposal.jailCardReceived[deck];
1262 }
1263 for (const auto& immunity : immunities)
1264 {
1265 if (immunity.count == 0)
1266 continue;
1267 if (immunity.fromPlayer == player)
1268 given = true;
1269 if (immunity.toPlayer == player)
1270 received = true;
1271 }
1272
1273 if (!given)
1274 {
1276 firstPlayerNeed = 1;
1277 else
1278 {
1279 proposals[dollarPlayer].cashReceived += 1;
1280 proposal.cashGiven += 1;
1281 }
1282 }
1283 if (!received)
1284 {
1286 firstPlayerNeed = 2;
1287 else
1288 {
1289 proposals[dollarPlayer].cashGiven += 1;
1290 proposal.cashReceived += 1;
1291 }
1292 }
1293
1295 dollarPlayer = player;
1296 else if (firstPlayerNeed != 0)
1297 {
1298 if (firstPlayerNeed == 1)
1299 {
1300 proposals[dollarPlayer].cashGiven += 1;
1301 proposal.cashReceived += 1;
1302 }
1303 else
1304 {
1305 proposals[dollarPlayer].cashReceived += 1;
1306 proposal.cashGiven += 1;
1307 }
1308 }
1309 }
1310 }
1311
1314 const TradeProposalList& proposals) noexcept
1315 {
1316 if (state.numberOfPlayers > rules::MaxPlayers)
1317 return;
1318
1319 for (rules::PlayerNumber player = 0;
1320 player < state.numberOfPlayers; ++player)
1321 {
1322 const auto& proposal = proposals[player];
1323 for (std::size_t index = 0;
1325 ++index)
1326 {
1327 const auto square = static_cast<rules::board::SquareType>(index);
1328 if ((rules::board::propertyBit(square) & proposal.propertiesReceived) != 0)
1329 state.squares[index].owner = player;
1330 }
1331 state.players[player].cash += proposal.cashReceived - proposal.cashGiven;
1332 for (std::size_t deck = 0; deck < DeckCount; ++deck)
1333 {
1334 if (proposal.jailCardReceived[deck])
1335 state.cards[deck].jailOwner = player;
1336 }
1337 }
1338 }
1339
1341 const rules::GameState& state,
1342 const TradeProposalList& proposals) noexcept
1343 {
1344 if (state.numberOfPlayers > rules::MaxPlayers)
1345 return false;
1346
1347 auto after = state;
1349 for (const auto square : ai::ExpensiveMonopolySquares)
1350 {
1351 const auto oldOwner = ai::isMonopoly(state, square)
1354 const auto newOwner = ai::isMonopoly(after, square)
1355 ? ai::firstOwnerInMonopoly(after, square)
1357 if (oldOwner != newOwner)
1358 return true;
1359 }
1360 return false;
1361 }
1362
1364 rules::PlayerNumber player,
1365 std::span<const FutureImmunityRecord> immunities) noexcept
1366 {
1367 for (const auto& immunity : immunities)
1368 {
1369 if (immunity.count == 0)
1370 continue;
1371 if (immunity.fromPlayer == player || immunity.toPlayer == player)
1372 return true;
1373 }
1374 return false;
1375 }
1376
1378 const rules::GameState& state,
1379 rules::PlayerNumber player,
1381 const ProposedTradeSendInputs& inputs) noexcept
1382 {
1384 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1385 player >= state.numberOfPlayers)
1386 return plan;
1387
1388 if (inputs.playerSendingTrade && !state.tradeInProgress)
1389 {
1391 return plan;
1392 }
1393
1394 std::size_t involved{};
1395 for (rules::PlayerNumber current = 0; current < state.numberOfPlayers; ++current)
1396 {
1397 if (playerInvolvedInTrade(proposals[current]))
1398 ++involved;
1399 }
1400 if (involved != 2)
1401 {
1403 return plan;
1404 }
1405
1406 auto append = [&](actions::Type type,
1407 std::int64_t a = 0, std::int64_t b = 0,
1408 std::int64_t c = 0, std::int64_t d = 0,
1409 std::int64_t e = 0) mutable {
1410 if (plan.count >= plan.actions.size())
1411 return false;
1412 auto& message = plan.actions[plan.count++];
1413 message.action = type;
1414 message.fromPlayer = player;
1415 message.toPlayer = rules::BankPlayer;
1416 message.numberA = a;
1417 message.numberB = b;
1418 message.numberC = c;
1419 message.numberD = d;
1420 message.numberE = e;
1421 return true;
1422 };
1423
1424 if (!state.tradeInProgress || inputs.tradeAccept)
1425 {
1428 else
1430 return plan;
1431 }
1432
1433 for (rules::PlayerNumber from = 0; from < state.numberOfPlayers; ++from)
1434 {
1435 for (rules::PlayerNumber to = 0; to < state.numberOfPlayers; ++to)
1436 {
1437 if (state.players[from].cashGivenInTrade[to] == 0)
1438 continue;
1439 if (!append(actions::Type::TradeItem, from, to,
1440 static_cast<std::int64_t>(rules::TradeItemKind::Cash), 0))
1441 {
1443 return plan;
1444 }
1445 }
1446 }
1447
1448 auto working = proposals;
1449 for (auto& proposal : working)
1450 {
1451 proposal.cashGiven -= proposal.cashReceived;
1452 proposal.cashReceived = 0;
1453 if (proposal.cashGiven < 0)
1454 {
1455 proposal.cashReceived = -proposal.cashGiven;
1456 proposal.cashGiven = 0;
1457 }
1458 }
1459
1460 for (;;)
1461 {
1464 break;
1465
1466 bool foundGiver{};
1467 for (rules::PlayerNumber current = 0; current < rules::MaxPlayers; ++current)
1468 {
1469 auto& giver = working[current];
1470 if (giver.cashGiven == 0)
1471 continue;
1472
1473 foundGiver = true;
1475 const auto amount = std::min(receiver.cashReceived, giver.cashGiven);
1477 static_cast<std::int64_t>(rules::TradeItemKind::Cash), amount))
1478 {
1480 return plan;
1481 }
1482 giver.cashGiven -= amount;
1483 receiver.cashReceived -= amount;
1484 if (receiver.cashReceived == 0)
1485 break;
1486 }
1487 if (!foundGiver)
1488 break;
1489 }
1490
1492 const auto inJail = static_cast<std::size_t>(rules::board::SquareType::InJail);
1493 for (rules::PlayerNumber current = 0; current < rules::MaxPlayers; ++current)
1494 {
1495 for (std::size_t squareNo = 0; squareNo < inJail; ++squareNo)
1496 {
1497 const auto square = static_cast<rules::board::SquareType>(squareNo);
1498 const auto bit = rules::board::propertyBit(square);
1499 if (bit == 0)
1500 continue;
1501 const auto owner = state.squares[squareNo].owner;
1502 const auto currentReceiver = state.squares[squareNo].offeredInTradeTo;
1503
1504 if ((proposals[current].propertiesReceived & bit) != 0)
1505 {
1506 if (currentReceiver != current &&
1507 !append(actions::Type::TradeItem, owner, current,
1508 static_cast<std::int64_t>(rules::TradeItemKind::Square),
1509 static_cast<std::int64_t>(squareNo)))
1510 {
1512 return plan;
1513 }
1515 }
1516 else if (currentReceiver == current &&
1517 (propertiesTraded & bit) == 0)
1518 {
1519 if (!append(actions::Type::TradeItem, owner, owner,
1520 static_cast<std::int64_t>(rules::TradeItemKind::Square),
1521 static_cast<std::int64_t>(squareNo)))
1522 {
1524 return plan;
1525 }
1526 }
1527 }
1528 }
1529
1530 std::array<rules::PlayerNumber, DeckCount> cardReceived{};
1532 for (rules::PlayerNumber current = 0; current < rules::MaxPlayers; ++current)
1533 {
1534 for (std::size_t deck = 0; deck < DeckCount; ++deck)
1535 {
1536 if (proposals[current].jailCardReceived[deck])
1537 cardReceived[deck] = current;
1538 }
1539 }
1540
1541 for (std::size_t deck = 0; deck < DeckCount; ++deck)
1542 {
1543 const auto currentReceiver = state.cards[deck].jailOfferedInTradeTo;
1545 continue;
1546 const auto owner = state.cards[deck].jailOwner;
1548 ? owner : cardReceived[deck];
1550 static_cast<std::int64_t>(rules::TradeItemKind::JailCard),
1551 static_cast<std::int64_t>(deck)))
1552 {
1554 return plan;
1555 }
1556 }
1557
1558 const auto doneA = inputs.sendTradeOffer ? 1 : 0;
1559 const auto doneB = inputs.sendTradeOffer
1560 ? static_cast<std::int64_t>(inputs.proposedPlayer)
1561 : 1;
1563 {
1565 return plan;
1566 }
1567
1569 return plan;
1570 }
1571
1574 FutureImmunityList& immunities,
1576 std::int64_t amount,
1577 rules::PlayerNumber fromPlayer,
1578 rules::PlayerNumber toPlayer,
1580 {
1581 bool applied{};
1582 const auto validPlayer = [](rules::PlayerNumber player) noexcept {
1583 return player < rules::MaxPlayers;
1584 };
1585
1586 switch (kind)
1587 {
1589 if (validPlayer(fromPlayer) && validPlayer(toPlayer))
1590 {
1591 proposals[fromPlayer].cashGiven = amount;
1592 proposals[toPlayer].cashReceived = amount;
1593 applied = true;
1594 }
1595 break;
1596
1598 if (amount >= 0 && amount < static_cast<std::int64_t>(
1600 {
1601 const auto square = static_cast<rules::board::SquareType>(amount);
1602 const auto propertyBit = rules::board::propertyBit(square);
1603 if (propertyBit != 0)
1604 {
1605 if (toPlayer == rules::NobodyPlayer)
1606 {
1607 for (auto& proposal : proposals)
1608 {
1609 proposal.propertiesReceived &= ~propertyBit;
1610 proposal.propertiesGiven &= ~propertyBit;
1611 }
1612 applied = true;
1613 }
1614 else if (validPlayer(fromPlayer) && validPlayer(toPlayer))
1615 {
1616 proposals[fromPlayer].propertiesGiven |= propertyBit;
1617 proposals[toPlayer].propertiesReceived |= propertyBit;
1618 applied = true;
1619 }
1620 }
1621 }
1622 break;
1623
1625 if (amount >= 0 && amount < static_cast<std::int64_t>(DeckCount))
1626 {
1627 const auto deck = static_cast<std::size_t>(amount);
1628 if (toPlayer == rules::NobodyPlayer)
1629 {
1630 for (auto& proposal : proposals)
1631 {
1632 proposal.jailCardGiven[deck] = false;
1633 proposal.jailCardReceived[deck] = false;
1634 }
1635 applied = true;
1636 }
1637 else if (validPlayer(fromPlayer) && validPlayer(toPlayer))
1638 {
1639 proposals[fromPlayer].jailCardGiven[deck] = true;
1640 proposals[toPlayer].jailCardReceived[deck] = true;
1641 applied = true;
1642 }
1643 }
1644 break;
1645
1648 if (validPlayer(fromPlayer) && validPlayer(toPlayer))
1649 {
1650 const auto count = static_cast<std::uint8_t>(amount);
1651 auto existing = std::find_if(
1652 immunities.begin(), immunities.end(),
1653 [&](const FutureImmunityRecord& entry) {
1654 return entry.count != 0 &&
1655 entry.hitType == kind &&
1656 entry.properties == propertySet &&
1657 entry.toPlayer == toPlayer;
1658 });
1659 if (existing != immunities.end())
1660 {
1661 existing->count = count;
1662 applied = true;
1663 }
1664 else
1665 {
1666 auto free = std::find_if(
1667 immunities.begin(), immunities.end(),
1668 [](const FutureImmunityRecord& entry) {
1669 return entry.count == 0;
1670 });
1671 if (free != immunities.end())
1672 {
1673 *free = {propertySet, fromPlayer, toPlayer, count, kind};
1674 applied = true;
1675 }
1676 }
1677 }
1678 break;
1679 }
1680
1681 // The retail loop forgot to advance list_point and normalized player 0
1682 // repeatedly. Normalize every proposal as the comment intended.
1683 for (auto& proposal : proposals)
1684 {
1685 if (proposal.cashGiven > proposal.cashReceived)
1686 {
1687 proposal.cashGiven -= proposal.cashReceived;
1688 proposal.cashReceived = 0;
1689 }
1690 else
1691 {
1692 proposal.cashReceived -= proposal.cashGiven;
1693 proposal.cashGiven = 0;
1694 }
1695 }
1696 return applied;
1697 }
1698
1699}
std::size_t count
std::array< rules::PlayerNumber, rules::MaxPlayers > players
decision::EconomicActionPlan plan
std::size_t offset
std::uint16_t group
bool enabled
Definition Engine.cpp:170
unsigned state
unsigned index
data::DataTag first
Kind kind
data::DataTag immunity
std::int64_t transferTax(const rules::GameState &state, rules::board::PropertySet properties) noexcept
void rememberTradedProperties(PropertyTradeMemory &memory, rules::PlayerNumber proposedPlayer, const TradeProposalRecord &proposal) noexcept
int findFreeTradeSpot(std::span< const std::int64_t > timeLastTrade, std::size_t maxTrades) noexcept
void forgetTradedProperties(PropertyTradeMemory &memory) noexcept
double cashMultiplier(double attitude, const CashMultiplierTable &multipliers) noexcept
PlayerPropertyAttitudeList createPlayerPropertyAttitudeList(const rules::GameState &state, rules::PlayerNumber player, const TradeProposalList &proposals, std::span< const double > playerAttitudes) noexcept
rules::PlayerNumber nextPlayerWantingCash(const TradeProposalList &proposals) noexcept
constexpr std::size_t DeckCount
std::array< TradeImportanceRecord, 6 > TradeImportanceList
ProposedTradeSendPlan buildProposedTradeSendPlan(const rules::GameState &state, rules::PlayerNumber player, const TradeProposalList &proposals, const ProposedTradeSendInputs &inputs) noexcept
bool tradeIsProper(const rules::GameState &state, const TradeProposalList &proposals, std::span< const FutureImmunityRecord > immunities) noexcept
rules::board::PropertySet propertiesAlreadyTraded(const PropertyTradeMemory &memory, rules::PlayerNumber proposedPlayer, const TradeProposalRecord &proposal, int dangerLevel) noexcept
bool playerInvolvedInTrade(const TradeProposalRecord &proposal) noexcept
constexpr std::uint8_t TradeSomewhatImportant
std::array< rules::board::SquareGroup, 8 > orderMonopolyImportance(std::int64_t liquidAssets, MonopolySortOrder order, std::span< const std::uint32_t > randomKeys) noexcept
int vetoMonopolies(rules::board::PropertySet properties) noexcept
MonopolyTradeGroup findSmallestMonopolyTrade(rules::PlayerNumber player, rules::board::PropertySet monopoly, std::span< const rules::PlayerNumber > candidates, const PropertySets &properties) noexcept
std::array< MonopolyTradeGenerosity, WhatToTradeEntries > WhatToTradeTable
PropertyImportanceResult findPlayerPropertyImportance(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, const PropertyImportanceConfig &config, std::int64_t moneyOwed) noexcept
TradeImportanceList createItemImportanceList(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, const PropertyImportanceConfig &config, double strategyAttitudeTowardPlayer, const CashMultiplierTable &multipliers) noexcept
bool addTypeProperty(const rules::GameState &before, const rules::GameState &after, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, rules::board::PropertySet &combinedProperties, TradeImportanceItem item, TradeProposalList &proposals, const PropertySets &properties, bool giveDescending, const WhatToTradeTable &whatToTrade, double strategyAttitudeTowardPlayer, std::int64_t playerMoneyOwed) noexcept
bool addTradeItem(TradeProposalList &proposals, FutureImmunityList &immunities, rules::TradeItemKind kind, std::int64_t amount, rules::PlayerNumber fromPlayer, rules::PlayerNumber toPlayer, rules::board::PropertySet propertySet) noexcept
std::array< double, WhatToTradeEntries > CashMultiplierTable
bool isMonopolyTrade(const rules::GameState &state, const TradeProposalList &proposals) noexcept
std::array< FutureImmunityRecord, rules::MaxCountHitSets > FutureImmunityList
void makeTradeProper(const rules::GameState &state, TradeProposalList &proposals, std::span< const FutureImmunityRecord > immunities) noexcept
bool onlyPlayerHasMonopoly(const rules::GameState &state, rules::PlayerNumber player) noexcept
void applyTradeToState(rules::GameState &state, const TradeProposalList &proposals) noexcept
bool addTradeMonopoly(const rules::GameState &state, rules::PlayerNumber toPlayer, rules::board::PropertySet &properties, PropertyClassification type, TradeProposalList &proposals, std::int64_t moneyOwed) noexcept
bool shouldTrade(const rules::GameState &state, rules::PlayerNumber player, std::uint8_t importance, std::span< const std::int64_t > timeLastTrade, std::size_t maxTrades, const TradeCadenceInputs &inputs) noexcept
std::array< rules::board::PropertySet, rules::MaxPlayers > PropertySets
bool playerHasFutureOrImmunity(rules::PlayerNumber player, std::span< const FutureImmunityRecord > immunities) noexcept
std::array< TradeProposalRecord, rules::MaxPlayers > TradeProposalList
int possibleMonopoly(const rules::GameState &state, rules::PlayerNumber owner, rules::board::SquareType square) noexcept
MonopolyTradeDecision shouldTradeForMonopoly(const rules::GameState &state, rules::PlayerNumber player) noexcept
double calculateTradePropertyImportance(const rules::GameState &before, const rules::GameState &after, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, const TradeProposalList &proposals, bool givingMonopolyAway, const PropertyImportanceConfig &config, std::span< const std::int64_t > moneyOwed) noexcept
constexpr std::size_t WhatToTradeEntries
bool hasMonopolyTrade(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber excludedPlayer) noexcept
rules::PlayerNumber findNonmonopolyPlayer(const rules::GameState &state, rules::PlayerNumber player, std::span< const std::int64_t > moneyOwed) noexcept
StrategicPropertyImportance strategicPropertyImportance(const rules::GameState &state, rules::board::SquareType property, rules::PlayerNumber excludedPlayer, rules::PlayerNumber includedPlayer, rules::PlayerNumber purchasingPlayer, rules::board::SquareType purchasingProperty) noexcept
double averageRentReceived(const rules::GameState &state, rules::PlayerNumber player, std::int64_t startSquare, bool dontCountDevelopedMonopolies, double cashCowMultiplier, rules::board::PropertySet propertiesOwned) noexcept
MonopolyStage monopolyStage(const rules::GameState &state, rules::PlayerNumber player) noexcept
bool playerOwnsMonopoly(const rules::GameState &state, rules::PlayerNumber player, bool countBaltic) noexcept
std::size_t removePlayersFromList(std::span< rules::PlayerNumber > players, std::size_t playerCount, std::span< const rules::PlayerNumber > remove) noexcept
rules::board::PropertySet monopolySet(SquareType square) noexcept
Definition AIUtility.cpp:63
bool isCashCow(SquareType square) noexcept
int numberRailroadsUtilitiesOwned(const rules::GameState &state, rules::PlayerNumber player, SquareGroup group, bool mortgageCounts, rules::board::PropertySet propertiesOwned) noexcept
int monopoliesBetweenPlayers(const rules::GameState &state, std::span< const rules::PlayerNumber > players, rules::board::SquareType extraProperty) noexcept
int monopoliesInSet(rules::board::PropertySet properties) noexcept
std::int64_t liquidAssets(const rules::GameState &state, rules::PlayerNumber player, bool countHouses, bool countMonopolies, std::int64_t moneyOwed) noexcept
bool isMonopoly(const rules::GameState &state, SquareType square) noexcept
constexpr std::array< rules::board::SquareType, 8 > ExpensiveMonopolySquares
Definition AIUtility.hpp:59
rules::PlayerNumber firstOwnerInMonopoly(const rules::GameState &state, SquareType square) noexcept
MonopolyCollection monopoliesOwned(const rules::GameState &state, rules::PlayerNumber player, bool mortgageCounts) noexcept
bool anyMonopoly(const rules::GameState &state) noexcept
std::int64_t averageRentPaid(const rules::GameState &state, rules::PlayerNumber player, std::int64_t startSquare, bool considerDevelopedMonopolies, double cashCowMultiplier) noexcept
GroupRange groupRange(SquareGroup group) noexcept
Definition AIUtility.cpp:41
rules::board::PropertySet propertiesOwnedByPlayer(const rules::GameState &state, rules::PlayerNumber player) noexcept
Definition AIUtility.cpp:49
const SquareDefinition & definition(SquareType square)
constexpr std::size_t MaxPropertyGroups
std::uint32_t PropertySet
PropertySet propertyBit(SquareType square)
const PendingPhase & current(const GameState &state)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
std::array< int, rules::MaxPlayers > playerIndex