// Copyright 2020 Arthur Sonzogni. All rights reserved. // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. #include // for operator""s, chrono_literals #include // for cout, ostream #include #include #include #include // for allocator, shared_ptr #include // for string, operator<< #include // for sleep_for #include // for hflow, paragraph, separator, hbox, vbox, filler, operator|, border, Element #include // for Render #include // for ftxui #include #include #include #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive #include #include #include #include #include #include #include #include "fsm.hpp" #include "map.hpp" #include "dbc.hpp" using std::string; using namespace fmt; using namespace std::chrono_literals; enum class Value { BLACK=0, DARK_DARK, DARK_MID, DARK_LIGHT, MID, LIGHT_DARK, LIGHT_MID, LIGHT_LIGHT, WHITE, TRANSPARENT }; std::array VALUES{ sf::Color{1, 4, 2}, // black sf::Color{9, 29, 16}, // dark dark sf::Color{14, 50, 26}, // dark mid sf::Color{0, 109, 44}, // dark light sf::Color{63, 171, 92}, // mid sf::Color{161, 217, 155}, // light dark sf::Color{199, 233, 192}, // light mid sf::Color{229, 245, 224}, // light light sf::Color{255, 255, 255}, // white sf::Color::Transparent, // white }; enum class EntityState { START, HUNTING, DEAD }; enum class EntityEvent { GO, HIT }; class Entity : public DeadSimpleFSM { public: Point location; int hp = 20; int damage = 10; Entity(Point loc) : location(loc) { }; // disable copy Entity(Entity &e) = delete; void move(Point loc) { location = loc; } void event(EntityEvent ev) { switch(_state) { FSM_STATE(EntityState, START, ev); FSM_STATE(EntityState, HUNTING, ev); FSM_STATE(EntityState, DEAD, ev); } } void START(EntityEvent ev) { state(EntityState::HUNTING); } void HUNTING(EntityEvent ev) { switch(ev) { case EntityEvent::HIT: hp -= damage; break; default: state(EntityState::HUNTING); } if(hp <= 0) { state(EntityState::DEAD); } else { state(EntityState::HUNTING); } } void DEAD(EntityEvent ev) { state(EntityState::DEAD); } }; sf::SoundBuffer g_hit_buf; sf::Sound g_hit_sound; int main() { using namespace ftxui; auto c = Canvas(60 * 2, 20 * 4); int res = g_hit_buf.loadFromFile("./assets/hit.wav"); dbc::check(res, "faile to load hit.wav"); g_hit_sound.setBuffer(g_hit_buf); Map game_map(50, 20); game_map.generate(); Matrix &walls = game_map.walls(); Room &start = game_map.room(0); Room &bad_room = game_map.room(1); Room &gold_room = game_map.room(game_map.room_count() - 1); Entity me({.x=start.x+1, .y=start.y+1}); Entity enemy({.x=bad_room.x+1, .y=bad_room.y+1}); Point goal{.x=gold_room.x+1, .y=gold_room.y+1}; string status_text = "NOT DEAD"; bool show_paths = false; bool bomb = false; auto map = Renderer([&] { game_map.set_target(me.location); game_map.make_paths(); Matrix &paths = game_map.paths(); if(me.in_state(EntityState::DEAD)) { status_text = "DEAD!"; } for(size_t x = 0; x < walls[0].size(); ++x) { for(size_t y = 0; y < walls.size(); ++y) { string tile = walls[y][x] == 1 ? "#" : format("{}", paths[y][x]); if(tile == "#") { c.DrawText(x*2, y*4, tile); } else if(show_paths) { //int pnum = paths[y][x]; c.DrawText(x*2, y*4, tile); } else { c.DrawText(x*2, y*4, "."); } } } if(bomb) { /// draw bomb = false; c.DrawPointCircle(me.location.x*2, me.location.y*4, 4); } c.DrawText(enemy.location.x*2, enemy.location.y*4, "!"); c.DrawText(me.location.x*2, me.location.y*4, "@"); c.DrawText(goal.x*2, goal.y*4, "$"); return canvas(c); }); auto document = Renderer([&]{ return hbox({ hflow( vbox( text(format("HP: {}", me.hp)) | border, text(status_text) | border ) | xflex_grow ), separator(), hbox(map->Render()), }); }); auto screen = Screen::Create(Dimension::Full()); sf::RenderWindow window(sf::VideoMode(1600,900), "Roguish"); sf::Font font; font.loadFromFile("./assets/text.otf"); sf::Text text; text.setFont(font); text.setCharacterSize(30); text.setFillColor(VALUES[size_t(Value::LIGHT_LIGHT)]); while(window.isOpen()) { Render(screen, document->Render()); std::string screen_out = screen.ToString(); std::wstring_convert> converter; std::wstring utf8 = converter.from_bytes(screen_out); text.setString(utf8); text.setPosition({0,0}); window.clear(); window.draw(text); window.display(); sf::Event event; while(window.pollEvent(event)) { if(event.type == sf::Event::Closed) { window.close(); } else if(event.type == sf::Event::KeyPressed) { size_t x = me.location.x; size_t y = me.location.y; if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { x -= 1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { x += 1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { y -= 1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { y += 1; } if(game_map.inmap(x,y) && !game_map.iswall(x,y)) { game_map.clear_target(me.location); me.move({x, y}); } else { g_hit_sound.play(); } // move enemy here bool found = game_map.neighbors(enemy.location, true); if(!found) { status_text = "ENEMY STUCK!"; } if(enemy.location.x == me.location.x && enemy.location.y == me.location.y) { me.event(EntityEvent::HIT); } else if(goal.x == me.location.x && goal.y == me.location.y) { status_text = "YOU WIN!"; } } } } return 0; }