123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- #include <iostream>
- #include <string>
- #include <cassert>
- #include "simple/geom.hpp"
- #include "simple/support/range.hpp"
- #include "simple/support/algorithm.hpp"
- #include "simple/support/vuple.hpp"
- #include "ecs.hpp"
- using namespace std::literals;
- namespace support = simple::support;
- using int2 = simple::geom::vector<int,2>;
- using int2_range = support::range<int2>;
- using int_rect = simple::geom::segment<int2>;
- struct motion
- {
- int2 destination;
- };
- enum class pixel_color : unsigned char
- { black, red, green, yellow, blue, magenta, cyan, white };
- enum class pixel_intensity : unsigned char
- { normal, high, low };
- struct pixel_properties
- {
- pixel_color fg = pixel_color::white;
- pixel_color bg = pixel_color::black;
- pixel_intensity intensity = pixel_intensity::normal;
- constexpr bool operator==(const pixel_properties other) noexcept
- { return fg == other.fg && bg == other.bg && intensity == other.intensity; }
- constexpr bool operator!=(const pixel_properties other) noexcept
- { return not((*this) == other); }
- };
- struct pixel
- {
- char c = ' ';
- pixel_properties p = {};
- };
- struct texture
- {
- std::string sprite;
- pixel_properties tint = {};
- bool mirror = false;
- bool tile = false;
- };
- using ui_entities = entities<int_rect, texture, motion>;
- struct ui_context
- {
- std::vector<std::string> guy_sprites;
- ui_entities everything = {};
- };
- class battle_ui
- {
- enum { force1, force2, grid };
- ui_context* ui;
- support::range<entity_id_t> blocks;
- int2_range make_force(entity_id_t block_id, int2 position, const game::force& gamestate)
- {
- int2_range force_bounds{position, position};
- position += 1; // padding
- assert(gamestate.guys.size() == ui->guy_sprites.size());
- for(auto&& [guy, guy_sprite] : support::range{
- // TODO: support::vrange, make_vrange? zip_range??
- support::vbegin(gamestate.guys, ui->guy_sprites),
- support::vend(gamestate.guys, ui->guy_sprites)
- })
- {
- auto& [guy_count, guy_color] = guy;
- if(guy_count != 0)
- {
- auto guy_rect = ui->everything.make(block_id, [&, guy_color=guy_color](ui_entities::components_type& c)
- {
- c.push(texture{ guy_sprite, {pixel_color(unsigned(guy_color))} });
- return c.push(int_rect{{1, 1}, position});
- }).release().components;
- auto count_rect = ui->everything.make(block_id, [&, guy_count=guy_count](ui_entities::components_type& c)
- {
- auto& txtr = c.push(texture{ " " + std::to_string(guy_count)});
- return c.push(int_rect{{int(txtr.sprite.size()), 1}, position + guy_rect.size * int2::i()}); // horizontal layout
- }).release().components;
- force_bounds.upper() = max(int2_range(guy_rect).upper(), int2_range(count_rect).upper()); // bounds
- position += max(guy_rect.size, count_rect.size) * int2::j(); // vertical layout
- }
- }
- force_bounds.upper().x() += 1; // padding
- return force_bounds;
- }
- public:
- // FIXME: std any can't handle this... -_-
- // battle_ui(const battle_ui&) = delete;
- // battle_ui(battle_ui&&) = delete;
- // battle_ui& operator=(const battle_ui&) = delete;
- // battle_ui& operator=(battle_ui&&) = delete;
- battle_ui(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); }
- battle_ui(battle_ui&&) { assert(false && "battle_ui shouldn't move"); }
- battle_ui& operator=(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); }
- battle_ui& operator=(battle_ui&&) { assert(false && "battle_ui shouldn't move"); }
- battle_ui(int2_range layout_context, game::battle& gamestate, ui_context& ui) : ui(&ui), blocks{}
- {
- // create da blocks
- blocks.lower() = this->ui->everything.next_block_id();
- blocks.upper() = blocks.lower();
- auto pos = layout_context.lower();
- auto left_bounds = make_force(blocks.upper(), pos, gamestate.forces[force1]);
- pos += (left_bounds.upper() - pos) * int2::i();
- int2 grid_size {gamestate.field_size * int2{2,1}};
- int2 grid_bounds {grid_size
- + int2::i() // separator
- };
- auto grid_pos = pos;
- pos += grid_bounds * int2::i();
- make_force(blocks.upper(), pos, gamestate.forces[force2]);
- auto grid_block_id = blocks.upper() = this->ui->everything.next_block_id();
- this->ui->everything.make(grid_block_id, [&grid_size, &grid_pos](ui_entities::components_type& c)
- {
- texture floor { " " };
- floor.tint.bg = pixel_color::black;
- floor.tint.intensity = pixel_intensity::low;
- floor.tile = true;
- texture wall { "|" };
- wall.tile = true;
- wall.tint.intensity = pixel_intensity::low;
- c.push(floor);
- c.push(int_rect{grid_size/int2(2,1), grid_pos});
- c.push(wall);
- c.push(int_rect{int2(1, grid_size.y()), grid_pos + int2::i()*grid_size/int2(2,1) });
- c.push(floor);
- return c.push(int_rect{grid_size/int2(2,1), grid_pos + int2::i()*grid_size/int2(2,1) + int2::i()});
- }).release();
- }
- std::size_t update(const std::vector<game::state_update>& updates)
- {
- // update forces
- // update guys
- //
- // update layouts
- return updates.size();
- }
- ~battle_ui()
- {
- ui->everything.erase_block(blocks.lower(), blocks.upper());
- }
- };
- struct ui_state
- {
- ui_context context;
- std::vector<pixel> canvas = {};
- std::optional<battle_ui> battle = std::nullopt;
- bool running = true;
- };
- char mirror(char c)
- {
- switch(c)
- {
- case '>': return '<'; case '<': return '>';
- case ')': return '('; case '(': return ')';
- case ']': return '['; case '[': return ']';
- case '}': return '{'; case '{': return '}';
- case '\'': return '`'; case '`': return '\'';
- case '/': return '\\'; case '\\': return '/';
- default: return c;
- }
- }
- ui::ui() : state(ui_state{
- {{"/"s,")"s,">"s}}
- }) {}
- void ui::intro(std::string title)
- {
- std::cout << "\e[34m"<< title << "\e[0m" << std::endl;
- // std::cout << "\e[999;999H";
- // std::cout << "\e[6n" << std::endl;
- }
- void ui::battle(game::battle& battle)
- {
- auto& self = std::any_cast<ui_state&>(state);
- self.battle.emplace(int2_range{}, battle, self.context);
- }
- std::optional<game::action> ui::action()
- {
- // TODO
- auto& self = std::any_cast<ui_state&>(state);
- int from, to;
- std::cout << ">> ";
- self.running = bool(std::cin >> from >> to);
- return std::nullopt;
- }
- std::size_t ui::update(const std::vector<game::state_update>& updates)
- {
- auto& self = std::any_cast<ui_state&>(state);
- if(self.battle) self.battle->update(updates);
- return updates.size();
- }
- void render(ui_state& self);
- bool ui::loop()
- {
- auto& self = std::any_cast<ui_state&>(state);
- if(self.running)
- render(self);
- else std::cout << '\n';
- return self.running;
- }
- void set_pixel(std::vector<pixel>& dest, const int2 size, const int2 pos, const pixel value)
- {
- assert(size.x()*size.y() == dest.size());
- assert(pos < size);
- dest[pos.y()*size.x() + pos.x()] = value;
- }
- pixel get_pixel(const texture& src, const int2 size, int2 pos)
- {
- assert(size >= int2::zero());
- assert(pos >= int2::zero());
- assert(src.tile || size.x()*size.y() == src.sprite.size());
- assert(src.tile || pos < size);
- if(src.tile) pos = pos%size;
- auto row_pos = src.mirror ? (size.x()-1) - pos.x() : pos.x();
- auto flat = pos.y()*size.x() + row_pos;
- if(src.tile) flat %= src.sprite.size();
- auto c = src.sprite[flat];
- return {src.mirror ? mirror(c) : c, src.tint};
- }
- template <typename OutIt>
- void write_escape_code(pixel_properties p, OutIt out)
- {
- std::string prefix = "\e[";
- std::string postfix = "m";
- std::copy(prefix.begin(), prefix.end(), out);
- *out++ = '0' + static_cast<unsigned char>(p.intensity);
- *out++ = ';';
- *out++ = '4';
- *out++ = '0' + static_cast<unsigned char>(p.bg);
- *out++ = ';';
- *out++ = '3';
- *out++ = '0' + static_cast<unsigned char>(p.fg);
- std::copy(postfix.begin(), postfix.end(), out);
- }
- void render(ui_state& self)
- {
- int2_range canvas_bounds{};
- auto all_bounds = self.context.everything.get<int_rect>();
- for(int2_range bounds : all_bounds)
- {
- canvas_bounds.lower().min(bounds.lower());
- canvas_bounds.upper().max(bounds.upper());
- }
- assert(canvas_bounds.lower() == int2::zero());
- self.canvas.resize(canvas_bounds.upper().x() * canvas_bounds.upper().y());
- auto all_textures = self.context.everything.get<texture>();
- for(auto&& [bounds, texture] : support::range{
- // TODO: support::vrange, make_vrange? zip_range??
- vbegin(all_bounds, all_textures),
- vend(all_bounds, all_textures)
- }) simple::geom::loop(bounds.size, [&](auto i)
- { set_pixel(self.canvas, canvas_bounds.upper(), bounds.position + i, get_pixel(texture, bounds.size, i)); });
- pixel_properties pp{};
- std::ostream_iterator<char> oi (std::cout,"");
- write_escape_code(pp, oi);
- for(int i = 0; i != canvas_bounds.upper().y(); ++i)
- {
- for(int j = 0; j != canvas_bounds.upper().x(); ++j)
- {
- auto p = self.canvas[i*canvas_bounds.upper().x() + j];
- if(pp != p.p)
- {
- pp = p.p;
- write_escape_code(pp, oi);
- }
- *oi++ = p.c;
- }
- pp = pixel_properties{};
- write_escape_code(pp, oi);
- *oi++ = '\n';
- }
- std::cout << "\e[0m" << '\n';
- }
|