aocpp/2018/15.cpp

209 lines
4.9 KiB
C++
Raw Normal View History

2022-11-26 10:01:10 -08:00
#include <cstdint>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include <deque>
#include <optional>
#include <doctest.h>
#include <aocpp/Startup.hpp>
#include <aocpp/Grid.hpp>
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<Coord, int> & hp,
Coord my_coord, char my_team
) -> std::optional<Coord>
{
std::optional<Coord> 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<Coord, int> & hp,
Coord my_coord, char my_team
) -> std::optional<Coord>
{
int best_distance;
std::optional<Coord> best_start, best_end;
std::map<Coord, std::pair<Coord, int>> seen;
std::deque<std::tuple<Coord, Coord, int>> 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<Coord, int> 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<Coord> 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;
}