diff --git a/day22/Makefile b/day22/Makefile index 9b0535c..5c4d30d 100644 --- a/day22/Makefile +++ b/day22/Makefile @@ -1,7 +1,7 @@ CC=g++ BIN_NAME=day22 -CCFLAGS=-o $(BIN_NAME) -g -std=c++17 -LDFLAGS=-lfolly +CCFLAGS=-o $(BIN_NAME) -O2 -std=c++17 +LDFLAGS= .PHONY: all, clean diff --git a/day22/day22.cpp b/day22/day22.cpp index e8698bf..a4815ec 100644 --- a/day22/day22.cpp +++ b/day22/day22.cpp @@ -1,18 +1,13 @@ -#include - #include #include #include #include #include -#include #include #include #include -// Pair of ingredients and allergens -using IngredientLineItem = std::pair, std::vector>; -auto constexpr INGREDIENT_PATTERN = R"((.*) \(contains (.*)\))"; +enum Player { PLAYER1, PLAYER2 }; std::vector readInput(const std::string &filename) { std::vector input; @@ -41,27 +36,58 @@ std::pair, std::vector> splitInput(const s std::move(beforeEmptyLine), std::move(afterEmptyLine)); } -template -void containerStoi(Iterator begin, Iterator end, IntContainer &out) { - std::transform(begin, end, std::back_inserter(out), [](const std::string &item) { return std::stoi(item); }); +/** + * Perform std::stoi on every element in the container, pushing the items to the output iterator. + * @tparam Iterator The iterator for the input container + * @tparam IntContainer The container to output to + * @param begin The start of the range + * @param end The end of the range + * @param out The output iterator + */ +template +void containerStoi(InputIterator begin, InputIterator end, OutputIterator out) { + std::transform(begin, end, out, [](const std::string &item) { return std::stoi(item); }); } +/** + * Parse the decks from the puzzle input + * @param input The puzzle input + * @return std::pair, std::vector> Both decks to play the game (player 1, player 2) + */ std::pair, std::vector> parseDecks(const std::vector &input) { std::pair, std::vector> inputComponents = splitInput(input); std::pair, std::vector> decks; - containerStoi(inputComponents.first.cbegin() + 1, inputComponents.first.cend(), decks.first); - containerStoi(inputComponents.second.cbegin() + 1, inputComponents.second.cend(), decks.second); + containerStoi(inputComponents.first.cbegin() + 1, inputComponents.first.cend(), std::back_inserter(decks.first)); + containerStoi(inputComponents.second.cbegin() + 1, inputComponents.second.cend(), std::back_inserter(decks.second)); return decks; } -std::vector playBasicGame(const std::pair, std::vector> &initialDecks) { +/** + * Calculate the score for a game + * @tparam ReverseIterator A reverse iterator for the winning deck. Could technically be any input iterator but the + * score calculation requires going backwards + * @param begin The start of the deck + * @param end The end of the deck + * @return int The game score + */ +template +int calculateScore(ReverseIterator begin, ReverseIterator end) { + int i = 1; + int total = 0; + for (auto it = begin; it != end; (++it, i++)) { + int value = *it; + total += i * value; + } + + return total; +} + +int part1(const std::pair, std::vector> &initialDecks) { std::pair, std::deque> decks{ std::deque(initialDecks.first.cbegin(), initialDecks.first.cend()), std::deque(initialDecks.second.cbegin(), initialDecks.second.cend())}; - int i = 0; while (!decks.first.empty() && !decks.second.empty()) { - i++; int player1Card = decks.first.front(); int player2Card = decks.second.front(); decks.first.pop_front(); @@ -77,19 +103,58 @@ std::vector playBasicGame(const std::pair, std::vector &winnerDeck = decks.first.empty() ? decks.second : decks.first; - return std::vector(winnerDeck.cbegin(), winnerDeck.cend()); + return calculateScore(winnerDeck.crbegin(), winnerDeck.crend()); } -int part1(const std::pair, std::vector> &decks) { - std::vector winnerDeck = playBasicGame(decks); - int i = 1; - int total = 0; - for (auto it = winnerDeck.crbegin(); it != winnerDeck.crend(); (++it, i++)) { - int value = *it; - total += i * value; - } +int part2(const std::pair, std::vector> &initialDecks) { + using DeckPair = std::pair, std::deque>; + std::function(DeckPair &)> playGame = + [&playGame](DeckPair &decks) -> std::pair, std::deque>> { + // Holds game states that have already been used, preventing their resuse within a single sub-game + std::set usedDecks; + while (!decks.first.empty() && !decks.second.empty()) { + if (usedDecks.find(decks) != usedDecks.end()) { + return std::make_pair(PLAYER1, decks); + } - return total; + usedDecks.insert(decks); + int player1Card = decks.first.front(); + int player2Card = decks.second.front(); + Player roundWinner; + if (player1Card < decks.first.size() && player2Card < decks.second.size()) { + // Must be done after the size check... + decks.first.pop_front(); + decks.second.pop_front(); + DeckPair truncatedDecks; + std::copy_n(decks.first.cbegin(), player1Card, std::back_inserter(truncatedDecks.first)); + std::copy_n(decks.second.cbegin(), player2Card, std::back_inserter(truncatedDecks.second)); + roundWinner = playGame(truncatedDecks).first; + } else { + roundWinner = player1Card > player2Card ? PLAYER1 : PLAYER2; + decks.first.pop_front(); + decks.second.pop_front(); + } + + if (roundWinner == PLAYER1) { + decks.first.push_back(player1Card); + decks.first.push_back(player2Card); + } else { + decks.second.push_back(player2Card); + decks.second.push_back(player1Card); + } + } + + return std::make_pair(decks.first.empty() ? PLAYER2 : PLAYER1, decks); + }; + + std::pair, std::deque> decks{ + std::deque(initialDecks.first.cbegin(), initialDecks.first.cend()), + std::deque(initialDecks.second.cbegin(), initialDecks.second.cend())}; + + auto gameResult = playGame(decks); + auto winningDeck = gameResult.first == PLAYER1 ? gameResult.second.first : gameResult.second.second; + + return calculateScore(winningDeck.crbegin(), winningDeck.crend()); } int main(int argc, char *argv[]) { @@ -102,4 +167,5 @@ int main(int argc, char *argv[]) { auto decks = parseDecks(input); std::cout << part1(decks) << std::endl; + std::cout << part2(decks) << std::endl; }