Initial structure, C++23 build, SQLite schema, and run script
This commit is contained in:
commit
b3f037fb83
10 changed files with 420 additions and 0 deletions
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Build artifacts
|
||||
/build/
|
||||
/bin/
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Storage and databases
|
||||
/storage/*.db
|
||||
/storage/*.sqlite
|
||||
/storage/*.log
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
|
||||
# IDE / OS files
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
15
CMakeLists.txt
Normal file
15
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
cmake_minimum_required(VERSION 3.12)
|
||||
project(BugLabOptimizer CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
# Оптимизации
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall")
|
||||
|
||||
add_executable(optimizer src/main.cpp)
|
||||
17
Makefile
Normal file
17
Makefile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
BUILD_DIR = build
|
||||
|
||||
all: build
|
||||
|
||||
# Конфигурация через CMake
|
||||
configure:
|
||||
cmake -S . -B $(BUILD_DIR)
|
||||
|
||||
# Сборка
|
||||
build: configure
|
||||
cmake --build $(BUILD_DIR)
|
||||
|
||||
# Очистка
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
||||
.PHONY: all configure build clean
|
||||
38
README.md
Normal file
38
README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# BugLab - Maze Pathfinding & Optimization Project
|
||||
|
||||
C++ pathfinding and maze optimization project for [buglab.ru](https://buglab.ru/).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```text
|
||||
buglab/
|
||||
├── CMakeLists.txt # CMake build configuration (C++23)
|
||||
├── Makefile # Build shortcuts (wraps CMake)
|
||||
├── run.sh # Background runner with cpulimit (160% CPU)
|
||||
├── README.md # Project documentation
|
||||
├── src/
|
||||
│ └── main.cpp # Tabu Search optimizer engine
|
||||
├── docs/
|
||||
│ ├── ideas.md # Short-term ideas and backlog
|
||||
│ ├── observations.md # Key heuristics and insights
|
||||
│ ├── research.md # General research and milestone notes
|
||||
│ └── experiments/ # Detailed logs for specific experiments
|
||||
└── storage/
|
||||
├── labyrinths.db # SQLite database for saved labyrinths & metadata
|
||||
└── schema.sql # Database schema definition
|
||||
```
|
||||
|
||||
## Build & Run
|
||||
|
||||
1. **Build the project:**
|
||||
```bash
|
||||
make
|
||||
```
|
||||
2. **Run optimizer in background with resource limit:**
|
||||
```bash
|
||||
./run.sh ./build/optimizer
|
||||
```
|
||||
3. **Clean build artifacts:**
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
0
docs/ideas.md
Normal file
0
docs/ideas.md
Normal file
0
docs/observations.md
Normal file
0
docs/observations.md
Normal file
0
docs/research.md
Normal file
0
docs/research.md
Normal file
24
run.sh
Normal file
24
run.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Использование: $0 <путь_к_бинарнику> [путь_к_логу]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BINARY="$1"
|
||||
LOG_FILE="${2:-/home/node/forge/buglab/storage/optimizer.log}"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "[!] Бинарник '$BINARY' не найден!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Запуск в фоне
|
||||
nohup "$BINARY" > "$LOG_FILE" 2>&1 &
|
||||
PID=$!
|
||||
echo "[*] Процесс '$BINARY' запущен с PID: $PID"
|
||||
echo "[*] Логи пишутся в: $LOG_FILE"
|
||||
|
||||
# Лимит 160% CPU (80% от каждого из двух ядер)
|
||||
cpulimit -p $PID -l 160 -z -b > /dev/null 2>&1
|
||||
echo "[+] cpulimit успешно привязан к PID $PID (лимит 160%)"
|
||||
286
src/main.cpp
Executable file
286
src/main.cpp
Executable file
|
|
@ -0,0 +1,286 @@
|
|||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef uint8_t dir;
|
||||
typedef int16_t ind;
|
||||
typedef uint64_t val;
|
||||
|
||||
auto now = chrono::high_resolution_clock::now;
|
||||
|
||||
constexpr ind n = 19, m = 29, k = __lg(m - 1) + 1, w = 1 << k, sz = (n + 2) << k;
|
||||
constexpr ind dx[] = {w, 1, -w, -1};
|
||||
constexpr val wall = val(1) << 63;
|
||||
|
||||
constexpr ind to(ind x, ind y) { return (x << k) | y; }
|
||||
|
||||
constexpr auto neighbors = [] {
|
||||
array<array<ind, 4>, sz> neighbors{};
|
||||
for (ind i = 0; i < n * m; i++) {
|
||||
ind x = to(i / 29 + 1, i % 29 + 1);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
neighbors[x][j] = x + dx[j];
|
||||
}
|
||||
}
|
||||
return neighbors;
|
||||
}();
|
||||
|
||||
struct field {
|
||||
struct record { ind x = 0; val score = 0; };
|
||||
|
||||
array<val, sz> data{};
|
||||
val score = 0;
|
||||
bool calced = false;
|
||||
record backup;
|
||||
|
||||
field() {
|
||||
for (ind i = 1; i <= n; i++) data[to(i, 0)] = data[to(i, m + 1)] = wall;
|
||||
for (ind j = 1; j <= m; j++) data[to(0, j)] = data[to(n + 1, j)] = wall;
|
||||
}
|
||||
|
||||
void clear() { for (ind i = 0; i < sz; i++) data[i] &= wall; }
|
||||
|
||||
val get_score() { if (!score) calc_score(); return score; }
|
||||
|
||||
void inv(ind x, ind y) { inv(to(x, y)); }
|
||||
|
||||
void inv(ind x) {
|
||||
backup = {x, score};
|
||||
data[x] = (data[x] & wall) ^ wall;
|
||||
if (!calced) {
|
||||
score = 0;
|
||||
return;
|
||||
}
|
||||
for (dir i = 0; i < 4; i++) {
|
||||
if (data[neighbors[x][i]] & (wall - 1)) {
|
||||
score = 0;
|
||||
calced = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void undo() {
|
||||
auto [x, prev_score] = backup;
|
||||
inv(x);
|
||||
score = prev_score;
|
||||
}
|
||||
|
||||
bool check() {
|
||||
if (calced) return true;
|
||||
array<bool, sz> used;
|
||||
for (ind i = 0; i < sz; i++) used[i] = data[i] & wall;
|
||||
if (used[to(1, 1)] || used[to(n, m)]) return false;
|
||||
queue<ind> q;
|
||||
q.push(to(1, 1));
|
||||
used[to(1, 1)] = true;
|
||||
while (!q.empty()) {
|
||||
auto x = q.front();
|
||||
q.pop();
|
||||
if (x == to(n, m)) return true;
|
||||
for (dir i = 0; i < 4; i++) {
|
||||
if (auto xn = neighbors[x][i]; !used[xn]) {
|
||||
used[xn] = true;
|
||||
q.push(xn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void calc_score() {
|
||||
clear();
|
||||
ind x = to(1, 1);
|
||||
dir prev = 0;
|
||||
while (x ^ to(n, m)) {
|
||||
data[x]++;
|
||||
auto cur = prev;
|
||||
for (dir i = 0; i < 4; i++) {
|
||||
if (data[neighbors[x][i]] < data[neighbors[x][cur]]) {
|
||||
cur = i;
|
||||
}
|
||||
}
|
||||
x = neighbors[x][cur];
|
||||
prev = cur;
|
||||
}
|
||||
score = 0;
|
||||
for (ind i = 0; i < sz; i++) score += data[i] & (wall - 1);
|
||||
calced = true;
|
||||
}
|
||||
|
||||
ind operator^(const field& other) {
|
||||
ind cnt = 0;
|
||||
for (ind i = 0; i < sz; i++) cnt += (data[i] ^ other.data[i]) >> 63;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
friend istream& operator>>(istream& in, field& f) {
|
||||
for (ind i = 1; i <= n; i++) {
|
||||
for (ind j = 1; j <= m; j++) {
|
||||
char c;
|
||||
in >> c;
|
||||
f.data[to(i, j)] = c == '#' ? wall : 0;
|
||||
}
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
friend ostream& operator<<(ostream& out, field& f) {
|
||||
for (ind i = 1; i <= n; i++) {
|
||||
for (ind j = 1; j <= m; j++) {
|
||||
out << (f.data[to(i, j)] & wall ? '#' : '.');
|
||||
}
|
||||
out << '\n';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
field get_random_field() {
|
||||
std::random_device rd;
|
||||
std::seed_seq seed{rd(), rd(), rd(), rd()};
|
||||
std::mt19937 gen(seed);
|
||||
std::uniform_int_distribution<> dist(0, 2);
|
||||
field f;
|
||||
for (ind i = 1; i < n * m - 1; i++) {
|
||||
ind x = to(i / 29 + 1, i % 29 + 1);
|
||||
if (dist(gen)) continue;
|
||||
f.inv(x);
|
||||
if (!f.check()) f.undo();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
struct optimizer {
|
||||
std::mt19937 gen;
|
||||
std::uniform_real_distribution<> rdst;
|
||||
std::uniform_int_distribution<> idst;
|
||||
|
||||
field f, best;
|
||||
|
||||
optimizer() : rdst(0, 1), idst(1, n * m - 2) {
|
||||
std::random_device rd;
|
||||
std::seed_seq seed{rd(), rd(), rd(), rd()};
|
||||
gen.seed(seed);
|
||||
}
|
||||
|
||||
void update_best() {
|
||||
if (f.get_score() > best.get_score()) {
|
||||
best = f;
|
||||
ofstream out("best.txt");
|
||||
out << best.get_score() << '\n';
|
||||
out << best;
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
void set_field(field new_f) {
|
||||
f = new_f;
|
||||
if (f.get_score() > best.get_score()) best = f;
|
||||
}
|
||||
|
||||
int random_cell() { ind x = idst(gen); return to(x / 29 + 1, x % 29 + 1);}
|
||||
};
|
||||
|
||||
struct annealing : optimizer {
|
||||
double t = 1;
|
||||
double dt = 0.999;
|
||||
|
||||
void run() { while (true) step(); }
|
||||
|
||||
bool cond(val cur, val next) {
|
||||
auto p = 1.0 * (cur - next) / cur;
|
||||
return next < best.get_score() / 2 || (next < cur && rdst(gen) < exp(-10 * p / t));
|
||||
}
|
||||
|
||||
bool step() {
|
||||
ind x = random_cell();
|
||||
auto cur_score = f.get_score();
|
||||
f.inv(x);
|
||||
if (!f.check()) { f.undo(); return false; }
|
||||
auto new_score = f.get_score();
|
||||
if (cond(cur_score, new_score)) { f.undo(); return true; }
|
||||
if (f.get_score() > best.get_score()) best = f;
|
||||
cout << f.get_score() << ' ' << t << '\n';
|
||||
t *= dt;
|
||||
if (t < 0.01) t = 1;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct tabusearch : optimizer {
|
||||
vector<ind> blocked;
|
||||
int next_blocked = 0;
|
||||
vector<bool> used;
|
||||
|
||||
uint64_t ksteps = 0;
|
||||
chrono::time_point<chrono::high_resolution_clock> prev_log;
|
||||
|
||||
tabusearch(int max_used = 50) : blocked(max_used), used(n * m) {
|
||||
used[0] = used[n * m - 1] = true;
|
||||
prev_log = chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void run() { while (true) step(); }
|
||||
|
||||
void step() {
|
||||
ind best_step;
|
||||
field best_local;
|
||||
for (ind i = 1; i < n * m - 1; i++) {
|
||||
if (used[i]) continue;
|
||||
ind x = to(i / 29 + 1, i % 29 + 1);
|
||||
f.inv(x);
|
||||
if (!f.check()) { f.undo(); continue; }
|
||||
if (best_local.get_score() < f.get_score()) {
|
||||
best_local = f;
|
||||
best_step = i;
|
||||
}
|
||||
f.undo();
|
||||
}
|
||||
used[blocked[next_blocked]] = false;
|
||||
used[blocked[next_blocked] = best_step] = true;
|
||||
next_blocked = (next_blocked + 1) % blocked.size();
|
||||
f = std::move(best_local);
|
||||
if (f.get_score() > best.get_score()) {
|
||||
update_best();
|
||||
cout << "new best: " << best.get_score() << '\n';
|
||||
ksteps = 0;
|
||||
}
|
||||
|
||||
ksteps++;
|
||||
if (chrono::duration_cast<chrono::minutes>(now() - prev_log).count() > 15) {
|
||||
cerr << ksteps << ' ' << f.get_score() << ' ' << (f ^ best) << ' ' << best.get_score() << '\n';
|
||||
prev_log = now();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// int k = 250;
|
||||
// field f;
|
||||
// ifstream in("field.txt");
|
||||
// in >> f;
|
||||
// in.close();
|
||||
// val score;
|
||||
// auto start = now();
|
||||
// for (int i = 0; i < k; i++) {
|
||||
// score = f.get_score();
|
||||
// f.score = 0;
|
||||
// }
|
||||
// auto duration = chrono::duration_cast<chrono::milliseconds>(now() - start);
|
||||
// cout << score << '\n';
|
||||
// cout << duration.count() / k << '\n';
|
||||
|
||||
field f = get_random_field();
|
||||
if (argc > 1) {
|
||||
string filename = argv[1];
|
||||
ifstream in(filename);
|
||||
int score;
|
||||
in >> score >> f;
|
||||
in.close();
|
||||
}
|
||||
tabusearch algo;
|
||||
algo.set_field(f);
|
||||
algo.run();
|
||||
return 0;
|
||||
}
|
||||
23
storage/schema.sql
Normal file
23
storage/schema.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
-- Database schema for buglab labyrinths storage
|
||||
|
||||
CREATE TABLE IF NOT EXISTS labyrinths (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
score INTEGER NOT NULL,
|
||||
field_blob BLOB NOT NULL,
|
||||
width INTEGER DEFAULT 29,
|
||||
height INTEGER DEFAULT 19,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS labyrinth_tags (
|
||||
labyrinth_id INTEGER REFERENCES labyrinths(id) ON DELETE CASCADE,
|
||||
tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (labyrinth_id, tag_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_labyrinths_score ON labyrinths(score DESC);
|
||||
Loading…
Add table
Reference in a new issue