Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
RuleTurnActions.cpp
Go to the documentation of this file.
1#include "RuleTurnActions.hpp"
2
3#include "BoardRules.hpp"
4#include "LegacyTextIds.hpp"
5#include "Messaging.hpp"
6#include "PhaseStack.hpp"
7#include "RuleRejections.hpp"
8#include "RuleEconomy.hpp"
9#include "RuleAuction.hpp"
10#include "RuleRandom.hpp"
11#include "RuleStartingOrder.hpp"
12#include "RulePrediction.hpp"
13#include "RuleLifecycle.hpp"
14#include "RuleCoreActions.hpp"
15
16#include <algorithm>
17#include <cstddef>
18#include <cstdint>
19
21{
22 namespace
23 {
24 constexpr std::uint8_t GoSquare = 0;
25 constexpr std::uint8_t JustVisitingSquare = 10;
26 constexpr std::uint8_t GoToJailSquare = 30;
27 constexpr std::uint8_t BoardwalkSquare = 39;
28 constexpr std::uint8_t InJailSquare = 40;
29 constexpr std::uint8_t OffBoardSquare = 41;
30
31
33 const actions::Message& action,
34 bool success)
35 {
40 static_cast<std::int64_t>(action.action),
41 success ? 1 : 0,
42 action.fromPlayer,
43 action.numberA
44 );
45 }
46
47
48 void sendRestart()
49 {
54 );
55 }
56
57
59 {
62 }
63
64
66 const GameState& state)
67 {
68 return
70 state.players[state.currentPlayer].currentSquare <
71 OffBoardSquare;
72 }
73
74
75 void sendWrongPhase(
76 const GameState& state,
77 const actions::Message& message)
78 {
80 }
81
82
83 void sendWrongPlayer(
84 const GameState& state,
85 const actions::Message& message)
86 {
88 }
89
90
91 std::uint8_t generateDie()
92 {
93 return static_cast<std::uint8_t>(
94 (random::generator()() % 6u) + 1u
95 );
96 }
97
98
101 std::uint8_t& dieA,
102 std::uint8_t& dieB)
103 {
104 // ActionRollDice() original pré-génère le prochain lancer.
105 if (state.nextDice[0] == 0)
106 {
107 dieA = generateDie();
108
109 // Source :
110 // rand(); rand(); rand();
111 // Reduce chance of doubles.
115
116 dieB = generateDie();
117 }
118 else
119 {
120 dieA = state.nextDice[0];
121 dieB = state.nextDice[1];
122 }
123
125
129
131 }
132
133
134 std::uint8_t cardPreviewForSquare(
135 const GameState& state,
136 std::uint8_t squareNo)
137 {
138 if (squareNo >= SquareCount)
139 {
140 return 0;
141 }
142
143 const board::SquareDefinition& square =
145 static_cast<board::SquareType>(squareNo)
146 );
147
148 if (square.group ==
150 {
151 return state.cards[
152 static_cast<std::size_t>(
154 )
155 ].cardPile[0];
156 }
157
158 if (square.group ==
160 {
161 return state.cards[
162 static_cast<std::size_t>(
164 )
165 ].cardPile[0];
166 }
167
168 return 0;
169 }
170
171
173 actions::Type type,
174 std::uint8_t destination,
175 std::uint8_t previous,
176 PlayerNumber player,
177 std::uint8_t card,
178 bool firstMove)
179 {
181
182 notification.action = type;
183 notification.fromPlayer = BankPlayer;
184 notification.toPlayer = AllPlayers;
185
186 notification.numberA = destination;
187 notification.numberB = previous;
188 notification.numberC = player;
189 notification.numberD = card;
190 notification.numberE = firstMove ? 1 : 0;
191
193 }
194
195
198 PlayerNumber player,
199 std::int64_t amount)
200 {
201 if (player >= state.numberOfPlayers ||
202 amount <= 0)
203 {
204 return;
205 }
206
207 state.players[player].cash += amount;
208
214 player,
215 amount
216 );
217
222 player,
223 amount,
224 state.players[player].cash
225 );
226 }
227
228
230 {
231 if (
234 {
235 return;
236 }
237
238
239 PlayerState& player =
241
242
243 if (
244 player.currentSquare >=
245 OffBoardSquare)
246 {
248 state,
250 0,
251 0,
252 0
253 );
254
255
261 );
262
263
264 return;
265 }
266
267 // Le traitement des propriétés que la banque attend
268 // d'enchérir sera raccordé avec le moteur d'enchères.
269
270 // Rule.cpp GF_PREROLL :
271 //
272 // avant toute autre chose, la banque vend
273 // les propriétés laissées en attente après
274 // un refus d'achat.
275
276 if (
277 auction::
278 startPendingBankPropertyAuction(
279 state
280 ))
281 {
282 return;
283 }
284
285
286 if (player.currentSquare == InJailSquare)
287 {
288 if (state.dice[0] == 0)
289 {
291 state,
293 0,
294 0,
295 0
296 );
297
298 sendRestart();
299 }
300 else
301 {
303 state,
305 0,
306 0,
307 0
308 );
309
315 );
316 }
317
318 return;
319 }
320
321 // Premier lancer :
322 // 0 == 0.
323 //
324 // Ou lancer précédent en double.
325 if (state.dice[0] == state.dice[1] &&
327 {
329 state,
331 0,
332 0,
333 0
334 );
335
336 sendRestart();
337 return;
338 }
339
341 state,
343 0,
344 0,
345 0
346 );
347
353 );
354 }
355
356
359 {
361 {
363 return;
364 }
365
366
375 ].currentSquare
376 );
377 }
378
379
382 {
388 );
389 }
390
391
394 {
396 {
398 return;
399 }
400
401
403const std::uint8_t squareNo =
406 ].currentSquare;
407
408 const board::SquareDefinition& square =
410 static_cast<board::SquareType>(squareNo)
411 );
412
413 const DeckType deck =
414 square.group ==
418
419 const CardDeck& cards =
420 state.cards[
421 static_cast<std::size_t>(deck)
422 ];
423
429 static_cast<std::int64_t>(deck),
430 cards.cardPile[0]
431 );
432 }
433
434
437 {
439 {
441 return;
442 }
443
444
446const std::uint8_t squareNo =
449 ].currentSquare;
450
451 const auto square =
452 static_cast<board::SquareType>(
454 );
455
461 squareNo,
463 );
464 }
465
466
469 {
471 {
473 return;
474 }
475
476
478const PlayerState& player =
480
481 const bool canRoll =
482 player.turnsInJail < 99;
483
484 const bool canPay =
485 player.cash >=
487
488 const bool ownsJailCard =
489 state.cards[
490 static_cast<std::size_t>(
492 )
493 ].jailOwner == state.currentPlayer ||
494 state.cards[
495 static_cast<std::size_t>(
497 )
498 ].jailOwner == state.currentPlayer;
499
505 canRoll ? 1 : 0,
506 canPay ? 1 : 0,
507 (canRoll && ownsJailCard) ? 1 : 0
508 );
509 }
510
511
514 {
515 // GF_GET_OUT_OF_JAIL original.
516 //
517 // Le paiement peut avoir provoqué une faillite.
518
519 if (
524 ].currentSquare >=
525 OffBoardSquare)
526 {
528
533 );
534
535 return;
536 }
537
538
540 state,
542 0,
543 0,
544 0
545 );
546
547
553 );
554 }
555 }
556
557
560 const actions::Message& message)
561 {
562 // ActionStartTurn().
563
564 if (phases::current(state).phase !=
566 {
567 sendWrongPhase(state, message);
568 return;
569 }
570
571 if (message.fromPlayer != BankPlayer)
572 {
573 sendWrongPlayer(state, message);
574 return;
575 }
576
578 {
579 // L'original appelle RULE_InitializeSystem() ici.
580 //
581 // Dans le runtime moderne, le reset est sérialisé dans
582 // MESS afin de ne pas réinitialiser l'état au milieu
583 // d'un dispatch de message.
585 state
586 );
587
588 return;
589 }
590
592
593 state.dice = { 0, 0 };
594 state.utilityDice = { 0, 0 };
595
598
604 );
605
607 state,
609 0,
610 0,
611 0
612 );
613
614 sendRestart();
615 }
616
617
620 const actions::Message& message)
621 {
622 // Version finale du source :
623 //
624 // fromPlayer doit être CurrentPlayer,
625 // et non RULE_BANK_PLAYER.
626
627 if (message.fromPlayer !=
629 {
630 sendWrongPlayer(state, message);
631 return;
632 }
633
634 if (phases::current(state).phase !=
636 {
637 sendWrongPhase(state, message);
638 return;
639 }
640
641 PlayerState& current =
643
644 if (current.currentSquare ==
646 {
647 ++current.turnsInJail;
648 }
649
650
651 // Trouver le joueur suivant non-bankrupt.
654
655 while (true)
656 {
657 ++nextPlayer;
658
659 if (nextPlayer >=
661 {
662 nextPlayer = 0;
663 }
664
665 if (nextPlayer ==
667 {
668 break;
669 }
670
672 .currentSquare <
673 OffBoardSquare)
674 {
675 break;
676 }
677 }
678
680
681
682 // ActionEndTurn() original :
683 //
684 // 1. Nth bankruptcy
685 // 2. time limit
686 // 3. un seul survivant
687
688 if (
690 state
691 ))
692 {
694 state
695 );
696
697 // Le source retourne AVANT
698 // NOTIFY_ACTION_COMPLETED(ACTION_END_TURN).
699 return;
700 }
701
702
704 message,
705 true
706 );
707
709 state,
711 0,
712 0,
713 0
714 );
715
716
717 // Source :
718 //
719 // si la queue est déjà vide, démarrage immédiat.
720 // Sinon ACTION_TICK attend qu'elle se vide.
722 {
727 );
728 }
729 }
730
731
734 const actions::Message& message)
735 {
736 const GamePhase phase =
738
739 if (phase != GamePhase::WaitMoveRoll &&
740 phase != GamePhase::WaitJailRoll &&
743 {
744 sendWrongPhase(state, message);
745 return;
746 }
747
748 if (message.fromPlayer !=
750 {
751 sendWrongPlayer(state, message);
752 return;
753 }
754
756 {
757 return;
758 }
759
760 std::uint8_t dieA = 0;
761 std::uint8_t dieB = 0;
762
763
764 if (
765 message.action ==
768 {
769 // ActionRollDice() original :
770 // clamp chaque dé entre 1 et 6.
771
772 dieA =
773 static_cast<std::uint8_t>(
774 std::clamp<std::int64_t>(
775 message.numberA,
776 1,
777 6
778 )
779 );
780
781
782 dieB =
783 static_cast<std::uint8_t>(
784 std::clamp<std::int64_t>(
785 message.numberB,
786 1,
787 6
788 )
789 );
790
791
797 0,
798 message.fromPlayer
799 );
800 }
801 else
802 {
803 // Même ACTION_CHEAT_ROLL_DICE avec cheatingAllowed
804 // false tombe volontairement ici dans Rule.cpp :
805 // il produit simplement un vrai lancer.
806
808 state,
809 dieA,
810 dieB
811 );
812 }
813
814 const std::uint8_t total =
815 static_cast<std::uint8_t>(
816 dieA + dieB
817 );
818
819 const bool doubles =
820 dieA == dieB;
821
822
824 message,
825 true
826 );
827
832 dieA,
833 dieB,
835 );
836
837
838 if (
839 phase ==
841 {
843 state,
844 dieA,
845 dieB
846 );
847
848 return;
849 }
850
851
852 if (phase == GamePhase::WaitMoveRoll)
853 {
854 state.dice[0] = dieA;
855 state.dice[1] = dieB;
856
857 if (doubles)
858 {
860 }
861
863 state,
865 0,
866 0,
867 0
868 );
869
870
872 {
878 );
879
880 return;
881 }
882
883
884 const PlayerState& player =
886
887 std::uint8_t newSquare =
888 static_cast<std::uint8_t>(
889 player.currentSquare + total
890 );
891
893 {
894 newSquare =
895 static_cast<std::uint8_t>(
896 newSquare -
897 (BoardwalkSquare + 1)
898 );
899 }
900
906 );
907
908 return;
909 }
910
911
912 // GF_WAIT_UTILITY_ROLL.
913 if (phase == GamePhase::WaitUtilityRoll)
914 {
917
919
921
926 );
927
928 return;
929 }
930
931
932 // GF_WAIT_JAIL_ROLL.
933 state.dice[0] = dieA;
934 state.dice[1] = dieB;
935
936 // Le source positionne ce flag même si le lancer
937 // n'est finalement pas un double.
939
940 PlayerState& player =
942
943 if (doubles)
944 {
946 state,
948 0,
949 0,
950 0
951 );
952
953 sendRestart();
954 return;
955 }
956
957
959 player.turnsInJail >=
961 {
962 player.turnsInJail = 99;
963
965 state,
967 0,
968 0,
969 0
970 );
971
972 sendRestart();
973 return;
974 }
975
976
978 }
979
980
983 const actions::Message& message)
984 {
985 if (message.fromPlayer != BankPlayer)
986 {
987 sendWrongPlayer(state, message);
988 return;
989 }
990
991 if (phases::current(state).phase !=
993 {
994 sendWrongPhase(state, message);
995 return;
996 }
997
999 {
1000 return;
1001 }
1002
1003 if (message.numberA < GoSquare ||
1004 message.numberA > BoardwalkSquare)
1005 {
1006 return;
1007 }
1008
1009 PlayerState& player =
1011
1012 const std::uint8_t destination =
1013 static_cast<std::uint8_t>(
1014 message.numberA
1015 );
1016
1017 const std::uint8_t previous =
1018 player.currentSquare;
1019
1020 const std::uint8_t card =
1022 state,
1024 );
1025
1026
1030 previous,
1032 card,
1033 !player.firstMoveMade
1034 );
1035
1036 player.firstMoveMade = true;
1037
1038
1039 // Passage par GO :
1040 // destination <= previous.
1041 if (destination <= previous)
1042 {
1045 BankPlayer,
1046 AllPlayers,
1048 );
1049
1050 std::int64_t salary =
1052
1055 {
1056 salary *= 2;
1057 }
1058
1060 state,
1062 salary
1063 );
1064 }
1065
1066
1067 player.currentSquare =
1069
1072 BankPlayer,
1074 );
1075 }
1076
1077
1080 const actions::Message& message)
1081 {
1082 if (message.fromPlayer != BankPlayer)
1083 {
1084 sendWrongPlayer(state, message);
1085 return;
1086 }
1087
1088 if (phases::current(state).phase !=
1090 {
1091 sendWrongPhase(state, message);
1092 return;
1093 }
1094
1096 {
1097 return;
1098 }
1099
1100 if (message.numberA < GoSquare ||
1101 message.numberA > BoardwalkSquare)
1102 {
1103 return;
1104 }
1105
1106 PlayerState& player =
1108
1109 const std::uint8_t destination =
1110 static_cast<std::uint8_t>(
1111 message.numberA
1112 );
1113
1114 const std::uint8_t previous =
1115 player.currentSquare;
1116
1120 previous,
1123 state,
1125 ),
1126 false
1127 );
1128
1129 player.currentSquare =
1131
1134 BankPlayer,
1136 );
1137 }
1138
1139
1142 const actions::Message& message)
1143 {
1144 if (message.fromPlayer != BankPlayer)
1145 {
1146 sendWrongPlayer(state, message);
1147 return;
1148 }
1149
1150 if (phases::current(state).phase !=
1152 {
1153 sendWrongPhase(state, message);
1154 return;
1155 }
1156
1158 {
1159 return;
1160 }
1161
1162 if (message.numberA < GoSquare ||
1163 message.numberA > InJailSquare)
1164 {
1165 return;
1166 }
1167
1168 PlayerState& player =
1170
1171 const std::uint8_t destination =
1172 static_cast<std::uint8_t>(
1173 message.numberA
1174 );
1175
1178 BankPlayer,
1179 AllPlayers,
1181 player.currentSquare,
1183 0
1184 );
1185
1186
1187 if (destination == GoSquare)
1188 {
1191 BankPlayer,
1192 AllPlayers,
1194 );
1195
1196 std::int64_t salary =
1198
1200 {
1201 salary *= 2;
1202 }
1203
1205 state,
1207 salary
1208 );
1209 }
1210
1211
1212 player.currentSquare =
1214
1217 BankPlayer,
1219 );
1220 }
1221
1222
1225 const actions::Message& message)
1226 {
1227 if (message.fromPlayer != BankPlayer)
1228 {
1229 sendWrongPlayer(state, message);
1230 return;
1231 }
1232
1233 if (phases::current(state).phase !=
1235 {
1236 sendWrongPhase(state, message);
1237 return;
1238 }
1239
1241 {
1242 return;
1243 }
1244
1245 PlayerState& player =
1247
1248 const auto square =
1249 static_cast<board::SquareType>(
1250 player.currentSquare
1251 );
1252
1253
1254 switch (square)
1255 {
1262 {
1264 state,
1266 0,
1267 0,
1268 0
1269 );
1270
1271 sendRestart();
1272 return;
1273 }
1274
1275
1277 {
1279 {
1282 BankPlayer,
1283 BankPlayer,
1285 state.dice[0] +
1286 state.dice[1]
1287 );
1288 }
1289 else
1290 {
1292 }
1293
1294 return;
1295 }
1296
1297
1299 {
1300 player.turnsInJail = 0;
1301
1303 return;
1304 }
1305
1306
1308 {
1311 BankPlayer,
1312 BankPlayer,
1314 );
1315
1316 return;
1317 }
1318
1319
1321 {
1323 return;
1324 }
1325
1326
1328 {
1330 return;
1331 }
1332
1333
1335 {
1337 return;
1338 }
1339
1340
1342 {
1344 return;
1345 }
1346
1347
1348 default:
1349 break;
1350 }
1351
1352
1353 if (board::isOwnable(square))
1354 {
1355 const PlayerNumber owner =
1356 state.squares[
1357 static_cast<std::size_t>(
1358 square
1359 )
1360 ].owner;
1361
1362
1363 if (owner < MaxPlayers)
1364 {
1365 // Source :
1366 //
1367 // PopPhase();
1368 // CollectRent();
1369 // ACTION_RESTART_PHASE
1370
1372
1374
1377 BankPlayer,
1379 );
1380
1381 return;
1382 }
1383
1384
1385 if (owner == NobodyPlayer)
1386 {
1388 state,
1390 0,
1391 0,
1392 0
1393 );
1394
1397 BankPlayer,
1399 );
1400
1401 return;
1402 }
1403
1404
1405 // BANK / ESCROW :
1406 // le source considère cela comme un état
1407 // transitoire et ignore la case.
1408
1410 return;
1411 }
1412
1413
1414 // Case sans effet particulier.
1416 }
1417
1418
1421 const actions::Message& message)
1422 {
1423 switch (phases::current(state).phase)
1424 {
1427 return true;
1428
1431 // Le source ne fait volontairement rien lors
1432 // d'un restart de ces phases.
1433 return true;
1434
1435
1438 return true;
1439
1440
1441 case GamePhase::PreRoll:
1442 if (
1443 message.fromPlayer !=
1444 BankPlayer)
1445 {
1446 return true;
1447 }
1448
1450 return true;
1451
1452
1457 return true;
1458
1459
1462 return true;
1463
1464
1467 return true;
1468
1469
1472 return true;
1473
1474
1477 return true;
1478
1479
1481 {
1483 {
1485 return true;
1486 }
1487
1488
1490 state
1491 );
1492
1493
1496 BankPlayer,
1497 AllPlayers,
1501 );
1502
1503 return true;
1504 }
1505
1506
1507 default:
1508 return false;
1509 }
1510 }
1511
1512
1515 {
1516 // ActionTick() original :
1517 //
1518 // GF_WAIT_START_TURN attend que la queue soit vide
1519 // avant de lancer ACTION_START_TURN.
1520
1522 {
1523 return;
1524 }
1525
1526 if (phases::current(state).phase !=
1528 {
1529 return;
1530 }
1531
1532 if (messaging::currentQueueSize() != 0)
1533 {
1534 return;
1535 }
1536
1539 BankPlayer,
1541 );
1542 }
1543}
1544
1545
1546
1547
1548
1549
1550
unsigned index
constexpr std::int64_t ErrorPlayerCheating
std::size_t currentQueueSize()
bool sendAction(const actions::Message &message)
const SquareDefinition & definition(SquareType square)
bool isOwnable(SquareType square)
void resetFreshUnconditional(GameState &state)
void landOnLuxuryTax(GameState &state)
void landOnFreeParking(GameState &state)
void collectRent(GameState &state)
void landOnIncomeTax(GameState &state)
void declareWinner(GameState &state)
bool shouldEndGame(const GameState &state)
bool pop(GameState &state)
const PendingPhase & current(const GameState &state)
bool push(GameState &state, GamePhase phase, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
void switchTo(GameState &state, GamePhase phase, PlayerNumber fromPlayer, PlayerNumber toPlayer, std::int64_t amount)
void sendNextMove(GameState &state)
std::mt19937 & generator()
void wrongPlayer(const GameState &state, const actions::Message &message)
void wrongPhase(const GameState &state, const actions::Message &message)
void recordDiceRoll(GameState &state, std::uint8_t dieA, std::uint8_t dieB)
void actionEndTurn(GameState &state, const actions::Message &message)
void actionMoveForwards(GameState &state, const actions::Message &message)
void actionJumpToSquare(GameState &state, const actions::Message &message)
void actionStartTurn(GameState &state, const actions::Message &message)
void actionMoveBackwards(GameState &state, const actions::Message &message)
void onIdleTick(GameState &state)
bool restartGameplayPhase(GameState &state, const actions::Message &message)
void actionRollDice(GameState &state, const actions::Message &message)
void actionLandedOnSquare(GameState &state, const actions::Message &message)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
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, MaxCardsInDeck > cardPile
Definition CardTypes.hpp:68
std::array< std::uint8_t, 2 > dice
std::array< CardDeck, static_cast< std::size_t >(DeckType::Count) > cards
std::uint8_t numberOfDoublesRolled
std::array< SquareState, SquareCount > squares
std::array< std::uint8_t, 2 > utilityDice
std::array< std::uint8_t, 2 > nextDice
std::uint8_t numberOfPendingPhases
std::array< PlayerState, MaxPlayers > players