pretty-automata/src/options.cpp

184 lines
11 KiB
C++

#include "options.h"
#include "argparser.h"
#include "colormaps/colormaps.h"
#include "rule_presets.h"
#include <format>
#include <stdexcept>
#include <string>
static Color parse_hex_color(const char *begin, const char *end, const char *&parse_end, argparser::internal::parser_allow_undelimited) {
if (begin >= end) {
throw std::runtime_error("cannot parse empty string");
}
if (*begin == '#') {
throw std::runtime_error("input is not a hex color");
}
if (end - begin < 7) {
throw std::runtime_error("input is too short");
}
uint8_t red, green, blue;
auto res = std::from_chars(begin + 1, begin + 3, red, 16);
if (res.ec != std::errc{} || res.ptr < begin + 3) {
throw std::runtime_error("input is not a hex color");
}
res = std::from_chars(begin + 3, begin + 5, green, 16);
if (res.ec != std::errc{} || res.ptr < begin + 5) {
throw std::runtime_error("input is not a hex color");
}
res = std::from_chars(begin + 5, begin + 7, blue, 16);
if (res.ec != std::errc{} || res.ptr < begin + 7) {
throw std::runtime_error("input is not a hex color");
}
return Color::from_rgb(red, green, blue);
}
Options do_parse_arguments(const std::vector<std::string> &args) {
std::vector<std::string> colormap_names = {};
for (const auto &c : colormaps) {
colormap_names.push_back(c.first);
}
std::vector<std::string> preset_names = {};
for (const auto &preset_ruleset : rule_presets) {
preset_names.push_back(preset_ruleset.first);
}
argparser::parser p{};
auto uint8 = p.basic_type<uint8_t>("uint8");
auto uint16 = p.basic_type<uint16_t>("uint16");
auto uint32 = p.basic_type<uint32_t>("uint32");
auto doubleType = p.basic_type<double>("float");
auto neighbourcount = p.basic_type<uint8_t>("neighbourcount")->validate([](uint8_t val) { return val >= 0 && val <= 8; });
auto neighbourList = p.list_type("neighbourcount-list", neighbourcount);
auto sizeType = p.tuple_type("size", uint16, uint16);
auto boolType = p.enum_type<bool>("bool", {{"true", true}, {"false", false}});
auto hexColor = p.basic_type<Color>("hex-color", parse_hex_color);
auto rgbColor = p.tuple_type<Color>("rgb", std::make_tuple(uint8, uint8, uint8), [](uint8_t r, uint8_t g, uint8_t b) {
return Color::from_rgb(r, g, b);
});
auto normalizedRgbColor = p.tuple_type<Color>("rgb-n", std::make_tuple(doubleType, doubleType, doubleType), [](double r, double g, double b) {
return Color::from_rgb(r, g, b);
});
auto labColor = p.tuple_type<Color>("lab", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double a, double b) {
return Color{L, a, b, ColorSpace::CIELAB};
});
auto luvColor = p.tuple_type<Color>("luv", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double u, double v) {
return Color{L, u, v, ColorSpace::CIELUV};
});
auto oklabColor = p.tuple_type<Color>("oklab", std::make_tuple(doubleType, doubleType, doubleType), [](double L, double a, double b) {
return Color{L, a, b, ColorSpace::OKLAB};
});
auto color = p.union_type("color", hexColor, rgbColor, normalizedRgbColor, labColor, luvColor, oklabColor);
auto colorList = p.list_type("colorlist", color);
auto colormapScaleEnum = p.enum_type<ColorMapScale>("colormap-scale-enum", {{"inherent", ColorMapScale::INHERENT},
{"max-age", ColorMapScale::MAX_AGE},
{"global-max-age", ColorMapScale::GLOBAL_MAX_AGE}});
auto colormapScale = p.union_type("colormap-scale", colormapScaleEnum, uint16);
auto map = p.tuple_type<ColorMapData>("map", colorList);
auto colormapName = p.enum_type("colormap-name", colormap_names);
auto colormapDef = p.union_type("colormapdef", colormapName, map);
auto colormapInvert = p.enum_type<bool>("colormap-invert", {{"invert", true}});
auto colormap1 = p.tuple_type<ColorMapOption>("colormapopt1", colormapDef);
auto colormap2 = p.tuple_type<ColorMapOption>("colormapopt2", colormapDef, colormapScale);
auto colormap3 = p.tuple_type<ColorMapOption>("colormapopt3", colormapDef, colormapInvert);
auto colormap4 = p.tuple_type<ColorMapOption>("colormapopt4", colormapDef, colormapScale, colormapInvert);
auto colormap5 = p.tuple_type<ColorMapOption>("colormapopt5", colormapDef, colormapInvert, colormapScale);
auto colormap = p.union_type("colormapopt", colormap1, colormap2, colormap3, colormap4, colormap5);
auto colorOrColormap = p.union_type("color-or-colormap", color, colormap);
auto initRect = p.tuple_type<std::shared_ptr<GridInitialiser>>("rect", std::make_tuple(uint16, uint16),
[](uint16_t w, uint16_t h) { return std::make_shared<RectGridInitialiser>(w, h); });
auto initSquare = p.tuple_type<std::shared_ptr<GridInitialiser>>("square", std::make_tuple(uint16),
[](uint16_t w) { return std::make_shared<RectGridInitialiser>(w, w); });
;
auto initEllipse = p.tuple_type<std::shared_ptr<GridInitialiser>>("ellipse", std::make_tuple(uint16, uint16),
[](uint16_t a, uint16_t b) { return std::make_shared<EllipseGridInitialiser>(a, b); });
auto initCircle = p.tuple_type<std::shared_ptr<GridInitialiser>>("circle", std::make_tuple(uint16),
[](uint16_t r) { return std::make_shared<EllipseGridInitialiser>(r, r); });
auto initPerlin = p.tuple_type<std::shared_ptr<GridInitialiser>>("perlin", std::make_tuple(doubleType, doubleType),
[](double scale, double cutoff) { return std::make_shared<PerlinGridInitialiser>(scale, cutoff); });
auto init = p.union_type("init", initRect, initSquare, initEllipse, initCircle, initPerlin);
auto noMaxAge = p.enum_type<std::shared_ptr<MaxAgeProvider>>("none", {{"none", nullptr}});
auto uniformMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("uniform", std::make_tuple(uint16),
[](uint16_t v) { return std::make_shared<UniformMaxAgeProvider>(v); });
auto radialMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("radial", std::make_tuple(uint16, uint16),
[](uint16_t center, uint16_t corners) { return std::make_shared<RadialMaxAgeProvider>(center, corners); });
auto radialFitMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("radial-fit", std::make_tuple(uint16, uint16),
[](uint16_t center, uint16_t corners) { return std::make_shared<RadialFitMaxAgeProvider>(center, corners); });
auto perlinStaticMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("perlin-static", std::make_tuple(doubleType, uint16, uint16),
[](double scale, uint16_t min, uint16_t max) { return std::make_shared<PerlinStaticMaxAgeProvider>(scale, min, max); });
auto perlinDynamicMaxAge = p.tuple_type<std::shared_ptr<MaxAgeProvider>>("perlin-dynamic", std::make_tuple(doubleType, uint16, uint16, doubleType, uint16),
[](double scale, uint16_t min, uint16_t max, double time_scale, uint16_t time_step) { return std::make_shared<PerlinDynamicMaxAgeProvider>(scale, min, max, time_scale, time_step); });
auto maxAge = p.union_type("max-age", noMaxAge, uniformMaxAge, radialMaxAge, radialFitMaxAge, perlinStaticMaxAge, perlinDynamicMaxAge);
auto presetName = p.enum_type("preset-name", preset_names);
auto presetOpt = p.option("--preset", "-p", presetName);
auto sizeOpt = p.option("--size", "-s", sizeType)->default_value(std::make_tuple(32, 32));
auto birthOpt = p.option("--birth", "-b", neighbourList)->default_value({});
auto surviveOpt = p.option("--survive", "-u", neighbourList)->default_value({});
auto maxAgeOpt = p.option("--max-age", "-a", maxAge)->default_value(nullptr);
auto starveDelayOpt = p.option("--starve-delay", "-d", uint16)->default_value(0);
auto starveRecoverOpt = p.option("--starve-recover", "-r", boolType)->default_value(false);
auto initialiseOpt = p.option("--init", "-i", init)->default_value(std::make_shared<RectGridInitialiser>(4, 4));
auto displaySizeOpt = p.option("--display-size", "-S", sizeType)->default_value(std::make_tuple(256, 256));
auto fullscreenFlag = p.flag("--fullscreen", "-F");
auto colorAliveOpt = p.option("--color-alive", "-A", colorOrColormap)->default_value(Color::from_rgb(1., 1., 1.));
auto colorDeadOpt = p.option("--color-dead", "-D", color)->default_value(Color::from_rgb(0., 0., 0.));
auto generationDurationOpt = p.option("--generation-duration", "-G", uint16)->default_value(0);
auto blendFlag = p.flag("--blend", "-B");
auto pr = p.parse(args);
Options options{};
auto size = sizeOpt->get(pr);
options.automaton_options.width = std::get<0>(size);
options.automaton_options.height = std::get<1>(size);
if (initialiseOpt->has(pr)) {
options.automaton_options.initialiser = initialiseOpt->get(pr);
}
bool uses_preset = false;
if (presetOpt->has(pr)) {
uses_preset = true;
options.automaton_options.rule = rule_presets.at(presetOpt->get(pr));
}
if (birthOpt->has(pr) || !uses_preset) {
options.automaton_options.rule.birth = birthOpt->get(pr);
}
if (surviveOpt->has(pr) || !uses_preset) {
options.automaton_options.rule.survive = surviveOpt->get(pr);
}
if (maxAgeOpt->has(pr) || !uses_preset) {
options.automaton_options.rule.max_age = maxAgeOpt->get(pr);
}
if (starveDelayOpt->has(pr) || !uses_preset) {
options.automaton_options.rule.starve_delay = starveDelayOpt->get(pr);
}
if (starveRecoverOpt->has(pr) || !uses_preset) {
options.automaton_options.rule.starve_recover = starveRecoverOpt->get(pr);
}
auto displaySize = displaySizeOpt->get(pr);
options.display_options.width = std::get<0>(displaySize);
options.display_options.height = std::get<1>(displaySize);
options.display_options.fullscreen = fullscreenFlag->get(pr);
options.display_options.dead_color = colorDeadOpt->get(pr);
options.display_options.alive_color = colorAliveOpt->get(pr);
options.display_options.generation_duration_ms = generationDurationOpt->get(pr);
options.display_options.blend = blendFlag->get(pr);
return options;
}
Options parse_arguments(int argc, const char *const *argv) {
std::vector<std::string> args(argc - 1);
for (size_t i = 1; i < argc; ++i) {
args[i - 1] = std::string(argv[i]);
}
return do_parse_arguments(args);
}