From b3f037fb836516d646e9068bc0b5847a5b366172 Mon Sep 17 00:00:00 2001 From: Maksim Koval Date: Fri, 14 Aug 2026 10:14:28 +0000 Subject: [PATCH] Initial structure, C++23 build, SQLite schema, and run script --- .gitignore | 17 +++ CMakeLists.txt | 15 +++ Makefile | 17 +++ README.md | 38 ++++++ docs/ideas.md | 0 docs/observations.md | 0 docs/research.md | 0 run.sh | 24 ++++ src/main.cpp | 286 +++++++++++++++++++++++++++++++++++++++++++ storage/schema.sql | 23 ++++ 10 files changed, 420 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 docs/ideas.md create mode 100644 docs/observations.md create mode 100644 docs/research.md create mode 100644 run.sh create mode 100755 src/main.cpp create mode 100644 storage/schema.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4605dce --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5418eb1 --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d733e9 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..65d78bd --- /dev/null +++ b/README.md @@ -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 + ``` diff --git a/docs/ideas.md b/docs/ideas.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/observations.md b/docs/observations.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/research.md b/docs/research.md new file mode 100644 index 0000000..e69de29 diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..7a0e36f --- /dev/null +++ b/run.sh @@ -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%)" diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..53e841a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,286 @@ +#include + +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, 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 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 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 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 blocked; + int next_blocked = 0; + vector used; + + uint64_t ksteps = 0; + chrono::time_point 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(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(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; +} diff --git a/storage/schema.sql b/storage/schema.sql new file mode 100644 index 0000000..90b46e2 --- /dev/null +++ b/storage/schema.sql @@ -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);