Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
RuleEconomy.cpp
Go to the documentation of this file.
1#include "RuleEconomy.hpp"
2
3#include "BoardRules.hpp"
4#include "LegacyTextIds.hpp"
5#include "Messaging.hpp"
6#include "PhaseStack.hpp"
7#include "RuleRejections.hpp"
8#include "RuleLifecycle.hpp"
9#include "RuleResync.hpp"
10
11#include <algorithm>
12#include <array>
13#include <cstddef>
14#include <cstdint>
15#include <utility>
16
18{
19 namespace
20 {
21 constexpr std::uint8_t BoardwalkSquare = 39;
22 constexpr std::uint8_t InJailSquare = 40;
23 constexpr std::uint8_t OffBoardSquare = 41;
24
25
26 void sendRestart()
27 {
32 );
33 }
34
35
37 {
40 }
41
42
44 const actions::Message& message,
45 bool success,
46 std::int64_t result = 0)
47 {
52 static_cast<std::int64_t>(
53 message.action
54 ),
55 success ? 1 : 0,
56 message.fromPlayer,
57 result
58 );
59 }
60
61
64 PlayerNumber player,
65 std::int64_t delta)
66 {
67 if (player >= state.numberOfPlayers)
68 {
69 return;
70 }
71
76 player,
77 delta,
78 state.players[player].cash
79 );
80 }
81
82
84 const GameState& state,
85 PlayerNumber player)
86 {
87 // CashPlayerCouldRaiseIfNeeded() original.
88 //
89 // N'inclut pas :
90 // - les trades
91 // - les propriétés en escrow
92 //
93 // Inclut :
94 // - cash
95 // - hypothèque possible
96 // - vente des bâtiments.
97
98 if (player >= state.numberOfPlayers)
99 {
100 return 0;
101 }
102
103 if (state.players[player].currentSquare >=
104 OffBoardSquare)
105 {
106 return 0;
107 }
108
109 std::int64_t cash =
110 state.players[player].cash;
111
112 for (std::size_t squareNo = 0;
114 ++squareNo)
115 {
116 const SquareState& square =
118
119 if (square.owner != player)
120 {
121 continue;
122 }
123
126 static_cast<board::SquareType>(
128 )
129 );
130
131 if (!square.mortgaged)
132 {
133 cash += predefined.mortgageCost;
134 }
135
136 cash +=
137 static_cast<std::int64_t>(
138 square.houses
139 ) *
140 ((predefined.housePurchaseCost + 1) / 2);
141 }
142
143 return cash;
144 }
145
146
147 void transferProperty(
150 PlayerNumber toPlayer)
151 {
152 // TransferProperty() original.
153
154 const std::size_t squareNo =
155 static_cast<std::size_t>(
157 );
158
159 SquareState& square =
161
163 square.owner;
164
165
166 const bool targetIsBankOrInvalid =
167 toPlayer >= state.numberOfPlayers ||
168 (
169 toPlayer < MaxPlayers &&
170 state.players[toPlayer].currentSquare >=
171 OffBoardSquare
172 );
173
174
176 {
181 squareNo,
184 );
185
186 square.owner = BankPlayer;
187 square.offeredInTradeTo =
189
190
191 // La banque dés-hypothèque automatiquement.
192 if (square.mortgaged)
193 {
198 squareNo,
199 0,
200 1
201 );
202
203 square.mortgaged = false;
204 }
205
206 return;
207 }
208
209
210 // Transfert à un joueur :
211 // propriété placée temporairement dans l'escrow.
212
217 squareNo,
220 );
221
222 square.owner =
224
225 square.offeredInTradeTo =
226 toPlayer;
227 }
228
229
233 {
234 // SellAllBuildingsOnMonopoly() original.
235
236 const auto group =
239 ).group;
240
241 const std::size_t referenceNo =
242 static_cast<std::size_t>(
244 );
245
246 const PlayerNumber player =
249 ].owner;
250
251 std::int64_t cash = 0;
252
253
254 for (std::size_t squareNo = 0;
256 ++squareNo)
257 {
259 static_cast<board::SquareType>(
261 )
262 ).group != group)
263 {
264 continue;
265 }
266
267 SquareState& square =
269
270 const std::uint8_t houses =
271 square.houses;
272
273 square.houses = 0;
274
275 if (houses == 0)
276 {
277 continue;
278 }
279
280
285 squareNo,
286 0,
288 );
289
290
291 cash +=
292 (
293 static_cast<std::int64_t>(
294 houses
295 ) *
297 static_cast<board::SquareType>(
299 )
300 ).housePurchaseCost
301 + 1
302 ) / 2;
303 }
304
305
306 if (cash <= 0)
307 {
308 return;
309 }
310
311
313 state,
315 player,
316 cash,
317 true
318 );
319
320
326 player,
327 cash,
329 );
330 }
331
332
334 CardDeck& deck,
336 {
337 if (deck.cardCount >=
339 {
340 return;
341 }
342
343 deck.cardPile[
344 deck.cardCount++
345 ] =
346 static_cast<std::uint8_t>(
347 card
348 );
349 }
350
351
352 void transferGetOutOfJail(
354 PlayerNumber fromPlayer,
355 PlayerNumber toPlayer,
357 {
358 CardDeck& deck =
359 state.cards[
360 static_cast<std::size_t>(
362 )
363 ];
364
366 fromPlayer;
367
369 toPlayer;
370
371
373 {
376 }
377
379 {
382 }
383
384
385 if (deck.jailOwner !=
387 {
388 return;
389 }
390
391
392 if (deck.jailOwner < MaxPlayers &&
393 normalizedTo ==
395 {
396 const CardType jailCard =
400
402 deck,
404 );
405 }
406
407
408 deck.jailOwner =
410
411 deck.jailOfferedInTradeTo =
413
414
419 deck.jailOwner,
420 static_cast<std::int64_t>(
422 )
423 );
424 }
425
426
428 GamePhase phase)
429 {
430 switch (phase)
431 {
443 return true;
444
445 default:
446 return false;
447 }
448 }
449
450
452 GamePhase phase)
453 {
454 switch (phase)
455 {
468 return true;
469
470 default:
471 return false;
472 }
473 }
474
475
478 {
479 const PendingPhase debt =
481
482
483 if (debt.toPlayer < MaxPlayers &&
484 (
485 debt.toPlayer >=
488 debt.toPlayer
489 ].currentSquare >=
490 OffBoardSquare
491 ))
492 {
498 debt.fromPlayer,
499 debt.toPlayer,
500 debt.amount
501 );
502
504 return;
505 }
506
507
508 if (debt.fromPlayer < MaxPlayers &&
509 (
510 debt.fromPlayer >=
513 debt.fromPlayer
514 ].currentSquare >=
515 OffBoardSquare
516 ))
517 {
523 debt.fromPlayer,
524 debt.toPlayer,
525 debt.amount
526 );
527
529 return;
530 }
531
532
533 const bool bankPays =
534 debt.fromPlayer >=
536
537 const bool playerCanPay =
538 !bankPays &&
540 debt.fromPlayer
541 ].cash >=
542 debt.amount;
543
544
545 if (bankPays || playerCanPay)
546 {
548 state,
549 debt.fromPlayer,
550 debt.toPlayer,
551 debt.amount,
552 true
553 );
554
555
559 debt.fromPlayer,
560 static_cast<std::int64_t>(
562 ),
563 1,
564 debt.fromPlayer
565 );
566
567
569 return;
570 }
571
572
573 // Premier passage dans une dette impossible
574 // à payer immédiatement :
575 //
576 // SaveGameStateInCurrentPhase() original.
577
579 {
581 }
582
583
585
588
589 paymentRequest.fromPlayer =
591
592 paymentRequest.toPlayer =
594
595 paymentRequest.numberA =
596 debt.fromPlayer;
597
598 paymentRequest.numberB =
599 debt.toPlayer;
600
601 paymentRequest.numberC =
602 debt.amount;
603
604 paymentRequest.numberD =
605 debt.amount -
607 debt.fromPlayer
608 ].cash;
609
610 paymentRequest.numberE =
612 state,
613 debt.fromPlayer
614 ) < debt.amount
615 ? 1
616 : 0;
617
620 );
621 }
622
623
626 {
627 const PendingPhase phase =
629
630 const PlayerNumber target =
631 phase.toPlayer;
632
633 const std::uint32_t properties =
634 static_cast<std::uint32_t>(
635 phase.amount
636 );
637
638
639 const bool bankrupt =
640 target >=
643 target
644 ].currentSquare >=
645 OffBoardSquare;
646
647
648 for (std::size_t squareNo = 0;
650 ++squareNo)
651 {
653 static_cast<board::SquareType>(
655 );
656
657 if (
658 (
661 ) &
662 properties
663 ) == 0)
664 {
665 continue;
666 }
667
668
669 SquareState& square =
671
672
673 if (square.owner !=
674 EscrowPlayer ||
675 square.offeredInTradeTo !=
676 target)
677 {
678 continue;
679 }
680
681
682 square.owner =
684 ? BankPlayer
685 : target;
686
687 square.offeredInTradeTo =
689
690
695 squareNo,
696 square.owner,
698 );
699 }
700
701
702 // Original :
703 // GF_TRANSFER_ESCROW_PROPERTY
704 // ↓
705 // GF_FREE_UNMORTGAGE
706
708 state,
710 0,
711 target,
712 properties
713 );
714
715 sendRestart();
716 }
717
718
721 {
722 PendingPhase& phase =
723 state.phaseStack[0];
724
725 const PlayerNumber target =
726 phase.toPlayer;
727
728
729 if (target >=
732 target
733 ].currentSquare >=
734 OffBoardSquare)
735 {
737 return;
738 }
739
740
741 std::uint32_t stillMortgaged = 0;
742
743
744 for (std::size_t squareNo = 0;
746 ++squareNo)
747 {
749 static_cast<board::SquareType>(
751 );
752
753 const std::uint32_t bit =
756 );
757
758
759 if (
760 (
761 bit &
762 static_cast<std::uint32_t>(
763 phase.amount
764 )
765 ) == 0)
766 {
767 continue;
768 }
769
770
771 const SquareState& square =
773
774
775 if (square.owner == target &&
776 square.mortgaged)
777 {
779 }
780 }
781
782
783 phase.amount =
784 static_cast<std::int64_t>(
786 );
787
788
789 if (stillMortgaged != 0)
790 {
795 target,
797 );
798
799 return;
800 }
801
802
804 }
805 }
806
807
810 std::uint8_t squareNo)
811 {
812 if (squareNo >= SquareCount)
813 {
814 return;
815 }
816
818 state,
819 static_cast<board::SquareType>(
821 )
822 );
823 }
824
825
828 std::uint8_t squareNo,
829 PlayerNumber toPlayer)
830 {
832 state,
833 static_cast<board::SquareType>(
835 ),
836 toPlayer
837 );
838 }
839
840
842 {
843 // Undo state is owned by GameState and reset with that state.
844 }
845
846
849 PlayerNumber fromPlayer,
850 PlayerNumber toPlayer,
851 std::int64_t amount,
852 bool sendAnimation)
853 {
854 // BlindlyTransferCash() original.
855
856 if (amount <= 0)
857 {
858 return;
859 }
860
861
862 if (fromPlayer <
864 {
866 fromPlayer
867 ].cash -= amount;
868 }
869
870
871 if (toPlayer <
873 {
875 toPlayer
876 ].cash += amount;
877 }
878
879
880 if (sendAnimation)
881 {
886 fromPlayer,
887 toPlayer,
888 amount
889 );
890 }
891
892
893 // Comme l'original :
894 // notifications APRES l'animation.
895
896 if (fromPlayer <
898 {
900 state,
901 fromPlayer,
902 amount
903 );
904 }
905
906
907 if (toPlayer <
909 {
911 state,
912 toPlayer,
913 amount
914 );
915 }
916 }
917
918
921 std::int64_t amount)
922 {
923 // AddMoneyToFreeParkingPot().
924
926 amount;
927
928
930 {
936 amount
937 );
938 }
939 }
940
941
944 PlayerNumber fromPlayer,
945 PlayerNumber toPlayer,
946 std::int64_t amount)
947 {
948 // StackDebt().
949
951 state,
953 fromPlayer,
954 toPlayer,
955 amount
956 );
957 }
958
959
962 PlayerNumber fromPlayer,
963 PlayerNumber toPlayer,
964 std::int64_t amount)
965 {
966 stackDebt(
967 state,
968 fromPlayer,
969 toPlayer,
970 amount
971 );
972
973 sendRestart();
974 }
975
976
978 {
979 // CollectRent() original, maintenant avec
980 // futures et immunités actifs.
981
984
985
986 if (landingPlayer >=
988 {
989 return;
990 }
991
992
993 const std::uint8_t squareNo =
996 ].currentSquare;
997
998
999 if (squareNo >= SquareCount)
1000 {
1001 return;
1002 }
1003
1004
1005 SquareState& square =
1007
1008
1010 static_cast<board::SquareType>(
1011 squareNo
1012 );
1013
1014
1018 );
1019
1020
1021 const PlayerNumber owner =
1022 square.owner;
1023
1024
1025 const std::uint32_t squareBit =
1028 );
1029
1030
1031 std::int64_t rent = 0;
1032
1033 bool needUtilityRoll = false;
1034
1035
1036 // ----------------------------------------------------
1037 // Calcul normal du loyer.
1038 // ----------------------------------------------------
1039
1040 if (!square.mortgaged)
1041 {
1042 int squaresOwnedInGroup = 0;
1043 int totalSquaresInGroup = 0;
1044
1045
1046 for (std::size_t testNo = 0;
1048 ++testNo)
1049 {
1050 if (
1052 static_cast<board::SquareType>(
1053 testNo
1054 )
1055 ).group !=
1057 {
1058 continue;
1059 }
1060
1061
1063
1064
1065 const SquareState& testSquare =
1067
1068
1069 if (
1070 testSquare.owner == owner &&
1071 (
1072 !testSquare.mortgaged ||
1075 ))
1076 {
1078 }
1079 }
1080
1081
1082 // Terrains.
1083
1084 if (
1085 static_cast<std::uint8_t>(
1086 predefined.group
1087 ) <=
1088 static_cast<std::uint8_t>(
1090 ))
1091 {
1092 const std::size_t houseIndex =
1093 std::min<std::size_t>(
1094 square.houses,
1096 );
1097
1098
1099 rent =
1100 predefined.rent[
1102 ];
1103
1104
1105 if (
1106 square.houses == 0 &&
1109 {
1110 rent += rent;
1111 }
1112 }
1113
1114
1115 // Gares.
1116
1117 if (
1118 predefined.group ==
1120 {
1121 const std::size_t index =
1122 std::min<std::size_t>(
1125 );
1126
1127
1128 rent =
1129 predefined.rent[index];
1130
1131
1132 if (
1134 CardType::
1139 {
1140 rent += rent;
1141
1144 }
1145 }
1146
1147
1148 // Utilities.
1149
1150 if (
1151 predefined.group ==
1153 {
1154 if (
1156 CardType::
1158 {
1159 if (
1160 state.utilityDice[0] == 0)
1161 {
1162 needUtilityRoll = true;
1163
1164 // Sentinel exact du source.
1165 rent = 12345;
1166 }
1167 else
1168 {
1171
1172
1173 rent =
1174 state.utilityDice[0] +
1175 state.utilityDice[1];
1176
1177
1178 rent *= 10;
1179
1180
1182 { 0, 0 };
1183 }
1184 }
1185 else
1186 {
1187 rent =
1188 state.dice[0] +
1189 state.dice[1];
1190
1191
1192 rent *=
1194 ? 4
1195 : 10;
1196 }
1197 }
1198 }
1199
1200
1201 // ----------------------------------------------------
1202 // Chercher un FUTURE RENT.
1203 //
1204 // Un hit compte même si la propriété est hypothéquée.
1205 // ----------------------------------------------------
1206
1207 CountHitRecord* future = nullptr;
1208
1209
1210 for (CountHitRecord& hit :
1212 {
1213 if (
1214 hit.toPlayer != NobodyPlayer &&
1215 hit.hitType ==
1217 (
1218 hit.properties &
1219 squareBit
1220 ) != 0)
1221 {
1222 future = &hit;
1223 break;
1224 }
1225 }
1226
1227
1229 future != nullptr
1230 ? future->toPlayer
1231 : owner;
1232
1233
1234 // ----------------------------------------------------
1235 // Immunité du joueur qui vient d'atterrir.
1236 // ----------------------------------------------------
1237
1238 CountHitRecord* immunity = nullptr;
1239
1240
1241 for (CountHitRecord& hit :
1243 {
1244 if (
1245 hit.toPlayer ==
1246 landingPlayer &&
1247 hit.hitType ==
1249 (
1250 hit.properties &
1251 squareBit
1252 ) != 0)
1253 {
1254 immunity = &hit;
1255 break;
1256 }
1257 }
1258
1259
1260 // Pas de loyer à soi-même.
1261 //
1262 // L'original n'utilise PAS non plus une immunité
1263 // lorsqu'elle serait inutile.
1264
1265 if (
1266 landingPlayer ==
1268 {
1269 rent = 0;
1270 immunity = nullptr;
1271 }
1272
1273
1274 if (immunity != nullptr)
1275 {
1276 rent = 0;
1277 }
1278
1279
1280 // ----------------------------------------------------
1281 // Utility Chance :
1282 //
1283 // si une immunité annule le loyer, inutile de lancer.
1284 // ----------------------------------------------------
1285
1286 if (needUtilityRoll)
1287 {
1288 if (rent > 0)
1289 {
1291 state,
1293 0,
1294 0,
1295 0
1296 );
1297
1298
1301 BankPlayer,
1303 );
1304
1305
1306 return;
1307 }
1308
1309
1312 }
1313
1314
1315 // ----------------------------------------------------
1316 // Dette de loyer.
1317 // ----------------------------------------------------
1318
1319 if (rent > 0)
1320 {
1323 BankPlayer,
1324 AllPlayers,
1326 rent,
1328 squareNo
1329 );
1330
1331
1332 stackDebt(
1333 state,
1336 rent
1337 );
1338
1339
1340 square.gameEarnings +=
1341 rent;
1342 }
1343
1344
1345 // ----------------------------------------------------
1346 // UseUpOneHit().
1347 // ----------------------------------------------------
1348
1349 auto useHit =
1350 [&state, squareNo](
1352 {
1353 if (hit == nullptr)
1354 {
1355 return;
1356 }
1357
1358
1359 int count =
1360 hit->hitCount - 1;
1361
1362
1363 if (count < 0)
1364 {
1365 count = 0;
1366 }
1367
1368
1369 hit->hitCount =
1370 count;
1371
1372
1374
1376 hit->hitType ==
1382
1383 notification.fromPlayer =
1384 BankPlayer;
1385
1386 notification.toPlayer =
1387 AllPlayers;
1388
1389 notification.numberA =
1390 hit->toPlayer;
1391
1392 notification.numberB =
1393 count;
1394
1395 notification.numberC =
1396 squareNo;
1397
1398 notification.numberD =
1399 hit->fromPlayer;
1400
1401 notification.numberE =
1402 hit->properties;
1403
1404
1407 );
1408
1409
1410 if (count <= 0)
1411 {
1412 hit->toPlayer =
1414 }
1415 };
1416
1417
1418 // ----------------------------------------------------
1419 // FUTURE :
1420 // consommé à CHAQUE arrivée sur la case.
1421 // ----------------------------------------------------
1422
1423 if (future != nullptr)
1424 {
1427
1429 owner;
1430
1431
1432 useHit(future);
1433
1434
1435 actions::Message info{};
1436
1437 info.action =
1439
1440 info.fromPlayer =
1441 BankPlayer;
1442
1443 info.toPlayer =
1444 AllPlayers;
1445
1446 info.numberA =
1448
1449 info.numberB =
1451
1452 info.numberC =
1454
1455 info.numberD =
1456 squareNo;
1457
1458 info.numberE =
1459 future->hitCount;
1460
1461
1463 }
1464
1465
1466 // ----------------------------------------------------
1467 // IMMUNITÉ.
1468 // ----------------------------------------------------
1469
1470 if (immunity != nullptr)
1471 {
1474
1475
1477
1478
1479 actions::Message info{};
1480
1481 info.action =
1483
1484 info.fromPlayer =
1485 BankPlayer;
1486
1487 info.toPlayer =
1488 AllPlayers;
1489
1490 info.numberA =
1492
1493 info.numberB =
1495
1496 info.numberC =
1498
1499 info.numberD =
1500 squareNo;
1501
1502 info.numberE =
1503 immunity->hitCount;
1504
1505
1507 }
1508 }
1509
1512 const actions::Message& message)
1513 {
1514 // ActionBuyOrAuctionDecision() original.
1515
1516 if (
1517 phases::current(state).phase !=
1519 {
1520 rejections::wrongPhase(state, message);
1521
1522 return;
1523 }
1524
1525
1526 if (
1527 message.fromPlayer !=
1529 {
1531
1532 return;
1533 }
1534
1535
1537 message,
1538 true,
1539 message.numberA
1540 );
1541
1542
1543 PlayerState& player =
1544 state.players[
1546 ];
1547
1548
1549 const auto squareType =
1550 static_cast<board::SquareType>(
1551 player.currentSquare
1552 );
1553
1554
1555 SquareState& square =
1556 state.squares[
1557 player.currentSquare
1558 ];
1559
1560
1561 if (message.numberA != 0)
1562 {
1563 // Achat :
1564 //
1565 // propriété → ESCROW
1566 // ↓
1567 // GF_TRANSFER_ESCROW_PROPERTY
1568 // ↓
1569 // GF_COLLECTING_PAYMENT
1570 //
1571 // Le joueur ne peut donc pas hypothéquer
1572 // immédiatement la propriété qu'il achète
1573 // pour payer son propre achat.
1574
1576 state,
1577 squareType,
1579 );
1580
1581
1583 state,
1585 0,
1589 )
1590 );
1591
1592
1594 state,
1596 BankPlayer,
1599 ).purchaseCost
1600 );
1601
1602 return;
1603 }
1604
1605
1606 // Refus :
1607 // la banque la marque pour enchère ultérieure.
1608
1609 square.owner =
1610 BankPlayer;
1611
1613 }
1614
1615
1618 const actions::Message& message)
1619 {
1620 // ActionTaxDecision() original.
1621
1622 const PlayerNumber player =
1623 message.fromPlayer;
1624
1625
1626 if (player !=
1628 {
1630
1631 return;
1632 }
1633
1634
1635 if (
1636 phases::current(state).phase !=
1638 {
1639 rejections::wrongPhase(state, message);
1640
1641 return;
1642 }
1643
1644
1645 std::int64_t taxFee = 0;
1646
1647
1648 if (message.numberA != 0)
1649 {
1650 // Impôt proportionnel :
1651 //
1652 // valeur d'achat pleine des propriétés
1653 // même hypothéquées
1654 // + prix d'achat des bâtiments
1655 // + cash.
1656
1657 for (std::size_t squareNo = 0;
1659 ++squareNo)
1660 {
1661 if (
1662 state.squares[
1663 squareNo
1664 ].owner != player)
1665 {
1666 continue;
1667 }
1668
1669
1672 static_cast<board::SquareType>(
1673 squareNo
1674 )
1675 );
1676
1677
1678 taxFee +=
1679 predefined.purchaseCost;
1680
1681
1682 taxFee +=
1683 predefined.housePurchaseCost *
1684 state.squares[
1685 squareNo
1686 ].houses;
1687 }
1688
1689
1690 taxFee +=
1691 state.players[
1692 player
1693 ].cash;
1694
1695
1696 taxFee =
1697 (
1698 taxFee *
1700 50
1701 ) / 100;
1702 }
1703 else
1704 {
1705 taxFee =
1707 }
1708
1709
1711 message,
1712 true,
1713 message.numberA
1714 );
1715
1716
1718
1719
1721 state,
1722 player,
1723 BankPlayer,
1724 taxFee
1725 );
1726
1727
1729 state,
1730 taxFee
1731 );
1732
1733
1736 BankPlayer,
1737 AllPlayers,
1739 taxFee,
1740 player
1741 );
1742 }
1743
1744
1747 const actions::Message& message)
1748 {
1749 // ActionFreeUnmortgageDone() original.
1750
1752 {
1754 message,
1755 false
1756 );
1757
1758 return;
1759 }
1760
1761
1762 if (
1763 message.fromPlayer !=
1765 {
1767
1768 return;
1769 }
1770
1771
1772 if (
1773 phases::current(state).phase !=
1775 {
1776 rejections::wrongPhase(state, message);
1777
1778 return;
1779 }
1780
1781
1783 message,
1784 true
1785 );
1786
1787
1789
1790
1793 BankPlayer,
1795 );
1796 }
1797
1798
1801 const actions::Message& message)
1802 {
1803 // ActionMortgaging() original.
1804
1805 const PlayerNumber player =
1806 message.fromPlayer;
1807
1808
1809 if (
1810 message.numberA < 0 ||
1811 message.numberA >
1813 {
1815 message,
1816 false
1817 );
1818
1819 return;
1820 }
1821
1822
1823 const std::size_t squareNo =
1824 static_cast<std::size_t>(
1825 message.numberA
1826 );
1827
1828
1829 const GamePhase phase =
1831
1832
1833 const bool quick =
1834 message.numberD != 0;
1835
1836
1837 if (quick)
1838 {
1840 phase))
1841 {
1842 rejections::wrongPhase(state, message);
1843
1844 return;
1845 }
1846 }
1847 else
1848 {
1849 if (
1850 phase !=
1852 phase !=
1854 {
1855 rejections::wrongPhase(state, message);
1856
1857 return;
1858 }
1859
1860
1861 if (
1862 phase ==
1864 player !=
1866 state
1867 ).fromPlayer)
1868 {
1870
1871 return;
1872 }
1873
1874
1875 if (
1876 phase ==
1878 player !=
1880 state
1881 ).toPlayer)
1882 {
1884
1885 return;
1886 }
1887 }
1888
1889
1890 SquareState& square =
1891 state.squares[
1892 squareNo
1893 ];
1894
1895
1897 static_cast<board::SquareType>(
1898 squareNo
1899 );
1900
1901
1902 if (square.owner != player)
1903 {
1905 message,
1906 false
1907 );
1908
1909
1912 BankPlayer,
1913 AllPlayers,
1915 0,
1916 player,
1917 squareNo
1918 );
1919
1920 return;
1921 }
1922
1923
1924 const auto group =
1927 ).group;
1928
1929
1930 // Aucun bâtiment sur le groupe.
1931
1932 for (std::size_t testNo = 0;
1934 ++testNo)
1935 {
1936 if (
1938 static_cast<board::SquareType>(
1939 testNo
1940 )
1941 ).group != group)
1942 {
1943 continue;
1944 }
1945
1946
1947 if (
1948 state.squares[
1949 testNo
1950 ].houses > 0)
1951 {
1953 message,
1954 false
1955 );
1956
1957
1960 BankPlayer,
1961 AllPlayers,
1963 0,
1964 player,
1965 squareNo
1966 );
1967
1968 return;
1969 }
1970 }
1971
1972
1973 const bool wasMortgaged =
1974 square.mortgaged;
1975
1976
1977 std::int64_t fees =
1980 ).mortgageCost;
1981
1982
1983 if (wasMortgaged)
1984 {
1985 // Dés-hypothèque.
1986
1987 const bool noInterestFee =
1988 phase ==
1990 (
1993 ) &
1994 static_cast<std::uint32_t>(
1996 state
1997 ).amount
1998 )
1999 ) != 0;
2000
2001
2002 if (!noInterestFee)
2003 {
2004 fees +=
2005 (
2006 fees *
2008 50
2009 ) / 100;
2010 }
2011
2012
2013 if (
2014 state.players[
2015 player
2016 ].cash < fees)
2017 {
2019 message,
2020 false
2021 );
2022
2023
2026 BankPlayer,
2027 AllPlayers,
2029 fees,
2030 player,
2031 squareNo
2032 );
2033
2034 return;
2035 }
2036
2037
2039 state,
2040 player,
2041 BankPlayer,
2042 fees,
2043 true
2044 );
2045
2046
2047 if (noInterestFee)
2048 {
2049 state.phaseStack[0].amount &=
2053 )
2054 );
2055
2056 sendRestart();
2057 }
2058 }
2059 else
2060 {
2061 // Hypothèque :
2062 // la banque verse la valeur d'hypothèque.
2063
2065 state,
2066 BankPlayer,
2067 player,
2068 fees,
2069 true
2070 );
2071 }
2072
2073
2074 const bool newMortgaged =
2075 !wasMortgaged;
2076
2077
2079 message,
2080 true,
2081 newMortgaged ? 1 : 0
2082 );
2083
2084
2087 BankPlayer,
2088 AllPlayers,
2089 squareNo,
2090 newMortgaged ? 1 : 0,
2091 wasMortgaged ? 1 : 0
2092 );
2093
2094
2097 BankPlayer,
2098 AllPlayers,
2102 fees,
2103 player,
2104 squareNo
2105 );
2106
2107
2108 square.mortgaged =
2110
2111
2112 // Quicky mortgage pendant une dette :
2113 // refaire immédiatement le test de solvabilité.
2114
2115 if (
2116 quick &&
2117 !wasMortgaged &&
2118 phase ==
2120 {
2121 sendRestart();
2122 }
2123 }
2124
2125
2128 const actions::Message& message)
2129 {
2130 // Le chemin "sell everything" de
2131 // ActionSellBuildings() est porté intégralement ici.
2132 //
2133 // C'est aussi le chemin nécessaire pour liquider
2134 // rapidement un groupe pendant GF_COLLECTING_PAYMENT.
2135
2136 const PlayerNumber player =
2137 message.fromPlayer;
2138
2139
2140 if (
2141 message.numberA < 0 ||
2142 message.numberA >
2144 {
2146 message,
2147 false
2148 );
2149
2150 return;
2151 }
2152
2153
2154 const std::size_t squareNo =
2155 static_cast<std::size_t>(
2156 message.numberA
2157 );
2158
2159
2160 const GamePhase phase =
2162
2163
2164 const bool quick =
2165 message.numberD != 0;
2166
2167
2168 if (
2169 quick &&
2170 !quickSellPhaseAllowed(phase))
2171 {
2173 message,
2174 false
2175 );
2176
2177 return;
2178 }
2179
2180
2181 if (
2182 !quick &&
2183 phase !=
2185 phase !=
2187 {
2189 message,
2190 false
2191 );
2192
2193 return;
2194 }
2195
2196
2197 if (
2198 message.numberB == 0)
2199 {
2200 // La vente unitaire dépend de
2201 // RULE_TestBuildingPlacement() +
2202 // GF_DECOMPOSE_HOTEL.
2203 //
2204 // Nous ne contournons pas cette validation.
2205 // Le chemin complet viendra avec le moteur
2206 // de construction/housing shortage.
2207
2209 message,
2210 false
2211 );
2212
2213 return;
2214 }
2215
2216
2217 if (
2218 state.squares[
2219 squareNo
2220 ].owner != player)
2221 {
2223 message,
2224 false
2225 );
2226
2227
2230 BankPlayer,
2231 AllPlayers,
2233 0,
2234 player,
2235 squareNo
2236 );
2237
2238 return;
2239 }
2240
2241
2243 message,
2244 true
2245 );
2246
2247
2249 state,
2250 static_cast<board::SquareType>(
2251 squareNo
2252 )
2253 );
2254
2255
2256 if (
2257 quick &&
2258 phase ==
2260 {
2261 sendRestart();
2262 }
2263 }
2264
2265
2268 const actions::Message& message)
2269 {
2270 // Restore pre-debt state before transferring bankruptcy assets.
2271
2272 const PlayerNumber player =
2273 message.fromPlayer;
2274
2275
2276 if (
2277 phases::current(state).phase !=
2279 {
2280 rejections::wrongPhase(state, message);
2281
2282 return;
2283 }
2284
2285
2286 if (
2287 player !=
2289 state
2290 ).fromPlayer)
2291 {
2293
2294 return;
2295 }
2296
2297
2298 // Defensive legacy fallback when bankruptcy arrives before the prompt.
2300
2301
2302 if (
2304 state,
2305 player
2306 ) >=
2308 state
2309 ).amount)
2310 {
2312 message,
2313 false
2314 );
2315
2316
2319 BankPlayer,
2320 AllPlayers,
2323 state
2324 ).toPlayer,
2325 player,
2327 state
2328 ).amount
2329 );
2330
2331
2332 sendRestart();
2333 return;
2334 }
2335
2336
2338 message,
2339 true
2340 );
2341
2342
2345 {
2347 }
2348
2349
2350 const PendingPhase debt =
2352
2353
2354 const PlayerNumber creditor =
2355 debt.toPlayer;
2356
2357
2358 std::int64_t mortgageAmount = 0;
2359
2360 std::uint32_t oldProperties = 0;
2361
2362
2363 for (std::size_t squareNo = 0;
2365 ++squareNo)
2366 {
2367 SquareState& square =
2369
2370
2371 if (
2372 square.owner != player &&
2373 !(
2374 square.owner ==
2375 EscrowPlayer &&
2376 square.offeredInTradeTo ==
2377 player
2378 ))
2379 {
2380 continue;
2381 }
2382
2383
2385 static_cast<board::SquareType>(
2386 squareNo
2387 );
2388
2389
2390 oldProperties |=
2393 );
2394
2395
2397 state,
2399 );
2400
2401
2402 if (square.mortgaged)
2403 {
2407 ).mortgageCost;
2408 }
2409
2410
2412 state,
2413 squareType,
2414 creditor
2415 );
2416 }
2417
2418
2419 // Tout le cash restant va au créancier.
2420
2422 state,
2423 player,
2424 creditor,
2425 state.players[
2426 player
2427 ].cash,
2428 true
2429 );
2430
2431
2432 // Cartes sortie de prison.
2433
2434 transferGetOutOfJail(
2435 state,
2436 player,
2437 creditor,
2439 );
2440
2441
2442 transferGetOutOfJail(
2443 state,
2444 player,
2445 creditor,
2447 );
2448
2449
2450 // Cancel active rent contracts owned by bankrupt player.
2451 //
2452 // ActionGoBankrupt() original :
2453 // seul toPlayer est testé ; le test fromPlayer avait été
2454 // volontairement commenté dans le source final.
2455
2456 for (CountHitRecord& hit :
2458 {
2459 if (
2460 !hit.tradedItem &&
2461 hit.toPlayer != NobodyPlayer &&
2462 hit.toPlayer == player)
2463 {
2465
2467 hit.hitType ==
2473
2474 contractUpdate.fromPlayer =
2475 BankPlayer;
2476
2477 contractUpdate.toPlayer =
2478 AllPlayers;
2479
2480 contractUpdate.numberA =
2481 hit.toPlayer;
2482
2483 contractUpdate.numberB = 0;
2484
2485 contractUpdate.numberC = 41;
2486
2487 contractUpdate.numberD =
2488 hit.fromPlayer;
2489
2490 contractUpdate.numberE =
2491 hit.properties;
2492
2493
2496 );
2497
2498
2499 hit.toPlayer =
2501 }
2502 }
2503
2504
2507 BankPlayer,
2508 AllPlayers,
2511 creditor,
2512 player,
2513 debt.amount
2514 );
2515
2516
2517 // Retirer le joueur du plateau.
2518
2521 BankPlayer,
2522 AllPlayers,
2523 OffBoardSquare,
2524 state.players[
2525 player
2526 ].currentSquare,
2527 player
2528 );
2529
2530
2531 state.players[
2532 player
2533 ].currentSquare =
2534 OffBoardSquare;
2535
2536
2537 // Si le créancier est un joueur, les propriétés
2538 // héritées passent par ESCROW et le créancier doit
2539 // payer les intérêts de transfert des hypothèques.
2540
2541 if (creditor < MaxPlayers)
2542 {
2544 state,
2546 0,
2547 creditor,
2549 );
2550
2551
2553 state,
2554 creditor,
2555 BankPlayer,
2556 (
2559 50
2560 ) / 100
2561 );
2562
2563
2564 // ActionGoBankrupt() vérifie immédiatement
2565 // s'il ne reste plus qu'un survivant, même si
2566 // les frais hypothécaires viennent d'être empilés.
2567 if (
2569 state
2570 ))
2571 {
2573 state
2574 );
2575 }
2576
2577 return;
2578 }
2579
2580
2581 // Faillite envers la banque :
2582 // aucune commission de transfert.
2583
2585
2586
2587 // ActionGoBankrupt() original :
2588 // victoire immédiate lorsqu'il ne reste qu'un joueur.
2589
2590 if (
2592 state
2593 ))
2594 {
2596 state
2597 );
2598 }
2599 }
2600
2601
2604 {
2605 // ActionLandedOnSquare(SQ_INCOME_TAX).
2606
2607 if (state.options.taxRate == 0)
2608 {
2610
2611
2613 state,
2615 BankPlayer,
2617 );
2618
2619
2621 state,
2623 );
2624
2625
2628 BankPlayer,
2629 AllPlayers,
2633 );
2634
2635 return;
2636 }
2637
2638
2640 state,
2642 0,
2643 0,
2644 0
2645 );
2646
2647
2648 sendRestart();
2649 }
2650
2651
2654 {
2656
2657
2659 state,
2661 BankPlayer,
2663 );
2664
2665
2667 state,
2669 );
2670 }
2671
2672
2675 {
2677 {
2678 const std::int64_t jackpot =
2680
2681
2683 state,
2684 BankPlayer,
2686 jackpot,
2687 true
2688 );
2689
2690
2693 BankPlayer,
2694 AllPlayers,
2696 jackpot,
2698 );
2699
2700
2702
2703
2704 // Commentaire original :
2705 // "Someone said that the parking pot gets
2706 // reseeded every time."
2707
2709 state,
2711 );
2712 }
2713
2714
2716 }
2717
2718
2721 const actions::Message& message)
2722 {
2723 switch (
2725 state
2726 ).phase)
2727 {
2730 state
2731 );
2732 return true;
2733
2734
2736 if (
2737 message.fromPlayer !=
2738 BankPlayer)
2739 {
2740 return true;
2741 }
2742
2744 state
2745 );
2746 return true;
2747
2748
2751 state
2752 );
2753 return true;
2754
2755
2756 default:
2757 return false;
2758 }
2759 }
2760}
2761
2762
2763
2764
2765
2766
2767
std::size_t count
std::uint16_t group
unsigned index
data::DataTag immunity
data::DataTag future
constexpr std::int64_t ErrorMortgagingOnUnowned
constexpr std::int64_t ErrorDebtorBankrupt
constexpr std::int64_t ErrorUnmortgageInfo
constexpr std::int64_t ErrorBuildingAllSold
constexpr std::int64_t ErrorMortgageNoCash
constexpr std::int64_t ErrorRentInfo
constexpr std::int64_t ErrorBankruptTo
constexpr std::int64_t ErrorFutureRentUsed
constexpr std::int64_t ErrorCollectorBankrupt
constexpr std::int64_t FreeParkingPotCollected
constexpr std::int64_t ErrorUndoForBankruptcyTo
constexpr std::int64_t ErrorMortgagingHouses
constexpr std::int64_t ErrorBuildingOnUnowned
constexpr std::int64_t ErrorImmunityUsed
constexpr std::int64_t ErrorMortgageInfo
constexpr std::int64_t ErrorCannotGoBankrupt
constexpr std::int64_t ErrorTaxesCharged
bool sendAction(const actions::Message &message)
const SquareDefinition & definition(SquareType square)
constexpr std::size_t RentStepCount
PropertySet propertyBit(SquareType square)
void landOnLuxuryTax(GameState &state)
void blindlyTransferCash(GameState &state, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount, bool sendAnimation)
void actionFreeUnmortgageDone(GameState &state, const actions::Message &message)
void landOnFreeParking(GameState &state)
void stackDebt(GameState &state, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
bool restartEconomyPhase(GameState &state, const actions::Message &message)
void actionMortgaging(GameState &state, const actions::Message &message)
void actionTaxDecision(GameState &state, const actions::Message &message)
void stackDebtAndRestart(GameState &state, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
void actionSellBuildings(GameState &state, const actions::Message &message)
void actionGoBankrupt(GameState &state, const actions::Message &message)
void transferPropertyForSettlement(GameState &state, std::uint8_t squareNo, PlayerNumber toPlayer)
void actionBuyOrAuctionDecision(GameState &state, const actions::Message &message)
void sellAllBuildingsForSettlement(GameState &state, std::uint8_t squareNo)
void collectRent(GameState &state)
void addMoneyToFreeParkingPot(GameState &state, std::int64_t amount)
void landOnIncomeTax(GameState &state)
void declareWinner(GameState &state)
bool onlyOneSurvivor(const GameState &state)
bool pop(GameState &state)
const PendingPhase & current(const GameState &state)
void saveCurrent(GameState &state)
bool push(GameState &state, GamePhase phase, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
bool currentStateChanged(const GameState &state)
bool hasCurrentSnapshot(const GameState &state)
bool restoreCurrent(GameState &state)
void switchTo(GameState &state, GamePhase phase, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
void wrongPlayer(const GameState &state, const actions::Message &message)
void wrongPhase(const GameState &state, const actions::Message &message)
void sendClientState(const GameState &state, PlayerNumber toPlayer, Cause cause)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr std::size_t MaxCardsInDeck
Definition CardTypes.hpp:54
constexpr PlayerNumber EscrowPlayer
Definition RuleTypes.hpp:19
constexpr std::size_t SquareCount
Definition RuleTypes.hpp:24
const GameState & state()
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
constexpr PlayerNumber AllPlayers
Definition RuleTypes.hpp:17
rules::PlayerNumber fromPlayer
Definition Actions.hpp:128
std::array< std::uint8_t, 2 > dice
std::array< CardDeck, static_cast< std::size_t >(DeckType::Count) > cards
std::array< CountHitRecord, MaxCountHitSets > countHits
std::int64_t freeParkingJackpotAmount
std::array< SquareState, SquareCount > squares
std::array< std::uint8_t, 2 > utilityDice
std::array< PendingPhase, MaxPendingPhases > phaseStack
std::uint8_t numberOfPendingPhases
std::array< PlayerState, MaxPlayers > players