227 lines
5.5 KiB
C++
227 lines
5.5 KiB
C++
|
#include <algorithm>
|
||
|
#include <iostream>
|
||
|
#include <map>
|
||
|
#include <tuple>
|
||
|
#include <utility>
|
||
|
#include <vector>
|
||
|
|
||
|
#include <aocpp/Coord.hpp>
|
||
|
#include <aocpp/Grid.hpp>
|
||
|
#include <aocpp/Startup.hpp>
|
||
|
#include <intcode/intcode.hpp>
|
||
|
|
||
|
using namespace aocpp;
|
||
|
using namespace intcode;
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
auto GatherOutput(Machine m) -> Grid
|
||
|
{
|
||
|
Grid grid;
|
||
|
bool eol = true; // next output starts a new line
|
||
|
|
||
|
Run(m, []() -> ValueType {
|
||
|
throw std::runtime_error{"input not supported"};
|
||
|
},
|
||
|
[&](ValueType c) -> void {
|
||
|
if (eol) {
|
||
|
grid.rows.emplace_back();
|
||
|
eol = false;
|
||
|
}
|
||
|
if ('\n' == c) {
|
||
|
eol = true;
|
||
|
} else {
|
||
|
grid.rows.back().push_back(static_cast<char>(c));}
|
||
|
}
|
||
|
);
|
||
|
return grid;
|
||
|
}
|
||
|
|
||
|
/// Find all the 4-way intersections.
|
||
|
/// @return Sum of product of coordinates
|
||
|
auto ScaffoldAlignments(Grid const& grid) -> std::size_t
|
||
|
{
|
||
|
std::int64_t answer = 0;
|
||
|
std::int64_t h = grid.rows.size();
|
||
|
for (std::int64_t y = 1; y+1 < h; y++) {
|
||
|
auto const& row = grid.rows[y];
|
||
|
std::int64_t w = row.size();
|
||
|
for (std::int64_t x = 1; x+1 < w; x++) {
|
||
|
Coord c = {x,y};
|
||
|
if (grid[c] == '#' &&
|
||
|
grid[Up(c)] == '#' &&
|
||
|
grid[Down(c)] == '#' &&
|
||
|
grid[Left(c)] == '#' &&
|
||
|
grid[Right(c)] == '#')
|
||
|
{
|
||
|
answer += x*y;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return answer;
|
||
|
}
|
||
|
|
||
|
auto ComputePath(Grid const& grid)
|
||
|
-> std::vector<std::pair<char, std::int64_t>>
|
||
|
{
|
||
|
// Determine starting location and direction
|
||
|
Coord start{}, step{};
|
||
|
grid.each([&](Coord c, char v) {
|
||
|
switch (v) {
|
||
|
case '^': start = c; step = { 0, -1}; break;
|
||
|
case '>': start = c; step = { 0, 1}; break;
|
||
|
case '<': start = c; step = {-1, 0}; break;
|
||
|
case 'v': start = c; step = { 0, 1}; break;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
auto is_path = [&](Coord c) {
|
||
|
return grid.contains(c) && grid[c] == '#';
|
||
|
};
|
||
|
|
||
|
std::vector<std::pair<char, std::int64_t>> result;
|
||
|
for(;;) {
|
||
|
char dir;
|
||
|
if (is_path(start + CW(step))) {
|
||
|
dir = 'R';
|
||
|
step = CW(step);
|
||
|
} else if (is_path(start + CCW(step))) {
|
||
|
dir = 'L';
|
||
|
step = CCW(step);
|
||
|
} else {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
std::int64_t n = 0;
|
||
|
while (is_path(start + step)) {
|
||
|
start += step;
|
||
|
n++;
|
||
|
}
|
||
|
result.emplace_back(dir, n);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename It>
|
||
|
auto RenderCommands(It begin, It end) -> std::string
|
||
|
{
|
||
|
std::string result;
|
||
|
while (begin != end) {
|
||
|
auto const& [m,n] = *begin++;
|
||
|
result += m;
|
||
|
result += ',';
|
||
|
result += std::to_string(n);
|
||
|
result += ',';
|
||
|
}
|
||
|
result.pop_back(); // trailing comma
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template<class InputIter1, class InputIter2>
|
||
|
auto constexpr prefix(
|
||
|
InputIter1 first1, InputIter1 last1,
|
||
|
InputIter2 first2, InputIter2 last2
|
||
|
) -> bool
|
||
|
{
|
||
|
for(;;) {
|
||
|
if (first1 == last1) return true;
|
||
|
if (first2 == last2) return false;
|
||
|
if (*first1++ != *first2++) return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
using Path = std::vector<std::pair<char, std::int64_t>>;
|
||
|
|
||
|
auto ComputeProgramLoop(
|
||
|
Path::const_iterator begin,
|
||
|
Path::const_iterator end,
|
||
|
std::vector<char> & solution,
|
||
|
std::map<char, std::pair<Path::const_iterator, Path::const_iterator>> & subroutines
|
||
|
) -> bool
|
||
|
{
|
||
|
// no path left to encode, we're done
|
||
|
if (begin == end) return true;
|
||
|
|
||
|
// Try using an existing subroutine
|
||
|
for (auto const& [k,v] : subroutines) {
|
||
|
if (prefix(v.first, v.second, begin, end)) {
|
||
|
solution.push_back(k);
|
||
|
auto begin_ = begin + (v.second - v.first);
|
||
|
if (ComputeProgramLoop(begin_, end, solution, subroutines)) return true;
|
||
|
solution.pop_back(); // rollback k
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try allocating a new subroutine
|
||
|
if (subroutines.size() == 3) return false;
|
||
|
auto next_routine = 'A' + subroutines.size();
|
||
|
auto sub = subroutines.insert({next_routine, {}}).first;
|
||
|
solution.push_back(next_routine);
|
||
|
|
||
|
// Divide begin-end into begin-cursor and cursor-end
|
||
|
// Assign begin-end to a new subroutine and recursively solve
|
||
|
for (
|
||
|
auto cursor = begin+1;
|
||
|
cursor != end && RenderCommands(begin, cursor).size() <= 20;
|
||
|
++cursor
|
||
|
) {
|
||
|
sub->second = {begin, cursor};
|
||
|
if (ComputeProgramLoop(cursor, end, solution, subroutines)) return true;
|
||
|
}
|
||
|
|
||
|
// Deallocate the new subroutine; there was no solution
|
||
|
subroutines.erase(sub);
|
||
|
solution.pop_back();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto ComputeProgram(Path const& path) -> std::string {
|
||
|
std::vector<char> solution;
|
||
|
std::map<char, std::pair<Path::const_iterator, Path::const_iterator>> subroutines;
|
||
|
|
||
|
if (!ComputeProgramLoop(path.begin(), path.end(), solution, subroutines)) {
|
||
|
throw std::runtime_error{"no solution found"};
|
||
|
}
|
||
|
|
||
|
std::string result;
|
||
|
for (char c : solution) {
|
||
|
result += c;
|
||
|
result += ',';
|
||
|
}
|
||
|
result.back() = '\n';
|
||
|
|
||
|
for (auto const& [_,v] : subroutines) {
|
||
|
result += RenderCommands(v.first, v.second);
|
||
|
result += '\n';
|
||
|
}
|
||
|
result += "n\n"; // no video feed
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
auto GatherScore(Machine m, std::string const& program) -> ValueType {
|
||
|
ValueType score {};
|
||
|
auto it = program.begin();
|
||
|
m.At(0) = 2; // put into fancy mode
|
||
|
Run(m,
|
||
|
[&]() -> ValueType {
|
||
|
if (it < program.end()) { return *it++; }
|
||
|
throw std::runtime_error{"insufficient input"};
|
||
|
},
|
||
|
[&](ValueType o) { score = o; });
|
||
|
return score;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
auto main(int argc, char** argv) -> int {
|
||
|
auto machine = Machine{ParseStream(aocpp::Startup(argc, argv))};
|
||
|
auto grid = GatherOutput(machine);
|
||
|
auto part1 = ScaffoldAlignments(grid);
|
||
|
auto path = ComputePath(grid);
|
||
|
auto program = ComputeProgram(path);
|
||
|
std::cout << "Part 1: " << part1 << std::endl;
|
||
|
|
||
|
auto part2 = GatherScore(std::move(machine), program);
|
||
|
std::cout << "Part 2: " << part2 << std::endl;
|
||
|
}
|