Initial commit
This commit is contained in:
commit
0bd849d409
|
@ -0,0 +1,3 @@
|
||||||
|
cmake-build-*
|
||||||
|
build
|
||||||
|
.idea
|
|
@ -0,0 +1,12 @@
|
||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
project(tsp)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
||||||
|
find_package(SDL2 COMPONENTS SDL2 REQUIRED)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
add_executable(tsp main.cpp renderwindow.cpp renderwindow.h)
|
||||||
|
target_link_libraries(tsp Qt6::Core Qt6::Widgets SDL2::SDL2)
|
|
@ -0,0 +1,29 @@
|
||||||
|
# tsp
|
||||||
|
|
||||||
|
An implementation of a Traveling Salesman Problem (TSP) solver in C++.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
* Qt 6
|
||||||
|
* SDL2
|
||||||
|
* CMake 3.25+
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tsp
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Consider the content of this repository licensed under the Unlicense, but
|
||||||
|
note that the dependencies may be licensed differently, and that some parts
|
||||||
|
of the code have been adapted from other sources (marked via comments).
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include "renderwindow.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication application(argc, argv);
|
||||||
|
|
||||||
|
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||||
|
|
||||||
|
if((SDL_Init(SDL_INIT_VIDEO) == -1)) {
|
||||||
|
std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto window = std::make_shared<RenderWindow>();
|
||||||
|
window->show();
|
||||||
|
|
||||||
|
int res = QApplication::exec();
|
||||||
|
|
||||||
|
if (res != 0) {
|
||||||
|
std::cerr << "QApplication::exec() returned " << res << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
//
|
||||||
|
// Created by natty on 15.7.23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "renderwindow.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <random>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<Strategy> strategyFromName(const std::string& name) {
|
||||||
|
if (name == "Anna") {
|
||||||
|
return std::make_unique<AnnaStrategy>();
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Unknown strategy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderWindow::RenderWindow() : window(nullptr), renderer(nullptr) {
|
||||||
|
this->mainWindowWidget = new QWidget();
|
||||||
|
this->layout = new QVBoxLayout(this->mainWindowWidget);
|
||||||
|
this->renderArea = new QWidget();
|
||||||
|
this->layout->addWidget(this->renderArea);
|
||||||
|
this->controlsLayout = new QHBoxLayout();
|
||||||
|
this->layout->addLayout(this->controlsLayout);
|
||||||
|
|
||||||
|
auto* strategyLabel = new QLabel("Strategy:");
|
||||||
|
this->controlsLayout->addWidget(strategyLabel);
|
||||||
|
|
||||||
|
this->strategySelector = new QComboBox();
|
||||||
|
this->strategySelector->addItem("Anna");
|
||||||
|
QObject::connect(this->strategySelector, &QComboBox::currentIndexChanged, this, &RenderWindow::resetState);
|
||||||
|
this->controlsLayout->addWidget(this->strategySelector);
|
||||||
|
|
||||||
|
this->controlsLayout->addStretch();
|
||||||
|
|
||||||
|
this->distanceLabel = new QLabel("Distance: 0");
|
||||||
|
this->controlsLayout->addWidget(this->distanceLabel);
|
||||||
|
|
||||||
|
this->controlsLayout->addStretch();
|
||||||
|
|
||||||
|
auto *resetButton = new QPushButton("Reset");
|
||||||
|
QObject::connect(resetButton, &QPushButton::clicked, this, &RenderWindow::resetState);
|
||||||
|
this->controlsLayout->addWidget(resetButton);
|
||||||
|
|
||||||
|
auto *hardResetButton = new QPushButton("Reset (new vertices)");
|
||||||
|
QObject::connect(hardResetButton, &QPushButton::clicked, this, &RenderWindow::hardResetState);
|
||||||
|
this->controlsLayout->addWidget(hardResetButton);
|
||||||
|
|
||||||
|
auto* solveButton = new QPushButton("Solve");
|
||||||
|
QObject::connect(solveButton, &QPushButton::clicked, this, &RenderWindow::solve);
|
||||||
|
this->controlsLayout->addWidget(solveButton);
|
||||||
|
|
||||||
|
this->playButton = new QPushButton("Play");
|
||||||
|
this->playButton->setCheckable(true);
|
||||||
|
QObject::connect(this->playButton, &QPushButton::toggled, this, &RenderWindow::togglePlay);
|
||||||
|
this->controlsLayout->addWidget(this->playButton);
|
||||||
|
|
||||||
|
auto* stepButton = new QPushButton("Step");
|
||||||
|
QObject::connect(stepButton, &QPushButton::clicked, this, &RenderWindow::step);
|
||||||
|
this->controlsLayout->addWidget(stepButton);
|
||||||
|
|
||||||
|
this->setCentralWidget(this->mainWindowWidget);
|
||||||
|
|
||||||
|
this->animTimer = new QTimer(this);
|
||||||
|
QObject::connect(this->animTimer, &QTimer::timeout, this, &RenderWindow::step);
|
||||||
|
|
||||||
|
this->renderArea->setUpdatesEnabled(false);
|
||||||
|
this->renderArea->setFixedSize(800, 800);
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT, "1");
|
||||||
|
|
||||||
|
this->window = SDL_CreateWindowFrom(reinterpret_cast<const void*>(this->renderArea->winId()));
|
||||||
|
|
||||||
|
if (this->window == nullptr) {
|
||||||
|
std::cerr << "Error creating SDL window: " << SDL_GetError() << std::endl;
|
||||||
|
throw std::runtime_error("Error creating SDL window");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->renderer = SDL_CreateRenderer(this->window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
|
if (this->renderer == nullptr) {
|
||||||
|
std::cerr << "Error creating SDL renderer: " << SDL_GetError() << std::endl;
|
||||||
|
throw std::runtime_error("Error creating SDL renderer");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->hardResetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::hardResetState()
|
||||||
|
{
|
||||||
|
this->state.vertices = {};
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
std::uniform_real_distribution<double> dist(100.0, 700.0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 100; i++) {
|
||||||
|
this->state.vertices.push_back({
|
||||||
|
.x = dist(gen),
|
||||||
|
.y = dist(gen)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this->resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::resetState() {
|
||||||
|
this->animTimer->stop();
|
||||||
|
|
||||||
|
this->strategy = strategyFromName(this->strategySelector->currentText().toStdString());
|
||||||
|
|
||||||
|
this->state.edges = {};
|
||||||
|
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::paintEvent(QPaintEvent* event)
|
||||||
|
{
|
||||||
|
QMainWindow::paintEvent(event);
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::render() {
|
||||||
|
SDL_SetRenderDrawColor(this->renderer, 0, 0, 0, 255);
|
||||||
|
SDL_RenderClear(this->renderer);
|
||||||
|
|
||||||
|
SDL_SetRenderDrawColor(this->renderer, 255, 255, 255, 255);
|
||||||
|
|
||||||
|
for (auto& vertex : this->state.vertices) {
|
||||||
|
SDL_Rect rect = {
|
||||||
|
static_cast<int>(vertex.x - 3),
|
||||||
|
static_cast<int>(vertex.y - 3),
|
||||||
|
6,
|
||||||
|
6
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_RenderFillRect(this->renderer, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
double dist = 0.0;
|
||||||
|
|
||||||
|
for (auto& edge : this->state.edges) {
|
||||||
|
SDL_SetRenderDrawColor(this->renderer, 255, 255, 0, 255);
|
||||||
|
|
||||||
|
dist += std::hypot(
|
||||||
|
this->state.vertices[edge.from].x - this->state.vertices[edge.to].x,
|
||||||
|
this->state.vertices[edge.from].y - this->state.vertices[edge.to].y
|
||||||
|
);
|
||||||
|
|
||||||
|
SDL_RenderDrawLine(
|
||||||
|
this->renderer,
|
||||||
|
static_cast<int>(this->state.vertices[edge.from].x),
|
||||||
|
static_cast<int>(this->state.vertices[edge.from].y),
|
||||||
|
static_cast<int>(this->state.vertices[edge.to].x),
|
||||||
|
static_cast<int>(this->state.vertices[edge.to].y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->distanceLabel->setText(QString("Distance: %1").arg(dist));
|
||||||
|
|
||||||
|
SDL_RenderPresent(this->renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::step() {
|
||||||
|
if (this->strategy->step(this->state)) {
|
||||||
|
this->animTimer->stop();
|
||||||
|
this->playButton->setChecked(false);
|
||||||
|
}
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::solve() {
|
||||||
|
if (this->state.edges.size() == this->state.vertices.size()) {
|
||||||
|
this->resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!this->strategy->step(this->state)) {}
|
||||||
|
|
||||||
|
this->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderWindow::~RenderWindow() {
|
||||||
|
if (this->renderer != nullptr) {
|
||||||
|
SDL_DestroyRenderer(this->renderer);
|
||||||
|
this->renderer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->window != nullptr) {
|
||||||
|
SDL_DestroyWindow(this->window);
|
||||||
|
this->window = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderWindow::togglePlay(bool play)
|
||||||
|
{
|
||||||
|
if (play) {
|
||||||
|
if (this->state.edges.size() == this->state.vertices.size()) {
|
||||||
|
this->resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->animTimer->start(50);
|
||||||
|
} else {
|
||||||
|
this->animTimer->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnnaStrategy::step(State& state) {
|
||||||
|
if (!this->init) {
|
||||||
|
this->init = true;
|
||||||
|
this->unvisited = {};
|
||||||
|
for (size_t i = 0; i < state.vertices.size(); i++) {
|
||||||
|
this->unvisited.push_back(i);
|
||||||
|
}
|
||||||
|
std::shuffle(this->unvisited.begin(), this->unvisited.end(), std::mt19937(std::random_device()()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.vertices.size() < 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.vertices.size() == 2) {
|
||||||
|
state.edges = {{
|
||||||
|
.from = 0,
|
||||||
|
.to = 1
|
||||||
|
}};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->unvisited.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (state.vertices.size() - this->unvisited.size() == 0) {
|
||||||
|
size_t a, b, c;
|
||||||
|
a = this->unvisited.back();
|
||||||
|
this->unvisited.pop_back();
|
||||||
|
b = this->unvisited.back();
|
||||||
|
this->unvisited.pop_back();
|
||||||
|
c = this->unvisited.back();
|
||||||
|
this->unvisited.pop_back();
|
||||||
|
|
||||||
|
state.edges = {{
|
||||||
|
.from = a,
|
||||||
|
.to = b
|
||||||
|
}, {
|
||||||
|
.from = b,
|
||||||
|
.to = c
|
||||||
|
}, {
|
||||||
|
.from = c,
|
||||||
|
.to = a
|
||||||
|
}};
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = this->unvisited.back();
|
||||||
|
this->unvisited.pop_back();
|
||||||
|
|
||||||
|
const auto& vertex = state.vertices[i];
|
||||||
|
|
||||||
|
auto& closest_edge = *std::min_element(state.edges.begin(), state.edges.end(), [&state, &vertex](const Edge& a, const Edge& b) {
|
||||||
|
return a.distanceOf(state, vertex) < b.distanceOf(state, vertex);
|
||||||
|
});
|
||||||
|
|
||||||
|
size_t old_to = closest_edge.to;
|
||||||
|
closest_edge.to = i;
|
||||||
|
state.edges.push_back({
|
||||||
|
.from = i,
|
||||||
|
.to = old_to
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Edge::distanceOf(const State& state, const Vertex& vertex) const {
|
||||||
|
// Adapted from https://stackoverflow.com/a/1501725
|
||||||
|
auto x1 = state.vertices[this->from].x;
|
||||||
|
auto y1 = state.vertices[this->from].y;
|
||||||
|
auto x2 = state.vertices[this->to].x;
|
||||||
|
auto y2 = state.vertices[this->to].y;
|
||||||
|
|
||||||
|
auto dx = x2 - x1;
|
||||||
|
auto dy = y2 - y1;
|
||||||
|
|
||||||
|
auto l2 = dx * dx + dy * dy;
|
||||||
|
|
||||||
|
constexpr auto epsilon = 0.0001;
|
||||||
|
|
||||||
|
if (l2 < epsilon)
|
||||||
|
return std::hypot(vertex.x - x1, vertex.y - y1);
|
||||||
|
|
||||||
|
const double t = std::max(0.0, std::min(1.0, ((vertex.x - x1) * dx + (vertex.y - y1) * dy) / l2));
|
||||||
|
|
||||||
|
const double projection_x = x1 + t * dx;
|
||||||
|
const double projection_y = y1 + t * dy;
|
||||||
|
|
||||||
|
return std::hypot(vertex.x - projection_x, vertex.y - projection_y);
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
//
|
||||||
|
// Created by natty on 15.7.23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TSP_RENDERWINDOW_H
|
||||||
|
#define TSP_RENDERWINDOW_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <qmainwindow.h>
|
||||||
|
#include <qwidget.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <random>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
struct State;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Edge {
|
||||||
|
size_t from;
|
||||||
|
size_t to;
|
||||||
|
|
||||||
|
double distanceOf(const State& state, const Vertex& vertex) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
std::vector<Vertex> vertices;
|
||||||
|
std::vector<Edge> edges;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Strategy {
|
||||||
|
public:
|
||||||
|
virtual ~Strategy() = default;
|
||||||
|
virtual bool step(State& state) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AnnaStrategy : public Strategy {
|
||||||
|
public:
|
||||||
|
bool step(State& state) override;
|
||||||
|
private:
|
||||||
|
bool init;
|
||||||
|
std::vector<size_t> unvisited;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
RenderWindow();
|
||||||
|
~RenderWindow() override;
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent* event) override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void render();
|
||||||
|
void step();
|
||||||
|
void solve();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout *layout;
|
||||||
|
QHBoxLayout *controlsLayout;
|
||||||
|
QWidget *renderArea;
|
||||||
|
QWidget *mainWindowWidget;
|
||||||
|
QLabel *distanceLabel;
|
||||||
|
QPushButton *playButton;
|
||||||
|
QComboBox *strategySelector;
|
||||||
|
QTimer *animTimer;
|
||||||
|
SDL_Window *window{};
|
||||||
|
SDL_Renderer* renderer{};
|
||||||
|
|
||||||
|
State state;
|
||||||
|
|
||||||
|
std::unique_ptr<Strategy> strategy;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void resetState();
|
||||||
|
void hardResetState();
|
||||||
|
void togglePlay(bool play);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //TSP_RENDERWINDOW_H
|
Loading…
Reference in New Issue