Advertisement
Guest User

Untitled

a guest
Dec 21st, 2021
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 6.37 KB | None | 0 0
  1. // KingFlerple, 2021-12-21, Advent of Code 2021 Day 21: "Dirac Dice", Part 2.
  2. #include <iostream>
  3. #include <vector>
  4. #include <deque>
  5. #include <map>
  6. #include <set>
  7. #include <algorithm>
  8. #include <cassert>
  9.  
  10. using namespace std;
  11.  
  12. constexpr auto winningScore = 21;
  13. constexpr auto largestSquareNum = 10;
  14.  
  15. struct GameState
  16. {
  17.     int playerScores[2] = {-1,-1};
  18.     int playerPositions[2] = {-1, -1}; // 1 ... 10.
  19.     int currentPlayerTurn = -1; // {0, 1}
  20.  
  21.     bool operator<(const GameState& other) const
  22.     {
  23.         // Impose some arbitrary ordering for use in set/ maps.
  24.         // Would be nice to use operator<=>, but oh well.
  25.         if (currentPlayerTurn != other.currentPlayerTurn)
  26.             return currentPlayerTurn < other.currentPlayerTurn;
  27.         for (const auto playerId : {0, 1})
  28.         {
  29.             if (playerScores[playerId] != other.playerScores[playerId])
  30.                 return playerScores[playerId] < other.playerScores[playerId];
  31.             if (playerPositions[playerId] != other.playerPositions[playerId])
  32.                 return playerPositions[playerId] < other.playerPositions[playerId];
  33.         }
  34.  
  35.         return false;
  36.     }
  37. };
  38.  
  39. ostream& operator<<(ostream& os, const GameState& gameState)
  40. {
  41.     os << "Gamestate: currentPlayerTurn: " << gameState.currentPlayerTurn << " scores: {" << gameState.playerScores[0] << ", " << gameState.playerScores[1] << "} positions: {" << gameState.playerPositions[0] << ", " << gameState.playerPositions[1] << "}";
  42.     return os;
  43. }
  44.  
  45. int newPos(int pos, int diceRoll)
  46. {
  47.     assert(diceRoll > 0);
  48.     pos += diceRoll;
  49.     while (pos > largestSquareNum)
  50.         pos -= largestSquareNum;
  51.     return pos;
  52. }
  53.  
  54. int64_t numUniversesReachingGameState(const GameState& gameState, map<GameState, int64_t>& numUniversesReachingGameStateLookup, map<GameState, vector<GameState>>& predecessorStates)
  55. {
  56.     if (numUniversesReachingGameStateLookup.contains(gameState))
  57.         return numUniversesReachingGameStateLookup[gameState];
  58.  
  59.     int64_t result = 0;
  60.  
  61.     for (const auto& predecessor : predecessorStates[gameState])
  62.     {
  63.         result += numUniversesReachingGameState(predecessor, numUniversesReachingGameStateLookup, predecessorStates);
  64.     }
  65.  
  66.     numUniversesReachingGameStateLookup[gameState] = result;
  67.     cout << "numUniversesReachingGameState: " << gameState << " result: " << result << endl;
  68.     return result;
  69. }
  70.  
  71.  
  72. int main()
  73. {
  74.     const string playerStartPosPrefix = "Player X starting position: ";
  75.  
  76.     string player1StartPosLine;
  77.     getline(cin, player1StartPosLine);
  78.     const int player1StartPos = stoi(player1StartPosLine.substr(playerStartPosPrefix.size()));
  79.  
  80.     string player2StartPosLine;
  81.     getline(cin, player2StartPosLine);
  82.     const int player2StartPos = stoi(player2StartPosLine.substr(playerStartPosPrefix.size()));
  83.  
  84.     cout << "player1StartPos: " << player1StartPos << endl;
  85.     cout << "player2StartPos: " << player2StartPos << endl;
  86.  
  87.     map<GameState, int64_t> numUniversesReachingGameStateLookup;
  88.     GameState initialGameState = {
  89.         {0, 0},
  90.         {player1StartPos, player2StartPos},
  91.         0
  92.     };
  93.     numUniversesReachingGameStateLookup[initialGameState] = 1;
  94.  
  95.     // Build the list of "predecessor states" for all reachable states.
  96.     // For each state S, this is a list of states P that can reach S by having P's current player take a turn.
  97.     // Such P will occur in predecessorStates[S] as many times as there are turns from P that reach S: could be optimised
  98.     // so that there is a _count_ of P instead of just duplicating P, but oh well :)
  99.     // S and P will alway be states that are reachable from the initialGameState via valid turns.
  100.     map<GameState, vector<GameState>> predecessorStates;
  101.     set<GameState> seenStates = { initialGameState };
  102.     deque<GameState> toProcess = { initialGameState };
  103.     while (!toProcess.empty())
  104.     {
  105.         const auto gameState = toProcess.front();
  106.         toProcess.pop_front();
  107.         const int numWinningPlayers = static_cast<int>(count_if(begin(gameState.playerScores), end(gameState.playerScores), [](const auto score) { return score >= winningScore; }));
  108.         assert(numWinningPlayers == 0 || numWinningPlayers == 1);
  109.         if (numWinningPlayers == 1)
  110.             continue;
  111.         // Take all possible turns from gameState.
  112.         // Again, could be optimised (many dice outcomes are "redundant" i.e. have the same diceSum), but don't care :p
  113.         for (int valDice1 : {1, 2, 3})
  114.         {
  115.             for (int valDice2 : {1, 2, 3})
  116.             {
  117.                 for (int valDice3 : {1, 2, 3})
  118.                 {
  119.                     const int diceSum = valDice1 + valDice2 + valDice3;
  120.                     GameState nextState = gameState;
  121.                     nextState.currentPlayerTurn = 1 - nextState.currentPlayerTurn;
  122.                     nextState.playerPositions[gameState.currentPlayerTurn] = newPos(gameState.playerPositions[gameState.currentPlayerTurn], diceSum);
  123.                     nextState.playerScores[gameState.currentPlayerTurn] += nextState.playerPositions[gameState.currentPlayerTurn];
  124.                     predecessorStates[nextState].push_back(gameState);
  125.  
  126.                     if (!seenStates.contains(nextState))
  127.                     {
  128.                         toProcess.push_back(nextState);
  129.                         seenStates.insert(nextState);
  130.                     }
  131.                 }
  132.             }
  133.         }
  134.     }
  135.  
  136.     // Now do the dynamic programming step to compute, for all valid states S, the number of universes in which S is reached.
  137.     // While we are are it, decide if each such S is a Win for either player, and update numUniversesInWhichPlayerWins accordingly.
  138.     int64_t numUniversesInWhichPlayerWins[2] = {0,0};
  139.     for (const auto& [state, predecessors] : predecessorStates)
  140.     {
  141.         for (int playerId : {0, 1})
  142.         {
  143.             if (state.playerScores[playerId] >= winningScore)
  144.             {
  145.                 numUniversesInWhichPlayerWins[playerId] += numUniversesReachingGameState(state, numUniversesReachingGameStateLookup, predecessorStates);
  146.             }
  147.         }
  148.     }
  149.     for (int playerId : {0, 1})
  150.     {
  151.         cout << "player:" << playerId << " numUniversesInWhichPlayerWins: " << numUniversesInWhichPlayerWins[playerId] << endl;
  152.     }
  153.     cout << *max_element(begin(numUniversesInWhichPlayerWins), end(numUniversesInWhichPlayerWins)) << endl;
  154. }
  155.  
  156.  
  157.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement