#include #include #include #include #include #include #include #include #include #include #include #include using namespace aocpp; namespace { int const initial_hp = 200; auto IsUnit(char c) -> bool { return c == 'G' || c == 'E'; } auto IsOpposed(char x, char y) -> bool { return x == 'G' && y == 'E' || x == 'E' && y == 'G'; } auto PickTarget( Grid const& grid, std::map & hp, Coord my_coord, char my_team ) -> std::optional { std::optional best; int best_hp; for (auto const dir : {Coord{0,-1}, Coord{-1,0}, Coord{1,0}, Coord{0,1}}) { //reading order auto here = dir + my_coord; if (IsOpposed(my_team, grid[here]) && (!best || best_hp > hp[here])) { best = here; best_hp = hp[here]; } } return best; } auto Before(Coord a, Coord b) { return a.y < b.y || a.y == b.y && a.x < b.x; } auto Move( Grid const& grid, std::map & hp, Coord my_coord, char my_team ) -> std::optional { int best_distance; std::optional best_start, best_end; std::map> seen; std::deque> todo; for (auto const dir : {Coord{0,-1}, Coord{-1,0}, Coord{1,0}, Coord{0,1}}) { auto start = my_coord + dir; todo.push_back({start,start,1}); } while (!todo.empty()) { auto [start, cursor, steps] = todo.front(); todo.pop_front(); if (grid[cursor] != '.') { continue; } if (best_start && best_distance < steps) break; auto [it,success] = seen.insert({cursor, {start, steps}}); if (!success) { if (Before(start, it->second.first)) continue; it->second.first = start; } for (auto const dir : {Coord{0,-1}, Coord{-1,0}, Coord{1,0}, Coord{0,1}}) { auto next = cursor + dir; if (seen.insert({next, {start, steps+1}}).second) { todo.push_back({start, next, steps+1}); } } if (PickTarget(grid, hp, cursor, my_team) && (!best_start || steps < best_distance || steps == best_distance && Before(cursor, *best_end) || steps == best_distance && cursor == *best_end && Before(start, *best_start))) { best_end = cursor; best_distance = steps; best_start = start; } } return best_start; } auto Simulate(Grid & grid) { // hit points for unit at coordinate std::map hp; // set each unit to its initial hit points grid.each([&](auto k, auto v) { if (IsUnit(v)) { hp[k] = initial_hp; } }); for (int rounds = 0;; rounds++) { std::cout << "round " << rounds << std::endl; for (auto const& row : grid.rows) { std::cout << row << std::endl; } // determine order of unit updates std::deque turn_order; grid.each([&](auto k, auto v) { if (IsUnit(v)) { turn_order.push_back(k); } }); while (!turn_order.empty()) { auto active_coord = turn_order.front(); char active_team = grid[active_coord]; turn_order.pop_front(); // Detect when no targets remain for this unit and end the game bool game_over = true; grid.each([&](auto k, auto v) { if (IsOpposed(active_team, v)) game_over = false; }); if (game_over) { int total_hp = 0; for (auto const& [_,v] : hp) { total_hp += v; } return rounds * total_hp; } // Try to pick a target without moving auto target = PickTarget(grid, hp, active_coord, active_team); // only move when necessary if (!target) { // move if we can if (auto const destination = Move(grid, hp, active_coord, active_team)) { std::swap(grid[*destination], grid[active_coord]); auto const it = hp.find(active_coord); hp[*destination] = it->second; hp.erase(it); active_coord = *destination; } target = PickTarget(grid, hp, active_coord, active_team); } // target in range, do the attack if (target) { // apply damage auto remaining = hp[*target] -= 3; // handle death if (remaining <= 0) { hp.erase(*target); // free up location on the map grid[*target] = '.'; // possibly remove dead unit from turn queue auto it = std::find(turn_order.begin(), turn_order.end(), *target); if (it != turn_order.end()) { turn_order.erase(it); } } } } } } } // namespace TEST_SUITE("2018-15 examples") { TEST_CASE("example") { } } auto main(int argc, char** argv) -> int { auto grid = Grid::Parse(*aocpp::Startup(argc, argv)); auto part1 = Simulate(grid); std::cout << "Part 1: " << part1 << std::endl; //std::cout << "Part 2: " << Run2(std::move(input)) << std::endl; }