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