@ -25,6 +25,32 @@ using namespace fmt;
using namespace std : : chrono_literals ;
using namespace ftxui ;
struct Player {
DinkyECS : : Entity entity ;
} ;
struct Position {
Point location ;
} ;
struct Motion {
int dx ;
int dy ;
} ;
struct Combat {
int hp ;
int damage ;
} ;
struct Treasure {
int amount ;
} ;
struct Tile {
std : : string chr = " ! " ;
} ;
std : : array < sf : : Color , 10 > VALUES {
sf : : Color { 1 , 4 , 2 } , // black
sf : : Color { 9 , 29 , 16 } , // dark dark
@ -69,22 +95,18 @@ GUI::GUI() : $game_map(GAME_MAP_X, GAME_MAP_Y),
$ map_text . setFillColor ( color ( Value : : MID ) ) ;
$ game_map . generate ( ) ;
$ player . location = $ game_map . place_entity ( 0 ) ;
$ enemy . location = $ game_map . place_entity ( 1 ) ;
$ goal = $ game_map . place_entity ( $ game_map . room_count ( ) - 1 ) ;
}
void GUI : : create_renderer ( ) {
$ map_view = Renderer ( [ & ] {
auto player = $ world . get < Player > ( ) ;
$ map_view = Renderer ( [ & , player ] {
const auto & player_position = $ world . component < Position > ( player . entity ) ;
Matrix & walls = $ game_map . walls ( ) ;
$ game_map . set_target ( $ player . location ) ;
$ game_map . set_target ( player_position . location ) ;
$ game_map . make_paths ( ) ;
Matrix & paths = $ game_map . paths ( ) ;
if ( $ player . 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 ? WALL_TILE : format ( " {} " , paths [ y ] [ x ] ) ;
@ -99,18 +121,19 @@ void GUI::create_renderer() {
}
}
$ canvas . DrawText ( $ enemy . location . x * 2 , $ enemy . location . y * 4 , ENEMY_TILE ) ;
$ canvas . DrawText ( $ player . location . x * 2 , $ player . location . y * 4 , PLAYER_TILE ) ;
$ canvas . DrawText ( $ goal . x * 2 , $ goal . y * 4 , " $ " ) ;
$ world . system < Position , Tile > ( [ & ] ( const auto & ent , auto & pos , auto & tile ) {
$ canvas . DrawText ( pos . location . x * 2 , pos . location . y * 4 , tile . chr ) ;
} ) ;
return canvas ( $ canvas ) ;
} ) ;
$ document = Renderer ( [ & ] {
$ document = Renderer ( [ & , player ] {
const auto & player_combat = $ world . component < Combat > ( player . entity ) ;
return hbox ( {
hflow (
vbox (
text ( format ( " HP: {} " , $ player . hp ) ) | border ,
text ( format ( " HP: {} " , player_combat . hp ) ) | border ,
text ( $ status_text ) | border
) | xflex_grow
) ,
@ -122,46 +145,61 @@ void GUI::create_renderer() {
void GUI : : handle_events ( ) {
sf : : Event event ;
auto player = $ world . get < Player > ( ) ;
auto & player_motion = $ world . component < Motion > ( player . entity ) ;
while ( $ window . pollEvent ( event ) ) {
if ( event . type = = sf : : Event : : Closed ) {
$ window . close ( ) ;
} else if ( event . type = = sf : : Event : : KeyPressed ) {
size_t x = $ player . location . x ;
size_t y = $ player . location . y ;
if ( sf : : Keyboard : : isKeyPressed ( sf : : Keyboard : : Left ) ) {
x - = 1 ;
player_motion . dx = - 1 ;
} else if ( sf : : Keyboard : : isKeyPressed ( sf : : Keyboard : : Right ) ) {
x + = 1 ;
player_motion . dx = 1 ;
} else if ( sf : : Keyboard : : isKeyPressed ( sf : : Keyboard : : Up ) ) {
y - = 1 ;
player_motion . dy = - 1 ;
} else if ( sf : : Keyboard : : isKeyPressed ( sf : : Keyboard : : Down ) ) {
y + = 1 ;
player_motion . dy = 1 ;
}
if ( $ game_map . inmap ( x , y ) & & ! $ game_map . iswall ( x , y ) ) {
$ game_map . clear_target ( $ player . location ) ;
$ player . move ( { x , y } ) ;
} else {
$ shake_it = true ;
$ hit_sound . play ( ) ;
}
// COMPOSE system? You create a bunch of callbacks and then combine them into
// a single run over the data?
// move $enemy here
// BUG: when the enemy has no path it goes through walls, which means
// this neighbors function is not working right. Probably updating
// enemy.location in an out parameter isn't the best idea.
bool found = $ game_map . neighbors ( $ enemy . location , true ) ;
if ( ! found ) {
$ status_text = " ENEMY STUCK! " ;
}
if ( $ enemy . location . x = = $ player . location . x & & $ enemy . location . y = = $ player . location . y ) {
$ player . event ( EntityEvent : : HIT ) ;
$ burn_baby_burn = true ;
} else if ( $ goal . x = = $ player . location . x & & $ goal . y = = $ player . location . y ) {
$ status_text = " YOU WIN! " ;
}
// move enemies system
$ world . system < Position , Motion > ( [ & ] ( const auto & ent , auto & position , auto & motion ) {
if ( ent ! = player . entity ) {
Point out = position . location ;
$ game_map . neighbors ( out , false ) ;
motion = { int ( out . x - position . location . x ) , int ( out . y - position . location . y ) } ;
}
} ) ;
// motion system
$ world . system < Position , Motion > ( [ & ] ( const auto & ent , auto & position , auto & motion ) {
Point move_to = {
position . location . x + motion . dx ,
position . location . y + motion . dy
} ;
motion = { 0 , 0 } ; // clear it after getting it
if ( $ game_map . inmap ( move_to . x , move_to . y ) & & ! $ game_map . iswall ( move_to . x , move_to . y ) ) {
$ game_map . clear_target ( position . location ) ;
position . location = move_to ;
}
} ) ;
// combat system
auto combatSystem = [ & ] ( ) {
const auto & player_position = $ world . component < Position > ( player . entity ) ;
$ world . system < Position , Combat > ( [ & ] ( const auto & ent , auto & pos , auto & combat ) {
if ( ent ! = player . entity & & pos . location . x = = player_position . location . x & &
pos . location . y = = player_position . location . y ) {
$ burn_baby_burn = true ;
}
} ) ;
} ;
combatSystem ( ) ;
}
}
}
@ -187,6 +225,7 @@ void GUI::draw_screen(bool clear, float map_off_x, float map_off_y) {
}
void GUI : : shake ( ) {
$ hit_sound . play ( ) ;
for ( int i = 0 ; i < 10 ; + + i ) {
int x = Random : : uniform < int > ( - 10 , 10 ) ;
int y = Random : : uniform < int > ( - 10 , 10 ) ;
@ -196,6 +235,35 @@ void GUI::shake() {
}
}
void GUI : : configure_world ( ) {
dbc : : check ( $ game_map . room_count ( ) > 1 , " not enough rooms in map. " ) ;
// configure a player as a fact of the world
Player player { $ world . entity ( ) } ;
$ world . set < Player > ( player ) ;
$ world . assign < Position > ( player . entity , { $ game_map . place_entity ( 0 ) } ) ;
$ world . assign < Motion > ( player . entity , { 0 , 0 } ) ;
$ world . assign < Combat > ( player . entity , { 100 , 10 } ) ;
$ world . assign < Tile > ( player . entity , { PLAYER_TILE } ) ;
auto enemy = $ world . entity ( ) ;
$ world . assign < Position > ( enemy , { $ game_map . place_entity ( 1 ) } ) ;
$ world . assign < Motion > ( enemy , { 0 , 0 } ) ;
$ world . assign < Combat > ( enemy , { 20 , 10 } ) ;
$ world . assign < Tile > ( enemy , { ENEMY_TILE } ) ;
auto enemy2 = $ world . entity ( ) ;
$ world . assign < Position > ( enemy2 , { $ game_map . place_entity ( 2 ) } ) ;
$ world . assign < Motion > ( enemy2 , { 0 , 0 } ) ;
$ world . assign < Combat > ( enemy2 , { 20 , 10 } ) ;
$ world . assign < Tile > ( enemy2 , { " * " } ) ;
auto gold = $ world . entity ( ) ;
$ world . assign < Position > ( gold , { $ game_map . place_entity ( $ game_map . room_count ( ) - 1 ) } ) ;
$ world . assign < Treasure > ( gold , { 100 } ) ;
$ world . assign < Tile > ( gold , { " $ " } ) ;
}
void GUI : : render_scene ( ) {
Render ( $ map_screen , $ map_view - > Render ( ) ) ;
Render ( $ screen , $ document - > Render ( ) ) ;
@ -222,6 +290,7 @@ void GUI::render_scene() {
}
int GUI : : main ( ) {
configure_world ( ) ;
create_renderer ( ) ;
while ( $ window . isOpen ( ) ) {