|
|
|
@ -3,6 +3,9 @@ |
|
|
|
|
// the LICENSE file.
|
|
|
|
|
#include <chrono> // for operator""s, chrono_literals |
|
|
|
|
#include <iostream> // for cout, ostream |
|
|
|
|
#include <locale> |
|
|
|
|
#include <codecvt> |
|
|
|
|
#include <fstream> |
|
|
|
|
#include <memory> // for allocator, shared_ptr |
|
|
|
|
#include <string> // for string, operator<< |
|
|
|
|
#include <thread> // for sleep_for |
|
|
|
@ -14,8 +17,15 @@ |
|
|
|
|
#include <ftxui/component/loop.hpp> |
|
|
|
|
#include <ftxui/screen/color.hpp> |
|
|
|
|
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive |
|
|
|
|
#include <SFML/Window.hpp> |
|
|
|
|
#include <SFML/System.hpp> |
|
|
|
|
#include <SFML/Graphics/Text.hpp> |
|
|
|
|
#include <SFML/Graphics/Font.hpp> |
|
|
|
|
#include <SFML/Graphics/RenderWindow.hpp> |
|
|
|
|
#include <SFML/Audio.hpp> |
|
|
|
|
|
|
|
|
|
#include <fmt/core.h> |
|
|
|
|
#include "fsm.hpp" |
|
|
|
|
#include "map.hpp" |
|
|
|
|
#include "dbc.hpp" |
|
|
|
|
|
|
|
|
@ -23,42 +33,128 @@ 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<sf::Color, 10> 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<EntityState, EntityEvent> { |
|
|
|
|
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; |
|
|
|
|
std::string reset_position; |
|
|
|
|
auto c = Canvas(60 * 2, 20 * 4); |
|
|
|
|
|
|
|
|
|
auto c = Canvas(60 * 2, 27 * 4); |
|
|
|
|
int res = g_hit_buf.loadFromFile("./hit.wav"); |
|
|
|
|
dbc::check(res, "faile to load hit.wav"); |
|
|
|
|
g_hit_sound.setBuffer(g_hit_buf); |
|
|
|
|
|
|
|
|
|
Map game_map(50, 27); |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
Point me = {.x=start.x+1, .y=start.y+1}; |
|
|
|
|
Point enemy = {.x=bad_room.x+1, .y=bad_room.y+1}; |
|
|
|
|
Point goal = {.x=gold_room.x+1, .y=gold_room.y+1}; |
|
|
|
|
string dead_yet = "NOT DEAD"; |
|
|
|
|
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); |
|
|
|
|
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, Color::GrayDark); |
|
|
|
|
c.DrawText(x*2, y*4, tile); |
|
|
|
|
} else if(show_paths) { |
|
|
|
|
int pnum = paths[y][x]; |
|
|
|
|
c.DrawText(x*2, y*4, tile, Color::Palette16(pnum % 7 + 1)); |
|
|
|
|
//int pnum = paths[y][x];
|
|
|
|
|
c.DrawText(x*2, y*4, tile); |
|
|
|
|
} else { |
|
|
|
|
c.DrawText(x*2, y*4, ".", Color::GrayDark); |
|
|
|
|
c.DrawText(x*2, y*4, "."); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -66,12 +162,12 @@ int main() { |
|
|
|
|
if(bomb) { |
|
|
|
|
/// draw
|
|
|
|
|
bomb = false; |
|
|
|
|
c.DrawPointCircle(me.x*2, me.y*4, 4, Color::Red); |
|
|
|
|
c.DrawPointCircle(me.location.x*2, me.location.y*4, 4); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
c.DrawText(enemy.x*2, enemy.y*4, "!", Color::Red); |
|
|
|
|
c.DrawText(me.x*2, me.y*4, "@", Color::Blue); |
|
|
|
|
c.DrawText(goal.x*2, goal.y*4, "$", Color::Yellow); |
|
|
|
|
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); |
|
|
|
|
}); |
|
|
|
@ -80,56 +176,76 @@ int main() { |
|
|
|
|
return hbox({ |
|
|
|
|
hflow( |
|
|
|
|
vbox( |
|
|
|
|
gauge(0.5) | border, |
|
|
|
|
text(dead_yet) | border |
|
|
|
|
text(format("HP: {}", me.hp)) | border, |
|
|
|
|
text(status_text) | border |
|
|
|
|
) | xflex_grow |
|
|
|
|
), |
|
|
|
|
separator(), |
|
|
|
|
hbox(map->Render()), |
|
|
|
|
}) | border; |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
document |= CatchEvent([&](Event e) -> bool { |
|
|
|
|
size_t x = me.x; |
|
|
|
|
size_t y = me.y; |
|
|
|
|
|
|
|
|
|
if(e == Event::ArrowLeft) { |
|
|
|
|
x -= 1; |
|
|
|
|
} else if(e == Event::ArrowRight) { |
|
|
|
|
x += 1; |
|
|
|
|
} else if(e == Event::ArrowDown) { |
|
|
|
|
y += 1; |
|
|
|
|
} else if(e == Event::ArrowUp) { |
|
|
|
|
y -= 1; |
|
|
|
|
} else if(e == Event::Backspace) { |
|
|
|
|
bomb = true; |
|
|
|
|
} else { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
auto screen = Screen::Create(Dimension::Full()); |
|
|
|
|
|
|
|
|
|
sf::RenderWindow window(sf::VideoMode(1600,900), "Roguish"); |
|
|
|
|
|
|
|
|
|
sf::Font font; |
|
|
|
|
font.loadFromFile("./unifont-16.0.01.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<std::codecvt_utf8_utf16<wchar_t>> 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); |
|
|
|
|
me.x = x; |
|
|
|
|
me.y = y; |
|
|
|
|
} |
|
|
|
|
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, true); |
|
|
|
|
// move enemy here
|
|
|
|
|
bool found = game_map.neighbors(enemy.location, true); |
|
|
|
|
if(!found) { |
|
|
|
|
status_text = "ENEMY STUCK!"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(enemy.x == me.x && enemy.y == me.y) { |
|
|
|
|
dead_yet = "YOU DIED!"; |
|
|
|
|
} else if(goal.x == me.x && goal.y == me.y) { |
|
|
|
|
dead_yet = "YOU WIN!"; |
|
|
|
|
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 true; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
auto screen = ScreenInteractive::Fullscreen(); |
|
|
|
|
Loop loop(&screen, document); |
|
|
|
|
|
|
|
|
|
while(!loop.HasQuitted()) { |
|
|
|
|
loop.RunOnceBlocking(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|