diff --git a/CMakeLists.txt b/CMakeLists.txt index ddc7e66..97a47ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,18 @@ cmake_minimum_required( VERSION 2.8.7 ) # Set version information set( TTRTS_VERSION_MAJOR 0 ) -set( TTRTS_VERSION_MINOR 3 ) -set( TTRTS_VERSION_PATCH 1 ) +set( TTRTS_VERSION_MINOR 4 ) +set( TTRTS_VERSION_PATCH 0 ) + +# Set defaults for ttrts variables +set( TTRTS_MAPS "/usr/local/share/ttrts/maps/" ) +set( TTRTS_GAMES "/tmp/" ) +set( TTRTS_PORT 11715 ) + +# define these defaults in code +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_MAPS=${TTRTS_MAPS}" ) +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_GAMES=${TTRTS_GAMES}" ) +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_PORT=${TTRTS_PORT}" ) # Use c++1y (14) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" ) @@ -26,16 +36,19 @@ install( FILES scripts/ttrts_complete DESTINATION /etc/bash_completion.d/ ) # Run the gen_usage script to generate our usage header add_custom_target( ttrts-gen-manpage ALL - ${CMAKE_SOURCE_DIR}/scripts/gen_manpage.sh "${TTRTS_VERSION_MAJOR}" "${TTRTS_VERSION_MINOR}" "${TTRTS_VERSION_PATCH}" "ttrts.6" "${CMAKE_SOURCE_DIR}/source/client/README.md" + ${CMAKE_SOURCE_DIR}/scripts/gen_manpage.sh "${TTRTS_VERSION_MAJOR}" "${TTRTS_VERSION_MINOR}" "${TTRTS_VERSION_PATCH}" "ttrts.6" "${CMAKE_SOURCE_DIR}/source/README.md" ) # Install the ttrts man page -install( FILES "${CMAKE_BINARY_DIR}/ttrts.6" DESTINATION man/man6/ ) +if( ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" ) + set ( MANPAGE_LOC share/man/man6 ) +elseif( ${CMAKE_SYSTEM_NAME} MATCHES "Linux" ) + set ( MANPAGE_LOC man/man6 ) +else() + message(ERROR "Unsupported system detected") +endif() + +install( FILES "${CMAKE_BINARY_DIR}/ttrts.6" DESTINATION ${MANPAGE_LOC} ) # Subprojects -add_subdirectory( source/ttrts ) -add_subdirectory( source/client ) - -# Auxhilary binaries -add_subdirectory( source/test ) -add_subdirectory( source/gen ) +add_subdirectory( source ) diff --git a/README.md b/README.md index f13350c..a6b299b 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,21 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An ----------------------------------------------------------- ## Changelog +#### v0.4.0 +* Updated with network functionality + * Game can now be hosted with ttrts --server option + * Server can be connected to with ttrts --client +* Updated command line interface with new launcher script + * map file must now be specified with --map=FILE +* Slight refactor of libraries to account for new run targets + +#### v0.3.2 +* Fix bug when loading map files with walls +* Fix ttrts on OSX + * Install man files to correct location + * Update usage of sed to be compatible with BSD as well as GNU versions +* New maps and renames of old ones + #### v0.3.1 * Upgraded install target to repository * libttrts static library binary in /usr/local/lib diff --git a/api/perl/ttrts.pm b/api/perl/ttrts.pm index 6dc279e..793269b 100644 --- a/api/perl/ttrts.pm +++ b/api/perl/ttrts.pm @@ -238,6 +238,13 @@ sub PrintGameFromGamestateString $map[$invalidPos[0]][$invalidPos[1]] = "~"; } + # Fill with walls + foreach my $wall ( $info[8] =~ /\[(\d+,\d+)\]/g ) + { + $wall =~ /(\d+),(\d+)/; + $map[$1][$2] = "|"; + } + # Fill with units for my $unit (@units) { diff --git a/bootstrap.sh b/bootstrap.sh index 26e306d..701f47b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -16,7 +16,7 @@ if [[ $? != 0 ]]; then exit fi -echo "Performing install" +echo "TTRTS: Performing install" sudo make install if [[ $? != 0 ]]; then echo "TTRTS: Install failed, check output" diff --git a/scripts/gen_manpage.sh b/scripts/gen_manpage.sh index 9b7e02a..e699063 100755 --- a/scripts/gen_manpage.sh +++ b/scripts/gen_manpage.sh @@ -1,12 +1,17 @@ #! /bin/bash # Used to a man page from markdown +FILE="$4" +TEMP="$FILE.tmp" +echo ".\" Man page for the ttrts project" > $TEMP +echo ".\" this man page is auto-generated, do not edit directly" >> $TEMP -echo ".\" Man page for the ttrts project" > $4 -echo ".\" this man page is auto-generated, do not edit directly" >> $4 +echo ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" >> $TEMP -echo ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" >> $4 +# NOTE: For the OSX version of sed we use -E, which on linux appears be an undocumented switch for -r +# we also have to use [A-Za-z] instead of \w for some reason +# as well as escaped new line characters and literal tabs instead of \n and \t # sections to section headers # sub-sections in man page sub-sections @@ -19,13 +24,21 @@ echo ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" > # underline mapfile opt # ensure name section uses correct cat "$5" \ - | sed -r 's/^# (\w+)/.SH \1/g' \ - | sed -r 's/^##+ (\w+)/.SS \1/g' \ - | sed -r 's/^ (.*)$/\n\t\1\n/g' \ - | sed -r 's/^\t//g' \ - | sed -r 's/\$\{(\w+)\}/\\fB\$\1\\fR/g' \ - | sed -r 's/\{(\w+)\}/\\fI\1\\fR/g' \ - | sed -r 's/-----+//g' \ - | sed -r 's/`(.*?)`/\\fB\1\\fR/g' \ - | sed -r 's/MAPFILE/\\fImapfile\\fR/g' \ - | sed -r 's/\tttrts -/\tttrts \\-/g' >> $4 + | sed -E 's/^# ([A-Za-z]+)/.SH \1/g' \ + | sed -E 's/^##+ ([A-Za-z]+)/.SS \1/g' \ + | sed -E 's/^ (.*)$/\ + \1\ +/g' \ + | sed -E 's/^ //g' \ + | sed -E 's/\$\{([A-Za-z]+)\}/\\fB\$\1\\fR/g' \ + | sed -E 's/\{([A-Za-z]+)\}/\\fI\1\\fR/g' \ + | sed -E 's/-----+//g' \ + | sed -E 's/`(.*)`/\\fB\1\\fR/g' \ + | sed -E 's/MAPFILE/\\fImapfile\\fR/g' \ + | sed -E 's/HOSTNAME/\\fIhostname\\fR/g' \ + | sed -E 's/ ttrts -/ ttrts \\-/g' >> $TEMP + + +if [ ! -e $FILE ] || [ ! -z "$( diff $FILE $TEMP )" ]; then + mv -f $TEMP $FILE +fi diff --git a/scripts/gen_usage.sh b/scripts/gen_usage.sh index c989b2d..5e33166 100755 --- a/scripts/gen_usage.sh +++ b/scripts/gen_usage.sh @@ -1,10 +1,18 @@ #! /bin/bash # Used to generate usage text from markdown + +FILE="$1" +TEMP="${FILE}_tmp" + cat README.md \ - | sed 's/^#\+ //g' \ - | sed 's/^\t/\\t/g' \ - | sed 's/^ /\\t/g' \ - | sed ':a;N;$!ba;s/\n/\\n\n/g' \ - | sed 's/^/\"/' \ - | sed 's/$/\"/' \ - > $1 + | sed -E 's/^#+ //g' \ + | sed -E 's/^ /\\t/g' \ + | sed -E 's/^ /\\t/g' \ + | sed -E 's/^/\"/' \ + | sed -E 's/$/\\n\"/' \ + > $TEMP + +# If no difference +if [ ! -e $FILE ] || [ ! -z "$( diff $TEMP $FILE )" ]; then + mv -f $TEMP $FILE +fi diff --git a/scripts/gen_version_header.sh b/scripts/gen_version_header.sh index 907c2be..aec60e2 100755 --- a/scripts/gen_version_header.sh +++ b/scripts/gen_version_header.sh @@ -10,4 +10,9 @@ HEADER="// Auto generated ttrts version header #endif //_TTRTS_VERSION_H_" -echo "$HEADER" > "version.h" \ No newline at end of file +echo "$HEADER" > "version.h.tmp" + +# If no difference +if [ ! -e version.h ] || [ ! -z "$( diff version.h version.h.tmp )" ]; then + mv -f version.h.tmp version.h +fi diff --git a/scripts/ttrts_complete b/scripts/ttrts_complete index 7b884b7..d257ed4 100755 --- a/scripts/ttrts_complete +++ b/scripts/ttrts_complete @@ -5,11 +5,11 @@ test ! -z TTRTS_MAPS && TTRTS_MAPS=/usr/share/ttrts/maps/ have ttrts && function _ttrts { - commandnames="" + commandnames="--server --client --host= --host=localhost " for filename in ${TTRTS_MAPS}/* do map="${filename##*/}" - commandnames+="$map " + commandnames+="--map=$map " done local cur prev diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..d73e509 --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,13 @@ +# Main libraries +add_subdirectory( ttrts ) +add_subdirectory( system ) + +# Main binaries +add_subdirectory( launcher ) +add_subdirectory( client ) +add_subdirectory( server ) +add_subdirectory( local ) + +# Auxhilary binaries +add_subdirectory( test ) +add_subdirectory( gen ) \ No newline at end of file diff --git a/source/README.md b/source/README.md index 5ca2d8e..22b8dea 100644 --- a/source/README.md +++ b/source/README.md @@ -1,13 +1,121 @@ -# Targets -### client -Main TTRTS binary, runs from the command line and acts as host for games +# NAME + ttrts - Tiny Terminal RTS -### test -Test binary, to be compiled and run to test various functionality +# SYNOPSIS + ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE] + +# DESCRIPTION + ttrts is a tiny terminal based RTS that uses text files as order lists to control the units -### gen -Binary to generate example map files + This means that any user, program or cat that can read and write to text files can play the game -# Libraries -### ttrts -Implementation of the RTS rules and simulation +# RETURN VALUE + ttrts will return -1 on error, or the winning player on completion + +# OPTIONS + --server - Run in server mode, must provide a map file + + --client - Run in client mode, must provide a hostname for a running server + + --map=MAPFILE - File to read in the initial game state. Local or in ${TTRTS_MAPS} + + --host=HOSTNAME - Name of host to connect to in client mode + +# USAGE + When invoked, ttrts will set up the game in a directory within ${TTRTS_GAMES} by the name of the map + + The files in this directory can be read and interpreted by human, robot or cat + + ttrts will then await order files from each participant + + Once all order files have been received ttrts will calculate the turn and output a new gamestate file + + In server and client mode, the client will output and read in these files while the server simulates the game + + This process repeats until the game is over + +# ENVIRONMENT + ${TTRTS_MAPS} - Map file lookup location, defaults to `/usr/share/ttrts/maps/` + + ${TTRTS_GAMES} - Game directory for I/O, defaults to `/tmp/` + +----------------------------------------------------------- +# FILES + `/usr/share/ttrts/maps/` holds a sample set of maps + +## Gamestate File + Turn_{TURNNUMBER}.txt + +### Contents + ===== ttrts v{MAJOR}.{MINOR}.{PATCH} ===== + NAME:{GAMENAME} + SIZE:[{X},{Y}] + TURN:{TURNNUMBER} + WALL:[{X},{Y}][{X},{Y}][{X},{Y}]...{repeat for all walls} + ~~~~ + UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}] + ... {continue for all units} + END + +## Order File + Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt + +### Contents + ORDER:{ORDER_CHAR} id:{UNIT_ID} + ... {continue for all orders} + END + +----------------------------------------------------------- +# SERVER/CLIENT + When in server or client mode, the game can be played across a network. If desired, a player could design an AI to act as a client instead of using the client mode and intermediary filesystem. + +## Protocol + The server is accesible on port 11715 + + To perform the handshake the server will write to the socket with the format "player PLAYER_ID name GAME_NAME", it will expect this exact information to be written back to in reply. + + Once handshake is performed, the server will write to the socket in the form of the Gamestate file as above. + + The server will then wait for a new-line delimited and END terminated list of orders + + This will be repeated until the game is over + +----------------------------------------------------------- +# GAMEPLAY + + The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board + + Each turn, the client outputs a gamestate file and waits for an order file from each player + + All commands are evaluated simultaneously with friendly fire enabled by default + + The game is over when any of three conditions are met - + * All remaining units are controlled by a single player + * No units are left (draw) + * All units left are unable to move (draw) + +# UNITS + Each unit occupies a single tile on the board, facing in a compass direction (NESW) + + Units will only accept orders from their owner + + Units can receive only a single order each turn + + Units cannot occupy the same tile as other units/walls + +# ORDERS +### F - Move unit [F]orward one space, leaving a wall + + This wall will remain until the end of the game, blocking movement to that tile + + Movement orders have no effect if impossible, eg. + * Attempting to move outside of map + * Attempting to move on to tile occupied by unit/wall + +### L/R - Rotate unit [L]eft or [R]ight + + Unit will rotate clockwise or counter-clockwise, this order cannot fail + +### A - [A]ttack in straight line in front of unit + + Attack will continue forward until unit can't progress, all units within the path of the attack are destroyed. diff --git a/source/client/CMakeLists.txt b/source/client/CMakeLists.txt index 7d12cef..1ae8054 100644 --- a/source/client/CMakeLists.txt +++ b/source/client/CMakeLists.txt @@ -3,32 +3,21 @@ project( ttrts-client ) include_directories( - ${CMAKE_CURRENT_BINARY_DIR} - ../maths + ${CMAKE_CURRENT_BINARY_DIR} + ../system ../ttrts ) # Add the sources set( SOURCES - main.cpp + client.cpp ) # Add the executable add_executable( ${PROJECT_NAME} ${SOURCES} ) -# Set our output name to ttrts -set_target_properties( ${PROJECT_NAME} PROPERTIES OUTPUT_NAME ttrts ) - # dependent on main ttrts libary -target_link_libraries( ${PROJECT_NAME} ttrts ) +target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system ) # Installation target install( TARGETS ${PROJECT_NAME} DESTINATION bin ) - -# Run the gen_usage script to generate our usage header -add_custom_target( - ttrts-client-usage - cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_SOURCE_DIR}/scripts/gen_usage.sh "${CMAKE_CURRENT_BINARY_DIR}/usage.h" -) - -add_dependencies(${PROJECT_NAME} ttrts-client-usage) \ No newline at end of file diff --git a/source/client/README.md b/source/client/README.md deleted file mode 100644 index 9e8cf27..0000000 --- a/source/client/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# NAME - ttrts - Tiny Terminal RTS - -# SYNOPSIS - ttrts MAPFILE - -# DESCRIPTION - ttrts is a tiny terminal based RTS that uses text files as order lists to control the units - - This means that any user, program or cat that can read and write to text files can play the game - -# RETURN VALUE - ttrts will return -1 on error, or the winning player on completion - -# OPTIONS - MAPFILE - File to read in the initial game state. Local or in ${TTRTS_MAPS} - -# USAGE - When invoked, ttrts will set up the game in a directory within ${TTRTS_GAMES} by the name of the map - - The files in this directory can be read and interpreted by human, robot or cat - - ttrts will then await order files from each participant - - Once all order files have been received ttrts will calculate the turn and output a new gamestate file - - This process repeats until the game is over - -# ENVIRONMENT - ${TTRTS_MAPS} - Map file lookup location, defaults to `/usr/share/ttrts/maps/` - - ${TTRTS_GAMES} - Game directory for I/O, defaults to `/tmp/` - ------------------------------------------------------------ -# FILES - `/usr/share/ttrts/maps/` holds a sample set of maps - -## Gamestate File - Turn_{TURNNUMBER}.txt - -### Contents - ===== ttrts v{MAJOR}.{MINOR}.{PATCH} ===== - NAME:{GAMENAME} - SIZE:[{X},{Y}] - TURN:{TURNNUMBER} - WALL:[{X},{Y}][{X},{Y}][{X},{Y}]...{repeat for all walls} - ~~~~ - UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}] - ... {continue for all units} - END - -## Order File - Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt - -### Contents - ORDER:{ORDER_CHAR} id:{UNIT_ID} - ... {continue for all orders} - END - ------------------------------------------------------------ -# GAMEPLAY - - The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board - - Each turn, the client outputs a gamestate file and waits for an order file from each player - - All commands are evaluated simultaneously with friendly fire enabled by default - - The game is over when any of three conditions are met - - * All remaining units are controlled by a single player - * No units are left (draw) - * All units left are unable to move (draw) - -# UNITS - Each unit occupies a single tile on the board, facing in a compass direction (NESW) - - Units will only accept orders from their owner - - Units can receive only a single order each turn - - Units cannot occupy the same tile as other units/walls - -# ORDERS -### F - Move unit [F]orward one space, leaving a wall - - This wall will remain until the end of the game, blocking movement to that tile - - Movement orders have no effect if impossible, eg. - * Attempting to move outside of map - * Attempting to move on to tile occupied by unit/wall - -### L/R - Rotate unit [L]eft or [R]ight - - Unit will rotate clockwise or counter-clockwise, this order cannot fail - -### A - [A]ttack in straight line in front of unit - - Attack will continue forward until unit can't progress, all units within the path of the attack are destroyed. diff --git a/source/client/client.cpp b/source/client/client.cpp new file mode 100644 index 0000000..116a5a0 --- /dev/null +++ b/source/client/client.cpp @@ -0,0 +1,69 @@ +#include + +#include "net.h" +#include "game.h" +#include "error.h" +#include "filesystem.h" + +int main(int argc, char* argv[]) +{ + // must provide information + if (argc < 2) + fatal_error("Usage: ttrts-client HOST"); + + std::string hostname = argv[1]; + + sockaddr_in serv_addr; // Server address + memset(&serv_addr,0, sizeof(serv_addr)); + + // Set the server to AF_INET + serv_addr.sin_family = AF_INET; + // Set our server address port to the port number provided + serv_addr.sin_port = htons(TTRTS_PORT); + + std::cout<<"TTRTS: Connecting to "<= 0 ) + { + std::cout<<"TTRTS: Waiting for gamestate"< -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "game.h" - -static const char* sk_usage = -#include "usage.h" -; - -// Verbose mode -static const bool env_verbose = getenv("VERBOSE"); - -// time for waiting between file stats -static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100); - -// Check if a file exists -inline bool FileExists( const std::string& name ) -{ - struct stat buffer; - return (stat (name.c_str(), &buffer) == 0); -} - -// Wait for a file to exist -inline void WaitForFile( const std::string& name, const std::chrono::milliseconds& time ) -{ - while( !FileExists(name) ) std::this_thread::sleep_for(time); -} - -bool OutputGameStateFile(CTTRTSGame &game, std::string &gameDir) -{ - char turnFileName[128]; - snprintf(turnFileName,128,"%s/Turn_%i.txt",gameDir.c_str(),game.GetTurn()); - std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists - - if ( turnFile.bad() ) - { - return false; - } - - // Output the turn description - std::string turnDescriptor = GetStringFromGame(game); - - turnFile<(file)),std::istreambuf_iterator()); - - if( gameDescriptor.size() == 0 ) - { - std::cerr<<"Error: failed to read in any information from "<>input; - if( !input.size() || std::tolower(input[0]) != 'y' ) - { - std::cerr<<"Aborting..."<(turnFile)),std::istreambuf_iterator()); - - // Issue the orders to the game - if( game.IssueOrders(player, orders) ) - std::cerr<<"Warning: Orders for player "<<(int) player <<" failed to correctly parse"<', uvector2(1, 1), game); AddUnitToGame( player_t::Green, '>', uvector2(1, 3), game); + game.AddWall(uvector2(3,2)); + game.AddWall(uvector2(3,3)); + OutputGame(std::move(game)); } - // Basic 5v5 game + // Basic 1v1 game + // -------------------- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -------------------- { CTTRTSGame game(20, 12); - game.SetName("Big2v2"); + game.SetName("Big2Player"); for ( ucoord_t y : { 2,4,6,8,10 } ) AddUnitToGame( player_t::Red, '<', uvector2(18, y), game); @@ -51,7 +72,15 @@ int main() OutputGame(std::move(game)); } - // Chess 10v10 game + // Sort of like Chess + //GG------ + //------RR + //GG------ + //------RR + //GG------ + //------RR + //GG------ + //------RR { CTTRTSGame game(8, 8); game.SetName("Chess"); @@ -68,4 +97,69 @@ int main() OutputGame(std::move(game)); } + + // Medium 4 player game + //---------- + //---------- + //---GGGG--- + //--R -- B-- + //--R- -B-- + //--R- -B-- + //--R -- B-- + //---YYYY--- + //---------- + //---------- + { + CTTRTSGame game(10, 10); + game.SetName("Medium4Player"); + + for ( ucoord_t y : { 2,3,4,5 } ) { + AddUnitToGame(player_t::Red, '>', uvector2(2, y), game); + AddUnitToGame(player_t::Blue, '<', uvector2(7, y), game); + } + + for ( ucoord_t x : { 2,3,4,5 } ) { + AddUnitToGame(player_t::Yellow, '^', uvector2(x,7), game); + AddUnitToGame(player_t::Green, 'v', uvector2(x,2), game); + } + + // Diagonal walls + game.AddWall(uvector2(3,3)); + game.AddWall(uvector2(3,6)); + game.AddWall(uvector2(6,3)); + game.AddWall(uvector2(6,6)); + + // middle walls + game.AddWall(uvector2(4,4)); + game.AddWall(uvector2(4,5)); + game.AddWall(uvector2(5,4)); + game.AddWall(uvector2(5,5)); + + OutputGame(std::move(game)); + } + + // Medium 3 player game + //---------- + //--------Y- + //--------Y- + //---------- + //-G-------- + //-G-------- + //---------- + //--------R- + //--------R- + //---------- + { + CTTRTSGame game(10, 10); + game.SetName("Medium3Player"); + + AddUnitToGame(player_t::Red, '<', uvector2(8, 1), game); + AddUnitToGame(player_t::Red, '<', uvector2(8, 2), game); + AddUnitToGame(player_t::Green, '>', uvector2(1, 4), game); + AddUnitToGame(player_t::Green, '>', uvector2(1, 5), game); + AddUnitToGame(player_t::Yellow, '<', uvector2(8,7), game); + AddUnitToGame(player_t::Yellow, '<', uvector2(8,8), game); + + OutputGame(std::move(game)); + } } \ No newline at end of file diff --git a/source/launcher/CMakeLists.txt b/source/launcher/CMakeLists.txt new file mode 100644 index 0000000..c6726c7 --- /dev/null +++ b/source/launcher/CMakeLists.txt @@ -0,0 +1,6 @@ +# ====================== ttrts ======================= +# Project name +project( ttrts-perl-launch ) + +# Add bash completion to install +install( PROGRAMS ttrts.pl DESTINATION bin RENAME ttrts ) diff --git a/source/launcher/ttrts.pl b/source/launcher/ttrts.pl new file mode 100755 index 0000000..886fed6 --- /dev/null +++ b/source/launcher/ttrts.pl @@ -0,0 +1,44 @@ +#! /usr/bin/perl +# Main ttrts launcher script + +use strict; +use warnings; +use 5.0; + +use Getopt::Long qw(GetOptions); + +sub print_usage +{ + print "Unknown option: @_\n" if ( @_ ); + print "Usage: ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE]\n"; + exit; +} + +our $VERBOSE = $ENV{"VERBOSE"}; + +our $server; +our $client; +our $host; +our $map; + +print_usage() if ( @ARGV < 1 or + !GetOptions( + 'client' => \$client, + 'server' => \$server, + 'host=s' => \$host, + 'map=s' => \$map, + ) ); + +# Verify we have the right parameters +print "Cannot run as both client and server\n" and exit if $client and $server; +print "Client requires hostname\n" and exit if $client and not $host; +print "Server requires mapfile\n" and exit if $server and not $map; +print "Running locally requires mapfile\n" and exit if not $server and not $client and not $map; + +# Run client, server or local +my $res = -1; +$res = system("ttrts-client $host") if $client and $host; +$res = system("ttrts-server $map") if $server and $map; +$res = system("ttrts-local $map") if not $server and not $client and $map; + +return $res \ No newline at end of file diff --git a/source/local/CMakeLists.txt b/source/local/CMakeLists.txt new file mode 100644 index 0000000..172b736 --- /dev/null +++ b/source/local/CMakeLists.txt @@ -0,0 +1,23 @@ +# ====================== ttrts ======================= +# Project name +project( ttrts-local ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ../system + ../ttrts +) + +# Add the sources +set( SOURCES + local.cpp +) + +# Add the executable +add_executable( ${PROJECT_NAME} ${SOURCES} ) + +# dependent on main ttrts libary +target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system pthread ) + +# Installation target +install( TARGETS ${PROJECT_NAME} DESTINATION bin ) \ No newline at end of file diff --git a/source/local/local.cpp b/source/local/local.cpp new file mode 100644 index 0000000..b2697e7 --- /dev/null +++ b/source/local/local.cpp @@ -0,0 +1,62 @@ +#include "game.h" +#include "filesystem.h" +#include "error.h" +#include "net.h" + +#include + +// ===================================================================================================================== +int main(int argc, char* argv[]) +{ + // must provide information + if (argc < 2) + fatal_error("Usage: ttrts-local MAPFILE"); + + std::string gamefile = argv[1]; + + std::cout<<"TTRTS: Launching with "< +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "net.h" +#include "filesystem.h" + +void RunServerForGame(CTTRTSGame &game) +{ + std::cout<<"TTRTS: Setting up server"< players = game.GetPlayers(); + unsigned int numClients = players.size(); + auto player_iterator = players.begin(); + + // game mutex + std::mutex gameMutex; + + // Set of clients + std::vector myClients; + + std::cout<<"TTRTS: Waiting for "< +#include +#include + +//====================================================================================================================== +// Error functions + +// For local fatal errors +inline void fatal_error(const char *msg) +{ + std::cerr< +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// ===================================================================================================================== +// time for waiting between file stats +static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100); + +// Check if a file exists +bool FileExists( const std::string& name ) +{ + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); +} + +// Wait for a file to exist +void WaitForFile( const std::string& name, const std::chrono::milliseconds& time ) +{ + while( !FileExists(name) ) std::this_thread::sleep_for(time); +} + +bool OutputGameStateFile(CTTRTSGame &game) +{ + char turnFileName[128]; + snprintf(turnFileName,128,"%s%s/Turn_%i.txt", getGamesDir().c_str(),game.GetName().c_str(),game.GetTurn()); + std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists + + if ( turnFile.bad() ) + { + return false; + } + + // Output the turn description + std::string turnDescriptor = GetStringFromGame(game); + + turnFile<(file)),std::istreambuf_iterator()); + + if( gameDescriptor.size() == 0 ) + fatal_error("failed to read in any information from gamefile"); + + // Create the game + return GetGameFromString(gameDescriptor); +} + +std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player) +{ + std::string gameDir = getGamesDir(); + + char playerOrderFileName[128]; + snprintf(playerOrderFileName, 128, "%s%s/Player_%i_Turn_%i.txt", gameDir.c_str(),game.GetName().c_str(),(int) player, game.GetTurn()); + + // Wait for the player order file to be created + std::clog<<"TTRTS: Waiting for "<< playerOrderFileName << std::endl; + bool hasOrderFile = false; + while(!hasOrderFile) + { + WaitForFile(playerOrderFileName,sk_waitTime); // Wait for the file + + // File must have END + // Method taken from http://stackoverflow.com/questions/11876290/c-fastest-way-to-read-only-last-line-of-text-file + std::ifstream turnFile(playerOrderFileName); + turnFile.seekg(-1, std::ios_base::end); + + // Loop back from the end of file + bool keepLooping = true; + while(keepLooping) { + char ch; + turnFile.get(ch); // Get current byte's data + + if((int)turnFile.tellg() <= 1) { // If the data was at or before the 0th byte + turnFile.seekg(0); // The first line is the last line + keepLooping = false; // So stop there + } + else if(ch == '\n') { // If the data was a newline + keepLooping = false; // Stop at the current position. + } + else { // If the data was neither a newline nor at the 0 byte + turnFile.seekg(-2, std::ios_base::cur); // Move to the front of that data, then to the front of the data before it + } + } + + // Grab this line + std::string lastLine; + getline(turnFile,lastLine); + if(lastLine == "END") + hasOrderFile = true; + } + + std::ifstream turnFile(playerOrderFileName); + + // Reserve the full order string + std::string orders; + turnFile.seekg(0, std::ios_base::end); + orders.reserve(turnFile.tellg()); + turnFile.seekg(0, std::ios_base::beg); + + // Grab the string from the file + orders.assign((std::istreambuf_iterator(turnFile)), std::istreambuf_iterator()); + return orders; +} + +int CreateAndCleanGameDir(const std::string& gameName) +{ + std::string gameDir = getGamesDir()+gameName; + struct stat info; + int ret = stat( gameDir.c_str(), &info ); + if( ret == 0 && info.st_mode & S_IFDIR ) + { + std::cout<<"TTRTS: " << gameDir << " game directory already exists"<>input; + if( !input.size() || std::tolower(input[0]) != 'y' ) + return -1; + } + else if ( ret == 0 ) + { + fatal_error("TTRTS_GAMES exists but is not directory \nAborting..."); + } + + // Create the game directory + char cmd2[128]; + snprintf(cmd2,128, "test -d %s || mkdir %s",gameDir.c_str(),gameDir.c_str()); + if( system(cmd2) == -1) + fatal_error("Error: Failed to create the game directory"); + + // Clean out the game directory + char cmd1[128]; + snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str()); + if ( system(cmd1) == -1 ) + fatal_error("Error: Failed to clean the game directory"); + + return 0; +} + +int OutputGameEnd(const CTTRTSGame &game) { + std::cout<<"TTRTS: Game Over!"<< std::endl; + + // Get the winning player + player_t winningPlayer = game.GetWinningPlayer(); + + // Print the winner! + if ( winningPlayer != player_t::NUM_INVALID ) + { + std::cout<<"TTRTS: Winner:"<<(int) winningPlayer < +#include + +#include "game.h" + +#define STRINGIFY(x) _STRINGIFY(x) +#define _STRINGIFY(x) #x + +bool FileExists( const std::string& name ); + +void WaitForFile( const std::string& name, const std::chrono::milliseconds& time ); + +bool OutputGameStateFile(CTTRTSGame &game); + +std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player); + +CTTRTSGame GetGameFromFile( const std::string& file ); + +std::string getMapsDir(); +std::string getGamesDir(); + +int runFromFilesystem(int argc, char* argv[]); + +int CreateAndCleanGameDir(const std::string& gameName); + +#endif \ No newline at end of file diff --git a/source/system/net.cpp b/source/system/net.cpp new file mode 100644 index 0000000..29c23b9 --- /dev/null +++ b/source/system/net.cpp @@ -0,0 +1,261 @@ +#include "net.h" +#include "error.h" + +#include + +#include +#include + +#include +#include +#include +#include + +void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut) +{ + char buffer[1028]; // buffer for orders + + std::clog<<"TTRTS: Waiting for player "<<(int)info.player<<" at "< &myClients, CTTRTSGame &game, std::mutex &gameMutex) +{ + // Spawn threads + std::vector clientThreads; + for(auto client : myClients) + { + std::thread clientThread(WaitForOrdersFromClient, client, std::ref(game), ref(gameMutex)); + clientThreads.push_back(move(clientThread)); + } + + // Join up all the threads + for ( std::thread& thread : clientThreads ) + { + thread.join(); + } +} + +void SendGamestateToClients(std::vector &myClients, const CTTRTSGame &game, std::mutex &gameMutex) +{ + gameMutex.lock(); + std::string gamestate_string = GetStringFromGame(game); + gameMutex.unlock(); + + for (auto client : myClients) + { + // Write to the socket with the buffer + if ( write( client.clientsockfd, gamestate_string.c_str(), gamestate_string.length() ) < 0 ) + fatal_perror("ERROR sending to client"); + } +} + + +void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString) +{ + char handshakeBuffer[128]; + memset(handshakeBuffer,0,sizeof(handshakeBuffer)); + + if (read(sockfd, handshakeBuffer,sizeof(handshakeBuffer)-1) < 0) + fatal_perror("ERROR recieving handshake from server"); + + std::string handshake(handshakeBuffer); + + if ( write( sockfd, handshake.c_str(), handshake.length()+1 ) < 0 ) + fatal_perror("ERROR sending handshake to server"); + + char gameName[64]; + if ( sscanf(handshake.c_str(),TTRTS_HANDSHAKE_FORMAT,&player,gameName) < 2 ) + fatal_error("Handshake failed"); + + gameNameString = gameName; +} + +void PerformServerHandshake(const ClientInfo &client, const std::string &game) +{ + char handshake[64]; + snprintf(handshake, sizeof(handshake), TTRTS_HANDSHAKE_FORMAT,(unsigned int)client.player,game.c_str()); + + // Send handshake + if ( write( client.clientsockfd,handshake,sizeof(handshake) ) < 0 ) + fatal_perror("ERROR sending to client"); + + // Receive handshake + char buffer[64]; + if ( read(client.clientsockfd,buffer,sizeof(buffer)-1) < 0 ) + fatal_perror("ERROR reading from client"); + + // Verify handshake + if ( std::string(buffer) != std::string(handshake) ) + fatal_error("Error in client handshake"); + + std::clog<<"TTRTS: Success on handshake with player "<<(int)client.player<< std::endl; +} + +void TryBindSocket(int sockfd, const sockaddr_in &serv_addr) +{ + std::clog<<"TTRTS: Binding to socket"<< std::endl; + int retry = 1; + while (1) + { + if(retry > 10) + fatal_error("Binding failed"); + + // Attempt to bind our listening socket + if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) >= 0) + break; + + std::cerr<<"Warning: Binding failed on try "<h_addr, server->h_length); + + // Attempt to connect to the server using the socket and server address info + if (connect(sockfd, (const sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) + fatal_perror("ERROR connecting"); + + return sockfd; +} + +std::string WaitForGamestateMessage(int sockfd) +{ + std::string gamestate; + char gamestateBuffer[1028]; + while( gamestate.find("END") == std::string::npos ) + { + memset(gamestateBuffer,0,sizeof(gamestateBuffer)); + + // Receive gamestate + if (read(sockfd,gamestateBuffer,sizeof(gamestateBuffer)-1) < 0) + fatal_perror("ERROR reading from client"); + + gamestate+=gamestateBuffer; + } + return gamestate; +} + +int SendOrdersToServer(int sockfd, const std::string &orders) +{ + int n = write(sockfd,orders.c_str(),orders.length()); + if (n < 0) + fatal_perror("ERROR writing to socket"); + return n; +} + + +int OutputGameEnd( CTTRTSGame& game ) +{ + std::cout<<"TTRTS: Game Over!"< +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define TTRTS_HANDSHAKE_FORMAT "player %u name %s" + +//====================================================================================================================== +// Structs for net management + +// Struct for net client info +struct ClientInfo +{ + sockaddr_in cli_addr; + int clientsockfd; + player_t player; +}; + + +//====================================================================================================================== +// Server side function + +// Get the address of a local server +sockaddr_in GetLocalServerAddress(); + +// Set up a new listening socket for the server +int SetUpServerListeningSocket(const sockaddr_in &serv_addr); + +// Wait for client connection on listening socket sockfd +// Will fill clientInfo with client information +ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo); + +// Wait for orders from a client, will not return until client has send valid orders +// Will automatically add orders to the game +void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut); + +// Iterates through a list of clients calling WaitForOrdersFromClient +void WaitForOrdersFromClients(std::vector &myClients, CTTRTSGame &game, std::mutex &gameMutex); + +// Sends current gamestate to each client +void SendGamestateToClients(std::vector &myClients, const CTTRTSGame &game, std::mutex &gameMutex); + +// Tries to bind to a socket, will attempt 10 times with longer waits between +void TryBindSocket(int sockfd, const sockaddr_in &serv_addr); + +// Perform the server side handshake with a client +void PerformServerHandshake(const ClientInfo &client, const std::string &game); + +//====================================================================================================================== +// Client side functions + +// Connect to the host, returns new socket for communication +int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr); + +// Perform the client side handshake with the server +void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString); + +// Wait for gamestate message from host +std::string WaitForGamestateMessage(int sockfd); + +// Send orders to the server +int SendOrdersToServer(int sockfd, const std::string &orders); + +//====================================================================================================================== +// Other functions +int OutputGameEnd( CTTRTSGame& game ); + +#endif \ No newline at end of file diff --git a/source/test/CMakeLists.txt b/source/test/CMakeLists.txt index 179f7a8..3e6a60c 100644 --- a/source/test/CMakeLists.txt +++ b/source/test/CMakeLists.txt @@ -4,8 +4,7 @@ project( ttrts-test ) include_directories( - ../ttrts - ../maths + ../ttrts ) set( SOURCES diff --git a/source/ttrts/CMakeLists.txt b/source/ttrts/CMakeLists.txt index b528b3f..93a6d94 100644 --- a/source/ttrts/CMakeLists.txt +++ b/source/ttrts/CMakeLists.txt @@ -5,8 +5,7 @@ project( ttrts ) # Include the maths include_directories( - ${CMAKE_CURRENT_BINARY_DIR} - ../maths + ${CMAKE_CURRENT_BINARY_DIR} ) # Add our sources @@ -44,4 +43,4 @@ install( ) # Install the ttrts static lib -install( TARGETS ttrts DESTINATION lib ) \ No newline at end of file +install( TARGETS ttrts DESTINATION lib ) diff --git a/source/ttrts/formatters.cpp b/source/ttrts/formatters.cpp index 0127803..332c5ac 100644 --- a/source/ttrts/formatters.cpp +++ b/source/ttrts/formatters.cpp @@ -92,7 +92,7 @@ CTTRTSGame GetGameFromString( const std::string& input ) size_t pos; while ( ( pos = walls_str.find(']') ) != std::string::npos ) { - std::string pos_string = walls_str.substr(1,pos); + std::string pos_string = walls_str.substr(0,pos+1); // Use scanf to extract positions @@ -209,4 +209,4 @@ SOrder GetOrderFromString( const std::string& order ) ret.unit = unit; return ret; -} \ No newline at end of file +} diff --git a/source/ttrts/game.cpp b/source/ttrts/game.cpp index 96800d5..d649060 100644 --- a/source/ttrts/game.cpp +++ b/source/ttrts/game.cpp @@ -17,6 +17,7 @@ CTTRTSGame::CTTRTSGame(CTTRTSGame&& game) , dimensions(std::move(game.dimensions)) , turn(std::move(game.turn)) , name(std::move(game.name)) +, m_walls(std::move(game.m_walls)) { }