Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AIDecisionUtility.cpp
Go to the documentation of this file.
2#include "AITradeUtility.hpp"
3
4#include <algorithm>
5#include <cmath>
6#include <limits>
7
9{
10 namespace
11 {
12 [[nodiscard]] std::int64_t reserveForHighestDevelopedRent(
14 rules::PlayerNumber player) noexcept
15 {
16 const auto highest = ai::highestRentSquare(state, player);
17 if (highest.square == rules::board::SquareType::Go ||
18 ai::housesOnMonopoly(state, highest.square) == 0)
19 return 0;
20 return highest.rent;
21 }
22
23 [[nodiscard]] double monopolyFrequency(
24 rules::board::SquareType representative) noexcept
25 {
26 const auto group = rules::board::definition(representative).group;
27 const auto index = static_cast<std::size_t>(group);
30 }
31
32 inline constexpr std::array<rules::board::SquareType, 28> MortgageOrder{
61 }
62
63 std::int64_t excessCashAvailable(
66 bool cashOnly,
67 CashStrategy strategy,
68 std::int64_t minCashOnHand,
69 std::int64_t moneyOwed) noexcept
70 {
71 if (state.numberOfPlayers > rules::MaxPlayers ||
72 player >= state.numberOfPlayers ||
73 strategy >= CashStrategy::Count)
74 return 0;
75
76 auto liquid = cashOnly
77 ? state.players[player].cash
78 : ai::liquidAssets(state, player, false, false, moneyOwed);
79 std::int64_t payments{};
80
81 switch (strategy)
82 {
84 payments = minCashOnHand;
85 break;
87 payments = ai::averageRentPaid(state, player, 0, true, 1.0);
88 payments = std::max(payments, minCashOnHand);
89 break;
91 payments = ai::averageRentPaid(state, player, 0, false, 1.0);
92 payments = std::max(payments, minCashOnHand);
93 break;
95 payments = reserveForHighestDevelopedRent(state, player);
96 payments += ai::averageRentPaid(state, player, 0, false, 1.0);
97 payments = std::max(payments, minCashOnHand);
98 break;
100 if (ai::anyMonopoly(state) &&
102 ai::bestCurrentRent(state) == player)
103 {
104 payments = reserveForHighestDevelopedRent(state, player);
105 }
106 payments += ai::averageRentPaid(state, player, 0, false, 1.0);
107 payments = std::max(payments, minCashOnHand);
108 break;
110 return 0;
111 }
112
113 return std::max<std::int64_t>(0, liquid - payments);
114 }
115
117 const rules::GameState& state,
118 rules::PlayerNumber player,
119 CashStrategy strategy,
120 std::int64_t minCashOnHand,
121 std::int64_t moneyOwed) noexcept
122 {
123 if (state.numberOfPlayers > rules::MaxPlayers ||
124 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
125 return 0;
126
127 auto simulated = state;
128 const bool shortage = ai::housingShortage(state, CriticalHousingLevel);
129 do
130 {
131 if (!shortage)
132 while (hypotheticalBuyHouse(simulated, player, moneyOwed)) {}
134 simulated, player, true, strategy, minCashOnHand, moneyOwed) !=
136
137 return excessCashAvailable(
138 simulated, player, false, strategy, minCashOnHand, moneyOwed);
139 }
140
142 const rules::GameState& state,
143 rules::PlayerNumber player,
145 CashStrategy strategy,
146 std::int64_t minCashOnHand,
147 std::span<const std::int64_t> moneyOwed) noexcept
148 {
152 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
153 player >= state.numberOfPlayers || property >= SquareType::InJail ||
154 strategy >= CashStrategy::Count)
155 return false;
156
157 const auto& definition = rules::board::definition(property);
158 const auto propertyCost = definition.purchaseCost;
159 const auto debt = player < moneyOwed.size() ? moneyOwed[player] : 0;
160 auto reserve = ai::minimumCalculatedExpenses(state, player);
161 if (reserve < 0)
162 return false;
163 reserve = std::max(reserve, minCashOnHand) - definition.mortgageCost;
164
165 const auto liquid = ai::liquidAssets(state, player, false, false, debt);
166 const auto withHouses = ai::liquidAssets(state, player, true, true, debt);
167 const auto protectBare = ai::liquidAssets(state, player, true, false, debt);
168 if (withHouses < propertyCost || withHouses - propertyCost < reserve)
169 return false;
170
171 // Retail computes both values, but only the all-assets result is consumed.
172 (void)ai::mostLiquidAssets(state, player, false, moneyOwed);
173 if (ai::mostLiquidAssets(state, player, true, moneyOwed) < propertyCost)
174 return false;
175
176 const auto importanceOthers = trade::strategicPropertyImportance(
177 state, property, player, rules::NobodyPlayer);
178 const auto importanceUs = trade::strategicPropertyImportance(
179 state, property, rules::NobodyPlayer, player);
180
181 if (liquid - propertyCost < reserve)
182 {
183 if (importanceOthers == StrategicPropertyImportance::GivesDirectMonopoly)
184 {
185 const auto monopolyPlayer = ai::firstOwnerInMonopoly(state, property);
186 if (monopolyPlayer >= state.numberOfPlayers)
187 return false;
188 const auto otherDebt = monopolyPlayer < moneyOwed.size() ? moneyOwed[monopolyPlayer] : 0;
189 const auto otherLiquid = ai::liquidAssets(
190 state, monopolyPlayer, true, false, otherDebt);
191 if (otherLiquid < propertyCost)
192 return definition.group <= SquareGroup::OrientalAvenue;
193 return true;
194 }
195
196 if (importanceUs == StrategicPropertyImportance::GivesDirectMonopoly)
197 {
198 if (definition.group <= SquareGroup::OrientalAvenue)
199 return true;
200 const auto monopolies = ai::monopoliesOwned(state, player, false);
201 if (monopolies.count == 0)
202 return false;
203 for (std::size_t monopolyIndex = 0; monopolyIndex < monopolies.count; ++monopolyIndex)
204 {
205 const auto representative = monopolies.representatives[monopolyIndex];
206 const auto lots = ai::monopolyLots(representative);
207 const auto houses = ai::housesOnMonopoly(state, representative);
208 if (houses < static_cast<std::int64_t>(lots.count * 3))
209 return false;
210 }
211 const auto shortfall = propertyCost + reserve - liquid;
212 const auto last = monopolies.representatives[monopolies.count - 1];
213 if (static_cast<double>(shortfall) >
214 static_cast<double>(rules::board::definition(last).housePurchaseCost) * 1.5)
215 return false;
216 return true;
217 }
218
219 if (trade::onlyPlayerHasMonopoly(state, player) &&
220 !ai::ownsPropertyFromMonopoly(state, player, property) &&
221 !ai::isCashCow(property))
222 return true;
223 return false;
224 }
225
226 bool buy = ai::isCashCow(property);
227 if (importanceUs == StrategicPropertyImportance::GivesDirectMonopoly)
228 return true;
229 if (importanceOthers == StrategicPropertyImportance::AllowsTrade ||
230 importanceOthers == StrategicPropertyImportance::GivesDirectMonopoly)
231 return true;
232
233 if (definition.group == SquareGroup::ParkPlace ||
234 definition.group == SquareGroup::MediterraneanAvenue ||
235 definition.group == SquareGroup::StJamesPlace)
236 buy = true;
237 if (importanceUs != StrategicPropertyImportance::NotImportant && !buy)
238 buy = true;
239
240 if (trade::onlyPlayerHasMonopoly(state, player) &&
241 !ai::ownsPropertyFromMonopoly(state, player, property) &&
242 !ai::isCashCow(property))
243 return true;
244
245 if (protectBare - propertyCost < reserve && !buy)
246 return false;
248 state, player, strategy, minCashOnHand, debt) < propertyCost)
249 return false;
250 return true;
251 }
252
255 rules::PlayerNumber player,
256 std::int64_t moneyOwed) noexcept
257 {
258 if (state.numberOfPlayers > rules::MaxPlayers ||
259 player >= state.numberOfPlayers)
260 return false;
261
262 const auto liquid = ai::liquidAssets(
263 state, player, false, false, moneyOwed);
264 if (liquid == 0)
265 return false;
266
267 const auto monopolies = ai::monopoliesOwned(state, player, true);
268 auto bestSquare = rules::board::SquareType::Go;
269 double bestMetric{};
270
271 for (std::size_t index = 0; index < monopolies.count; ++index)
272 {
273 const auto representative = monopolies.representatives[index];
274 const auto lots = ai::monopolyLots(representative);
275 const auto houses = ai::housesOnMonopoly(state, representative);
276 const auto toThree = static_cast<std::int64_t>(lots.count * 3) - houses;
277 if (toThree < 1)
278 continue;
279 const auto& definition = rules::board::definition(representative);
280 if (toThree * definition.housePurchaseCost > liquid)
281 continue;
282 if (bestMetric < static_cast<double>(definition.rent[0]))
283 {
284 bestMetric = static_cast<double>(definition.rent[0]);
285 bestSquare = representative;
286 }
287 }
288
289 if (bestSquare == rules::board::SquareType::Go)
290 {
291 bestMetric = 0.0;
292 for (std::size_t index = 0; index < monopolies.count; ++index)
293 {
294 const auto representative = monopolies.representatives[index];
295 const auto lots = ai::monopolyLots(representative);
296 if (ai::housesOnMonopoly(state, representative) >=
297 static_cast<std::int64_t>(lots.count * 3))
298 continue;
299 const auto& definition = rules::board::definition(representative);
300 if (definition.housePurchaseCost > liquid)
301 continue;
302 const auto frequency = monopolyFrequency(representative);
303 if (bestMetric < frequency)
304 {
305 bestMetric = frequency;
306 bestSquare = representative;
307 }
308 }
309 }
310
311 if (bestSquare == rules::board::SquareType::Go)
312 {
313 bestMetric = 0.0;
314 for (std::size_t index = 0; index < monopolies.count; ++index)
315 {
316 const auto representative = monopolies.representatives[index];
317 const auto lots = ai::monopolyLots(representative);
318 if (ai::housesOnMonopoly(state, representative) >=
319 static_cast<std::int64_t>(lots.count * state.options.housesPerHotel))
320 continue;
321 const auto& definition = rules::board::definition(representative);
322 if (definition.housePurchaseCost > liquid)
323 continue;
324 const auto frequency = monopolyFrequency(representative);
325 if (bestMetric < frequency)
326 {
327 bestMetric = frequency;
328 bestSquare = representative;
329 }
330 }
331 }
332
333 if (bestSquare == rules::board::SquareType::Go)
334 return false;
335
336 const auto lots = ai::monopolyLots(bestSquare);
337 if (lots.count == 0)
338 return false;
339 auto topHousesOnLot = state.options.housesPerHotel;
340 if (state.options.evenBuildRule)
341 {
342 const auto balancedTop = static_cast<std::uint8_t>(
343 ai::housesOnMonopoly(state, bestSquare) /
344 static_cast<std::int64_t>(lots.count) + 1);
345 if (balancedTop < topHousesOnLot)
346 topHousesOnLot = balancedTop;
347 }
348
349 auto chosen = rules::board::SquareType::Go;
350 for (std::size_t index = 0; index < lots.count; ++index)
351 {
352 const auto square = lots.squares[index];
353 if (state.squares[static_cast<std::size_t>(square)].houses < topHousesOnLot)
354 {
355 chosen = square;
356 break;
357 }
358 }
359 if (chosen == rules::board::SquareType::Go)
360 return false;
361
362 ++state.squares[static_cast<std::size_t>(chosen)].houses;
363 // Retail subtracts monopolies[0]'s house price here even when another
364 // monopoly was selected. Debit the selected monopoly's actual house price.
365 state.players[player].cash -=
367 return true;
368 }
371 rules::PlayerNumber player,
372 CashStrategy strategy,
373 std::int64_t minCashOnHand,
374 std::int64_t moneyOwed) noexcept
375 {
377 if (state.numberOfPlayers > rules::MaxPlayers ||
378 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
379 return SquareType::Go;
380
381 const auto owned = ai::propertiesOwnedByPlayer(state, player);
382 const auto excess = excessCashAvailable(
383 state, player, false, strategy, minCashOnHand, moneyOwed);
384
385 const auto tryRange = [&](std::size_t begin, std::size_t endExclusive) {
386 for (std::size_t index = begin; index < endExclusive; ++index)
387 {
388 const auto square = static_cast<SquareType>(index);
389 auto& runtime = state.squares[index];
390 const auto bit = rules::board::propertyBit(square);
391 if (!runtime.mortgaged || bit == 0 || (owned & bit) == 0 ||
392 !ai::testForMonopoly(owned, square))
393 continue;
394
395 const auto rawCost = static_cast<double>(
397 // Preserve the retail comparison against the untruncated double.
398 if (static_cast<double>(excess) < rawCost)
399 continue;
400
401 runtime.mortgaged = false;
402 state.players[player].cash -= static_cast<std::int64_t>(rawCost);
403 return square;
404 }
405 return SquareType::Go;
406 };
407
408 auto square = tryRange(
409 static_cast<std::size_t>(SquareType::OrientalAvenue),
410 static_cast<std::size_t>(SquareType::InJail));
411 if (square != SquareType::Go)
412 return square;
413
414 return tryRange(
415 static_cast<std::size_t>(SquareType::Go),
416 static_cast<std::size_t>(SquareType::BalticAvenue) + 1);
417 }
418
421 rules::PlayerNumber player,
422 bool onlyMonopolies,
423 CashStrategy strategy,
424 std::int64_t minCashOnHand,
425 std::int64_t moneyOwed) noexcept
426 {
428 if (state.numberOfPlayers > rules::MaxPlayers ||
429 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
430 return SquareType::Go;
431
433 state, player, strategy, minCashOnHand, moneyOwed);
434 if (monopoly != SquareType::Go || onlyMonopolies)
435 return monopoly;
436
437 const auto owned = ai::propertiesOwnedByPlayer(state, player);
438 const auto excessCash = excessCashAvailable(
439 state, player, true, strategy, minCashOnHand, moneyOwed);
440 const auto square = ai::findHighestRentMortgaged(
441 state, player, owned, excessCash);
442 if (square == SquareType::Go)
443 return SquareType::Go;
444
445 const auto rawCost = static_cast<double>(
447 // Retail's hypothetical non-monopoly fallback debits a stale
448 // Square_predefined_info pointer. Use the selected property's cost.
449 state.squares[static_cast<std::size_t>(square)].mortgaged = false;
450 state.players[player].cash -= static_cast<std::int64_t>(rawCost);
451 return square;
452 }
453
455 const rules::GameState& state,
456 rules::PlayerNumber player,
457 CashStrategy strategy,
458 std::int64_t minCashOnHand,
459 std::int64_t moneyOwed) noexcept
460 {
462 if (state.numberOfPlayers > rules::MaxPlayers ||
463 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
464 return false;
465
466 auto simulated = state;
468 {
469 while (hypotheticalBuyHouse(simulated, player, moneyOwed))
470 {
471 }
472 }
473
474 const auto owned = ai::propertiesOwnedByPlayer(state, player);
475 const auto cashOnly = excessCashAvailable(
476 simulated, player, true, strategy, minCashOnHand, moneyOwed);
477 const auto mortgageable = excessCashAvailable(
478 simulated, player, false, strategy, minCashOnHand, moneyOwed);
479
480 for (std::size_t index = 0;
481 index < static_cast<std::size_t>(SquareType::InJail); ++index)
482 {
483 const auto square = static_cast<SquareType>(index);
484 const auto bit = rules::board::propertyBit(square);
485 if (bit == 0 || (owned & bit) == 0 || !state.squares[index].mortgaged)
486 continue;
487
488 const auto cost = static_cast<std::int64_t>(
489 static_cast<double>(rules::board::definition(square).mortgageCost) * 1.1);
490 const auto available = ai::testForMonopoly(owned, square)
492 if (available >= cost)
493 return true;
494 }
495 return false;
496 }
497
499 const rules::GameState& state,
500 rules::PlayerNumber player,
502 std::int64_t minCashOnHand,
503 std::uint8_t housingPurchaseStrategy,
504 std::int64_t moneyOwed) noexcept
505 {
507 if (state.numberOfPlayers > rules::MaxPlayers ||
508 player >= state.numberOfPlayers || strategy >= CashStrategy::Count ||
509 state.options.housesPerHotel == 0)
511
512 const auto excess = excessCashAvailable(
513 state, player, false, strategy, minCashOnHand, moneyOwed);
514 if (excess == 0)
516
517 const auto monopolies = ai::monopoliesOwned(state, player, false);
518 if (monopolies.count == 0)
520
522 const auto topHousesOnLot = static_cast<std::uint8_t>(
523 shortage ? state.options.housesPerHotel - 1 : state.options.housesPerHotel);
525
526 for (std::size_t index = 0;
527 index < static_cast<std::size_t>(SquareType::InJail); ++index)
528 {
529 const auto square = static_cast<SquareType>(index);
530 const auto& definition = rules::board::definition(square);
531 bool belongsToMonopoly{};
532 for (std::size_t monopolyIndex = 0; monopolyIndex < monopolies.count; ++monopolyIndex)
533 {
534 if (definition.group == rules::board::definition(
535 monopolies.representatives[monopolyIndex]).group)
536 {
537 belongsToMonopoly = true;
538 break;
539 }
540 }
541 if (!belongsToMonopoly || state.squares[index].houses >= topHousesOnLot)
542 continue;
543
544 if ((housingPurchaseStrategy & HouseBuyWithin12) != 0 && !shortage &&
545 !ai::playerCloseToProperty(state, player, square, 2, 12))
546 {
548 continue;
549 }
550
551 std::int64_t buyCount = 1;
552 if ((housingPurchaseStrategy & HouseBuyAtLeast3) != 0 && !shortage)
553 {
554 const auto lots = ai::monopolyLots(square);
555 buyCount = static_cast<std::int64_t>(lots.count * 3) -
557 if (buyCount < 1)
558 buyCount = 1;
559 }
560
561 const auto unmortgageCost = ai::costUnmortgageMonopoly(state, square);
562 if (definition.housePurchaseCost * buyCount <= excess - unmortgageCost)
564 }
565
568 }
569
571 const rules::GameState& state,
572 rules::PlayerNumber player,
574 std::int64_t minCashOnHand,
575 std::uint8_t housingPurchaseStrategy,
576 std::int64_t moneyOwed) noexcept
577 {
580 if (state.numberOfPlayers > rules::MaxPlayers ||
581 player >= state.numberOfPlayers || strategy >= CashStrategy::Count ||
582 state.options.housesPerHotel == 0)
583 return none;
584
585 const auto planMortgageWorst = [&]() -> EconomicActionPlan {
586 auto simulated = state;
587 if (!mortgageWorstProperty(simulated, player, false, false))
588 return {};
589 for (std::size_t index = 0;
590 index < static_cast<std::size_t>(SquareType::InJail); ++index)
591 {
592 if (!state.squares[index].mortgaged && simulated.squares[index].mortgaged)
594 static_cast<SquareType>(index)};
595 }
596 return {};
597 };
598
599 const auto excess = excessCashAvailable(
600 state, player, false, strategy, minCashOnHand, moneyOwed);
601 if (excess == 0)
602 return none;
603
604 const int freeHouses = ai::freeHouses(state);
605 const int freeHotels = ai::freeHotels(state);
606 const auto ownedMonopolies = ai::monopoliesOwned(state, player, false);
607 if (ownedMonopolies.count == 0)
608 return none;
609
612 ? static_cast<int>(state.options.housesPerHotel) - 1
613 : static_cast<int>(state.options.housesPerHotel);
614
615 std::array<SquareType, 8> monopolies{};
616 std::size_t monopolyCount{};
617 for (std::size_t index = 0; index < ownedMonopolies.count; ++index)
618 {
619 const auto representative = ownedMonopolies.representatives[index];
621 if ((housingPurchaseStrategy & HouseBuyWithin12) != 0 &&
623 continue;
624 monopolies[monopolyCount++] = representative;
625 }
626 if (monopolyCount == 0)
627 return none;
628
629 SquareType bestSquare = SquareType::Go;
630 double bestMetric{};
631 if (freeHouses > 0)
632 {
633 for (std::size_t index = 0; index < monopolyCount; ++index)
634 {
635 const auto representative = monopolies[index];
637 auto buyHouses = static_cast<std::int64_t>(lots.count * 3) -
639 if (buyHouses < 1)
640 continue;
644 continue;
645 const auto baseRent = static_cast<double>(
647 if (bestMetric < baseRent)
648 {
651 }
652 }
653
654 if (bestSquare == SquareType::Go)
655 {
656 for (std::size_t index = 0; index < monopolyCount; ++index)
657 {
658 const auto representative = monopolies[index];
661 static_cast<int>(lots.count * 3))
662 continue;
663 if (rules::board::definition(representative).housePurchaseCost > excess)
664 continue;
666 if (bestMetric < frequency)
667 {
670 }
671 }
672 }
673 }
674
675 if (bestSquare == SquareType::Go)
676 {
677 bestMetric = 0.0;
678 for (std::size_t index = 0; index < monopolyCount; ++index)
679 {
680 const auto representative = monopolies[index];
682 const auto houses = ai::housesOnMonopoly(state, representative);
683 if (houses >= static_cast<int>(lots.count * topHousesOnLot))
684 continue;
688 continue;
689 const auto hotelThreshold =
690 (static_cast<int>(state.options.housesPerHotel) - 1) *
691 static_cast<int>(lots.count);
692 if (houses >= hotelThreshold && freeHotels == 0)
693 continue;
694 if (houses < hotelThreshold && freeHouses == 0)
695 continue;
697 if (bestMetric < frequency)
698 {
701 }
702 }
703 }
704
705 if (bestSquare == SquareType::Go)
706 return none;
707
708 const auto lots = ai::monopolyLots(bestSquare);
711 return none;
712 if (state.players[player].cash < unmortgageCost)
713 return planMortgageWorst();
714 if (unmortgageCost > 0)
715 {
716 for (std::size_t index = lots.count; index > 0; --index)
717 {
718 const auto square = lots.squares[index - 1];
719 if (state.squares[static_cast<std::size_t>(square)].mortgaged)
721 }
722 }
723
724 if (state.options.evenBuildRule)
725 {
727 static_cast<int>(lots.count) + 1;
730 }
731
732 std::array<SquareType, 3> buildable{};
733 std::size_t buildableCount{};
734 for (std::size_t index = 0; index < lots.count; ++index)
735 {
736 const auto square = lots.squares[index];
737 if (state.squares[static_cast<std::size_t>(square)].houses < topHousesOnLot)
738 buildable[buildableCount++] = square;
739 }
740 if (buildableCount == 0)
741 return none;
742
743 for (std::size_t index = 0; index < buildableCount; ++index)
744 {
745 const auto square = buildable[index];
746 if (!ai::playerCloseToProperty(state, player, square, 7, 7))
747 continue;
748 if (state.players[player].cash < rules::board::definition(square).housePurchaseCost)
749 return planMortgageWorst();
750 return {EconomicActionKind::BuyHouse, square};
751 }
752
753 const auto square = buildable[0];
754 if (state.players[player].cash < rules::board::definition(square).housePurchaseCost)
755 return planMortgageWorst();
756 return {EconomicActionKind::BuyHouse, square};
757 }
758
759 std::optional<rules::board::SquareType> chooseHousingAuctionSquare(
760 const rules::GameState& state,
761 rules::PlayerNumber player,
764 std::int64_t minCashOnHand,
766 bool reservedBuilding,
767 std::int64_t moneyOwed) noexcept
768 {
770 if (state.numberOfPlayers > rules::MaxPlayers ||
771 player >= state.numberOfPlayers || strategy >= CashStrategy::Count ||
772 state.options.housesPerHotel == 0)
773 return std::nullopt;
774
776 state, player, false, strategy, minCashOnHand, moneyOwed);
777 if (!reservedBuilding && excess == 0)
778 return std::nullopt;
779
781 ? 1 : ai::freeHouses(state);
783 ? 1 : ai::freeHotels(state);
784 const auto owned = ai::monopoliesOwned(state, player, false);
785 if (owned.count == 0)
786 return std::nullopt;
787
789 ? static_cast<int>(state.options.housesPerHotel) - 1
790 : static_cast<int>(state.options.housesPerHotel);
791 const int hotelThresholdPerLot = static_cast<int>(state.options.housesPerHotel) - 1;
792
793 const auto monopolyHasLegalSquare = [&](SquareType representative) {
794 if (legalSquares == 0)
795 return true;
797 for (std::size_t i = 0; i < lots.count; ++i)
798 {
799 if ((legalSquares & rules::board::propertyBit(lots.squares[i])) != 0)
800 return true;
801 }
802 return false;
803 };
804
805 SquareType bestSquare = SquareType::Go;
806 double bestMetric{};
808 {
809 for (std::size_t i = 0; i < owned.count; ++i)
810 {
811 const auto rep = owned.representatives[i];
812 const auto lots = ai::monopolyLots(rep);
814 continue;
815 auto buyHouses = static_cast<std::int64_t>(lots.count * 3) -
817 if (buyHouses < 1)
818 continue;
821 continue;
822 const auto rent = static_cast<double>(rules::board::definition(rep).rent[0]);
823 if (bestMetric < rent)
824 {
825 bestMetric = rent;
826 bestSquare = rep;
827 }
828 }
829
830 if (bestSquare == SquareType::Go)
831 {
832 bestMetric = 0.0;
833 for (std::size_t i = 0; i < owned.count; ++i)
834 {
835 const auto rep = owned.representatives[i];
836 const auto lots = ai::monopolyLots(rep);
838 ai::housesOnMonopoly(state, rep) >= static_cast<int>(lots.count * 3))
839 continue;
841 if (!reservedBuilding && cost > excess)
842 continue;
843 const auto frequency = monopolyFrequency(rep);
844 if (bestMetric < frequency)
845 {
847 bestSquare = rep;
848 }
849 }
850 }
851 }
852
853 if (bestSquare == SquareType::Go)
854 {
855 bestMetric = 0.0;
856 for (std::size_t i = 0; i < owned.count; ++i)
857 {
858 const auto rep = owned.representatives[i];
859 const auto lots = ai::monopolyLots(rep);
861 continue;
862 const auto houses = ai::housesOnMonopoly(state, rep);
863 if (houses >= static_cast<int>(lots.count * topHousesOnLot))
864 continue;
866 if (!reservedBuilding && cost > excess)
867 continue;
868 const auto hotelThreshold = hotelThresholdPerLot * static_cast<int>(lots.count);
870 {
871 if (houses < hotelThreshold || freeHotels == 0)
872 continue;
873 }
874 else if (houses >= hotelThreshold || freeHouses == 0)
875 continue;
876 const auto frequency = monopolyFrequency(rep);
877 if (bestMetric < frequency)
878 {
880 bestSquare = rep;
881 }
882 }
883 }
884 if (bestSquare == SquareType::Go)
885 return std::nullopt;
886
887 const auto lots = ai::monopolyLots(bestSquare);
888 if (state.options.evenBuildRule)
889 {
891 static_cast<int>(lots.count) + 1;
894 }
895
896 for (std::size_t i = 0; i < lots.count; ++i)
897 {
898 const auto square = lots.squares[i];
899 const auto bit = rules::board::propertyBit(square);
900 if (legalSquares != 0 && (legalSquares & bit) == 0)
901 continue;
902 const auto houses = state.squares[static_cast<std::size_t>(square)].houses;
903 if (houses >= topHousesOnLot)
904 continue;
906 continue;
908 continue;
909 return square;
910 }
911 return std::nullopt;
912 }
913
915 const rules::GameState& state,
916 rules::PlayerNumber player,
918 std::int64_t minCashOnHand,
919 std::int64_t moneyOwed) noexcept
920 {
923 if (state.numberOfPlayers > rules::MaxPlayers ||
924 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
925 return none;
926
927 const auto planMortgageWorst = [&]() -> EconomicActionPlan {
928 auto simulated = state;
929 if (!mortgageWorstProperty(simulated, player, false, false))
930 return {};
931 for (std::size_t index = 0;
932 index < static_cast<std::size_t>(SquareType::InJail); ++index)
933 {
934 if (!state.squares[index].mortgaged && simulated.squares[index].mortgaged)
936 static_cast<SquareType>(index)};
937 }
938 return {};
939 };
940
941 const auto owned = ai::propertiesOwnedByPlayer(state, player);
942 const auto cashOnly = excessCashAvailable(
943 state, player, true, strategy, minCashOnHand, moneyOwed);
945 state, player, false, strategy, minCashOnHand, moneyOwed);
946 const auto cash = state.players[player].cash;
947
948 const auto tryMonopolyRange = [&](std::size_t begin, std::size_t endExclusive)
950 for (std::size_t index = begin; index < endExclusive; ++index)
951 {
952 const auto square = static_cast<SquareType>(index);
953 const auto bit = rules::board::propertyBit(square);
954 if (bit == 0 || (owned & bit) == 0 ||
955 !state.squares[index].mortgaged ||
956 !ai::testForMonopoly(owned, square))
957 continue;
958 const auto rawCost = static_cast<double>(
960 if (static_cast<double>(mortgageable) < rawCost)
961 continue;
962 if (static_cast<double>(cash) < rawCost)
963 return planMortgageWorst();
965 }
966 return {};
967 };
968
969 auto plan = tryMonopolyRange(
970 static_cast<std::size_t>(SquareType::OrientalAvenue),
971 static_cast<std::size_t>(SquareType::InJail));
972 if (plan.acted())
973 return plan;
975 static_cast<std::size_t>(SquareType::Go),
976 static_cast<std::size_t>(SquareType::BalticAvenue) + 1);
977 if (plan.acted())
978 return plan;
979
980 const auto square = ai::findHighestRentMortgaged(
981 state, player, owned, cashOnly);
982 if (square == SquareType::Go)
983 return none;
984 const auto rawCost = static_cast<double>(
986 if (static_cast<double>(cash) < rawCost)
987 return none;
989 }
990
991 std::optional<bool> chooseFractionTax(
992 const rules::GameState& state,
993 rules::PlayerNumber player) noexcept
994 {
995 if (state.numberOfPlayers > rules::MaxPlayers ||
996 player >= state.numberOfPlayers)
997 return std::nullopt;
998 const auto percentage =
999 ai::totalWorth(state, player) * state.options.taxRate / 100;
1000 return percentage <= state.options.flatTaxFee;
1001 }
1002
1003 std::optional<JailExitChoice> chooseJailExitChoice(
1004 const rules::GameState& state,
1005 rules::PlayerNumber player,
1006 bool canRollDoubles,
1007 bool canPayFee,
1008 bool canUseCard) noexcept
1009 {
1011 if (state.numberOfPlayers > rules::MaxPlayers ||
1012 player >= state.numberOfPlayers)
1013 return std::nullopt;
1014
1015 // Retail receives can_pay_fifty but never consults it.
1016 (void)canPayFee;
1017
1018 if (canUseCard && state.players[player].turnsInJail == 2)
1019 return JailExitChoice::Card;
1020 if (!canRollDoubles)
1022
1023 const auto directMonopoly = [&](SquareType square) noexcept {
1024 const auto index = static_cast<std::size_t>(square);
1025 if (state.squares[index].owner != rules::NobodyPlayer)
1026 return false;
1027 const auto bit = rules::board::propertyBit(square);
1028 if (bit == 0)
1029 return false;
1030 for (rules::PlayerNumber current = 0;
1031 current < state.numberOfPlayers; ++current)
1032 {
1033 const auto owned = ai::propertiesOwnedByPlayer(state, current);
1034 if (ai::testForMonopoly(owned | bit, square))
1035 return true;
1036 }
1037 return false;
1038 };
1039
1040 const auto electric = static_cast<std::size_t>(SquareType::ElectricCompany);
1041 const auto water = static_cast<std::size_t>(SquareType::WaterWorks);
1042 if (state.squares[electric].owner == rules::NobodyPlayer &&
1043 state.squares[water].owner == player &&
1045 return JailExitChoice::Roll;
1046
1047 if (directMonopoly(SquareType::VirginiaAvenue) ||
1048 directMonopoly(SquareType::TennesseeAvenue) ||
1049 directMonopoly(SquareType::StJamesPlace))
1050 return JailExitChoice::Roll;
1051
1052 double averageRent = static_cast<double>(state.options.passingGoAmount) -
1053 static_cast<double>(ai::averageRentPaid(state, player, 0, true, 1.0));
1054 averageRent /= 5.0;
1055 averageRent *= 2.5;
1056 if (averageRent < static_cast<double>(state.options.getOutOfJailFee))
1057 return JailExitChoice::Roll;
1058
1060 }
1061
1063 const rules::GameState& state,
1064 rules::PlayerNumber player,
1065 const WorthFactors& factors) noexcept
1066 {
1069 if (state.numberOfPlayers > rules::MaxPlayers || player >= state.numberOfPlayers)
1070 return 0;
1071
1072 const auto owned = ai::propertiesOwnedByPlayer(state, player);
1074 state, player, SquareGroup::Railroad, false, owned);
1076 state, player, SquareGroup::Utility, false, owned);
1077
1078 std::int64_t total{};
1079 for (std::size_t index = 0;
1080 index < static_cast<std::size_t>(SquareType::InJail); ++index)
1081 {
1082 const auto square = static_cast<SquareType>(index);
1083 const auto bit = rules::board::propertyBit(square);
1084 if (bit == 0 || (owned & bit) == 0)
1085 continue;
1086
1087 const auto& definition = rules::board::definition(square);
1088 total += definition.housePurchaseCost * state.squares[index].houses;
1089
1090 double factor{};
1091 if (definition.group == SquareGroup::Railroad && railroadCount > 0)
1092 {
1093 const auto factorIndex = static_cast<std::size_t>(railroadCount - 1);
1094 if (factorIndex < 4)
1095 factor = factors.cashCow[factorIndex];
1096 }
1097 else if (definition.group == SquareGroup::Utility && utilityCount > 0)
1098 {
1099 const auto factorIndex = static_cast<std::size_t>(4 + utilityCount - 1);
1100 if (factorIndex < factors.cashCow.size())
1101 factor = factors.cashCow[factorIndex];
1102 }
1103 else
1104 {
1105 const auto factorIndex = static_cast<std::size_t>(definition.group);
1106 if (factorIndex < factors.property.size())
1107 factor = factors.property[factorIndex];
1108 }
1109 total += static_cast<std::int64_t>(
1110 static_cast<double>(definition.purchaseCost) * factor);
1111 }
1112 return total + state.players[player].cash;
1113 }
1114
1117 rules::PlayerNumber player,
1118 bool sellHouses,
1119 bool mortgageMonopoly) noexcept
1120 {
1123 if (state.numberOfPlayers > rules::MaxPlayers || player >= state.numberOfPlayers)
1124 return false;
1125
1126 const auto owned = ai::propertiesOwnedByPlayer(state, player);
1127 auto monopoliless = owned;
1128 auto housingless = owned;
1129
1130 const auto mortgage = [&](SquareType square) {
1131 const auto index = static_cast<std::size_t>(square);
1132 state.players[player].cash += rules::board::definition(square).mortgageCost;
1133 state.squares[index].mortgaged = true;
1134 };
1135
1136 for (const auto square : MortgageOrder)
1137 {
1138 const auto bit = rules::board::propertyBit(square);
1139 if (bit == 0 || (owned & bit) == 0)
1140 continue;
1141
1142 const auto index = static_cast<std::size_t>(square);
1143 // Retail tests unsigned-char houses >= 0, which is always true and
1144 // empties the intended "housingless" set. Keep only genuinely
1145 // undeveloped lots so the documented fallback can actually run.
1146 if (state.squares[index].houses > 0)
1147 housingless &= ~bit;
1148
1149 const bool monopoly = ai::ownsMonopoly(
1150 state, player, square, mortgageMonopoly);
1151 if (monopoly)
1152 monopoliless &= ~bit;
1153
1154 if (state.squares[index].mortgaged)
1155 continue;
1156
1157 const auto group = rules::board::definition(square).group;
1158 int groupCount{};
1159 if (group == SquareGroup::Railroad || group == SquareGroup::Utility)
1160 {
1162 state, player, group,
1163 state.options.mortgagedCountsInGroupRent, owned);
1164 }
1165
1166 if (!monopoly && groupCount < 2)
1167 {
1168 mortgage(square);
1169 return true;
1170 }
1171 }
1172
1173 SquareType mortgageSquare = SquareType::Go;
1175 {
1177 state, player, monopoliless);
1178 }
1179 else if (!sellHouses)
1180 {
1182 state, player, housingless);
1183 }
1185 {
1187 state, player, housingless);
1188 if (mortgageSquare == SquareType::Go)
1190 }
1191 else
1192 {
1194 }
1195
1196 if (mortgageSquare == SquareType::Go)
1197 return false;
1198
1200 {
1201 const auto lots = ai::monopolyLots(mortgageSquare);
1203 }
1204 else
1205 {
1207 }
1208 return true;
1209 }
1210
1212 const rules::GameState& state,
1214 {
1216 const auto raw = static_cast<std::size_t>(referenceSquare);
1217 if (raw >= static_cast<std::size_t>(SquareType::InJail))
1218 return {};
1219
1221 if (lots.count == 0)
1222 return {};
1223
1224 auto simulated = state;
1226
1227 SquareType soldSquare = SquareType::InJail;
1228 std::uint8_t maxHouses{};
1229 for (std::size_t index = 0; index < lots.count; ++index)
1230 {
1231 const auto square = lots.squares[index];
1232 const auto squareIndex = static_cast<std::size_t>(square);
1233 if (simulated.squares[squareIndex].houses >=
1234 state.squares[squareIndex].houses)
1235 continue;
1236 if (soldSquare == SquareType::InJail ||
1237 state.squares[squareIndex].houses > maxHouses)
1238 {
1239 soldSquare = square;
1240 maxHouses = state.squares[squareIndex].houses;
1241 }
1242 }
1243 if (soldSquare == SquareType::InJail)
1244 return {};
1246 }
1247
1249 const rules::GameState& state,
1250 rules::PlayerNumber player,
1251 bool sellHouses) noexcept
1252 {
1254 if (state.numberOfPlayers > rules::MaxPlayers ||
1255 player >= state.numberOfPlayers)
1256 return {};
1257
1258 auto simulated = state;
1259 if (!mortgageWorstProperty(simulated, player, sellHouses, true))
1260 return {};
1261
1262 for (std::size_t index = 0;
1263 index < static_cast<std::size_t>(SquareType::InJail); ++index)
1264 {
1265 if (!state.squares[index].mortgaged &&
1266 simulated.squares[index].mortgaged)
1268 static_cast<SquareType>(index)};
1269 }
1270
1271 SquareType soldSquare = SquareType::InJail;
1272 std::uint8_t maxHouses{};
1273 for (std::size_t index = 0;
1274 index < static_cast<std::size_t>(SquareType::InJail); ++index)
1275 {
1276 if (simulated.squares[index].houses >= state.squares[index].houses)
1277 continue;
1278 if (soldSquare == SquareType::InJail ||
1279 state.squares[index].houses > maxHouses)
1280 {
1281 soldSquare = static_cast<SquareType>(index);
1282 maxHouses = state.squares[index].houses;
1283 }
1284 }
1285 if (soldSquare != SquareType::InJail)
1287 return {};
1288 }
1289
1291 {
1292 if (state.numberOfPlayers > rules::MaxPlayers)
1293 return;
1294 for (rules::PlayerNumber player = 0; player < state.numberOfPlayers; ++player)
1295 {
1296 if (state.players[player].currentSquare ==
1297 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard))
1298 continue;
1299 while (state.players[player].cash < 0)
1300 {
1301 if (!mortgageWorstProperty(state, player, true, true))
1302 break;
1303 }
1304 }
1305 }
1306
1308 const rules::GameState& state,
1309 rules::PlayerNumber player,
1311 std::span<double> savePlayerChances) noexcept
1312 {
1314 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers)
1315 return 0.0;
1316 const bool saveAll = !savePlayerChances.empty();
1317 if ((!saveAll && player >= state.numberOfPlayers) ||
1318 (saveAll && savePlayerChances.size() < state.numberOfPlayers))
1319 return 0.0;
1320
1321 std::array<double, rules::MaxPlayers> localChances{};
1322 double* chances = saveAll ? savePlayerChances.data() : localChances.data();
1323 std::fill(chances, chances + state.numberOfPlayers, 0.0);
1324 auto simulated = state;
1326
1327 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1328 {
1329 if (state.players[current].currentSquare ==
1330 static_cast<std::uint8_t>(SquareType::OffBoard))
1331 continue;
1332 do
1333 {
1334 if (!shortage)
1335 {
1336 while (hypotheticalBuyHouse(
1337 simulated, current, config.moneyOwed[current]))
1338 {
1339 }
1340 }
1342 simulated, current, true, config.cashStrategy[current],
1343 config.minCashOnHand[current], config.moneyOwed[current]) != SquareType::Go);
1344 }
1345
1346 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1347 {
1348 if (state.players[current].currentSquare ==
1349 static_cast<std::uint8_t>(SquareType::OffBoard))
1350 continue;
1352 simulated, current, false, config.cashStrategy[current],
1353 config.minCashOnHand[current], config.moneyOwed[current]) != SquareType::Go)
1354 {
1355 }
1356 }
1357
1358 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1359 {
1360 if (state.players[current].currentSquare ==
1361 static_cast<std::uint8_t>(SquareType::OffBoard))
1362 continue;
1363 while (simulated.players[current].cash < 0)
1364 {
1365 if (!mortgageWorstProperty(simulated, current, false, false) &&
1366 !mortgageWorstProperty(simulated, current, false, true) &&
1367 !mortgageWorstProperty(simulated, current, true, true))
1368 break;
1369 }
1370 }
1371
1372 double totalAbsoluteChance{};
1373 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1374 {
1375 const auto owned = ai::propertiesOwnedByPlayer(state, current);
1377 simulated, current, 0, false, 0.07, owned) *
1378 static_cast<double>(simulated.numberOfPlayers - 1);
1379 chances[current] -= static_cast<double>(
1380 ai::averageRentPaid(simulated, current, 0, true, 0.07));
1381 chances[current] += static_cast<double>(state.options.passingGoAmount);
1382
1383 const auto liquidAssets = ai::liquidAssets(
1384 simulated, current, false, false, config.moneyOwed[current]);
1385 switch (ai::monopolyStage(simulated, current))
1386 {
1389 {
1390 constexpr double MonopolySquareCount = 22.0;
1391 const double buyingFraction =
1392 static_cast<double>(ai::propertiesLeftToBuy(state)) / MonopolySquareCount;
1393 double multiplier =
1394 config.buyingStageCashMultiplier * buyingFraction +
1395 config.noMonopolyStageCashMultiplier * (1.0 - buyingFraction);
1396 const double dependence = config.cashLiquidAssetsDependence > 0.0
1397 ? config.cashLiquidAssetsDependence : 1.0;
1398 double assetScale = 1.0 / std::exp(
1399 std::log(dependence) * static_cast<double>(liquidAssets) / 1000.0);
1400 assetScale = std::clamp(assetScale, 0.25, 1.0);
1402 chances[current] += multiplier * static_cast<double>(liquidAssets);
1403 break;
1404 }
1406 chances[current] += config.monopolyNotOwnedStageCashMultiplier *
1407 static_cast<double>(liquidAssets);
1408 break;
1410 chances[current] += config.monopolyOwnedStageCashMultiplier *
1411 static_cast<double>(liquidAssets);
1412 break;
1413 }
1414
1415 if (chances[current] > 0.0)
1416 totalAbsoluteChance += chances[current];
1417 }
1418
1419 if (totalAbsoluteChance == 0.0)
1420 totalAbsoluteChance = 1.0;
1421
1422 double normalizeConstant{};
1423 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1424 {
1425 chances[current] /= totalAbsoluteChance;
1426 chances[current] = std::exp(chances[current]);
1427 normalizeConstant += chances[current];
1428 }
1429 for (rules::PlayerNumber current = 0; current < simulated.numberOfPlayers; ++current)
1430 chances[current] /= normalizeConstant;
1431
1432 if (saveAll)
1433 return 0.0;
1434 return chances[player];
1435 }
1436
1438 const rules::GameState& state,
1439 rules::PlayerNumber player,
1442 const TradeEvaluationConfig& config) noexcept
1443 {
1444 if (state.numberOfPlayers > rules::MaxPlayers ||
1445 player >= state.numberOfPlayers || strategyPlayer >= state.numberOfPlayers)
1446 return -50.0;
1447
1448 auto before = state;
1449 auto after = state;
1452 if (config.purchasingPlayer < state.numberOfPlayers &&
1453 config.purchasingProperty < rules::board::SquareType::InJail)
1454 {
1455 const auto index = static_cast<std::size_t>(config.purchasingProperty);
1456 before.squares[index].owner = config.purchasingPlayer;
1457 after.squares[index].owner = config.purchasingPlayer;
1458 }
1459
1461 before, player, config.winningChance);
1462 const double chancesAfter = evaluateWinningChances(
1463 after, player, config.winningChance);
1464 if ((chancesBefore - chancesAfter) > config.chancesThreshold)
1465 return -50.0;
1466
1467 auto worthBefore = totalWorthWithFactors(before, player, config.worthFactors);
1468 auto worthAfter = totalWorthWithFactors(after, player, config.worthFactors);
1469 const auto debt = config.winningChance.moneyOwed[player];
1470 if (ai::liquidAssets(after, player, true, true, debt) <= 0)
1471 return -50.0;
1472 for (std::size_t deck = 0;
1474 {
1475 if (before.cards[deck].jailOwner == player)
1476 worthBefore += config.jailCardValue;
1477 if (after.cards[deck].jailOwner == player)
1478 worthAfter += config.jailCardValue;
1479 }
1480
1481 const double propertyImportance = ai::trade::calculateTradePropertyImportance(
1482 before, after, player, strategyPlayer, proposals, false,
1483 config.propertyImportance, config.winningChance.moneyOwed);
1484 return (chancesAfter - chancesBefore) * config.chancesFactor +
1485 static_cast<double>(worthAfter - worthBefore) * config.cashFactor +
1486 propertyImportance * config.tradeImportanceFactor;
1487 }
1488
1491 rules::board::SquareType property, std::int64_t currentBid,
1493 {
1495 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1496 player >= state.numberOfPlayers || property >= SquareType::InJail ||
1497 currentBid < 0 || currentBidder == player)
1498 return 0;
1499
1500 constexpr std::int64_t Change = 10;
1501 auto afterCurrentBid = state;
1502 auto afterOurBid = state;
1503 const auto index = static_cast<std::size_t>(property);
1504 const bool validBidder = currentBidder < state.numberOfPlayers;
1505 if (validBidder)
1506 {
1507 afterCurrentBid.squares[index].owner = currentBidder;
1508 afterCurrentBid.players[currentBidder].cash -= currentBid;
1509 }
1510 afterOurBid.squares[index].owner = player;
1511 afterOurBid.players[player].cash -= currentBid + Change;
1514
1515 const auto mortgageCost = rules::board::definition(property).mortgageCost;
1516 const auto debtFor = [&](rules::PlayerNumber current) {
1517 return current < config.evaluation.winningChance.moneyOwed.size()
1518 ? config.evaluation.winningChance.moneyOwed[current] : 0;
1519 };
1522 mortgageCost < 0)
1523 return 0;
1524 if (ai::liquidAssets(afterOurBid, player, true, true, debtFor(player)) -
1525 mortgageCost < 0)
1526 return 0;
1527
1529 afterCurrentBid, player, config.evaluation.winningChance);
1530 const double chancesAfter = evaluateWinningChances(
1531 afterOurBid, player, config.evaluation.winningChance);
1532 if ((chancesBefore - chancesAfter) > config.evaluation.chancesThreshold)
1533 return 0;
1534
1536 afterCurrentBid, player, config.evaluation.worthFactors);
1538 afterOurBid, player, config.evaluation.worthFactors);
1539 for (std::size_t deck = 0;
1541 {
1542 if (afterCurrentBid.cards[deck].jailOwner == player)
1543 worthBefore += config.evaluation.jailCardValue;
1544 if (afterOurBid.cards[deck].jailOwner == player)
1545 worthAfter += config.evaluation.jailCardValue;
1546 }
1547
1549 const auto bit = rules::board::propertyBit(property);
1550 proposal[player].propertiesReceived = bit;
1551 if (validBidder)
1552 proposal[currentBidder].propertiesGiven = bit;
1553 const double propertyImportance = ai::trade::calculateTradePropertyImportance(
1554 afterCurrentBid, afterOurBid, player, player, proposal, false,
1555 config.evaluation.propertyImportance,
1556 config.evaluation.winningChance.moneyOwed);
1557 const double evaluation =
1558 (chancesAfter - chancesBefore) * config.evaluation.chancesFactor +
1559 static_cast<double>(worthAfter - worthBefore) * config.evaluation.cashFactor +
1560 propertyImportance * config.evaluation.tradeImportanceFactor;
1561 return evaluation >= 0.0 ? currentBid + Change : 0;
1562 }
1563
1564 std::int64_t bidForAuctionItem(
1566 rules::board::SquareType item, std::int64_t currentBid,
1568 bool useTenDollarIncrement) noexcept
1569 {
1571 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1572 player >= state.numberOfPlayers || item >= SquareType::Count ||
1573 currentBid < 0 || currentBidder == player)
1574 return 0;
1575
1576 const auto debt = config.evaluation.winningChance.moneyOwed[player];
1577 const auto strategy = config.evaluation.winningChance.cashStrategy[player];
1578 const auto minCash = config.evaluation.winningChance.minCashOnHand[player];
1579 const auto liquid = ai::liquidAssets(state, player, false, false, debt);
1580 const auto liquidSellingHouses = ai::liquidAssets(state, player, true, false, debt);
1582 state, player, strategy, minCash, debt);
1583 const auto excessCash = excessCashAvailable(
1584 state, player, false, strategy, minCash, debt);
1585
1586 double bid{};
1587 std::int64_t mortgageCost{};
1588 std::int64_t purchaseCost{};
1590 std::int64_t tradeBid{};
1591 if (item < SquareType::InJail)
1592 {
1593 const auto& definition = rules::board::definition(item);
1594 mortgageCost = definition.mortgageCost;
1595 purchaseCost = definition.purchaseCost;
1598 state, player, item, currentBid, currentBidder, config);
1599 }
1600 else
1601 {
1602 if (shouldBuyHouse(state, player, strategy, minCash,
1603 config.housingPurchaseStrategy, debt) == HousePurchaseDecision::No)
1604 return 0;
1605 const auto monopolies = ai::monopoliesOwned(state, player, false);
1606 std::int64_t housesWanted{};
1607 for (std::size_t index = 0; index < monopolies.count; ++index)
1609 state, monopolies.representatives[index]);
1610 housesWanted = std::min<std::int64_t>(housesWanted, ai::freeHouses(state));
1611 if (housesWanted <= 0)
1612 return 0;
1613 bid = static_cast<double>(excessCash) /
1614 static_cast<double>(housesWanted);
1615 }
1616
1617 if (item < SquareType::InJail)
1618 {
1620 {
1621 const auto owner = ai::firstOwnerInMonopoly(state, item);
1622 if (currentBidder == owner)
1623 {
1624 if (ai::playerOwnsMonopoly(state, player, false))
1625 {
1627 ? (static_cast<double>(liquid) +
1628 static_cast<double>(liquidSellingHouses) *
1629 config.monopolySuicideFactor) / 2.0
1630 : static_cast<double>(excessCash);
1631 }
1632 else
1633 {
1634 const double remaining = static_cast<double>(
1636 bid = static_cast<double>(excessCash) *
1637 config.monopolySuicideFactor;
1638 bid *= 0.5 + (0.5 - remaining / 22.0 / 2.0);
1639 if (ai::trade::hasMonopolyTrade(state, player, owner))
1640 bid = static_cast<double>(excessCash) *
1641 config.monopolySuicideFactor * 0.5;
1642 if (static_cast<double>(tradeBid) > bid)
1643 bid = static_cast<double>(tradeBid);
1644 }
1645 }
1646 else if (owner == player)
1647 bid = static_cast<double>(cashAfterHousing);
1648 else
1649 bid = static_cast<double>(tradeBid);
1650 }
1651 else
1652 bid = static_cast<double>(tradeBid);
1653 }
1654
1655 if (bid > static_cast<double>(excessCash))
1656 bid = static_cast<double>(excessCash);
1657
1658 if (item < SquareType::InJail && bid < static_cast<double>(mortgageCost))
1659 {
1660 const auto floorBid = mortgageCost + static_cast<std::int64_t>(bid) / 2;
1661 bid = liquid >= floorBid
1662 ? static_cast<double>(floorBid)
1663 : static_cast<double>(liquid);
1664 }
1665
1666 if (item < SquareType::InJail && bid < static_cast<double>(purchaseCost) &&
1668 !ai::ownsPropertyFromMonopoly(state, player, item) &&
1669 !ai::isCashCow(item) && excessCash >= purchaseCost)
1670 {
1671 bid = static_cast<double>(purchaseCost);
1672 }
1673
1674 const std::int64_t maximumBid = static_cast<std::int64_t>(bid);
1675 const std::int64_t change = useTenDollarIncrement ? 10 : 5;
1677 return 0;
1678 return currentBid + change;
1679 }
1680
1682 const rules::GameState& state,
1683 std::span<const rules::PlayerNumber> players,
1688 std::span<double> evaluations) noexcept
1689 {
1690 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1691 strategyPlayer >= state.numberOfPlayers || evaluations.size() < players.size())
1692 return;
1693
1694 auto before = state;
1695 auto after = state;
1698 if (config.purchasingPlayer < state.numberOfPlayers &&
1699 config.purchasingProperty < rules::board::SquareType::InJail)
1700 {
1701 const auto index = static_cast<std::size_t>(config.purchasingProperty);
1702 before.squares[index].owner = config.purchasingPlayer;
1703 after.squares[index].owner = config.purchasingPlayer;
1704 }
1705 std::array<double, rules::MaxPlayers> chancesBefore{};
1706 std::array<double, rules::MaxPlayers> chancesAfter{};
1708 before, rules::NobodyPlayer, config.winningChance,
1709 std::span<double>(chancesBefore.data(), state.numberOfPlayers));
1711 after, rules::NobodyPlayer, config.winningChance,
1712 std::span<double>(chancesAfter.data(), state.numberOfPlayers));
1713
1714 for (std::size_t index = 0; index < players.size(); ++index)
1715 {
1716 const auto player = players[index];
1717 if (player >= state.numberOfPlayers)
1718 {
1719 evaluations[index] = -50.0;
1720 continue;
1721 }
1722 if ((chancesBefore[player] - chancesAfter[player]) > config.chancesThreshold)
1723 {
1724 evaluations[index] = -50.0;
1725 continue;
1726 }
1727
1728 auto worthBefore = totalWorthWithFactors(before, player, config.worthFactors);
1729 auto worthAfter = totalWorthWithFactors(after, player, config.worthFactors);
1730 const auto debt = config.winningChance.moneyOwed[player];
1731 if (ai::liquidAssets(after, player, true, true, debt) <= 0)
1732 {
1733 evaluations[index] = -50.0;
1734 continue;
1735 }
1736 for (std::size_t deck = 0;
1738 {
1739 if (before.cards[deck].jailOwner == player)
1740 worthBefore += config.jailCardValue;
1741 if (after.cards[deck].jailOwner == player)
1742 worthAfter += config.jailCardValue;
1743 }
1744
1745 const double propertyImportance = ai::trade::calculateTradePropertyImportance(
1746 before, after, player, strategyPlayer, proposals,
1747 givingMonopolyForCash, config.propertyImportance,
1748 config.winningChance.moneyOwed);
1750 (chancesAfter[player] - chancesBefore[player]) * config.chancesFactor +
1751 static_cast<double>(worthAfter - worthBefore) * config.cashFactor +
1752 propertyImportance * config.tradeImportanceFactor;
1753 }
1754 }
1755
1757 const rules::GameState& state,
1758 rules::PlayerNumber player,
1759 std::span<const rules::PlayerNumber> partners,
1760 std::int64_t giveMost,
1761 bool givingMonopoly,
1763 const FairTradeConfig& config,
1764 std::span<const ai::trade::FutureImmunityRecord> immunities) noexcept
1765 {
1766 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1767 player >= state.numberOfPlayers || partners.empty() ||
1768 partners.size() >= rules::MaxPlayers)
1769 return false;
1770
1771 std::array<rules::PlayerNumber, rules::MaxPlayers> participants{};
1772 for (std::size_t index = 0; index < partners.size(); ++index)
1773 {
1774 if (partners[index] >= state.numberOfPlayers || partners[index] == player)
1775 return false;
1777 }
1778 participants[partners.size()] = player;
1779 const auto participantCount = partners.size() + 1;
1780 std::array<std::int64_t, rules::MaxPlayers> cashGiven{};
1781 auto change = giveMost / 2;
1782 if (partners.size() == 1)
1783 {
1784 proposals[player].cashReceived = 0;
1785 proposals[player].cashGiven = 0;
1786 proposals[partners[0]].cashReceived = 0;
1787 proposals[partners[0]].cashGiven = 0;
1788 }
1789
1790 const double minEvaluation = givingMonopoly
1791 ? config.minGiveMonopolyEvaluation
1792 : config.minEvaluationThreshold;
1793 bool doneChangingCash{};
1794 std::array<double, rules::MaxPlayers> evaluations{};
1795 while (!doneChangingCash)
1796 {
1798 state,
1799 std::span<const rules::PlayerNumber>(participants.data(), participantCount),
1800 player, proposals, givingMonopoly, config.evaluation,
1801 std::span<double>(evaluations.data(), participantCount));
1802
1803 auto bestEvaluation = evaluations[0];
1804 auto worstEvaluation = evaluations[0];
1805 auto bestPlayer = partners[0];
1806 auto worstPlayer = partners[0];
1807 for (std::size_t index = 0; index < partners.size(); ++index)
1808 {
1809 const auto current = partners[index];
1811 {
1813 bestPlayer = current;
1814 }
1816 {
1818 worstPlayer = current;
1819 }
1820 }
1821
1822 if (evaluations[partners.size()] < minEvaluation ||
1824 {
1825 if (change <= 2)
1826 {
1827 change = 5;
1828 doneChangingCash = true;
1829 }
1830 proposals[player].cashReceived += change;
1831 proposals[bestPlayer].cashGiven += change;
1832 cashGiven[bestPlayer] -= change;
1833 }
1834 else
1835 {
1836 if (change <= 2)
1837 {
1838 doneChangingCash = true;
1839 continue;
1840 }
1841 proposals[worstPlayer].cashReceived += change;
1842 proposals[player].cashGiven += change;
1843 cashGiven[worstPlayer] += change;
1844 }
1845 change /= 2;
1846 }
1847
1848 for (const auto current : partners)
1849 {
1851 config.playerAttitude[current], config.cashMultipliers);
1852 if (multiplier <= 0.0)
1853 return false;
1854 const double factor = 1.0 / multiplier;
1855 std::int64_t adjustment{};
1856 if (cashGiven[current] > 0)
1857 adjustment = static_cast<std::int64_t>(
1858 static_cast<double>(cashGiven[current]) -
1859 static_cast<double>(cashGiven[current]) * factor);
1860 else
1861 adjustment = static_cast<std::int64_t>(
1862 static_cast<double>(cashGiven[current]) -
1863 static_cast<double>(cashGiven[current]) / factor);
1864 proposals[current].cashReceived -= adjustment;
1865 proposals[player].cashGiven -= adjustment;
1866 }
1867 for (std::size_t index = 0; index < participantCount; ++index)
1868 {
1869 const auto current = participants[index];
1870 proposals[current].cashGiven -= proposals[current].cashReceived;
1871 proposals[current].cashReceived = 0;
1872 if (proposals[current].cashGiven < 0)
1873 {
1874 proposals[current].cashReceived = -proposals[current].cashGiven;
1875 proposals[current].cashGiven = 0;
1876 }
1877
1879 state, proposals[current].propertiesReceived);
1880 auto properties = ai::propertiesOwnedByPlayer(state, current);
1881 properties &= ~proposals[current].propertiesGiven;
1882 canPay += ai::liquidAssetsForProperties(state, properties, false, false);
1883 canPay += state.players[current].cash;
1884 if (config.localAIPlayer[current])
1885 canPay -= config.evaluation.winningChance.moneyOwed[current];
1886
1887 if (canPay < proposals[current].cashGiven)
1888 {
1889 if (!givingMonopoly || canPay < -cashGiven[current])
1890 return false;
1891 change = (canPay - cashGiven[current]) / 2;
1892 change -= proposals[current].cashGiven;
1893 proposals[current].cashGiven += change;
1894 proposals[player].cashReceived += change;
1895 break;
1896 }
1897 }
1898 return ai::trade::tradeIsProper(state, proposals, immunities);
1899 }
1900
1902 const rules::GameState& state,
1903 rules::PlayerNumber player,
1905 std::span<const rules::PlayerNumber> partners,
1906 const ai::trade::PropertySets& properties,
1908 const MonopolyProposalConfig& config) noexcept
1909 {
1910 const auto groupIndex = static_cast<std::size_t>(group);
1911 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1912 player >= state.numberOfPlayers || groupIndex >= ai::ExpensiveMonopolySquares.size() ||
1913 partners.empty() || partners.size() >= rules::MaxPlayers)
1914 return false;
1915 auto combined = properties[player];
1916 for (const auto partner : partners)
1917 {
1918 if (partner >= state.numberOfPlayers || partner == player)
1919 return false;
1920 combined |= properties[partner];
1921 }
1922 auto next = proposals;
1924 for (std::size_t index = 0; index < lots.count; ++index)
1925 {
1926 const auto square = lots.squares[index];
1927 const auto owner = state.squares[static_cast<std::size_t>(square)].owner;
1928 if (owner == player)
1929 continue;
1930 if (owner >= state.numberOfPlayers)
1931 return false;
1932 const auto bit = rules::board::propertyBit(square);
1933 next[player].propertiesReceived |= bit;
1934 next[owner].propertiesGiven |= bit;
1935 }
1936 combined &= ~next[player].propertiesReceived;
1937 auto after = state;
1938 for (const auto partner : partners)
1939 {
1940 // Preserve retail's successive updates of the same simulation.
1943 state, after, partner, player, combined,
1945 config.whatToTrade, config.fairTrade.playerAttitude[partner],
1946 config.fairTrade.evaluation.winningChance.moneyOwed[partner]);
1947 }
1948 if (!makeTradeFair(state, player, partners, 2500, false, next, config.fairTrade))
1949 return false;
1950 proposals = next;
1951 return true;
1952 }
1953
1955 const rules::GameState& state,
1956 rules::PlayerNumber player,
1958 const ai::trade::PropertySets& properties,
1960 const MonopolyProposalConfig& config) noexcept
1961 {
1962 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
1963 player >= state.numberOfPlayers || firstPartner >= state.numberOfPlayers)
1964 return false;
1965 auto available = properties[player];
1966 const auto owned = ai::monopoliesOwned(state, player, false);
1967 for (std::size_t index = 0; index < owned.count; ++index)
1968 available ^= ai::monopolySet(owned.representatives[index]);
1969
1970 for (std::size_t offset = 0; offset < state.numberOfPlayers; ++offset)
1971 {
1972 const auto partner = static_cast<rules::PlayerNumber>(
1973 (firstPartner + offset) % state.numberOfPlayers);
1974 if (partner == player ||
1975 state.players[partner].currentSquare ==
1976 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard) ||
1978 continue;
1980 auto combined = available | properties[partner];
1982 state, state, partner, player, combined,
1984 config.whatToTrade, config.fairTrade.playerAttitude[partner],
1985 config.fairTrade.evaluation.winningChance.moneyOwed[partner]))
1986 continue;
1987 const std::array<rules::PlayerNumber, 1> partners{partner};
1988 if (makeTradeFair(state, player, partners, 16000, true, next, config.fairTrade) &&
1990 {
1991 proposals = next;
1992 return true;
1993 }
1994 }
1995 return false;
1996 }
1997
1999 const rules::GameState& state,
2000 rules::PlayerNumber player,
2001 std::span<const double> playerAttitudes,
2002 const ai::trade::PropertySets& properties,
2005 const MonopolyProposalConfig& config) noexcept
2006 {
2007 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
2008 player >= state.numberOfPlayers || playerAttitudes.size() < state.numberOfPlayers)
2009 return false;
2010 for (const auto group : inputs.wantedGroups)
2011 if (static_cast<std::size_t>(group) >= ai::ExpensiveMonopolySquares.size())
2012 return false;
2013 if (!inputs.deriveOfferedGroups)
2014 {
2015 for (const auto group : inputs.offeredGroups)
2016 if (static_cast<std::size_t>(group) >= ai::ExpensiveMonopolySquares.size())
2017 return false;
2018 }
2019
2020 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
2021 std::size_t candidateCount{};
2022 double totalWeight{};
2023 for (rules::PlayerNumber candidate = 0; candidate < state.numberOfPlayers; ++candidate)
2024 {
2025 if (candidate == player || candidate == inputs.excludedPlayer ||
2026 (inputs.requiredTarget != rules::NobodyPlayer &&
2027 candidate != inputs.requiredTarget) ||
2028 state.players[candidate].currentSquare ==
2029 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard) ||
2030 playerAttitudes[candidate] < -1.0)
2031 continue;
2033 totalWeight += (playerAttitudes[candidate] + 1.0) / 2.0;
2034 }
2035 if (candidateCount == 0 || totalWeight <= 0.0)
2036 return false;
2037
2038 double choice = inputs.partnerRoll;
2039 std::size_t start{};
2040 for (; start < candidateCount; ++start)
2041 {
2042 choice -= (playerAttitudes[candidates[start]] + 1.0) / 2.0 / totalWeight;
2043 if (choice <= 0.0)
2044 break;
2045 }
2046 if (start >= candidateCount)
2047 start = candidateCount - 1;
2048
2049 const int wantedLimit = static_cast<int>(inputs.wantedPropertyRoll % 3u) + 1;
2050 const int offeredLimit = wantedLimit + static_cast<int>(inputs.offeredPropertyRoll % 2u) - 1;
2051 for (std::size_t offset = 0; offset < candidateCount; ++offset)
2052 {
2053 const auto target = candidates[(start + offset) % candidateCount];
2055 int taken{};
2056 int given{};
2057
2058 if ((inputs.importance & ai::trade::TradeForCash) == 0)
2059 {
2060 auto available = properties[target];
2061 for (std::size_t index = 0; index < 3 && taken < wantedLimit; ++index)
2062 {
2064 static_cast<std::size_t>(inputs.wantedGroups[index])];
2066 auto wanted = available & groupSet;
2067 bool blockedOwner{};
2068 const auto lots = ai::monopolyLots(representative);
2069 for (std::size_t lot = 0; lot < lots.count; ++lot)
2070 {
2071 const auto owner = state.squares[
2072 static_cast<std::size_t>(lots.squares[lot])].owner;
2073 if (owner == player || owner >= state.numberOfPlayers)
2074 continue;
2075 if (playerAttitudes[owner] < -1.0 ||
2076 ai::playerOwnsMonopoly(state, owner, true))
2077 {
2078 blockedOwner = true;
2079 break;
2080 }
2081 }
2083 ai::testForMonopoly(properties[player] | wanted, representative) ||
2085 continue;
2086 next[target].propertiesGiven |= wanted;
2087 next[player].propertiesReceived |= wanted;
2088 available &= ~wanted;
2090 }
2091 // Retail's loop breaks after the first railroad/utility square, even
2092 // when the chosen partner does not own it.
2093 if (taken == 0)
2094 {
2095 for (std::size_t square = 0;
2097 {
2098 const auto type = static_cast<rules::board::SquareType>(square);
2099 const auto group = rules::board::definition(type).group;
2102 continue;
2103 const auto bit = rules::board::propertyBit(type);
2104 if ((available & bit) != 0)
2105 {
2106 next[target].propertiesGiven |= bit;
2107 next[player].propertiesReceived |= bit;
2108 ++taken;
2109 }
2110 break;
2111 }
2112 if (taken == 0)
2113 continue;
2114 }
2115 }
2116
2117 auto available = properties[player];
2118 auto offeredGroups = inputs.offeredGroups;
2119 if (inputs.deriveOfferedGroups)
2120 {
2121 offeredGroups = ai::trade::orderMonopolyImportance(
2123 state, target, false, false,
2124 config.fairTrade.evaluation.winningChance.moneyOwed[target]),
2126 }
2127 const std::size_t groupLimit = (inputs.importance & ai::trade::TradeForCash) != 0 ? 8 : 4;
2128 for (std::size_t index = 0; index < groupLimit; ++index)
2129 {
2131 static_cast<std::size_t>(offeredGroups[index])];
2133 auto offered = available & groupSet;
2134 if (offered == 0 || ai::testForMonopoly(offered, representative) ||
2135 ((inputs.importance & ai::trade::TradeDesperate) == 0 &&
2136 ai::testForMonopoly(properties[target] | offered, representative)))
2137 continue;
2138 next[target].propertiesReceived |= offered;
2139 next[player].propertiesGiven |= offered;
2140 available &= ~offered;
2141 given += ai::propertyCount(offered);
2142 if ((inputs.importance & ai::trade::TradeForCash) != 0)
2143 break;
2144 if (given > offeredLimit)
2145 {
2146 next[target].propertiesReceived ^= offered;
2147 next[player].propertiesGiven ^= offered;
2148 given -= ai::propertyCount(offered);
2149 break;
2150 }
2151 }
2152 if (given == 0 && taken == 0)
2153 continue;
2154 const std::array<rules::PlayerNumber, 1> partners{target};
2155 auto fair = config.fairTrade;
2157 if (attitude < inputs.minimumNonmonopolyTradeAttitude &&
2158 (inputs.importance & (ai::trade::TradeDesperate |
2160 attitude = inputs.minimumNonmonopolyTradeAttitude;
2161 int attitudeIndex = attitude <= -1.0 ? 0 : static_cast<int>((attitude + 1.0) * 10.0);
2162 if ((inputs.importance & ai::trade::TradeSomewhatImportant) != 0)
2163 attitudeIndex -= 2;
2164 if ((inputs.importance & ai::trade::TradeDesperate) != 0)
2165 attitudeIndex -= 5;
2166 attitudeIndex = std::clamp(attitudeIndex, 0,
2167 static_cast<int>(ai::trade::WhatToTradeEntries) - 1);
2168 fair.playerAttitude[target] = static_cast<double>(attitudeIndex) / 10.0 - 1.0;
2169 if (!makeTradeFair(state, player, partners, 2500, false, next, fair))
2170 continue;
2171 proposals = next;
2172 return true;
2173 }
2174 return false;
2175 }
2176
2178 const rules::GameState& state,
2179 rules::PlayerNumber player,
2180 std::span<const double> playerAttitudes,
2181 const ai::trade::PropertySets& properties,
2183 const MonopolyProposalConfig& config) noexcept
2184 {
2185 ProactiveTradeResult result{};
2186 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
2187 player >= state.numberOfPlayers || playerAttitudes.size() < state.numberOfPlayers)
2188 return result;
2189 for (const auto group : inputs.monopolyGroups)
2190 if (static_cast<std::size_t>(group) >= ai::ExpensiveMonopolySquares.size())
2191 return result;
2192
2193 std::array<rules::PlayerNumber, rules::MaxPlayers> candidates{};
2194 std::size_t candidateCount{};
2195 const bool desperate = (inputs.importance & ai::trade::TradeDesperate) != 0;
2196 for (rules::PlayerNumber candidate = 0; candidate < state.numberOfPlayers; ++candidate)
2197 {
2198 if (candidate == player || candidate == inputs.excludedPlayer ||
2199 state.players[candidate].currentSquare ==
2200 static_cast<std::uint8_t>(rules::board::SquareType::OffBoard))
2201 continue;
2202 if (!desperate &&
2204 playerAttitudes[candidate] < -1.0))
2205 continue;
2207 }
2208 // Retail exits before the give-away path when no trade partner is eligible.
2209 if (candidateCount == 0)
2210 return result;
2211
2212 if (inputs.shouldGiveAwayMonopoly)
2213 {
2215 state, player, config.fairTrade.evaluation.winningChance.moneyOwed);
2217 {
2220 state, player, partner, properties, proposal, config))
2221 {
2223 result.proposal = proposal;
2224 }
2225 // Retail returns immediately if a cash-sale target exists but the
2226 // monopoly-for-cash builder cannot produce a proposal.
2227 return result;
2228 }
2229 }
2230
2231 std::size_t monopolyLimit{};
2233 {
2235 monopolyLimit = 2;
2236 break;
2238 monopolyLimit = 7; // SG_PARK_PLACE: all but the worst-ranked group.
2239 break;
2241 monopolyLimit = 0;
2242 break;
2243 }
2244 if ((inputs.importance & ai::trade::TradeForCash) != 0)
2245 monopolyLimit = 0;
2246
2247 if ((inputs.importance & ai::trade::TradeGiveMonopoly) == 0)
2248 {
2249 for (std::size_t rank = 0; rank < monopolyLimit; ++rank)
2250 {
2251 const auto group = inputs.monopolyGroups[rank];
2253 static_cast<std::size_t>(group)];
2256 std::span<const rules::PlayerNumber>(candidates.data(), candidateCount),
2257 properties);
2258 // The retail build deliberately restricts this path to exactly
2259 // two partners despite the recursive helper supporting larger trades.
2260 if (smallest.count != 2)
2261 continue;
2262
2264 if (!buildMonopolyTrade(
2265 state, player, group,
2266 std::span<const rules::PlayerNumber>(
2267 smallest.players.data(), smallest.count),
2268 properties, proposal, config))
2269 continue;
2270
2271 if (inputs.importance == 0 ||
2273 {
2274 if (evaluateTrade(
2275 state, player, player, proposal,
2276 config.fairTrade.evaluation) <
2277 config.fairTrade.minEvaluationThreshold)
2278 continue;
2279 }
2280 if (!ai::trade::tradeIsProper(state, proposal))
2281 continue;
2282
2284 result.proposal = proposal;
2285 return result;
2286 }
2287 }
2288
2289 if (!inputs.semiImportant)
2290 {
2292 return result;
2293 }
2294 auto semiInputs = *inputs.semiImportant;
2295 semiInputs.importance = inputs.importance;
2296 semiInputs.excludedPlayer = inputs.excludedPlayer;
2299 state, player, playerAttitudes, properties, semiInputs,
2300 proposal, config) &&
2302 {
2304 result.proposal = proposal;
2305 }
2306 return result;
2307 }
2308
2310 const rules::GameState& state,
2311 rules::PlayerNumber player,
2312 const ai::trade::TradeProposalList& currentTrade,
2316 std::span<const ai::trade::FutureImmunityRecord> immunities) noexcept
2317 {
2319 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
2320 player >= state.numberOfPlayers || inputs.proposedPlayer >= state.numberOfPlayers)
2321 return result;
2322
2323 if (inputs.pendingActions != 0 || !state.tradeInProgress ||
2324 inputs.playerSendingTrade || inputs.auctionOn)
2325 {
2327 return result;
2328 }
2329 if (!ai::trade::playerInvolvedInTrade(currentTrade[player]))
2330 {
2331 result.status = CounterProposalStatus::NotInvolved;
2332 return result;
2333 }
2334 if (ai::trade::playerHasFutureOrImmunity(player, immunities))
2335 {
2337 return result;
2338 }
2339 if (session.timesCounteredTrade >= config.tradeCounterLimit)
2340 {
2342 return result;
2343 }
2344 if (session.timesCounteredTrade == 0 &&
2346 session.propertyMemory, inputs.proposedPlayer,
2347 currentTrade[player], config.numberTimesAllowPropertyTrade) != 0)
2348 {
2350 return result;
2351 }
2352 if (session.timesCounteredTrade == 0)
2354 session.propertyMemory, inputs.proposedPlayer, currentTrade[player]);
2355
2356 if (inputs.counterRoll > config.tradeCounterProbability &&
2357 inputs.tradeAccept && session.timesCounteredTrade == 0)
2358 {
2360 return result;
2361 }
2362 result.evaluation = evaluateTrade(
2363 state, player, player, currentTrade, config.evaluation);
2364 if (session.timesCounteredTrade != 0 &&
2365 result.evaluation <= session.lastTradeEvaluation)
2366 {
2368 return result;
2369 }
2370 session.lastTradeEvaluation = result.evaluation;
2371
2372 auto preparedState = state;
2373 if (config.evaluation.purchasingPlayer < state.numberOfPlayers &&
2374 config.evaluation.purchasingProperty < rules::board::SquareType::InJail)
2375 preparedState.squares[static_cast<std::size_t>(
2376 config.evaluation.purchasingProperty)].owner = config.evaluation.purchasingPlayer;
2378 preparedState, player, currentTrade, config.playerAttitude);
2379 if (result.preparation.tradePlayerCount < 1)
2380 {
2382 return result;
2383 }
2384 if (result.preparation.tradePlayerCount == 1)
2385 {
2386 const auto partner = result.preparation.tradePlayers[0];
2387 if (config.playerAttitude[partner] <= -1.0)
2388 {
2390 return result;
2391 }
2392 }
2393 result.averageAttitude = result.preparation.totalAttitude /
2394 static_cast<double>(result.preparation.tradePlayerCount);
2395 if (result.averageAttitude < -1.0)
2396 result.averageAttitude = -1.0;
2397 result.status = CounterProposalStatus::Ready;
2398 return result;
2399 }
2400
2402 const rules::GameState& state,
2403 rules::PlayerNumber player,
2407 std::span<const ai::trade::FutureImmunityRecord> immunities) noexcept
2408 {
2410 if (state.numberOfPlayers == 0 || state.numberOfPlayers > rules::MaxPlayers ||
2411 player >= state.numberOfPlayers ||
2413 preflight.preparation.tradePlayerCount == 0 ||
2414 preflight.preparation.tradePlayerCount >= rules::MaxPlayers ||
2415 config.maxIterations == 0)
2416 return result;
2417
2418 const auto partnerCount = preflight.preparation.tradePlayerCount;
2419 for (std::size_t index = 0; index < partnerCount; ++index)
2420 {
2421 const auto partner = preflight.preparation.tradePlayers[index];
2422 if (partner >= state.numberOfPlayers || partner == player)
2423 return result;
2424 }
2425
2426 auto oldState = state;
2427 auto newState = state;
2429 const auto& evaluationConfig = config.fairTrade.evaluation;
2430 if (evaluationConfig.purchasingPlayer < state.numberOfPlayers &&
2432 {
2433 const auto square = static_cast<std::size_t>(evaluationConfig.purchasingProperty);
2434 oldState.squares[square].owner = evaluationConfig.purchasingPlayer;
2435 newState.squares[square].owner = evaluationConfig.purchasingPlayer;
2436 }
2439 for (std::size_t index = 0; index < partnerCount; ++index)
2440 {
2441 const auto partner = preflight.preparation.tradePlayers[index];
2442 combinedProperties |= preflight.preparation.playerProperties[partner];
2443 combinedTraded |= proposals[partner].propertiesReceived |
2444 proposals[partner].propertiesGiven;
2445 }
2446 combinedProperties |= preflight.preparation.playerProperties[player];
2448
2449 result.propertyImportance = 0.0;
2450 do
2451 {
2452 result.evaluation = evaluateTrade(
2453 oldState, player, player, proposals, evaluationConfig);
2454 if (result.evaluation >= config.minEvaluationThreshold)
2455 break;
2456
2457 // Retail repeatedly applies the full trade to new_state here. This
2458 // intentionally reapplies cash deltas while property ownership stays idempotent.
2460 result.propertyImportance = ai::trade::calculateTradePropertyImportance(
2461 oldState, newState, player, player, proposals, false,
2462 evaluationConfig.propertyImportance,
2463 evaluationConfig.winningChance.moneyOwed);
2464 const auto importance = ai::trade::createItemImportanceList(
2465 oldState, player, player, evaluationConfig.propertyImportance,
2466 0.0, config.fairTrade.cashMultipliers);
2467 bool added{};
2468 for (const auto& record : importance)
2469 {
2470 if (-result.propertyImportance < record.importance)
2471 continue;
2473 state, newState, player, player, combinedProperties,
2474 record.item, proposals, preflight.preparation.playerProperties,
2475 false, config.whatToTrade, 0.0,
2476 evaluationConfig.winningChance.moneyOwed[player]))
2477 {
2478 added = true;
2479 break;
2480 }
2481 }
2482
2483 ++result.iterations;
2484 if (!added || result.iterations >= config.maxIterations)
2485 break;
2486 }
2487 while (result.propertyImportance < 0.0);
2488
2489 if (result.propertyImportance < config.lowestPropertyImportanceForCounter &&
2490 result.evaluation < config.minEvaluationThreshold)
2491 {
2493 return result;
2494 }
2495
2496 double gaveMonopolyValue{};
2497 if (partnerCount == 1)
2498 {
2499 const auto target = preflight.preparation.tradePlayers[0];
2500 const auto importance = ai::trade::createItemImportanceList(
2501 oldState, target, player, evaluationConfig.propertyImportance,
2502 config.fairTrade.playerAttitude[target],
2503 config.fairTrade.cashMultipliers);
2504 for (const auto& record : importance)
2505 {
2507 result.propertyImportance = ai::trade::calculateTradePropertyImportance(
2508 oldState, newState, target, player, proposals, false,
2509 evaluationConfig.propertyImportance,
2510 evaluationConfig.winningChance.moneyOwed);
2511 result.propertyImportance += gaveMonopolyValue;
2512 if (-result.propertyImportance < record.importance)
2513 continue;
2514
2516 {
2517 proposals[player].propertiesGiven = 0;
2518 proposals[target].propertiesReceived = 0;
2519 newState = state;
2521 result.propertyImportance = ai::trade::calculateTradePropertyImportance(
2522 oldState, newState, target, player, proposals, false,
2523 evaluationConfig.propertyImportance,
2524 evaluationConfig.winningChance.moneyOwed);
2525 }
2526
2527 const bool added = ai::trade::addTypeProperty(
2529 record.item, proposals, preflight.preparation.playerProperties,
2530 true, config.whatToTrade, config.fairTrade.playerAttitude[target],
2531 evaluationConfig.winningChance.moneyOwed[target]);
2532 if (!added && record.item == ai::trade::TradeImportanceItem::Monopoly)
2533 {
2535 return result;
2536 }
2537 if (added && record.item == ai::trade::TradeImportanceItem::Monopoly)
2538 {
2541 oldState, newState, target, player, proposals, false,
2542 evaluationConfig.propertyImportance,
2543 evaluationConfig.winningChance.moneyOwed);
2544 gaveMonopolyValue -= result.propertyImportance;
2545 gaveMonopolyValue = record.importance - gaveMonopolyValue;
2546 if (gaveMonopolyValue < 0.0)
2547 gaveMonopolyValue = 0.0;
2548 }
2549 }
2550 }
2551
2553 const auto partners = std::span<const rules::PlayerNumber>(
2554 preflight.preparation.tradePlayers.data(), partnerCount);
2555 if (!makeTradeFair(oldState, player, partners, config.maxGiveInTrade,
2556 false, proposals, config.fairTrade, immunities))
2557 {
2559 return result;
2560 }
2561 if (!ai::trade::tradeIsProper(state, proposals, immunities))
2562 {
2564 return result;
2565 }
2567 return result;
2568 }
2569
2571 const rules::GameState& state,
2572 rules::PlayerNumber player,
2574 std::int64_t minCashOnHand,
2575 int maxHousesPerSquareForGiveAway,
2576 std::span<const std::int64_t> moneyOwed) noexcept
2577 {
2578 if (state.numberOfPlayers > rules::MaxPlayers ||
2579 player >= state.numberOfPlayers || strategy >= CashStrategy::Count)
2580 return false;
2581 if (!ai::playerOwnsMonopoly(state, player, false) ||
2585 return false;
2586
2587 auto simulated = state;
2588 const auto debt = player < moneyOwed.size() ? moneyOwed[player] : 0;
2590 simulated, player, strategy, minCashOnHand, debt) !=
2592 {
2593 }
2594 while (hypotheticalBuyHouse(simulated, player, debt))
2595 {
2596 }
2597
2598 const auto monopolies = ai::monopoliesOwned(simulated, player, false);
2599 for (std::size_t index = 0; index < monopolies.count; ++index)
2600 {
2601 const auto representative = monopolies.representatives[index];
2602 const auto lots = ai::monopolyLots(representative);
2603 const auto houses = ai::housesOnMonopoly(simulated, representative);
2604 if (houses <= static_cast<std::int64_t>(lots.count) *
2605 maxHousesPerSquareForGiveAway)
2606 return true;
2607 }
2608 return false;
2609 }
2610
2611}
std::array< rules::PlayerNumber, rules::MaxPlayers > players
decision::EconomicActionPlan plan
std::size_t offset
std::uint16_t group
fonts::Runtime & runtime
unsigned state
unsigned index
constexpr std::uint8_t HouseBuyWithin12
EconomicActionPlan planBuyHouseAction(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::uint8_t housingPurchaseStrategy, std::int64_t moneyOwed) noexcept
CounterProposalPreflightResult counterProposalPreflight(const rules::GameState &state, rules::PlayerNumber player, const ai::trade::TradeProposalList &currentTrade, const CounterProposalPreflightInputs &inputs, const CounterProposalPreflightConfig &config, CounterProposalSession &session, std::span< const ai::trade::FutureImmunityRecord > immunities) noexcept
EconomicActionPlan planDebtLiquidationStep(const rules::GameState &state, rules::PlayerNumber player, bool sellHouses) noexcept
bool hypotheticalBuyHouse(rules::GameState &state, rules::PlayerNumber player, std::int64_t moneyOwed) noexcept
bool shouldGiveAwayMonopoly(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, int maxHousesPerSquareForGiveAway, std::span< const std::int64_t > moneyOwed) noexcept
bool mortgageWorstProperty(rules::GameState &state, rules::PlayerNumber player, bool sellHouses, bool mortgageMonopoly) noexcept
bool buildSemiImportantTrade(const rules::GameState &state, rules::PlayerNumber player, std::span< const double > playerAttitudes, const ai::trade::PropertySets &properties, const SemiImportantTradeInputs &inputs, ai::trade::TradeProposalList &proposals, const MonopolyProposalConfig &config) noexcept
double evaluateTrade(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber strategyPlayer, const ai::trade::TradeProposalList &proposals, const TradeEvaluationConfig &config) noexcept
CounterProposalBalanceResult counterProposalBalance(const rules::GameState &state, rules::PlayerNumber player, const CounterProposalPreflightResult &preflight, ai::trade::TradeProposalList &proposals, const CounterProposalBalanceConfig &config, std::span< const ai::trade::FutureImmunityRecord > immunities) noexcept
HousePurchaseDecision shouldBuyHouse(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::uint8_t housingPurchaseStrategy, std::int64_t moneyOwed) noexcept
std::int64_t cashAvailableAfterHousing(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
bool buildMonopolyTrade(const rules::GameState &state, rules::PlayerNumber player, rules::board::SquareGroup group, std::span< const rules::PlayerNumber > partners, const ai::trade::PropertySets &properties, ai::trade::TradeProposalList &proposals, const MonopolyProposalConfig &config) noexcept
std::int64_t excessCashAvailable(const rules::GameState &state, rules::PlayerNumber player, bool cashOnly, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
std::int64_t tradeBidForProperty(const rules::GameState &state, rules::PlayerNumber player, rules::board::SquareType property, std::int64_t currentBid, rules::PlayerNumber currentBidder, const AuctionBidConfig &config) noexcept
bool shouldBuyProperty(const rules::GameState &state, rules::PlayerNumber player, rules::board::SquareType property, CashStrategy strategy, std::int64_t minCashOnHand, std::span< const std::int64_t > moneyOwed) noexcept
std::optional< rules::board::SquareType > chooseHousingAuctionSquare(const rules::GameState &state, rules::PlayerNumber player, HousingAuctionBuilding building, CashStrategy strategy, std::int64_t minCashOnHand, rules::board::PropertySet legalSquares, bool reservedBuilding, std::int64_t moneyOwed) noexcept
EconomicActionPlan planUnmortgagePropertyAction(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
std::int64_t bidForAuctionItem(const rules::GameState &state, rules::PlayerNumber player, rules::board::SquareType item, std::int64_t currentBid, rules::PlayerNumber currentBidder, const AuctionBidConfig &config, bool useTenDollarIncrement) noexcept
bool buildMonopolyForCash(const rules::GameState &state, rules::PlayerNumber player, rules::PlayerNumber firstPartner, const ai::trade::PropertySets &properties, ai::trade::TradeProposalList &proposals, const MonopolyProposalConfig &config) noexcept
std::optional< bool > chooseFractionTax(const rules::GameState &state, rules::PlayerNumber player) noexcept
double evaluateWinningChances(const rules::GameState &state, rules::PlayerNumber player, const WinningChanceConfig &config, std::span< double > savePlayerChances) noexcept
EconomicActionPlan planHouseSaleAction(const rules::GameState &state, rules::board::SquareType referenceSquare) noexcept
ProactiveTradeResult buildProactiveTrade(const rules::GameState &state, rules::PlayerNumber player, std::span< const double > playerAttitudes, const ai::trade::PropertySets &properties, const ProactiveTradeInputs &inputs, const MonopolyProposalConfig &config) noexcept
constexpr std::uint8_t CriticalHousingLevel
std::int64_t totalWorthWithFactors(const rules::GameState &state, rules::PlayerNumber player, const WorthFactors &factors) noexcept
bool shouldUnmortgageProperty(const rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
rules::board::SquareType hypotheticalUnmortgageProperty(rules::GameState &state, rules::PlayerNumber player, bool onlyMonopolies, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
void mortgageNegativeCashPlayers(rules::GameState &state) noexcept
constexpr std::uint8_t HouseBuyAtLeast3
std::optional< JailExitChoice > chooseJailExitChoice(const rules::GameState &state, rules::PlayerNumber player, bool canRollDoubles, bool canPayFee, bool canUseCard) noexcept
rules::board::SquareType hypotheticalUnmortgageMonopolyProperty(rules::GameState &state, rules::PlayerNumber player, CashStrategy strategy, std::int64_t minCashOnHand, std::int64_t moneyOwed) noexcept
void evaluateTradePlayerList(const rules::GameState &state, std::span< const rules::PlayerNumber > players, rules::PlayerNumber strategyPlayer, const ai::trade::TradeProposalList &proposals, bool givingMonopolyForCash, const TradeEvaluationConfig &config, std::span< double > evaluations) noexcept
bool makeTradeFair(const rules::GameState &state, rules::PlayerNumber player, std::span< const rules::PlayerNumber > partners, std::int64_t giveMost, bool givingMonopoly, ai::trade::TradeProposalList &proposals, const FairTradeConfig &config, std::span< const ai::trade::FutureImmunityRecord > immunities) noexcept
constexpr std::array< double, 8 > MonopolyAverageLandingFrequency
std::int64_t transferTax(const rules::GameState &state, rules::board::PropertySet properties) noexcept
void rememberTradedProperties(PropertyTradeMemory &memory, rules::PlayerNumber proposedPlayer, const TradeProposalRecord &proposal) 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
constexpr std::uint8_t TradeForCash
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
MonopolyTradeGroup findSmallestMonopolyTrade(rules::PlayerNumber player, rules::board::PropertySet monopoly, std::span< const rules::PlayerNumber > candidates, const PropertySets &properties) noexcept
constexpr std::uint8_t TradeDesperate
constexpr std::uint8_t TradeGiveMonopoly
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
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
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
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 someoneCloseToMonopoly(const rules::GameState &state, rules::PlayerNumber player, const MonopolyLots &monopoly, std::int64_t minDistance, std::int64_t maxDistance) noexcept
int housesOnMonopoly(const rules::GameState &state, SquareType square) noexcept
MonopolyLots monopolyLots(SquareType square) noexcept
std::uint8_t freeHouses(const rules::GameState &state) noexcept
int propertyCount(rules::board::PropertySet properties) noexcept
Definition AIUtility.cpp:77
std::int64_t minimumCalculatedExpenses(const rules::GameState &state, rules::PlayerNumber player) noexcept
bool playerOwnsMonopoly(const rules::GameState &state, rules::PlayerNumber player, bool countBaltic) noexcept
rules::board::PropertySet monopolySet(SquareType square) noexcept
Definition AIUtility.cpp:63
bool isCashCow(SquareType square) noexcept
bool testForMonopoly(rules::board::PropertySet properties, SquareType square) noexcept
Definition AIUtility.cpp:89
bool ownsMonopoly(const rules::GameState &state, rules::PlayerNumber player, SquareType square, bool mortgageCounts) noexcept
SquareType findLowestRentProperty(const rules::GameState &state, rules::PlayerNumber player, rules::board::PropertySet properties) noexcept
int numberRailroadsUtilitiesOwned(const rules::GameState &state, rules::PlayerNumber player, SquareGroup group, bool mortgageCounts, rules::board::PropertySet propertiesOwned) noexcept
void testSellHouses(rules::GameState &state, const MonopolyLots &monopoly, SquareType testSquare) noexcept
std::int64_t mostLiquidAssets(const rules::GameState &state, rules::PlayerNumber excludedPlayer, bool countMonopolies, std::span< const std::int64_t > moneyOwed) noexcept
std::int64_t totalWorth(const rules::GameState &state, rules::PlayerNumber player) noexcept
int propertiesLeftToBuy(const rules::GameState &state) noexcept
std::int64_t liquidAssets(const rules::GameState &state, rules::PlayerNumber player, bool countHouses, bool countMonopolies, std::int64_t moneyOwed) noexcept
bool housingShortage(const rules::GameState &state, std::uint8_t criticalLevel) noexcept
constexpr std::array< rules::board::SquareType, 8 > ExpensiveMonopolySquares
Definition AIUtility.hpp:59
std::int64_t liquidAssetsForProperties(const rules::GameState &state, rules::board::PropertySet properties, bool countHouses, bool countMonopolies) noexcept
rules::PlayerNumber firstOwnerInMonopoly(const rules::GameState &state, SquareType square) noexcept
bool playerCloseToProperty(const rules::GameState &state, rules::PlayerNumber ignorePlayer, SquareType property, std::int64_t minDistance, std::int64_t maxDistance) noexcept
HighestRentResult highestRentSquare(const rules::GameState &state, rules::PlayerNumber player) 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
rules::PlayerNumber bestCurrentRent(const rules::GameState &state) noexcept
std::uint8_t freeHotels(const rules::GameState &state) noexcept
std::int64_t costUnmortgageMonopoly(const rules::GameState &state, SquareType square) noexcept
std::int64_t housesCanBuyOnMonopoly(const rules::GameState &state, SquareType square) noexcept
SquareType findHighestRentMortgaged(const rules::GameState &state, rules::PlayerNumber player, rules::board::PropertySet properties, std::int64_t topUnmortgageCost) noexcept
rules::board::PropertySet propertiesOwnedByPlayer(const rules::GameState &state, rules::PlayerNumber player) noexcept
Definition AIUtility.cpp:49
bool ownsPropertyFromMonopoly(const rules::GameState &state, rules::PlayerNumber player, SquareType square) noexcept
rules::PlayerNumber mostExpensivePotentialIncome(const rules::GameState &state) noexcept
const SquareDefinition & definition(SquareType square)
std::uint32_t PropertySet
PropertySet propertyBit(SquareType square)
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
std::array< std::int64_t, RentStepCount > rent