#include #include #include #include #include #include "../includes/render.hpp" #include "../includes/rules.hpp" #include "../includes/world.hpp" #include "../includes/types.hpp" #include "imgui.h" #include "raylib.h" #include "rlImGui.h" Vector2 snapToGrid(Vector2 screen, int cell_size) { return {static_cast(round(screen.x / cell_size) * cell_size), static_cast(round(screen.y / cell_size) * cell_size)}; } Vector2i screenToGrid(Vector2 screen, int cell_size) { return {static_cast(round(screen.x / cell_size)), static_cast(round(screen.y / cell_size))}; } int main(int ac, char **av) { // Load or default config const std::string config_file_name = "config.json"; nlohmann::json config_json; MenuState menu_state = MenuState::NONE; std::fstream config_file(config_file_name, std::ios::in | std::ios::out); if (!config_file.is_open()) { config_file.open(config_file_name, std::ios::out); if (!config_file) { std::cerr << "Failed to create config file, the settings will not be " "kepts after restart." << std::endl; return 1; } config_file.close(); config_file.open(config_file_name, std::ios::in | std::ios::out); config_file << "{}"; config_file.flush(); } // Try reading the configuration try { config_file.seekg(0); config_file >> config_json; } catch (const nlohmann::json::parse_error &e) { std::cerr << "An error occured while loading config : " << e.what() << std::endl; std::cerr << "Please fix the configuration or remove the config.json file." << std::endl; return 1; } config_file.close(); // Check config values or populate them if (!config_json.contains("cell_size") || (config_json["cell_size"] < 4 || config_json["cell_size"] > 100)) { config_json["cell_size"] = 10; } if (!config_json.contains("screen_width") || (config_json["screen_width"] < 800 || config_json["screen_width"] > 1920)) { config_json["screen_width"] = 800; } if (!config_json.contains("screen_height") || (config_json["screen_height"] < 600 || config_json["screen_height"] > 1200)) { config_json["screen_height"] = 600; } if (!config_json.contains("dark_theme")) { config_json["dark_theme"] = true; } if (!config_json.contains("fps") || (config_json["fps"] < 0 || config_json["fps"] > 30)) { config_json["fps"] = 800; } InitWindow(config_json["screen_width"], config_json["screen_height"], &av[0][2]); SetTargetFPS(60); rlImGuiSetup(config_json["dark_theme"]); // Selection window RenderTexture2D selectionTexture = LoadRenderTexture(200, 200); // Initialize objects World world(config_json["screen_width"].get() / config_json["cell_size"].get(), config_json["screen_height"].get() / config_json["cell_size"].get()); Rules rules = Rules(); Render render(config_json["cell_size"]); // Imgui control menu int fps_ctrl = config_json["fps"].get(); int cell_size_ctrl = config_json["cell_size"].get(); bool play_ctrl = true; bool step_ctrl = false; bool step_back_ctrl = false; bool rand_ctrl = false; bool edit_ctrl = false; bool clear_ctrl = false; bool settings_window = false; int width_ctrl = config_json["screen_width"].get(); int height_ctrl = config_json["screen_height"].get(); bool dark_theme_ctrl = config_json["dark_theme"].get(); bool apply_ctrl = false; // Speed handling values float sim_speed = 1.0f; float deltaTimeAccumulator = 0.0f; float timePerUpdate = 1.0f / 10.0f; // Selection window Vector2 sel_pos = {0., 0.}; bool selecting = false; std::vector sel_data = {}; bool sel_ctrl = false; char patern_name[255]; patern_name[0] = '\0'; std::string sel_txt_input_hint("patern name"); // Setups rules.setup(&world); // Diplay generations while (!WindowShouldClose()) { if (menu_state == MenuState::PLAY || menu_state == MenuState::EDIT) { if (!play_ctrl && !edit_ctrl) { menu_state = MenuState::NONE; } } // Frames shinenigans float deltaTime = GetFrameTime(); sim_speed = fps_ctrl / 10.0f; timePerUpdate = (1.0f / 10.0f) / sim_speed; auto gesture = GetGestureDetected(); auto mousePos = GetMousePosition(); if (rand_ctrl) { world.randomize(); rand_ctrl = false; } if (clear_ctrl) { world.clear(); clear_ctrl = false; } if (edit_ctrl && play_ctrl) { if (menu_state == MenuState::PLAY) { menu_state = MenuState::EDIT; play_ctrl = false; } else if (menu_state == MenuState::EDIT) { menu_state = MenuState::PLAY; edit_ctrl = false; } } else if (play_ctrl) { menu_state = MenuState::PLAY; } else if (edit_ctrl) { menu_state = MenuState::EDIT; } if (edit_ctrl && IsMouseButtonPressed(0) && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { menu_state = MenuState::EDIT; world.setCell(mousePos.x / config_json["cell_size"].get(), mousePos.y / config_json["cell_size"].get()); } // Selection behaviour if (!edit_ctrl && !play_ctrl) { if (gesture == GESTURE_TAP && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { sel_pos = snapToGrid(mousePos, cell_size_ctrl); selecting = true; } if (ImGui::IsMouseReleased(0) && selecting == true && mousePos.x >=0 && mousePos.x < width_ctrl && mousePos.y >=0 && mousePos.y < height_ctrl ) { selecting = false; Vector2i p1 = screenToGrid(sel_pos, cell_size_ctrl); Vector2i p2 = screenToGrid(mousePos, cell_size_ctrl); // Get origin Vector2i orig = { (p1.x < p2.x)?p1.x:p2.x, (p1.y < p2.y)?p1.y:p2.y, }; // Get selection size Vector2i sel_size = { (p1.x > p2.x)?p1.x-p2.x:p2.x-p1.x, (p1.y > p2.y)?p1.y-p2.y:p2.y-p1.y, }; // Ensure there is at least one cell selected if (!(sel_size.x == 0 || sel_size.y == 0)) { sel_data = std::move(world.getSelection(orig, sel_size)); sel_ctrl = true; } } } if (!sel_ctrl) { patern_name[0] = '\0'; } if (step_ctrl) { world.saveCompressed(); rules.update(); step_ctrl = false; } if (step_back_ctrl) { world.stepBack(); step_back_ctrl = false; } if (apply_ctrl) { bool resize_needed = false; config_json["fps"] = fps_ctrl; if (config_json["screen_width"].get() != width_ctrl || config_json["screen_height"].get() != height_ctrl) { config_json["screen_width"] = width_ctrl; config_json["screen_height"] = height_ctrl; rlImGuiShutdown(); SetWindowSize(width_ctrl, height_ctrl); rlImGuiSetup(dark_theme_ctrl); resize_needed = true; } if (cell_size_ctrl != config_json["cell_size"].get() || resize_needed) { world.resize(config_json["screen_width"].get() / cell_size_ctrl, config_json["screen_height"].get() / cell_size_ctrl); render.updateCellSize(cell_size_ctrl); rules.newWorld(&world); config_json["cell_size"] = cell_size_ctrl; } if (dark_theme_ctrl != config_json["dark_theme"]) { rlImGuiShutdown(); rlImGuiSetup(dark_theme_ctrl); config_json["dark_theme"] = dark_theme_ctrl; } apply_ctrl = false; settings_window = false; } // Accumulate time and update simulation at the adjusted speed deltaTimeAccumulator += deltaTime; if (deltaTimeAccumulator >= timePerUpdate) { // Reset accumulator deltaTimeAccumulator -= timePerUpdate; if (menu_state == MenuState::PLAY) { world.saveCompressed(); rules.update(); } } BeginDrawing(); ClearBackground(BLACK); render.display(&world); if (selecting) { auto grid_mouse = snapToGrid(mousePos, cell_size_ctrl); bool sel_x_less = (sel_pos.x < grid_mouse.x); bool sel_y_less = (sel_pos.y < grid_mouse.y); DrawRectangleLines(((sel_x_less)? sel_pos.x: grid_mouse.x), ((sel_y_less)? sel_pos.y: grid_mouse.y), ((sel_x_less)? grid_mouse.x - sel_pos.x : sel_pos.x - grid_mouse.x), ((sel_y_less)? grid_mouse.y - sel_pos.y : sel_pos.y - grid_mouse.y), RED); } // Start ImGui frame rlImGuiBegin(); bool control_panel_open = true; ImGuiWindowFlags control_panel_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; ImVec2 menu_pos(3, 3); ImGui::SetNextWindowPos(menu_pos, ImGuiCond_Always); ImGui::Begin("Control Panel", &control_panel_open, control_panel_flags); ImGui::Checkbox("Play", &play_ctrl); if (ImGui::Button("Step")) { step_ctrl = true; } if (ImGui::Button("Step Back")) { step_back_ctrl = true; } ImGui::Checkbox("Edit", &edit_ctrl); ImGui::Checkbox("Clear", &clear_ctrl); ImGui::Checkbox("Randomize", &rand_ctrl); if (ImGui::Button((settings_window) ? "Hide settings" : "Show settings")) { settings_window = !settings_window; } ImGui::Text("Generation: %zu", world.getCycle()); ImGui::End(); if (settings_window) { ImGuiWindowFlags settings_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; ImGui::Begin("Settings", &settings_window, settings_flags); ImGui::SliderInt("Window Width", &width_ctrl, 800, 1920); ImGui::SliderInt("Window Height", &height_ctrl, 600, 1200); ImGui::SliderInt("FPS", &fps_ctrl, 0, 30); ImGui::SliderInt("Cell Size", &cell_size_ctrl, 4, 100); ImGui::Checkbox("Dark Theme", &dark_theme_ctrl); if (ImGui::Button("Save & Apply")) { apply_ctrl = true; } ImGui::End(); } if (sel_ctrl) { BeginTextureMode(selectionTexture); ClearBackground(BLACK); auto max_size = (sel_data[0] > sel_data[1]) ? sel_data[0] : sel_data[1]; int fitted_width = 200 / max_size; auto sel_it = sel_data.begin(); sel_it += 2; // skip dimensions for (int j = 0; j < sel_data[1]; j++) { for (int i = 0; i < sel_data[0]; i++) { if (*sel_it == 1) { DrawRectangle(i * fitted_width, (sel_data[1] -j-1) * fitted_width, fitted_width, fitted_width, WHITE); } sel_it++; } } EndTextureMode(); ImGuiWindowFlags settings_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize; ImGui::Begin("Selection", &sel_ctrl, settings_flags); rlImGuiImageSize(&selectionTexture.texture, 200, 200); ImGui::InputText(sel_txt_input_hint.c_str(), patern_name, 255); if (ImGui::Button("Save")) { char path_buf[1024]; ssize_t len = readlink("/proc/self/exe", path_buf, sizeof(path_buf)-1); if (len != -1) { // Create paterns dir if not present std::filesystem::path paterns_dir = std::filesystem::path(path_buf).parent_path() / "paterns"; if (!std::filesystem::exists(paterns_dir) && !std::filesystem::create_directory(paterns_dir)) { std::cerr << "Failed to create paterns directory" << std::endl; } else { // Could be optimized by early returning in a function std::ofstream patern_file; paterns_dir += '/'; paterns_dir += patern_name; patern_file.open(paterns_dir); if (!patern_file) { std::cerr << "Failed to create the patern file" << std::endl; } else { auto sel_it = sel_data.begin(); sel_it += 2; // skip dimensions patern_file << sel_data[0]; patern_file << "|"; // Separator needed to split as ascii values patern_file << sel_data[1]; patern_file << "|"; // Separator needed to split as ascii values for (int j = 0; j < sel_data[1]; j++) { for (int i = 0; i < sel_data[0]; i++) { patern_file << std::to_string(*sel_it); if (*sel_it == 1) { } sel_it++; } } patern_file << std::flush; patern_file.close(); } } sel_ctrl = false; } } ImGui::SameLine(); if (ImGui::Button("Discard")) { sel_ctrl = false; } ImGui::End(); } // End ImGui frame rlImGuiEnd(); EndDrawing(); } config_file.open(config_file_name, std::ios::out | std::ios::trunc); config_file << config_json.dump(2); config_file.close(); // Cleanup Selection texture UnloadRenderTexture(selectionTexture); rlImGuiShutdown(); CloseWindow(); return 0; }