Initial structure, C++23 build, SQLite schema, and run script

This commit is contained in:
Maksim Koval 2026-08-14 10:14:28 +00:00
commit b3f037fb83
10 changed files with 420 additions and 0 deletions

17
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View file

0
docs/observations.md Normal file
View file

0
docs/research.md Normal file
View file

24
run.sh Normal file
View 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
View 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
View 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);