diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9b7bf87 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.0) +set(CMAKE_CXX_STANDARD 14) + +project(terminal_game_example CXX) + +# turns out that g++ will allow variable length arrays but I +# dont' want students getting dependant on non-standard features +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wvla -Werror=vla") + +# limit number of errors shown to 1 +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors") + +# location of source code files +include_directories(${CMAKE_SOURCE_DIR}/include) + +# tell cmake where to put the executables that it creates +file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) + +# where to put the object files it creates +file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) +SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib) + +add_library( drawable src/drawable.cpp ) +add_library( entity src/entity.cpp ) +add_library( keyboard src/keyboard.cpp ) +add_library( position src/position.cpp ) +add_library( textbox src/textbox.cpp ) +add_library( terminal src/terminal.cpp ) +add_library( tilecolor src/tilecolor.cpp ) +add_library( tile src/tile.cpp ) +add_library( view src/view.cpp ) +add_library( world src/world.cpp ) + +add_executable( main main.cpp ) +target_link_libraries( main world view tile entity terminal textbox position keyboard ) + diff --git a/README.md b/README.md index e1b8a81..09e64fa 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,10 @@ -### Getting started -There are various things you can do to quickly and efficiently configure your Codio Box to your exact requirements. +### terminal-gui -### GUI Applications and the Virtual Desktop -The Virtual Desktop allows you auto develop GUI based applications using any programming language. You can install a Virtual Desktop in your Box. You can then start the desktop and view it within the Codio IDE or in a new browser tab. +A simple and minimal terminal based GUI for Coventry ALL project development. +Students are welcome to contribute features to this repo via pull request. -[Virtual Desktop documentation](https://codio.com/docs/ide/boxes/installsw/gui/) +As this is a work in progress I do not promise backwards compatibility but I will try and avoid major changes. +### Documentation +Work in progress -### Command line access and the Terminal window -All Codio Boxes provide sudo level privileges to the underlying Ubuntu server. This means you can install and configure any component you like. You access the terminal from the **Tools->Terminal** menu item. - -### Debugger -The Codio IDE comes with a powerful visual debugger. Currently we support Python, Java, C, C++ and NodeJS. Other languages can be added on request. - -[Debugger documentation](https://codio.com/docs/ide/features/debugging/) - - -### Content authoring and assessments -Codio comes with a very powerful content authoring tool, Codio Guides. Guides is also where you create all forms of auto-graded assessments. - -- [Guides documentation](https://codio.com/docs/content/authoring/overview/) -- [Assessments documentation](https://codio.com/docs/content/authoring/assessments/) - -### Templating Box configurations and projects -Codio offers two very powerful templating options so you can create new projects from those templates with just a couple of clicks. **Stacks** allow you to create snapshots of the Box’s underlying software configuration. You can then create new projects from a Stack avoiding having to configure anew each time you start a new project. **Starter Packs** allow you to template an entire project, including workspace code. - -- [Stacks documentation](https://codio.com/docs/project/stacks/) -- [Starter Packs documentation](https://codio.com/docs/project/packs/) - -### Install software -You can always install software onto your Box using the command line. However, Codio offers a shortcut for commonly installed components that can be accessed from the **Tools->Install Software** menu. - -We can easily add new items to the Install Software screen, so feel free to submit requests. - -[Install Software documentation](https://codio.com/docs/ide/boxes/installsw/box-parts/) \ No newline at end of file diff --git a/include/drawable.h b/include/drawable.h new file mode 100644 index 0000000..4ceb38f --- /dev/null +++ b/include/drawable.h @@ -0,0 +1,19 @@ +#ifndef DRAWABLE_H +#define DRAWABLE_H + +#include "terminal.h" +#include "position.h" + +class Drawable : public Position +{ +public: + Terminal::Color foreground, background; + + Drawable( int _x=0, int _y=0, + Terminal::Color _foreground=Terminal::Color::FG_DEFAULT, Terminal::Color _background=Terminal::Color::BG_DEFAULT ) + : Position( _x, _y ), foreground(_foreground), background(_background) {} + + virtual void draw() const = 0; +}; + +#endif \ No newline at end of file diff --git a/include/entity.h b/include/entity.h new file mode 100644 index 0000000..81b2b77 --- /dev/null +++ b/include/entity.h @@ -0,0 +1,22 @@ +#ifndef ENTITY_H +#define ENTITY_H + +#include "drawable.h" +#include + +class Entity : public Drawable +{ +public: + char appearance; + + Entity( char _appearance, + int _x=0, int _y=0, + Terminal::Color _foreground=Terminal::Color::FG_DEFAULT, Terminal::Color _background=Terminal::Color::BG_DEFAULT ) + : Drawable( _x, _y, _foreground, _background ), appearance(_appearance) + { + } + + void draw() const; +}; + +#endif \ No newline at end of file diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..f94f9ac --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,30 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include +#include +#include +#include +#include +#include +#include + +class Keyboard +{ +private: + static termios oldSettings, newSettings; + static bool grabbed; + +public: + Keyboard(); + + static void grab(); + + static char get( float timeout = 0.5f ); + + static void release(); + + ~Keyboard(); +}; + +#endif \ No newline at end of file diff --git a/include/position.h b/include/position.h new file mode 100644 index 0000000..fc59951 --- /dev/null +++ b/include/position.h @@ -0,0 +1,23 @@ +#ifndef POSITION_H +#define POSITION_H + +#include + +class Position +{ +public: + int x, y; + + Position( int _x=0, int _y=0 ) + : x(_x), y(_y) + {} + + void up(); + void down(); + void left(); + void right(); + + friend std::ostream& operator<<( std::ostream& os, const Position& p ); +}; + +#endif \ No newline at end of file diff --git a/include/terminal.h b/include/terminal.h new file mode 100644 index 0000000..1ab31cc --- /dev/null +++ b/include/terminal.h @@ -0,0 +1,58 @@ +#ifndef TERMINAL_H +#define TERMINAL_H + +#include + +namespace Terminal +{ + enum Color + { + FG_BLACK = 30, + FG_RED = 31, + FG_GREEN = 32, + FG_YELLOW = 33, + FG_BLUE = 34, + FG_MAGENTA = 35, + FG_CYAN = 36, + FG_WHITE = 37, + FG_DEFAULT = 39, + + BG_BLACK = 40, + BG_RED = 41, + BG_GREEN = 42, + BG_YELLOW = 43, + BG_BLUE = 44, + BG_MAGENTA = 45, + BG_CYAN = 46, + BG_WHITE = 47, + BG_DEFAULT = 49 + }; + + std::ostream& operator<<( std::ostream& os, const Color& c ); + + class Position + { + private: + int x, y; + public: + Position( int _x, int _y ); + + friend std::ostream& operator<<( std::ostream& os, const Position& p ); + }; + + std::ostream& operator<<( std::ostream& os, const Position& p ); + + const std::string Default = "\033[39m\033[49m"; + const std::string Clear = "\033[2J\033[1;1H"; + + const std::string CursorOff = "\e[?25l"; + const std::string CursorOn = "\e[?25h"; + + void cursor( bool onoff ); + + void defaults(); + + void clear(); +} + +#endif \ No newline at end of file diff --git a/include/textbox.h b/include/textbox.h new file mode 100644 index 0000000..9d41b03 --- /dev/null +++ b/include/textbox.h @@ -0,0 +1,33 @@ +#ifndef TEXTBOX_H +#define TEXTBOX_H + +#include +#include +#include +#include +#include "drawable.h" + + + +class TextBox : public Drawable +{ +public: + int width; + std::stringstream ss; + + TextBox( int _x, int _y, int _width) + : Drawable( _x, _y ), width(_width) {} + + void draw() const; + + void clear(); +}; + +template +TextBox& operator<<( TextBox& os, T t ) +{ + os.ss << t; + return os; +} + +#endif \ No newline at end of file diff --git a/include/tile.h b/include/tile.h new file mode 100644 index 0000000..161daae --- /dev/null +++ b/include/tile.h @@ -0,0 +1,23 @@ +#ifndef TILE_H +#define TILE_H + +#include +#include "terminal.h" + +class Tile +{ +public: + static const char defaultContents = '-'; + + Terminal::Color foreground, background; + char contents; + + Tile( char _contents=defaultContents, + Terminal::Color _foreground=Terminal::Color::FG_DEFAULT, + Terminal::Color _background=Terminal::Color::BG_DEFAULT ) + : contents(_contents), foreground(_foreground), background(_background) {} + + void draw() const; +}; + +#endif \ No newline at end of file diff --git a/include/tilecolor.h b/include/tilecolor.h new file mode 100644 index 0000000..5732740 --- /dev/null +++ b/include/tilecolor.h @@ -0,0 +1,21 @@ +#ifndef TILECOLOR_H +#define TILECOLOR_H + +#include "terminal.h" +#include + +class TileColor +{ +public: + Terminal::Color foreground; + Terminal::Color background; + + TileColor( Terminal::Color _foreground=Terminal::Color::FG_DEFAULT, + Terminal::Color _background=Terminal::Color::BG_DEFAULT ) + : foreground(_foreground), background(_background) + {} + + friend std::ostream& operator<<( std::ostream& os, const TileColor& c ); +}; + +#endif \ No newline at end of file diff --git a/include/view.h b/include/view.h new file mode 100644 index 0000000..4fd2585 --- /dev/null +++ b/include/view.h @@ -0,0 +1,25 @@ +#ifndef VIEW_H +#define VIEW_H + +#include "drawable.h" +#include "world.h" + +class World; + +class View : public Position +{ +public: + World& world; + std::set drawables; + const int width, height; + + View( World& _world, int _width=-1, int _height=-1 ); + + bool move( int x, int y ); + + void add( const Drawable& drawable ); + + void draw() const; +}; + +#endif \ No newline at end of file diff --git a/include/world.h b/include/world.h new file mode 100644 index 0000000..ee7ae93 --- /dev/null +++ b/include/world.h @@ -0,0 +1,41 @@ +#ifndef WORLD_H +#define WORLD_H + +#include +#include +#include +#include +#include +#include + + +#include "terminal.h" +#include "tile.h" +#include "entity.h" +#include "view.h" + +class View; + +class World +{ +private: + std::vector world; + std::set entities; + +public: + const int width, height; + + World( int _width, int _height ); + + //bool set( int x, int y, Tile t ); + + void fill( Tile t ); + + Tile& get( int x, int y ); + + void add( Entity& entity ); + + friend class View; +}; + +#endif \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..39013da --- /dev/null +++ b/main.cpp @@ -0,0 +1,79 @@ +#include "world.h" +#include "textbox.h" +#include "keyboard.h" + +int main() +{ + // === create the world === + World world( 100, 100 ); // 100 tiles by 100 + View display( world, 60, 30 ); // create a view window of 60 by 30 tiles + + // === create a player === + Entity player( '#', 20, 20, Terminal::Color::FG_RED, Terminal::Color::BG_GREEN ); + int playerHealth = 100; + int playerMoves = 100; + + // === create some next boxes === + TextBox healthBox( 65, 10, 15 ); + TextBox moveBox( 65, 15, 15 ); + + display.add( healthBox ); + display.add( moveBox ); + + // === populate the world === + world.fill( Tile('-', Terminal::Color::FG_WHITE, Terminal::Color::BG_GREEN ) ); + + Tile water( 'w', Terminal::Color::FG_BLUE, Terminal::Color::BG_BLUE ); + world.get( 10, 10 ) = water; + world.get( 20, 35 ) = water; + + world.add( player ); + + // === set up the screen === + Terminal::clear(); // wipe it + Terminal::cursor(false); // turn cursor off + + Keyboard keys; + keys.grab(); // dont display key presses on screen + + char c = 0; + while( c != 'q' ) + { + // display player stats + healthBox.clear(); + healthBox << "Health: " << playerHealth; + + moveBox.clear(); + moveBox << "Moves: " << playerMoves; + + // move the viewpoint so it centers on the player + display.x = player.x - display.width/2; + display.y = player.y - display.height/2; + + display.draw(); + + const char c = keys.get(); + bool moved = true; + switch( c ) + { + case 'w': player.up(); break; + case 's': player.down(); break; + case 'a': player.left(); break; + case 'd': player.right(); break; + default: moved = false; break; + } + + if( moved ) + { + playerMoves -= 1; + + if( world.get( player.x, player.y ).contents == 'w' ) // player is in water + playerHealth -= 1; + } + } + + keys.release(); + Terminal::cursor(true); + + return 0; +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/drawable.cpp b/src/drawable.cpp new file mode 100644 index 0000000..ff5018b --- /dev/null +++ b/src/drawable.cpp @@ -0,0 +1 @@ +#include "drawable.h" \ No newline at end of file diff --git a/src/entity.cpp b/src/entity.cpp new file mode 100644 index 0000000..1d130c2 --- /dev/null +++ b/src/entity.cpp @@ -0,0 +1,6 @@ +#include "entity.h" + +void Entity::draw() const +{ + std::cout << foreground << background << appearance; +} diff --git a/src/keyboard.cpp b/src/keyboard.cpp new file mode 100644 index 0000000..818ec15 --- /dev/null +++ b/src/keyboard.cpp @@ -0,0 +1,60 @@ +#include "keyboard.h" + +termios Keyboard::oldSettings; +termios Keyboard::newSettings; +bool Keyboard::grabbed = false; + +Keyboard::Keyboard() +{ +} + +void Keyboard::grab() +{ + if( grabbed ) return; + + tcgetattr( fileno( stdin ), &oldSettings ); + newSettings = oldSettings; + newSettings.c_lflag &= (~ICANON & ~ECHO); + tcsetattr( fileno( stdin ), TCSANOW, &newSettings ); + + grabbed = true; +} + +char Keyboard::get( float timeout ) +{ + if( !grabbed ) return 0; + + fd_set set; + struct timeval tv; + + tv.tv_sec = int(timeout); + tv.tv_usec = int(timeout*1000000)%1000000; + + FD_ZERO( &set ); + FD_SET( fileno( stdin ), &set ); + + const int result = select( fileno( stdin )+1, &set, NULL, NULL, &tv ); + if( result > 0 ) + { + char c; + read( fileno( stdin ), &c, 1 ); + + return c; + } + + return 0; +} + +void Keyboard::release() +{ + if( !grabbed ) return; + + tcsetattr( fileno( stdin ), TCSANOW, &oldSettings ); + + grabbed = false; +} + +Keyboard::~Keyboard() +{ + release(); +} \ No newline at end of file diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 0000000..fe12d49 --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,21 @@ +#include "position.h" + +void Position::up() +{ + y--; +} + +void Position::down() +{ + y++; +} + +void Position::left() +{ + x--; +} + +void Position::right() +{ + x++; +} diff --git a/src/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000..2d31673 --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,29 @@ +#include "terminal.h" + +std::ostream& Terminal::operator<<( std::ostream& os, const Terminal::Color& c ) +{ + return os << "\033[" << (int)c << "m"; +} + +Terminal::Position::Position( int _x, int _y ) : x(_x), y(_y) {} + +std::ostream& Terminal::operator<<( std::ostream& os, const Terminal::Position& p ) +{ + return os << "\033[" << p.y << ";" << p.x << "H"; +} + +void Terminal::cursor( bool onoff ) +{ + if( onoff ) std::cout << CursorOn; + else std::cout << CursorOff; +} + +void Terminal::defaults() +{ + std::cout << Default; +} + +void Terminal::clear() +{ + std::cout << Clear; +} \ No newline at end of file diff --git a/src/textbox.cpp b/src/textbox.cpp new file mode 100644 index 0000000..1899ee2 --- /dev/null +++ b/src/textbox.cpp @@ -0,0 +1,22 @@ +#include "textbox.h" + +void TextBox::draw() const +{ + std::cout << Terminal::Default << Terminal::Position( x, y ) << "┏"; + for( int i=0; ix = x; + this->y = y; + + return true; +} + +void View::add( const Drawable& drawable ) +{ + const Drawable* d = &drawable; + drawables.insert( d ); +} + +void View::draw() const +{ + //std::cout << Terminal::Clear << std::endl; + std::cout << Terminal::Position(0,0) << std::endl; + + std::cout << Terminal::Default << " ┏"; + for( int i=0; iy + y; + + for( int x=0; xx + x; + + + + // if there is an entity to be drawn here + auto found = std::find_if( world.entities.begin(), world.entities.end(), + [mapX,mapY]( auto e ) { return e->x == mapX && e->y == mapY; } ); + + // if entity found draw entity + if( found != world.entities.end() ) + { + const Entity* e = *found;//found->draw(); + e->draw(); + } + // if no entity found draw world tile + else + { + try + { + world.get( mapX, mapY ).draw(); + } + // if no world found draw default + catch( std::out_of_range& err ) + { + terrorIncognito.draw(); + } + } + } + + std::cout << Terminal::Default << "┃" << std::endl; + + + /*for( int x=0; xx-this->x == x && e->y-this->y == y; } ); + if( found == world.entities.end() ) + { + world.get( this->x+x, this->y+x ).draw(); + } + else + { + Entity temp = **found; + if( temp.background == Terminal::BG_TRANSPARENT ) + temp.background = world.get( this->x+x, this->y+x ).back + } + }*/ + + } + + std::cout << " ┗"; + for( int i=0; ix < x || e->y < y ) continue; + + Entity temp = *e; + + if( temp.background == Terminal::BG_TRANSPARENT ) + temp.background = world.get( e->x, e->y ).background; + + temp.x -= x; + temp.y -= y; + + temp.draw(); + }*/ + + for( auto d : drawables ) + d->draw(); +} diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 0000000..4deff9c --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,34 @@ +#include "world.h" + +World::World( int _width, int _height ) : width(_width), height(_height) +{ + //assert( width>0 && height>0 ); + + world.resize( width*height ); +} + +/*bool World::set( int x, int y, Tile t ) +{ + if( x<0 || x>=width || y<0 || y>=height ) return false; + + world[ (y*width)+x ] = t; + return true; +}*/ + +void World::fill( Tile t ) +{ + std::fill( world.begin(), world.end(), t ); +} + +Tile& World::get( int x, int y ) +{ + if( x<0 || x>=width || y<0 || y>=height ) throw std::out_of_range(""); + + return world[ (y*width)+x ]; +} + +void World::add( Entity& entity ) +{ + const Entity* e = &entity; + entities.insert( e ); +}