#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace asio = boost::asio; enum class Direction : unsigned { UP = 1, RIGHT = 2, DOWN = 4, LEFT = 8, }; std::string_view DirectionToString(Direction direction) { switch (direction) { case Direction::UP: return "up"; case Direction::RIGHT: return "right"; case Direction::DOWN: return "down"; case Direction::LEFT: return "left"; } return "(invalid)"; } class Bot { asio::any_io_executor executor; asio::steady_timer timer; asio::ip::tcp::socket socket; std::string server; std::string name; std::string pass; std::random_device generator; int goal_x, goal_y; int x, y; bool up, right, down, left; std::map, unsigned> known_map; int field_width, field_height; asio::awaitable Protocol(); asio::awaitable Join(); asio::awaitable Move(Direction dir); unsigned AStarHeuristic(std::pair position); unsigned ShortestPath(int x, int y); asio::awaitable ChooseMove(); public: Bot(asio::any_io_executor executor, std::string_view name, std::string_view pass, std::string_view server); }; Bot::Bot(asio::any_io_executor executor, std::string_view name, std::string_view pass, std::string_view server) : executor{executor}, timer{executor}, socket{executor}, server{server}, name{name}, pass{pass} { asio::co_spawn(executor, std::bind(&Bot::Protocol, this), asio::detached); } asio::awaitable Bot::Protocol() { asio::ip::tcp::resolver resolver{executor}; asio::ip::tcp::resolver::query query{server, "4000"}; auto host = co_await resolver.async_resolve(query, asio::use_awaitable); while (true) { while (true) { try { co_await asio::async_connect(socket, host, asio::use_awaitable); break; } catch (std::exception &e) { std::cout << e.what() << std::endl; } timer.expires_after(std::chrono::seconds(1)); co_await timer.async_wait(asio::use_awaitable); } try { while (true) { asio::streambuf buf; co_await asio::async_read_until(socket, buf, '\n', asio::use_awaitable); std::istream is{&buf}; std::string line; while (std::getline(is, line)) { std::istringstream iss{line}; std::string field; char dummy; std::getline(iss, field, '|'); if (field == "motd") { std::getline(iss, field); std::cout << "-> MotD: " << field << std::endl; co_await Join(); } else if (field == "error") { std::getline(iss, field); std::cout << "-> Error: " << field << std::endl; } else if (field == "goal") { iss >> goal_x >> dummy >> goal_y; std::cout << "-> Goal is at (" << goal_x << ", " << goal_y << ")" << std::endl; known_map.clear(); } else if (field == "pos") { iss >> x >> dummy >> y >> dummy >> up >> dummy >> right >> dummy >> down >> dummy >> left; std::cout << "-> Position at (" << x << ", " << y << ")" << std::endl; #define X(b) (b ? #b ", " : "") std::cout << " Walls are (" << X(up) << X(right) << X(down) << X(left) << ")" << std::endl; #undef X co_await ChooseMove(); } else if (field == "win" || field == "lose") { int win, lose; iss >> win >> dummy >> lose; std::cout << "-> You " << field << '!' << std::endl; std::cout << " Score: " << win << '/' << lose << std::endl; } else { std::cout << "unhandled message: " << field << '|'; std::getline(iss, field); std::cout << field << std::endl; } } } } catch (std::exception &e) { std::cout << e.what() << std::endl; } } co_return; } asio::awaitable Bot::Join() { asio::streambuf buf; std::ostream os{&buf}; os << "join|" << name << '|' << pass << std::endl; std::cout << "<- Join as " << name << std::endl; co_await async_write(socket, buf.data(), asio::use_awaitable); co_return; } asio::awaitable Bot::Move(Direction direction) { asio::streambuf buf; std::ostream os{&buf}; if (direction == Direction::LEFT && left) { co_return false; } if (direction == Direction::UP && up) { co_return false; } if (direction == Direction::RIGHT && right) { co_return false; } if (direction == Direction::DOWN && down) { co_return false; } os << "move|" << DirectionToString(direction) << std::endl; std::cout << "<- Go " << DirectionToString(direction) << std::endl; co_await async_write(socket, buf.data(), asio::use_awaitable); co_return true; } unsigned Bot::AStarHeuristic(std::pair position) { return std::abs(position.first - goal_x) + std::abs(position.second - goal_y); } unsigned Bot::ShortestPath(int x, int y) { // A* std::set> visited; std::multimap> queue; auto goal = std::make_pair(goal_x, goal_y); queue.emplace(0, std::make_pair(x, y)); while (queue.begin() != queue.end()) { auto it = queue.begin(); auto distance = it->first; auto position = it->second; queue.erase(it); if (position == goal) { return distance; } if (visited.count(position)) { continue; } if (!(known_map[position] & static_cast(Direction::LEFT)) && position.first > 0) { auto pos = std::make_pair(position.first - 1, position.second); queue.emplace(distance + 1 + AStarHeuristic(pos), pos); } if (!(known_map[position] & static_cast(Direction::RIGHT)) && position.first < field_width) { auto pos = std::make_pair(position.first + 1, position.second); queue.emplace(distance + 1 + AStarHeuristic(pos), pos); } if (!(known_map[position] & static_cast(Direction::UP)) && position.second > 0) { auto pos = std::make_pair(position.first, position.second - 1); queue.emplace(distance + 1 + AStarHeuristic(pos), pos); } if (!(known_map[position] & static_cast(Direction::DOWN)) && position.second < field_height) { auto pos = std::make_pair(position.first, position.second + 1); queue.emplace(distance + 1 + AStarHeuristic(pos), pos); } visited.emplace(position); } // should never happen return -1; } asio::awaitable Bot::ChooseMove() { if (known_map.empty()) { field_width = x; field_height = y; } // update map known_map[std::make_pair(x, y)] = (up ? static_cast(Direction::UP) : 0) | (right ? static_cast(Direction::RIGHT) : 0) | (down ? static_cast(Direction::DOWN) : 0) | (left ? static_cast(Direction::LEFT) : 0); known_map[std::make_pair(x - 1, y)] |= (left ? static_cast(Direction::RIGHT) : 0); known_map[std::make_pair(x + 1, y)] |= (right ? static_cast(Direction::LEFT) : 0); known_map[std::make_pair(x, y - 1)] |= (up ? static_cast(Direction::DOWN) : 0); known_map[std::make_pair(x, y + 1)] |= (down ? static_cast(Direction::UP) : 0); // find best move Direction best; unsigned best_estimate = -1; if (!left) { unsigned estimate = ShortestPath(x - 1, y); if (estimate < best_estimate) { best = Direction::LEFT; best_estimate = estimate; } } if (!right) { unsigned estimate = ShortestPath(x + 1, y); if (estimate < best_estimate) { best = Direction::RIGHT; best_estimate = estimate; } } if (!up) { unsigned estimate = ShortestPath(x, y - 1); if (estimate < best_estimate) { best = Direction::UP; best_estimate = estimate; } } if (!down) { unsigned estimate = ShortestPath(x, y + 1); if (estimate < best_estimate) { best = Direction::DOWN; best_estimate = estimate; } } co_await Move(best); co_return; } int main(int argc, char **argv) { if (argc < 4) { std::cerr << "usage: " << argv[0] << " " << std::endl; return 1; } asio::io_context context; Bot bot{context.get_executor(), argv[1], argv[2], argv[3]}; context.run(); return 0; }