293 lines
8.8 KiB
C++
293 lines
8.8 KiB
C++
#include <boost/asio/any_io_executor.hpp>
|
|
#include <boost/asio/awaitable.hpp>
|
|
#include <boost/asio/co_spawn.hpp>
|
|
#include <boost/asio/connect.hpp>
|
|
#include <boost/asio/detached.hpp>
|
|
#include <boost/asio/io_context.hpp>
|
|
#include <boost/asio/ip/tcp.hpp>
|
|
#include <boost/asio/read_until.hpp>
|
|
#include <boost/asio/steady_timer.hpp>
|
|
#include <boost/asio/streambuf.hpp>
|
|
#include <boost/asio/use_awaitable.hpp>
|
|
#include <boost/asio/write.hpp>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <random>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
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<std::pair<int, int>, unsigned> known_map;
|
|
int field_width, field_height;
|
|
|
|
asio::awaitable<void> Protocol();
|
|
asio::awaitable<void> Join();
|
|
asio::awaitable<bool> Move(Direction dir);
|
|
unsigned AStarHeuristic(std::pair<int, int> position);
|
|
unsigned ShortestPath(int x, int y);
|
|
asio::awaitable<void> 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<void> Bot::Protocol() {
|
|
asio::ip::tcp::resolver resolver{executor};
|
|
asio::ip::tcp::resolver::query query{server, "4000"};
|
|
|
|
while (true) {
|
|
while (true) {
|
|
try {
|
|
auto host = co_await resolver.async_resolve(query, asio::use_awaitable);
|
|
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 == "game") {
|
|
iss >> field_width >> dummy >> field_height >> dummy >> 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<void> 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<bool> 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<int, int> position) {
|
|
return std::abs(position.first - goal_x) + std::abs(position.second - goal_y);
|
|
}
|
|
|
|
unsigned Bot::ShortestPath(int x, int y) {
|
|
// A*
|
|
std::set<std::pair<int, int>> visited;
|
|
std::multimap<unsigned, std::pair<int, int>> 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<unsigned>(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<unsigned>(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<unsigned>(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<unsigned>(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<void> Bot::ChooseMove() {
|
|
// update map
|
|
known_map[std::make_pair(x, y)] =
|
|
(up ? static_cast<unsigned>(Direction::UP) : 0) |
|
|
(right ? static_cast<unsigned>(Direction::RIGHT) : 0) |
|
|
(down ? static_cast<unsigned>(Direction::DOWN) : 0) |
|
|
(left ? static_cast<unsigned>(Direction::LEFT) : 0);
|
|
known_map[std::make_pair(x - 1, y)] |=
|
|
(left ? static_cast<unsigned>(Direction::RIGHT) : 0);
|
|
known_map[std::make_pair(x + 1, y)] |=
|
|
(right ? static_cast<unsigned>(Direction::LEFT) : 0);
|
|
known_map[std::make_pair(x, y - 1)] |=
|
|
(up ? static_cast<unsigned>(Direction::DOWN) : 0);
|
|
known_map[std::make_pair(x, y + 1)] |=
|
|
(down ? static_cast<unsigned>(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] << " <name> <pass> <server>" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
asio::io_context context;
|
|
Bot bot{context.get_executor(), argv[1], argv[2], argv[3]};
|
|
context.run();
|
|
return 0;
|
|
}
|