terminal.ipp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. #include <iostream>
  2. #include <string>
  3. #include <cassert>
  4. #include "simple/geom.hpp"
  5. #include "simple/support/range.hpp"
  6. #include "simple/support/algorithm.hpp"
  7. #include "simple/support/vuple.hpp"
  8. #include "ecs.hpp"
  9. using namespace std::literals;
  10. namespace support = simple::support;
  11. using int2 = simple::geom::vector<int,2>;
  12. using int2_range = support::range<int2>;
  13. using int_rect = simple::geom::segment<int2>;
  14. struct motion
  15. {
  16. int2 destination;
  17. };
  18. enum class pixel_color : unsigned char
  19. { black, red, green, yellow, blue, magenta, cyan, white };
  20. enum class pixel_intensity : unsigned char
  21. { normal, high, low };
  22. struct pixel_properties
  23. {
  24. pixel_color fg = pixel_color::white;
  25. pixel_color bg = pixel_color::black;
  26. pixel_intensity intensity = pixel_intensity::normal;
  27. constexpr bool operator==(const pixel_properties other) noexcept
  28. { return fg == other.fg && bg == other.bg && intensity == other.intensity; }
  29. constexpr bool operator!=(const pixel_properties other) noexcept
  30. { return not((*this) == other); }
  31. };
  32. struct pixel
  33. {
  34. char c = ' ';
  35. pixel_properties p = {};
  36. };
  37. struct texture
  38. {
  39. std::string sprite;
  40. pixel_properties tint = {};
  41. bool mirror = false;
  42. bool tile = false;
  43. };
  44. using ui_entities = entities<int_rect, texture, motion>;
  45. struct ui_context
  46. {
  47. std::vector<std::string> guy_sprites;
  48. ui_entities everything = {};
  49. };
  50. class battle_ui
  51. {
  52. enum { force1, force2, grid };
  53. ui_context* ui;
  54. support::range<entity_id_t> blocks;
  55. int2_range make_force(entity_id_t block_id, int2 position, const game::force& gamestate)
  56. {
  57. int2_range force_bounds{position, position};
  58. position += 1; // padding
  59. assert(gamestate.guys.size() == ui->guy_sprites.size());
  60. for(auto&& [guy, guy_sprite] : support::range{
  61. // TODO: support::vrange, make_vrange? zip_range??
  62. support::vbegin(gamestate.guys, ui->guy_sprites),
  63. support::vend(gamestate.guys, ui->guy_sprites)
  64. })
  65. {
  66. auto& [guy_count, guy_color] = guy;
  67. if(guy_count != 0)
  68. {
  69. auto guy_rect = ui->everything.make(block_id, [&, guy_color=guy_color](ui_entities::components_type& c)
  70. {
  71. c.push(texture{ guy_sprite, {pixel_color(unsigned(guy_color))} });
  72. return c.push(int_rect{{1, 1}, position});
  73. }).release().components;
  74. auto count_rect = ui->everything.make(block_id, [&, guy_count=guy_count](ui_entities::components_type& c)
  75. {
  76. auto& txtr = c.push(texture{ " " + std::to_string(guy_count)});
  77. return c.push(int_rect{{int(txtr.sprite.size()), 1}, position + guy_rect.size * int2::i()}); // horizontal layout
  78. }).release().components;
  79. force_bounds.upper() = max(int2_range(guy_rect).upper(), int2_range(count_rect).upper()); // bounds
  80. position += max(guy_rect.size, count_rect.size) * int2::j(); // vertical layout
  81. }
  82. }
  83. force_bounds.upper().x() += 1; // padding
  84. return force_bounds;
  85. }
  86. public:
  87. // FIXME: std any can't handle this... -_-
  88. // battle_ui(const battle_ui&) = delete;
  89. // battle_ui(battle_ui&&) = delete;
  90. // battle_ui& operator=(const battle_ui&) = delete;
  91. // battle_ui& operator=(battle_ui&&) = delete;
  92. battle_ui(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); }
  93. battle_ui(battle_ui&&) { assert(false && "battle_ui shouldn't move"); }
  94. battle_ui& operator=(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); }
  95. battle_ui& operator=(battle_ui&&) { assert(false && "battle_ui shouldn't move"); }
  96. battle_ui(int2_range layout_context, game::battle& gamestate, ui_context& ui) : ui(&ui), blocks{}
  97. {
  98. // create da blocks
  99. blocks.lower() = this->ui->everything.next_block_id();
  100. blocks.upper() = blocks.lower();
  101. auto pos = layout_context.lower();
  102. auto left_bounds = make_force(blocks.upper(), pos, gamestate.forces[force1]);
  103. pos += (left_bounds.upper() - pos) * int2::i();
  104. int2 grid_size {gamestate.field_size * int2{2,1}};
  105. int2 grid_bounds {grid_size
  106. + int2::i() // separator
  107. };
  108. auto grid_pos = pos;
  109. pos += grid_bounds * int2::i();
  110. make_force(blocks.upper(), pos, gamestate.forces[force2]);
  111. auto grid_block_id = blocks.upper() = this->ui->everything.next_block_id();
  112. this->ui->everything.make(grid_block_id, [&grid_size, &grid_pos](ui_entities::components_type& c)
  113. {
  114. texture floor { " " };
  115. floor.tint.bg = pixel_color::black;
  116. floor.tint.intensity = pixel_intensity::low;
  117. floor.tile = true;
  118. texture wall { "|" };
  119. wall.tile = true;
  120. wall.tint.intensity = pixel_intensity::low;
  121. c.push(floor);
  122. c.push(int_rect{grid_size/int2(2,1), grid_pos});
  123. c.push(wall);
  124. c.push(int_rect{int2(1, grid_size.y()), grid_pos + int2::i()*grid_size/int2(2,1) });
  125. c.push(floor);
  126. return c.push(int_rect{grid_size/int2(2,1), grid_pos + int2::i()*grid_size/int2(2,1) + int2::i()});
  127. }).release();
  128. }
  129. std::size_t update(const std::vector<game::state_update>& updates)
  130. {
  131. // update forces
  132. // update guys
  133. //
  134. // update layouts
  135. return updates.size();
  136. }
  137. ~battle_ui()
  138. {
  139. ui->everything.erase_block(blocks.lower(), blocks.upper());
  140. }
  141. };
  142. struct ui_state
  143. {
  144. ui_context context;
  145. std::vector<pixel> canvas = {};
  146. std::optional<battle_ui> battle = std::nullopt;
  147. bool running = true;
  148. };
  149. char mirror(char c)
  150. {
  151. switch(c)
  152. {
  153. case '>': return '<'; case '<': return '>';
  154. case ')': return '('; case '(': return ')';
  155. case ']': return '['; case '[': return ']';
  156. case '}': return '{'; case '{': return '}';
  157. case '\'': return '`'; case '`': return '\'';
  158. case '/': return '\\'; case '\\': return '/';
  159. default: return c;
  160. }
  161. }
  162. ui::ui() : state(ui_state{
  163. {{"/"s,")"s,">"s}}
  164. }) {}
  165. void ui::intro(std::string title)
  166. {
  167. std::cout << "\e[34m"<< title << "\e[0m" << std::endl;
  168. // std::cout << "\e[999;999H";
  169. // std::cout << "\e[6n" << std::endl;
  170. }
  171. void ui::battle(game::battle& battle)
  172. {
  173. auto& self = std::any_cast<ui_state&>(state);
  174. self.battle.emplace(int2_range{}, battle, self.context);
  175. }
  176. std::optional<game::action> ui::action()
  177. {
  178. // TODO
  179. auto& self = std::any_cast<ui_state&>(state);
  180. int from, to;
  181. std::cout << ">> ";
  182. self.running = bool(std::cin >> from >> to);
  183. return std::nullopt;
  184. }
  185. std::size_t ui::update(const std::vector<game::state_update>& updates)
  186. {
  187. auto& self = std::any_cast<ui_state&>(state);
  188. if(self.battle) self.battle->update(updates);
  189. return updates.size();
  190. }
  191. void render(ui_state& self);
  192. bool ui::loop()
  193. {
  194. auto& self = std::any_cast<ui_state&>(state);
  195. if(self.running)
  196. render(self);
  197. else std::cout << '\n';
  198. return self.running;
  199. }
  200. void set_pixel(std::vector<pixel>& dest, const int2 size, const int2 pos, const pixel value)
  201. {
  202. assert(size.x()*size.y() == dest.size());
  203. assert(pos < size);
  204. dest[pos.y()*size.x() + pos.x()] = value;
  205. }
  206. pixel get_pixel(const texture& src, const int2 size, int2 pos)
  207. {
  208. assert(size >= int2::zero());
  209. assert(pos >= int2::zero());
  210. assert(src.tile || size.x()*size.y() == src.sprite.size());
  211. assert(src.tile || pos < size);
  212. if(src.tile) pos = pos%size;
  213. auto row_pos = src.mirror ? (size.x()-1) - pos.x() : pos.x();
  214. auto flat = pos.y()*size.x() + row_pos;
  215. if(src.tile) flat %= src.sprite.size();
  216. auto c = src.sprite[flat];
  217. return {src.mirror ? mirror(c) : c, src.tint};
  218. }
  219. template <typename OutIt>
  220. void write_escape_code(pixel_properties p, OutIt out)
  221. {
  222. std::string prefix = "\e[";
  223. std::string postfix = "m";
  224. std::copy(prefix.begin(), prefix.end(), out);
  225. *out++ = '0' + static_cast<unsigned char>(p.intensity);
  226. *out++ = ';';
  227. *out++ = '4';
  228. *out++ = '0' + static_cast<unsigned char>(p.bg);
  229. *out++ = ';';
  230. *out++ = '3';
  231. *out++ = '0' + static_cast<unsigned char>(p.fg);
  232. std::copy(postfix.begin(), postfix.end(), out);
  233. }
  234. void render(ui_state& self)
  235. {
  236. int2_range canvas_bounds{};
  237. auto all_bounds = self.context.everything.get<int_rect>();
  238. for(int2_range bounds : all_bounds)
  239. {
  240. canvas_bounds.lower().min(bounds.lower());
  241. canvas_bounds.upper().max(bounds.upper());
  242. }
  243. assert(canvas_bounds.lower() == int2::zero());
  244. self.canvas.resize(canvas_bounds.upper().x() * canvas_bounds.upper().y());
  245. auto all_textures = self.context.everything.get<texture>();
  246. for(auto&& [bounds, texture] : support::range{
  247. // TODO: support::vrange, make_vrange? zip_range??
  248. vbegin(all_bounds, all_textures),
  249. vend(all_bounds, all_textures)
  250. }) simple::geom::loop(bounds.size, [&](auto i)
  251. { set_pixel(self.canvas, canvas_bounds.upper(), bounds.position + i, get_pixel(texture, bounds.size, i)); });
  252. pixel_properties pp{};
  253. std::ostream_iterator<char> oi (std::cout,"");
  254. write_escape_code(pp, oi);
  255. for(int i = 0; i != canvas_bounds.upper().y(); ++i)
  256. {
  257. for(int j = 0; j != canvas_bounds.upper().x(); ++j)
  258. {
  259. auto p = self.canvas[i*canvas_bounds.upper().x() + j];
  260. if(pp != p.p)
  261. {
  262. pp = p.p;
  263. write_escape_code(pp, oi);
  264. }
  265. *oi++ = p.c;
  266. }
  267. pp = pixel_properties{};
  268. write_escape_code(pp, oi);
  269. *oi++ = '\n';
  270. }
  271. std::cout << "\e[0m" << '\n';
  272. }