Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AIUtility.cpp
Go to the documentation of this file.
1#include "AIUtility.hpp"
2
3#include <algorithm>
4#include <array>
5#include <cstddef>
6#include <utility>
7
8namespace monopoly::ai
9{
10 namespace
11 {
14
15 constexpr std::array<GroupRange, rules::board::MaxPropertyGroups>
17 {1, 4}, {6, 10}, {11, 15}, {16, 20}, {21, 25},
18 {26, 30}, {31, 35}, {37, 40}, {5, 36}, {12, 29}}};
19
20 constexpr std::array<double, rules::SquareCount>
22 1.14, 0.77, 1.0, 0.8, 0.88, 1.14, 0.86, 1.0, 0.89, 0.9,
23 1.0, 1.05, 1.07, 0.87, 0.95, 1.08, 1.06, 1.0, 1.11, 1.14,
24 1.10, 1.07, 1.0, 1.02, 1.19, 1.14, 1.01, 1.02, 1.04, 0.98,
25 0.97, 0.99, 0.97, 1.0, 0.91, 0.88, 1.0, 0.8, 0.8, 0.98,
26 0.0, 0.0}};
27
28 [[nodiscard]] constexpr std::size_t indexOf(
29 SquareType square) noexcept
30 {
31 return static_cast<std::size_t>(square);
32 }
33
34 [[nodiscard]] constexpr SquareType squareAt(
35 std::size_t index) noexcept
36 {
37 return static_cast<SquareType>(index);
38 }
39 }
40
41 GroupRange groupRange(SquareGroup group) noexcept
42 {
43 const auto index = static_cast<std::size_t>(group);
44 if (index >= GroupRanges.size())
45 return {};
46 return GroupRanges[index];
47 }
48
51 rules::PlayerNumber player) noexcept
52 {
53 rules::board::PropertySet properties{};
54 for (std::size_t index = 0;
55 index < static_cast<std::size_t>(SquareType::InJail); ++index)
56 {
57 if (state.squares[index].owner == player)
59 }
60 return properties;
61 }
62
63 rules::board::PropertySet monopolySet(SquareType square) noexcept
64 {
65 const auto group = rules::board::definition(square).group;
66 const auto range = groupRange(group);
68 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
69 {
70 const auto candidate = squareAt(index);
73 }
74 return result;
75 }
76
78 {
79 int count{};
80 while (properties != 0)
81 {
82 if ((properties & 1U) != 0)
83 ++count;
84 properties >>= 1U;
85 }
86 return count;
87 }
88
91 SquareType square) noexcept
92 {
93 const auto group = rules::board::definition(square).group;
94 const auto range = groupRange(group);
95 if (range.begin == range.endExclusive)
96 return false;
97 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
98 {
99 const auto candidate = squareAt(index);
101 continue;
102 if ((properties & rules::board::propertyBit(candidate)) == 0)
103 return false;
104 }
105 return true;
106 }
107
108 MonopolyLots monopolyLots(SquareType square) noexcept
109 {
110 MonopolyLots result{};
111 const auto group = rules::board::definition(square).group;
112 const auto range = groupRange(group);
113 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
114 {
115 const auto candidate = squareAt(index);
117 result.count < result.squares.size())
118 {
119 result.squares[result.count++] = candidate;
120 }
121 }
122
123 for (std::size_t index = 0; index < result.count; ++index)
124 {
125 if (rules::board::definition(result.squares[index]).rent[0] >
126 rules::board::definition(result.squares[0]).rent[0])
127 std::swap(result.squares[index], result.squares[0]);
128 }
129 return result;
130 }
131
133 const rules::GameState& state,
134 SquareType square) noexcept
135 {
136 const auto group = rules::board::definition(square).group;
137 const auto range = groupRange(group);
138 int houses{};
139 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
140 {
141 const auto candidate = squareAt(index);
143 houses += state.squares[index].houses;
144 }
145 return houses;
146 }
147
149 const rules::GameState& state,
150 SquareType square) noexcept
151 {
152 const auto group = rules::board::definition(square).group;
153 if (static_cast<std::size_t>(group) >= rules::board::MaxPropertyGroups)
154 return false;
155
156 const auto owner = state.squares[indexOf(square)].owner;
157 if (owner == rules::NobodyPlayer)
158 return false;
159 const auto range = groupRange(group);
160 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
161 {
162 const auto candidate = squareAt(index);
164 state.squares[index].owner != owner)
165 {
166 return false;
167 }
168 }
169 return true;
170 }
171
173 const rules::GameState& state,
174 rules::PlayerNumber player,
175 SquareType square,
176 bool mortgageCounts) noexcept
177 {
178 const auto group = rules::board::definition(square).group;
179 if (group == SquareGroup::Railroad || group == SquareGroup::Utility)
180 return false;
181
182 const auto owned = propertiesOwnedByPlayer(state, player);
183 if (!mortgageCounts)
184 {
185 const auto set = monopolySet(square);
186 return set != 0 && (owned & set) == set;
187 }
188 const auto range = groupRange(group);
189 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
190 {
191 const auto candidate = squareAt(index);
193 continue;
195 state.squares[index].mortgaged)
196 {
197 return false;
198 }
199 }
200 return true;
201 }
202
204 const rules::GameState& state,
205 rules::PlayerNumber player,
206 bool countBaltic) noexcept
207 {
208 // Preserve the retail loop exactly: TRUE starts at index 1,
209 // despite the historical parameter/comment naming.
210 const std::size_t start = countBaltic ? 1U : 0U;
211 for (std::size_t index = start; index < ExpensiveMonopolySquares.size(); ++index)
212 {
213 const auto square = ExpensiveMonopolySquares[index];
214 if (isMonopoly(state, square) &&
215 state.squares[indexOf(square)].owner == player)
216 return true;
217 }
218 return false;
219 }
220
221 bool anyMonopoly(const rules::GameState& state) noexcept
222 {
223 for (const auto square : ExpensiveMonopolySquares)
224 {
225 if (isMonopoly(state, square))
226 return true;
227 }
228 return false;
229 }
230
232 const rules::GameState& state,
233 rules::PlayerNumber player,
234 SquareType square) noexcept
235 {
236 return (propertiesOwnedByPlayer(state, player) & monopolySet(square)) != 0;
237 }
238
240 const rules::GameState& state,
241 SquareType square) noexcept
242 {
243 const auto group = rules::board::definition(square).group;
244 const auto range = groupRange(group);
245 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
246 {
247 const auto candidate = squareAt(index);
249 state.squares[index].owner != rules::NobodyPlayer)
250 return state.squares[index].owner;
251 }
252 return rules::NobodyPlayer;
253 }
254
256 const rules::GameState& state,
257 rules::PlayerNumber player,
258 SquareGroup group,
259 bool mortgageCounts,
261 {
262 (void)player;
263 if (group != SquareGroup::Railroad && group != SquareGroup::Utility)
264 return 0;
265
266 const auto range = groupRange(group);
267 int owned{};
268 for (std::size_t index = range.begin; index < range.endExclusive; ++index)
269 {
270 const auto square = squareAt(index);
271 if (rules::board::definition(square).group != group)
272 continue;
273 if ((propertiesOwned & rules::board::propertyBit(square)) == 0)
274 continue;
275 if (!mortgageCounts || !state.squares[index].mortgaged)
276 ++owned;
277 }
278 return owned;
279 }
280 std::int64_t rentIfSteppedOn(
281 const rules::GameState& state,
282 SquareType square,
284 {
285 const auto index = indexOf(square);
286 const auto& squareState = state.squares[index];
287 const auto& definition = rules::board::definition(square);
288 if (squareState.mortgaged || squareState.owner == rules::NobodyPlayer)
289 return 0;
290
291 if (ownsMonopoly(state, squareState.owner, square,
292 state.options.mortgagedCountsInGroupRent))
293 {
294 if (squareState.houses == 0)
295 return definition.rent[0] * 2;
296 return definition.rent[squareState.houses];
297 }
298
300 state, squareState.owner, definition.group,
301 state.options.mortgagedCountsInGroupRent, propertiesOwned);
302 if (count > 0)
303 {
304 if (definition.group == SquareGroup::Utility)
305 return count == 2 ? 70 : 28;
306 if (definition.group == SquareGroup::Railroad)
307 return definition.rent[static_cast<std::size_t>(count)];
308 }
309 return definition.rent[0];
310 }
311
312 std::uint8_t freeHouses(const rules::GameState& state) noexcept
313 {
314 int used{};
315 for (std::size_t index = 0;
316 index < static_cast<std::size_t>(SquareType::InJail); ++index)
317 {
318 if (state.squares[index].houses < state.options.housesPerHotel)
319 used += state.squares[index].houses;
320 }
321 if (state.options.maximumHouses < used)
322 return 0;
323 return static_cast<std::uint8_t>(state.options.maximumHouses - used);
324 }
325
326 std::uint8_t freeHotels(const rules::GameState& state) noexcept
327 {
328 int used{};
329 for (std::size_t index = 0;
330 index < static_cast<std::size_t>(SquareType::InJail); ++index)
331 {
332 if (state.squares[index].houses >= state.options.housesPerHotel)
333 ++used;
334 }
335 return static_cast<std::uint8_t>(state.options.maximumHotels - used);
336 }
337
339 const rules::GameState& state,
340 std::uint8_t criticalLevel) noexcept
341 {
342 return freeHouses(state) <= criticalLevel;
343 }
344
346 const rules::GameState& state,
347 rules::board::PropertySet properties,
348 bool countHouses,
349 bool countMonopolies) noexcept
350 {
351 std::int64_t total{};
352 for (std::size_t index = 0;
353 index < static_cast<std::size_t>(SquareType::InJail); ++index)
354 {
355 const auto square = squareAt(index);
356 if (state.squares[index].mortgaged ||
357 (properties & rules::board::propertyBit(square)) == 0)
358 continue;
359
360 const auto& definition = rules::board::definition(square);
361 if (housesOnMonopoly(state, square) == 0)
362 {
363 if (isMonopoly(state, square) && !countMonopolies)
364 continue;
365 total += definition.mortgageCost;
366 }
367 else if (countHouses)
368 {
369 total += (definition.housePurchaseCost / 2) *
370 state.squares[index].houses;
371 total += definition.mortgageCost;
372 }
373 }
374 return total;
375 }
376
377 std::int64_t liquidAssets(
378 const rules::GameState& state,
379 rules::PlayerNumber player,
380 bool countHouses,
381 bool countMonopolies,
382 std::int64_t moneyOwed) noexcept
383 {
384 if (player >= state.numberOfPlayers || player >= rules::MaxPlayers)
385 return 0;
386 const auto owned = propertiesOwnedByPlayer(state, player);
387 return state.players[player].cash +
389 moneyOwed;
390 }
391
392 std::int64_t mostLiquidAssets(
393 const rules::GameState& state,
394 rules::PlayerNumber excludedPlayer,
395 bool countMonopolies,
396 std::span<const std::int64_t> moneyOwed) noexcept
397 {
398 if (state.numberOfPlayers > rules::MaxPlayers ||
399 excludedPlayer >= state.numberOfPlayers)
400 return 0;
401 std::int64_t most{};
402 for (rules::PlayerNumber current = 0;
403 current < state.numberOfPlayers; ++current)
404 {
405 if (current == excludedPlayer ||
406 state.players[current].currentSquare ==
407 static_cast<std::uint8_t>(SquareType::OffBoard))
408 continue;
409 const auto debt = current < moneyOwed.size() ? moneyOwed[current] : 0;
410 most = std::max(most, liquidAssets(
412 }
413 return most;
414 }
415
416 std::int64_t totalWorth(
417 const rules::GameState& state,
418 rules::PlayerNumber player) noexcept
419 {
420 std::int64_t total = state.players[player].cash;
421 const auto owned = propertiesOwnedByPlayer(state, player);
422 for (std::size_t index = 0;
423 index < static_cast<std::size_t>(SquareType::InJail); ++index)
424 {
425 const auto square = squareAt(index);
426 if ((owned & rules::board::propertyBit(square)) == 0)
427 continue;
428 const auto& definition = rules::board::definition(square);
429 total += definition.housePurchaseCost * state.squares[index].houses;
430 total += definition.purchaseCost;
431 }
432 return total;
433 }
434
436 const rules::GameState& state,
437 SquareType square) noexcept
438 {
439 return static_cast<std::int64_t>(state.options.housesPerHotel) *
441 }
443 const rules::GameState& state,
445 SquareType property,
446 std::int64_t minDistance,
447 std::int64_t maxDistance) noexcept
448 {
449 const auto propertyIndex = static_cast<std::int64_t>(indexOf(property));
450 const auto boardwalk = static_cast<std::int64_t>(indexOf(SquareType::Boardwalk));
451 const auto freeParking = static_cast<std::int64_t>(indexOf(SquareType::FreeParking));
452 for (rules::PlayerNumber player = 0; player < state.numberOfPlayers; ++player)
453 {
454 if (player == ignorePlayer)
455 continue;
456 const auto current = static_cast<std::int64_t>(state.players[player].currentSquare);
457 if (current > boardwalk)
458 continue;
459 auto distance = propertyIndex - current;
460 if (distance < -freeParking)
461 distance += boardwalk + 1;
463 return true;
464 }
465 return false;
466 }
467
469 const rules::GameState& state,
470 rules::PlayerNumber player,
471 const MonopolyLots& monopoly,
472 std::int64_t minDistance,
473 std::int64_t maxDistance) noexcept
474 {
475 for (std::size_t index = 0; index < monopoly.count; ++index)
476 {
477 if (playerCloseToProperty(state, player, monopoly.squares[index],
479 return true;
480 }
481 return false;
482 }
483
485 const rules::GameState& state,
486 SquareType square) noexcept
487 {
488 const auto lots = monopolyLots(square);
489 std::int64_t cost{};
490 for (std::size_t index = 0; index < lots.count; ++index)
491 {
492 const auto lot = lots.squares[index];
493 if (!state.squares[indexOf(lot)].mortgaged)
494 continue;
495 cost += static_cast<std::int64_t>(
496 static_cast<double>(rules::board::definition(lot).mortgageCost) * 1.1);
497 }
498 return cost;
499 }
500
501 bool isCashCow(SquareType square) noexcept
502 {
503 const auto group = rules::board::definition(square).group;
504 return group == SquareGroup::Utility || group == SquareGroup::Railroad;
505 }
506
507 double landingFrequency(SquareType square) noexcept
508 {
509 const auto index = indexOf(square);
510 return index < LandingFrequencies.size() ? LandingFrequencies[index] : 0.0;
511 }
512
514 const rules::GameState& state,
515 rules::PlayerNumber player,
516 std::int64_t startSquare,
518 double cashCowMultiplier,
520 {
521 (void)player;
522 if (startSquare < 0)
523 return 0.0;
524 double total{};
525 const auto end = static_cast<std::int64_t>(indexOf(SquareType::InJail));
526 for (auto index = startSquare; index < end; ++index)
527 {
528 const auto square = squareAt(static_cast<std::size_t>(index));
529 if ((propertiesOwned & rules::board::propertyBit(square)) == 0)
530 continue;
532 continue;
533 const double multiplier = isCashCow(square) ? cashCowMultiplier : 1.0;
534 total += static_cast<double>(rentIfSteppedOn(state, square, propertiesOwned)) *
536 }
537 return total / 7.0;
538 }
539
540 std::int64_t averageRentPaid(
541 const rules::GameState& state,
542 rules::PlayerNumber player,
543 std::int64_t startSquare,
545 double cashCowMultiplier) noexcept
546 {
547 double total{};
548 for (rules::PlayerNumber counter = 0; counter < state.numberOfPlayers; ++counter)
549 {
550 if (counter == player ||
551 state.players[counter].currentSquare == indexOf(SquareType::OffBoard))
552 continue;
554 total += averageRentReceived(state, counter, startSquare,
556 }
557
558 if (static_cast<std::int64_t>(indexOf(SquareType::IncomeTax)) >= startSquare)
559 {
560 const auto worth = totalWorth(state, player);
561 if (state.options.luxuryTaxAmount > 0)
562 {
563 const double quotient = static_cast<double>(worth) /
564 static_cast<double>(state.options.luxuryTaxAmount);
565 if (quotient > static_cast<double>(state.options.flatTaxFee))
566 total += static_cast<double>(state.options.flatTaxFee) / 7.0;
567 else
568 total += quotient / 7.0;
569 }
570 }
571 if (static_cast<std::int64_t>(indexOf(SquareType::LuxuryTax)) >= startSquare)
572 total += static_cast<double>(state.options.luxuryTaxAmount) / 7.0;
573 return static_cast<std::int64_t>(total);
574 }
575
577 const rules::GameState& state,
578 rules::PlayerNumber player) noexcept
579 {
580 auto current = static_cast<std::int64_t>(state.players[player].currentSquare);
581 if (current == static_cast<std::int64_t>(indexOf(SquareType::InJail)))
582 current = static_cast<std::int64_t>(indexOf(SquareType::JustVisiting));
583 if (current > static_cast<std::int64_t>(indexOf(SquareType::Boardwalk)))
584 return -1;
585 return averageRentPaid(state, player, current, false, 1.0);
586 }
587
588 std::int64_t potentialIncome(
589 const rules::GameState& state,
590 rules::PlayerNumber player) noexcept
591 {
592 std::int64_t income{};
593 const auto maxRentIndex = static_cast<std::size_t>(
594 std::clamp(state.options.housesPerHotel, 0,
595 static_cast<int>(rules::board::RentStepCount - 1)));
596 for (std::size_t index = 0;
597 index < static_cast<std::size_t>(SquareType::InJail); ++index)
598 {
599 if (state.squares[index].owner != player)
600 continue;
601 const auto square = squareAt(index);
602 const auto& definition = rules::board::definition(square);
603 if (isMonopoly(state, square))
604 {
605 if (state.squares[index].houses == 0)
606 income += definition.rent[0] * 2;
607 else
608 income += definition.rent[maxRentIndex];
609 }
610 else
611 income += definition.rent[0];
612 }
613 return income;
614 }
615
617 const rules::GameState& state) noexcept
618 {
619 std::int64_t highest{};
621 for (rules::PlayerNumber player = 0; player < state.numberOfPlayers; ++player)
622 {
623 const auto income = potentialIncome(state, player);
624 if (income > highest)
625 {
626 highest = income;
627 highestPlayer = player;
628 }
629 }
630 return highestPlayer;
631 }
632
634 const rules::GameState& state,
635 rules::PlayerNumber player) noexcept
636 {
637 HighestRentResult result{};
638 for (std::size_t index = 0;
639 index < static_cast<std::size_t>(SquareType::InJail); ++index)
640 {
641 const auto& squareState = state.squares[index];
642 if (squareState.mortgaged || squareState.owner == player ||
645 continue;
646
647 const auto ownerProperties =
649 const auto square = squareAt(index);
650 const auto currentRent =
652 if (currentRent > result.rent)
653 {
654 result.rent = currentRent;
655 result.square = square;
656 }
657 }
658 return result;
659 }
660
662 const rules::GameState& state) noexcept
663 {
664 double highestIncome{};
666 for (rules::PlayerNumber player = 0;
667 player < state.numberOfPlayers; ++player)
668 {
669 const auto owned = propertiesOwnedByPlayer(state, player);
670 const auto income = averageRentReceived(
671 state, player, 0, false, 1.0, owned);
672 if (income > highestIncome)
673 {
675 highestPlayer = player;
676 }
677 }
678 return highestPlayer;
679 }
681 rules::board::PropertySet properties) noexcept
682 {
683 int count{};
684 for (const auto square : ExpensiveMonopolySquares)
685 {
686 if (testForMonopoly(properties, square))
687 ++count;
688 }
689 return count;
690 }
691
693 const rules::GameState& state,
694 std::span<const rules::PlayerNumber> players,
696 {
698 for (const auto player : players)
702 }
703
705 std::span<rules::PlayerNumber> players,
706 std::size_t playerCount,
707 std::span<const rules::PlayerNumber> remove) noexcept
708 {
709 playerCount = std::min(playerCount, players.size());
710 for (auto removeIndex = remove.size(); removeIndex > 0; --removeIndex)
711 {
712 const auto target = remove[removeIndex - 1];
713 for (std::size_t current = 0; current < playerCount; ++current)
714 {
715 if (players[current] != target)
716 continue;
717 --playerCount;
718 for (std::size_t index = current; index < playerCount; ++index)
719 players[index] = players[index + 1];
720 }
721 }
722 return playerCount;
723 }
724
726 rules::board::PropertySet properties,
727 std::span<const rules::board::SquareType> squares) noexcept
728 {
729 for (const auto square : squares)
730 {
731 if (square != SquareType::Go)
732 properties ^= rules::board::propertyBit(square);
733 }
734 return properties;
735 }
736
738 const rules::GameState& state,
739 rules::PlayerNumber player,
740 bool mortgageCounts) noexcept
741 {
742 MonopolyCollection result{};
743 for (const auto square : ExpensiveMonopolySquares)
744 {
745 if (ownsMonopoly(state, player, square, mortgageCounts))
746 result.representatives[result.count++] = square;
747 }
748 return result;
749 }
750
752 {
753 int count{};
754 for (std::size_t index = 0;
755 index < static_cast<std::size_t>(SquareType::InJail); ++index)
756 {
757 if (state.squares[index].owner != rules::NobodyPlayer)
758 continue;
760 if (static_cast<std::size_t>(group) <=
761 static_cast<std::size_t>(SquareGroup::ParkPlace))
762 ++count;
763 }
764 return count;
765 }
766
781
784 const MonopolyLots& monopoly,
785 SquareType testSquare) noexcept
786 {
787 if (monopoly.count == 0)
788 return;
789
790 const int housesPerHotel = state.options.housesPerHotel;
791 int hotelsOnMonopoly{};
792 int housesOnLots{};
793 for (std::size_t index = 0; index < monopoly.count; ++index)
794 {
795 const auto houses = state.squares[indexOf(monopoly.squares[index])].houses;
796 if (houses == housesPerHotel)
798 else
799 housesOnLots += houses;
800 }
801
802 const int totalHouseUnits =
803 hotelsOnMonopoly * housesPerHotel + housesOnLots;
804 int housesNeedToSell = 1;
805 if (hotelsOnMonopoly > 0 &&
806 freeHouses(state) < housesPerHotel - 1)
807 {
808 housesNeedToSell = hotelsOnMonopoly * housesPerHotel -
810 }
811
812 const int remaining = totalHouseUnits - housesNeedToSell;
813 const int lotCount = static_cast<int>(monopoly.count);
814 const int minHouses = remaining / lotCount;
815 const int maxHouses = (remaining + lotCount - 1) / lotCount;
816 int maxHouseLots = remaining % lotCount;
817
818 for (std::size_t index = 0; index < monopoly.count; ++index)
819 {
820 const auto square = monopoly.squares[index];
821 if (square == testSquare)
822 {
823 state.squares[indexOf(square)].houses =
824 static_cast<std::uint8_t>(minHouses);
825 }
826 else if (maxHouseLots > 0)
827 {
828 --maxHouseLots;
829 state.squares[indexOf(square)].houses =
830 static_cast<std::uint8_t>(maxHouses);
831 }
832 else
833 {
834 state.squares[indexOf(square)].houses =
835 static_cast<std::uint8_t>(minHouses);
836 }
837 }
838 }
839
841 const rules::GameState& state,
842 rules::PlayerNumber player,
843 rules::board::PropertySet properties) noexcept
844 {
845 auto bestSquare = SquareType::Go;
846 double highestRemainingRent = -1.0;
847 const auto propertiesOwned = propertiesOwnedByPlayer(state, player);
848
849 for (std::size_t index = 0;
850 index < static_cast<std::size_t>(SquareType::InJail); ++index)
851 {
852 const auto square = squareAt(index);
853 if ((properties & rules::board::propertyBit(square)) == 0 ||
854 state.squares[index].mortgaged)
855 continue;
856
857 auto candidate = state;
858 if (housesOnMonopoly(candidate, square) > 0)
859 {
860 const auto lots = monopolyLots(square);
861 testSellHouses(candidate, lots, square);
862 }
863 else
864 {
865 candidate.squares[index].mortgaged = true;
866 }
867
869 candidate, player, 0, false, 1.0, propertiesOwned);
870 if (highestRemainingRent < 0.0 ||
872 {
874 bestSquare = square;
875 }
876 }
877 return bestSquare;
878 }
879
881 const rules::GameState& state,
882 rules::PlayerNumber player,
883 rules::board::PropertySet properties,
884 std::int64_t topUnmortgageCost) noexcept
885 {
886 auto bestSquare = SquareType::Go;
887 double highestRent = -1.0;
888 const auto propertiesOwned = propertiesOwnedByPlayer(state, player);
889
890 for (std::size_t index = 0;
891 index < static_cast<std::size_t>(SquareType::InJail); ++index)
892 {
893 const auto square = squareAt(index);
894 if ((properties & rules::board::propertyBit(square)) == 0 ||
895 !state.squares[index].mortgaged)
896 continue;
897
898 const double cost = static_cast<double>(
900 if (cost > static_cast<double>(topUnmortgageCost))
901 continue;
902
903 auto candidate = state;
904 candidate.squares[index].mortgaged = false;
905 const auto currentRent = averageRentReceived(
906 candidate, player, 0, false, 1.0, propertiesOwned);
908 {
910 bestSquare = square;
911 }
912 }
913 return bestSquare;
914 }
915
917 const rules::GameState& state,
918 rules::PlayerNumber player,
919 SquareType landSquare,
920 rules::CardType card) noexcept
921 {
922 const bool cardSquare =
923 landSquare == SquareType::CommunityChest1 ||
924 landSquare == SquareType::CommunityChest2 ||
925 landSquare == SquareType::CommunityChest3 ||
926 landSquare == SquareType::Chance1 ||
927 landSquare == SquareType::Chance2 ||
928 landSquare == SquareType::Chance3 ||
929 landSquare == SquareType::Go;
930
931 if (cardSquare)
932 {
933 switch (card)
934 {
936 return HappyReactionRent <= 150
939 return squareMoveReaction(
940 state, player, SquareType::Boardwalk);
942 return squareMoveReaction(
943 state, player, SquareType::StCharlesPlace);
946 {
947 const auto monopolies = monopoliesOwned(state, player, true);
948 std::int64_t totalHouses{};
949 for (std::size_t index = 0; index < monopolies.count; ++index)
950 {
952 state, monopolies.representatives[index]);
953 }
954 const auto cost = totalHouses *
956 ? 25 : 40);
957 if (cost >= AngryReactionRent)
959 if (cost >= SadReactionRent)
960 return TokenReaction::Sad;
962 }
964 return squareMoveReaction(
965 state, player, SquareType::IllinoisAvenue);
968 {
969 const auto exposure = averageRentPaid(
970 state, player, 0, true, 1.0);
973 }
975 {
976 const auto current = static_cast<std::size_t>(landSquare);
977 if (current < 3)
979 const auto destination = squareAt(current - 3);
980 if (destination == SquareType::CommunityChest3)
982 return squareMoveReaction(state, player, destination);
983 }
987 return HappyReactionRent <= 100
990 return HappyReactionRent <= 200
993 if (AngryReactionRent <= 150)
995 return SadReactionRent <= 150
998 return SadReactionRent <= 100
1000 default:
1002 }
1003 }
1004
1005 const auto squareIndex = indexOf(landSquare);
1006 if (squareIndex >= state.squares.size())
1008 const auto owner = state.squares[squareIndex].owner;
1009 if (owner == player)
1011
1013 if (owner != rules::NobodyPlayer && owner != rules::BankPlayer)
1015 const auto rent = rentIfSteppedOn(state, landSquare, ownerProperties);
1016 if (rent >= AngryReactionRent)
1017 return TokenReaction::Angry;
1018 if (rent >= SadReactionRent)
1019 return TokenReaction::Sad;
1021 }
1022}
std::size_t count
std::array< rules::PlayerNumber, rules::MaxPlayers > players
std::uint16_t group
unsigned state
unsigned index
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
std::int64_t potentialIncome(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
double landingFrequency(SquareType square) noexcept
bool playerOwnsMonopoly(const rules::GameState &state, rules::PlayerNumber player, bool countBaltic) noexcept
std::size_t removePlayersFromList(std::span< rules::PlayerNumber > players, std::size_t playerCount, std::span< const rules::PlayerNumber > remove) noexcept
rules::board::PropertySet monopolySet(SquareType square) noexcept
Definition AIUtility.cpp:63
bool isCashCow(SquareType square) noexcept
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
constexpr int SadReactionRent
Definition AIUtility.hpp:54
SquareType findLowestRentProperty(const rules::GameState &state, rules::PlayerNumber player, rules::board::PropertySet properties) noexcept
constexpr int CriticalUnownedSquares
Definition AIUtility.hpp:56
std::int64_t rentIfSteppedOn(const rules::GameState &state, SquareType square, rules::board::PropertySet propertiesOwned) 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
rules::board::PropertySet xorProperties(rules::board::PropertySet properties, std::span< const rules::board::SquareType > squares) 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 monopoliesBetweenPlayers(const rules::GameState &state, std::span< const rules::PlayerNumber > players, rules::board::SquareType extraProperty) noexcept
int monopoliesInSet(rules::board::PropertySet properties) 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 isMonopoly(const rules::GameState &state, SquareType square) 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
GroupRange groupRange(SquareGroup group) noexcept
Definition AIUtility.cpp:41
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
TokenReaction squareMoveReaction(const rules::GameState &state, rules::PlayerNumber player, SquareType landSquare, rules::CardType card) noexcept
bool ownsPropertyFromMonopoly(const rules::GameState &state, rules::PlayerNumber player, SquareType square) noexcept
constexpr int HappyReactionRent
Definition AIUtility.hpp:53
rules::PlayerNumber mostExpensivePotentialIncome(const rules::GameState &state) noexcept
constexpr int AngryReactionRent
Definition AIUtility.hpp:55
const SquareDefinition & definition(SquareType square)
constexpr std::size_t RentStepCount
constexpr std::size_t MaxPropertyGroups
std::uint32_t PropertySet
PropertySet propertyBit(SquareType square)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
std::array< rules::board::SquareType, 8 > representatives
Definition AIUtility.hpp:33
std::array< std::int64_t, RentStepCount > rent