diff --git a/.gitignore b/.gitignore index 379896d..502564d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ *.user *.sublime* *.idea +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..97a47ba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required( VERSION 2.8.7 ) + +# Set version information +set( TTRTS_VERSION_MAJOR 0 ) +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" ) + +# Turn on all warnings +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder" ) + +# Turn off reorder warnings as they're kind of irrelevant +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) + +# This shouldn't be needed, but it looks like IDE's like clion can forget to set -g for Debug +if( CMAKE_BUILD_TYPE MATCHES "Debug" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g" ) +endif() + +# Add bash completion to install +install( FILES scripts/ttrts_complete DESTINATION /etc/bash_completion.d/ ) + +# Add target to generate man page +# 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/README.md" +) + +# Install the ttrts man page +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 ) diff --git a/README.md b/README.md index ed29f53..a6b299b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TTRTS v0.3.0 +# TTRTS *The Tiny Terminal RTS where the players write their AIs* ----------------------------------------------------------- @@ -11,7 +11,7 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An ## Building TTRTS #### Requirements -* cmake - our build system uses cmake +* CMake - our build system uses cmake * Linux/OSX - currently no support for Windows, tracked with [Issue #9](https://github.com/mdiluz/ttrts/issues/9) #### To Build @@ -23,15 +23,43 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An ----------------------------------------------------------- ## Development -* [master](master) branch always stores latest stable release +* master branch always stores latest stable release * master/{hotfix} branches store in progress hotfixes for the stable branch -* [dev](dev) branch stores in progress development +* dev branch stores in progress development * dev/{feature} branches store features ----------------------------------------------------------- ## Changelog -## v0.3.0 +#### 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 + * ttrts headers in /usr/local/include/ttrts + * bash completion into /etc/bash_completion.d/ + * man page into /usr/local/man/man6 + * maps into /usr/share/ttrts/maps +* client now supports env variable configuration + * TTRTS_MAPS for location of map files, defaults to /usr/share/ttrts/maps + * TTRTS_GAMES for gameplay directories, defaults to /tmp/ +* Map files now have proper header +* NOTE: This version is compatible with v0.3.0, but old generated mapfiles will need the additional header line added + +#### v0.3.0 * Additional functionality of walls * Walls are noted in gamestate file on new "WALL:[X,Y]..." line * Walls are impassable by all movement @@ -40,13 +68,13 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An * Various C++ api simplifications * Integration of perl api from [ttrts-players](https://github.com/mdiluz/ttrts-players) -## v0.2.0 +#### v0.2.0 * All team references changed to player * Order file format changed to Player_#_Turn_#.txt * Unit descriptors now use pl: instead of tm: * Various other C++ api corrections and refactors -## v0.1.0 +#### v0.1.0 * First playable version of ttrts ----------------------------------------------------------- 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 9b9a839..701f47b 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,38 +1,34 @@ #! /bin/bash +# Double check for cmakelist +if [ ! -e "CMakeLists.txt" ]; then + echo "TTRTS: No source cmakelist found" + exit +fi + +# Run cmake echo "TTRTS: Running cmake" test ! -e build && mkdir build cd build/ -cmake ../source +cmake .. if [[ $? != 0 ]]; then echo "TTRTS: CMake failed, exiting Bootstrap" + exit fi -echo "TTRTS: Running make" -make +echo "TTRTS: Performing install" +sudo make install if [[ $? != 0 ]]; then - echo "TTRTS: make failed, exiting Bootstrap" + echo "TTRTS: Install failed, check output" + exit fi +# Run final test to make sure echo "TTRTS: Running tests" -./test/ttrts-test +./source/test/ttrts-test if [[ $? != 0 ]]; then echo "TTRTS: Tests failed, build must be broken" + exit fi -echo "TTRTS: Generating maps" -test ! -e ../maps && mkdir ../maps -cd ../maps -./../build/gen/ttrts-gen -if [[ $? != 0 ]]; then - echo "TTRTS: Generating maps, exiting Bootstrap" -fi - -echo "TTRTS: Moving binaries" -cd .. -if [ ! -e build/ttrts/ttrts ]; then - echo "TTRTS: No TTRTS Binary found, something has gone wrong" -fi - -cp build/ttrts/ttrts . -chmod a+x ttrts +echo "TTRTS: Bootstrap complete" diff --git a/scripts/gen_manpage.sh b/scripts/gen_manpage.sh new file mode 100755 index 0000000..e699063 --- /dev/null +++ b/scripts/gen_manpage.sh @@ -0,0 +1,44 @@ +#! /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 ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" >> $TEMP + +# 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 +# 4-space lines to tabs +# tab starts removed +# Environment variables in bold +# User variables in italics +# remove all line-widths +# Put all back-ticks quotes in bold +# underline mapfile opt +# ensure name section uses correct +cat "$5" \ + | 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_maps.sh b/scripts/gen_maps.sh new file mode 100755 index 0000000..bb02331 --- /dev/null +++ b/scripts/gen_maps.sh @@ -0,0 +1,13 @@ +#! /bin/bash +# Use to generate the ttrts maps + +ttrtsgen=$1 + +test ! -e maps && mkdir maps # Make maps directory if needed +if [ ! -e maps ]; then + exit 1 +fi + +cd maps + +$ttrtsgen \ No newline at end of file diff --git a/scripts/gen_usage.sh b/scripts/gen_usage.sh new file mode 100755 index 0000000..5e33166 --- /dev/null +++ b/scripts/gen_usage.sh @@ -0,0 +1,18 @@ +#! /bin/bash +# Used to generate usage text from markdown + +FILE="$1" +TEMP="${FILE}_tmp" + +cat README.md \ + | 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 new file mode 100755 index 0000000..aec60e2 --- /dev/null +++ b/scripts/gen_version_header.sh @@ -0,0 +1,18 @@ +HEADER="// Auto generated ttrts version header +// do not edit manually +#ifndef _TTRTS_VERSION_H_ +#define _TTRTS_VERSION_H_ + +#define TTRTS_VERSION_MAJOR $1 +#define TTRTS_VERSION_MINOR $2 +#define TTRTS_VERSION_PATCH $3 +#define TTRTS_VERSION_STRING \"v$1.$2.$3\" + +#endif //_TTRTS_VERSION_H_" + +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 new file mode 100755 index 0000000..d257ed4 --- /dev/null +++ b/scripts/ttrts_complete @@ -0,0 +1,23 @@ +# ttrts completion + +test ! -z TTRTS_MAPS && TTRTS_MAPS=/usr/share/ttrts/maps/ + +have ttrts && +function _ttrts +{ + commandnames="--server --client --host= --host=localhost " + for filename in ${TTRTS_MAPS}/* + do + map="${filename##*/}" + commandnames+="--map=$map " + done + + local cur prev + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + COMPREPLY=( $(compgen -W "${commandnames}" -- ${cur}) ) +} + +complete -F _ttrts ttrts diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1f5def4..d73e509 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,37 +1,13 @@ -cmake_minimum_required( VERSION 2.8.7 ) - -# Set version information -set( TTRTS_VERSION_MAJOR 0 ) -set( TTRTS_VERSION_MINOR 3 ) -set( TTRTS_VERSION_PATCH 0 ) - -# Use c++1y (14) -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" ) - -# Turn on all warnings -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder" ) - -# Turn off reorder warnings as they're kind of irrelevant -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) - -# This shouldn't be needed, but it looks like IDE's like clion can forget to set -g for Debug -if( CMAKE_BUILD_TYPE MATCHES "Debug" ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g" ) -endif() - -# Add definitions for the version number -add_definitions( - -DTTRTS_VERSION_MAJOR=${TTRTS_VERSION_MAJOR} - -DTTRTS_VERSION_MINOR=${TTRTS_VERSION_MINOR} - -DTTRTS_VERSION_PATCH=${TTRTS_VERSION_PATCH} - -DTTRTS_VERSION_STRING=\"${TTRTS_VERSION_MAJOR}.${TTRTS_VERSION_MINOR}.${TTRTS_VERSION_PATCH}\" -) - - -# Subprojects +# Main libraries add_subdirectory( ttrts ) -add_subdirectory( game ) +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 ) +add_subdirectory( gen ) \ No newline at end of file diff --git a/source/README.md b/source/README.md index a18fa94..22b8dea 100644 --- a/source/README.md +++ b/source/README.md @@ -1,19 +1,121 @@ -# Targets -### ttrts -Main TTRTS binary, runs from the command line and acts as host for games +# NAME + ttrts - Tiny Terminal RTS -### test (ttrts-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 (ttrts-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 -### game -Implementation of the RTS rules and simulation +# RETURN VALUE + ttrts will return -1 on error, or the winning player on completion -### maths -Simple maths library for 2D calculations and types +# OPTIONS + --server - Run in server mode, must provide a map file -### scripts -Directory of scripts used in build process + --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 new file mode 100644 index 0000000..1ae8054 --- /dev/null +++ b/source/client/CMakeLists.txt @@ -0,0 +1,23 @@ +# ====================== ttrts ======================= +# Project name +project( ttrts-client ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ../system + ../ttrts +) + +# Add the sources +set( SOURCES + client.cpp +) + +# Add the executable +add_executable( ${PROJECT_NAME} ${SOURCES} ) + +# dependent on main ttrts libary +target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system ) + +# Installation target +install( TARGETS ${PROJECT_NAME} DESTINATION bin ) 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"< // std::numeric_limits - -#define GAME_POS_FORMATTER "[%u,%u]" - -#define GAME_HEADER_FORMATTER "NAME:%s\nSIZE:" GAME_POS_FORMATTER "\nTURN:%u\nWALL:%s" -#define GAME_HEADER_DELIMITER "~~~~\n" - -#define UNIT_FORMATTER "UNIT:%u pl:%u vs:%c dr:%c ps:" GAME_POS_FORMATTER - -// Type for a Player IDs -enum class player_t : char -{ - Red = 0, - Green, - Yellow, - Blue, - NUM_INVALID -}; - - -typedef unsigned short unit_id_t; // Type for unit IDs -typedef char unitvis_c; // Typedef for unit visual representations - -static const unit_id_t unit_id_invalid = std::numeric_limits::max(); -static const unitvis_c unitvis_invalid = std::numeric_limits::max(); - -#endif //_GAME_TYPES_H_ \ No newline at end of file diff --git a/source/game/order.cpp b/source/game/order.cpp deleted file mode 100644 index 8e0e82f..0000000 --- a/source/game/order.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "order.h" - -// Convert an order to a string -std::string GetStringFromOrder(const SOrder & order ) -{ - static char buff[128]; - memset(buff,0,sizeof(buff)); - - snprintf(buff,128, ORDER_FORMATTER, - order.command, - order.unit); - - return buff; -} - -// Convert a string to an order -SOrder GetOrderFromString( const std::string& order ) -{ - SOrder ret; - - char corder; - unsigned int unit; - - sscanf(order.c_str(), ORDER_FORMATTER, - &corder, - &unit); - - ret.command = (command_c)corder; - ret.unit = unit; - - return ret; -} diff --git a/source/gen/CMakeLists.txt b/source/gen/CMakeLists.txt index 9eff3b8..67167f0 100644 --- a/source/gen/CMakeLists.txt +++ b/source/gen/CMakeLists.txt @@ -3,8 +3,7 @@ project( ttrts-gen ) include_directories( - ../game - ../maths + ../ttrts ) set( SOURCES @@ -14,4 +13,15 @@ set( SOURCES # Add the executable add_executable( ttrts-gen ${SOURCES} ) -target_link_libraries( ttrts-gen game ) \ No newline at end of file +target_link_libraries( ttrts-gen ttrts ) + +# Run the gen_usage script to generate our usage header +add_custom_target( + ttrts-gen-maps ALL + ${CMAKE_SOURCE_DIR}/scripts/gen_maps.sh "${CMAKE_CURRENT_BINARY_DIR}/ttrts-gen" +) + +add_dependencies(ttrts-gen-maps ${PROJECT_NAME}) + +# Installation target +install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/maps DESTINATION /usr/local/share/ttrts ) diff --git a/source/gen/gen.cpp b/source/gen/gen.cpp index ac7ab2e..0f782b8 100644 --- a/source/gen/gen.cpp +++ b/source/gen/gen.cpp @@ -2,6 +2,7 @@ #include #include +#include "formatters.h" void AddUnitToGame( player_t player, char vis, uvector2 vec, CTTRTSGame& game ) { @@ -15,7 +16,7 @@ void OutputGame( CTTRTSGame&& game ) { std::ofstream output; output.open (game.GetName() + ".txt"); - output << game.GetStateAsString(); + output << GetStringFromGame(game); output.close(); __forceResetCUnitID(); @@ -23,23 +24,44 @@ void OutputGame( CTTRTSGame&& game ) int main() { - // Tiny 2v2 Game + // Tiny 1v1 Game + //------ + //-G---- + //----R- + //-G---- + //----R- + //------ { CTTRTSGame game(6, 6); - game.SetName("Tiny2v2"); + game.SetName("Tiny2Player"); AddUnitToGame( player_t::Red, '<', uvector2(4, 2), game); AddUnitToGame( player_t::Red, '<', uvector2(4, 4), game); AddUnitToGame( player_t::Green, '>', 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); @@ -50,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"); @@ -67,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 "< // std::numeric_limits - -#include "stdlib.h" // for size_t - -template -constexpr size_t _countof(T (&)[N]) { return N; } - -// Coordinate types -typedef short coord_t; -typedef unsigned short ucoord_t; - -// Invalid values -static const coord_t coord_invalid = std::numeric_limits::max(); -static const ucoord_t ucoord_invalid = std::numeric_limits::max(); - -// Direction representation -enum class dir_c : char -{ - N = 'N', - S = 'S', - E = 'E', - W = 'W' -}; - -#endif //_BASETYPES_H_ diff --git a/source/scripts/gen_usage.sh b/source/scripts/gen_usage.sh deleted file mode 100755 index a183af7..0000000 --- a/source/scripts/gen_usage.sh +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash -# Used to generate usage text from markdown -cat README.md \ - | sed 's/^#* //g' \ - | sed 's/\t/\\t/g' \ - | sed ':a;N;$!ba;s/\n/\\n\n/g' \ - | sed 's/^/\"/' \ - | sed 's/$/\"/' \ - > $1 diff --git a/source/server/CMakeLists.txt b/source/server/CMakeLists.txt new file mode 100644 index 0000000..40260b5 --- /dev/null +++ b/source/server/CMakeLists.txt @@ -0,0 +1,23 @@ +# ====================== ttrts ======================= +# Project name +project( ttrts-server ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ../system + ../ttrts +) + +# Add the sources +set( SOURCES + server.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 ) diff --git a/source/server/server.cpp b/source/server/server.cpp new file mode 100644 index 0000000..890451c --- /dev/null +++ b/source/server/server.cpp @@ -0,0 +1,98 @@ +#include "error.h" + +#include +#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 3779ca8..3e6a60c 100644 --- a/source/test/CMakeLists.txt +++ b/source/test/CMakeLists.txt @@ -4,8 +4,7 @@ project( ttrts-test ) include_directories( - ../game - ../maths + ../ttrts ) set( SOURCES @@ -15,4 +14,4 @@ set( SOURCES # Add the executable add_executable( ttrts-test ${SOURCES} ) -target_link_libraries( ttrts-test game ) +target_link_libraries( ttrts-test ttrts ) diff --git a/source/test/test.cpp b/source/test/test.cpp index 60d9028..5cbb700 100644 --- a/source/test/test.cpp +++ b/source/test/test.cpp @@ -1,3 +1,4 @@ +#include #include // std::cout #include "order.h" @@ -25,8 +26,8 @@ const char* tests() { CUnit unit1; - std::string unit1Desc = CUnit::GetStringFromUnit(unit1); - CUnit unit2 = CUnit::GetUnitFromString(unit1Desc); + std::string unit1Desc = GetStringFromUnit(unit1); + CUnit unit2 = GetUnitFromString(unit1Desc); if ( unit1 != unit2 ) return "Failed to convert an empty unit to string and back"; @@ -39,8 +40,8 @@ const char* tests() unit1.SetPlayer(player_t::Green); unit1.SetPos(uvector2(5, 10)); - std::string unit1Desc = CUnit::GetStringFromUnit(unit1); - CUnit unit2 = CUnit::GetUnitFromString(unit1Desc); + std::string unit1Desc = GetStringFromUnit(unit1); + CUnit unit2 = GetUnitFromString(unit1Desc); if ( unit1 != unit2 ) return "Failed to convert custom unit to string and back"; @@ -175,10 +176,10 @@ const char* tests() if ( game.GetWinningPlayer() != player_t::Blue ) return "Game failed to recognise a win for the right Player"; - std::string game_string = game.GetStateAsString(); - CTTRTSGame game2 = CTTRTSGame::CreateFromString(game_string); + std::string game_string = GetStringFromGame(game); + CTTRTSGame game2 = GetGameFromString(game_string); - std::string game2_string = game2.GetStateAsString(); + std::string game2_string = GetStringFromGame(game2); // Try matching up the game descriptors if( game_string != game2_string ) diff --git a/source/ttrts/CMakeLists.txt b/source/ttrts/CMakeLists.txt index d7aa9da..93a6d94 100644 --- a/source/ttrts/CMakeLists.txt +++ b/source/ttrts/CMakeLists.txt @@ -1,29 +1,46 @@ -# ====================== ttrts ======================= -# Project name +cmake_minimum_required(VERSION 2.8.7) + +# Main ttrts library project( ttrts ) +# Include the maths include_directories( - ${CMAKE_CURRENT_BINARY_DIR} - ../maths - ../game + ${CMAKE_CURRENT_BINARY_DIR} ) -# Add the sources -set( SOURCES - main.cpp +# Add our sources +set( SOURCES + game.cpp + unit.cpp + formatters.cpp ) -# Add the executable -add_executable( ${PROJECT_NAME} ${SOURCES} ) +# Add this library +add_library( ttrts ${SOURCES} ) -target_link_libraries( ${PROJECT_NAME} game ) - -# Installation target -install( TARGETS ${PROJECT_NAME} DESTINATION bin ) - -# Run the gen_usage script to generate our usage header add_custom_target( - gen_ttrts_usage - cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_SOURCE_DIR}/scripts/gen_usage.sh "${CMAKE_CURRENT_BINARY_DIR}/usage.h" + ttrts-version-header ALL + ${CMAKE_SOURCE_DIR}/scripts/gen_version_header.sh ${TTRTS_VERSION_MAJOR} ${TTRTS_VERSION_MINOR} ${TTRTS_VERSION_PATCH} ) -add_dependencies(${PROJECT_NAME} gen_ttrts_usage) \ No newline at end of file + +# ttrts is dependent on this +add_dependencies( ${PROJECT_NAME} ttrts-version-header ) + +# Install headers +install( + FILES + game.h + unit.h + order.h + formatters.h + vector2.h + orderunitpair.h + gametypes.h + ${CMAKE_CURRENT_BINARY_DIR}/version.h + + DESTINATION + include/ttrts +) + +# Install the ttrts static lib +install( TARGETS ttrts DESTINATION lib ) diff --git a/source/ttrts/README.md b/source/ttrts/README.md deleted file mode 100644 index 478fa65..0000000 --- a/source/ttrts/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# ttrts - Tiny Terminal RTS v0.3.0 - -## SYNOPSIS - ttrts MAPFILE - -## DESCRIPTION - ttrts is a tiny terminal based RTS where that uses text - files as order lists to control it's units - - This means that any user, program or cat that can read - and write to text files can play the game - -## OPTIONS - MAPFILE - File to read in the initial game state from - -## USAGE - When invoked, ttrts will set up the game in a local - directory called `ttrts_{GAME_NAME}` - - The GAMESTATE 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 - ------------------------------------------------------------ -# TTRTS GAMEPLAY - -## RULES - 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. - ------------------------------------------------------------ -# FILE FORMATS - -## Gamestate File - Turn_{TURN_NUMBER}.txt - -### Contents - ===== ttrts v{MAJOR}.{MINOR}.{PATCH} ===== - NAME:{GAMENAME} - SIZE:[{X},{Y}] - TURN:{TURN_NUMBER} - 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 - diff --git a/source/ttrts/formatters.cpp b/source/ttrts/formatters.cpp new file mode 100644 index 0000000..332c5ac --- /dev/null +++ b/source/ttrts/formatters.cpp @@ -0,0 +1,212 @@ +#include "formatters.h" +#include + +#include "version.h" + +// Get the game information as a string +std::string GetStringFromGame( const CTTRTSGame& game ) +{ + // Grab the walls + std::string walls; + if( game.GetWalls().size() == 0 ) + { + walls = "NONE"; + } + + for ( auto wall : game.GetWalls() ) + { + char pos[16]; + if( snprintf(pos, 16, GAME_POS_FORMATTER , wall.x, wall.y ) < 0 ) + { + return "BUFFER OVERFLOW"; + } + walls += pos; + } + + + // Print out the header + char header[512]; + if ( snprintf(header, 512, GAME_HEADER_FORMATTER , + TTRTS_VERSION_MAJOR, + TTRTS_VERSION_MINOR, + TTRTS_VERSION_PATCH, + game.GetName().c_str(), + game.GetDimensions().x, + game.GetDimensions().y, + game.GetTurn(), + walls.c_str() ) < 0 ) + { + return "BUFFER OVERFLOW"; + } + + // Gather unit information + std::string units; + for ( const SOrderUnitPair & pair : game.GetOrderUnitPairs() ) + { + units += GetStringFromUnit(pair.unit); + units += '\n'; + } + + // Append the header and units + std::string state(header); + state += '\n'; + state += GAME_HEADER_DELIMITER; + state += units; + state += "END"; + + return state; +} + +// Get the game information as a string +CTTRTSGame GetGameFromString( const std::string& input ) +{ + size_t headerEnd = input.find(GAME_HEADER_DELIMITER); + + std::string header = input.substr(0, headerEnd); + std::string units = input.substr(headerEnd + strlen(GAME_HEADER_DELIMITER)); + + // Grab information from the header + unsigned int major; + unsigned int minor; + unsigned int patch; + char name[64]; + unsigned int turn; + unsigned int sizex; + unsigned int sizey; + char walls[512]; + if( sscanf(header.c_str(), GAME_HEADER_FORMATTER, &major, &minor, &patch, name, &sizex, &sizey, &turn, walls) != 8 ) + { + std::cerr<<"Error: Failed to properly read game state from text"< walls_vector; + { + std::string walls_str = walls; + size_t pos; + while ( ( pos = walls_str.find(']') ) != std::string::npos ) + { + std::string pos_string = walls_str.substr(0,pos+1); + + // Use scanf to extract positions + + unsigned int x; + unsigned int y; + if( sscanf(pos_string.c_str(), GAME_POS_FORMATTER, &x, &y ) != 2 ) + { + return CTTRTSGame(0,0); + } + + // Erase this coordinate + walls_str.erase(0,pos+1); + + // Append our list + walls_vector.push_back({(ucoord_t)x,(ucoord_t)y}); + } + } + + CTTRTSGame game(sizex,sizey); + game.SetName(name); + game.SetTurn(turn); + + // For each line, construct a unit + { + size_t pos; + while ((pos = units.find('\n')) != std::string::npos) { + std::string unit_string = units.substr(0, pos); + units.erase(0, pos + 1); + game.AddUnit(GetUnitFromString(unit_string)); + } + } + + // Add all walls + for ( auto wall : walls_vector) + { + game.AddWall(wall); + } + + return game; +} + +// Get a string descriptor of a unit +std::string GetStringFromUnit(const CUnit& unit ) +{ + static char buff[128]; + memset(buff,0,sizeof(buff)); + + snprintf(buff,128, UNIT_FORMATTER, + unit.GetID(), + (int)unit.GetPlayer(), + unit.GetVisual(), + unit.GetDir(), + unit.GetPos().x, + unit.GetPos().y ); + + return buff; +} + +// Get a unit from a string descriptor +CUnit GetUnitFromString(const std::string& unit ) +{ + CUnit ret; + + unsigned int id; + int player; + char vis; + char dir; + unsigned int posx; + unsigned int posy; + + sscanf(unit.c_str(), UNIT_FORMATTER, + &id, + &player, + &vis, + &dir, + &posx, + &posy ); + + ret.ForceSetID((unit_id_t)id); + ret.SetPlayer((player_t) player); + ret.SetVisual((unitvis_c)vis); + ret.SetDir((dir_c)dir); + ret.SetPos(uvector2(posx,posy)); + + return ret; +} + +// Convert an order to a string +std::string GetStringFromOrder(const SOrder & order ) +{ + static char buff[128]; + memset(buff,0,sizeof(buff)); + + snprintf(buff,128, ORDER_FORMATTER, + order.command, + order.unit); + + return buff; +} + +// Convert a string to an order +SOrder GetOrderFromString( const std::string& order ) +{ + SOrder ret; + + char corder; + unsigned int unit; + + sscanf(order.c_str(), ORDER_FORMATTER, + &corder, + &unit); + + ret.command = (command_c)corder; + ret.unit = unit; + + return ret; +} diff --git a/source/ttrts/formatters.h b/source/ttrts/formatters.h new file mode 100644 index 0000000..31cf9e7 --- /dev/null +++ b/source/ttrts/formatters.h @@ -0,0 +1,29 @@ +#ifndef _TTRTS_FORMATTERS_H_ +#define _TTRTS_FORMATTERS_H_ + +#include "order.h" +#include "game.h" +#include "unit.h" + +#include "string.h" + +#define GAME_POS_FORMATTER "[%u,%u]" + +#define GAME_HEADER_FORMATTER "==== ttrts v%u.%u.%u ====\nNAME:%s\nSIZE:" GAME_POS_FORMATTER "\nTURN:%u\nWALL:%s" +#define GAME_HEADER_DELIMITER "~~~~\n" + +#define UNIT_FORMATTER "UNIT:%u pl:%u vs:%c dr:%c ps:" GAME_POS_FORMATTER + +// order <--> string conversion functions +std::string GetStringFromOrder(const SOrder & order ); +SOrder GetOrderFromString( const std::string& order ); + +// game <--> string conversion functions +CTTRTSGame GetGameFromString( const std::string& input ); +std::string GetStringFromGame( const CTTRTSGame& game ); + +// unit <--> string conversion functions +std::string GetStringFromUnit(const CUnit& unit ); +CUnit GetUnitFromString(const std::string& unit ); + +#endif \ No newline at end of file diff --git a/source/game/game.cpp b/source/ttrts/game.cpp similarity index 83% rename from source/game/game.cpp rename to source/ttrts/game.cpp index 05d2b6f..d649060 100644 --- a/source/game/game.cpp +++ b/source/ttrts/game.cpp @@ -2,6 +2,7 @@ #include #include +#include "formatters.h" CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r ) : dimensions( c,r ) @@ -16,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)) { } @@ -501,118 +503,6 @@ player_t CTTRTSGame::GetWinningPlayer() const return winningPlayer; } -// Get the game information as a string -std::string CTTRTSGame::GetStateAsString() const -{ - // Grab the walls - std::string walls; - if( m_walls.size() == 0 ) - { - walls = "NONE"; - } - - for ( auto wall : m_walls) - { - char pos[16]; - if( snprintf(pos, 16, GAME_POS_FORMATTER , wall.x, wall.y ) < 0 ) - { - return "BUFFER OVERFLOW"; - } - walls += pos; - } - - - // Print out the header - char header[512]; - if ( snprintf(header, 512, GAME_HEADER_FORMATTER , name.c_str(), dimensions.x, dimensions.y, turn, walls.c_str() ) < 0 ) - { - return "BUFFER OVERFLOW"; - } - - // Gather unit information - std::string units; - for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) - { - units += CUnit::GetStringFromUnit(pair.unit); - units += '\n'; - } - - // Append the header and units - std::string state(header); - state += '\n'; - state += GAME_HEADER_DELIMITER; - state += units; - state += "END"; - - return state; -} -// Get the game information as a string -CTTRTSGame CTTRTSGame::CreateFromString( const std::string& input ) -{ - size_t headerEnd = input.find(GAME_HEADER_DELIMITER); - - std::string header = input.substr(0, headerEnd); - std::string units = input.substr(headerEnd + strlen(GAME_HEADER_DELIMITER)); - - // Grab information from the header - char name[64]; - unsigned int turn; - unsigned int sizex; - unsigned int sizey; - char walls[512]; - if( sscanf(header.c_str(), GAME_HEADER_FORMATTER, name, &sizex, &sizey, &turn, walls) != 5 ) - { - return CTTRTSGame(0,0); - } - - std::vector walls_vector; - { - std::string walls_str = walls; - size_t pos; - while ( ( pos = walls_str.find(']') ) != std::string::npos ) - { - std::string pos_string = walls_str.substr(1,pos); - - // Use scanf to extract positions - - unsigned int x; - unsigned int y; - if( sscanf(pos_string.c_str(), GAME_POS_FORMATTER, &x, &y ) != 2 ) - { - return CTTRTSGame(0,0); - } - - // Erase this coordinate - walls_str.erase(0,pos+1); - - // Append our list - walls_vector.push_back({(ucoord_t)x,(ucoord_t)y}); - } - } - - CTTRTSGame game(sizex,sizey); - game.SetName(name); - game.SetTurn(turn); - - // For each line, construct a unit - { - size_t pos; - while ((pos = units.find('\n')) != std::string::npos) { - std::string unit_string = units.substr(0, pos); - units.erase(0, pos + 1); - game.AddUnit(CUnit::GetUnitFromString(unit_string)); - } - } - - // Add all walls - for ( auto wall : walls_vector) - { - game.AddWall(wall); - } - - return game; -} - // Check if any of the units can move bool CTTRTSGame::UnitsCanMove() const { diff --git a/source/game/game.h b/source/ttrts/game.h similarity index 94% rename from source/game/game.h rename to source/ttrts/game.h index c9c338d..1401b33 100644 --- a/source/game/game.h +++ b/source/ttrts/game.h @@ -13,9 +13,6 @@ class CTTRTSGame { public: - // Get the game information as a string - static CTTRTSGame CreateFromString( const std::string& input ); - // Constructors CTTRTSGame( ucoord_t c, ucoord_t r ); CTTRTSGame(CTTRTSGame&& game); @@ -38,9 +35,6 @@ public: // Check if any of the units can move bool UnitsCanMove() const; - // Get the game information as a string - std::string GetStateAsString() const; - // Issue orders to the game, returns non-zero if orders are incorrect int IssueOrders( player_t player, const std::string& orders ); int IssueOrders( player_t player, const COrderVector& orders ); @@ -61,6 +55,8 @@ public: const CUnit& GetUnitByIDConst( unit_id_t id ) const; const SOrder & GetOrderByIDConst( unit_id_t id ) const; + inline const OrderUnitPairVector& GetOrderUnitPairs() const { return m_OrderUnitPairs; } + // Get dimensions inline const uvector2& GetDimensions() const { return dimensions; } diff --git a/source/ttrts/gametypes.h b/source/ttrts/gametypes.h new file mode 100644 index 0000000..b01318d --- /dev/null +++ b/source/ttrts/gametypes.h @@ -0,0 +1,45 @@ +#ifndef _GAME_TYPES_H_ +#define _GAME_TYPES_H_ + +#include // std::numeric_limits +#include "stdlib.h" // for size_t + +// Type for a Player IDs +enum class player_t : char +{ + Red = 0, + Green, + Yellow, + Blue, + NUM_INVALID +}; + +// Type for unit IDs +typedef unsigned short unit_id_t; +static const unit_id_t unit_id_invalid = std::numeric_limits::max(); + +// Typedef for unit visual representations +typedef char unitvis_c; +static const unitvis_c unitvis_invalid = std::numeric_limits::max(); + +// Coordinate types +typedef short coord_t; +static const coord_t coord_invalid = std::numeric_limits::max(); + +typedef unsigned short ucoord_t; +static const ucoord_t ucoord_invalid = std::numeric_limits::max(); + +// Direction representation +enum class dir_c : char +{ + N = 'N', + S = 'S', + E = 'E', + W = 'W' +}; + +// Helper function for count of an array +template +constexpr size_t _countof(T (&)[N]) { return N; } + +#endif //_GAME_TYPES_H_ \ No newline at end of file diff --git a/source/ttrts/main.cpp b/source/ttrts/main.cpp deleted file mode 100644 index 272abef..0000000 --- a/source/ttrts/main.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "game.h" -#include "version.h" - -static const char* sk_usage = -#include "usage.h" -; - -// 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 = game.GetStateAsString(); - - // Append the version number - turnDescriptor = std::string("==== ttrts v") - + sk_ttrts_version_string - + std::string(" ====\n") - + turnDescriptor; - - 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"< COrderVector; -// string <--> order conversion functions -std::string GetStringFromOrder(const SOrder & order ); -SOrder GetOrderFromString( const std::string& order ); - #endif //_ORDERS_H_ diff --git a/source/game/orderunitpair.h b/source/ttrts/orderunitpair.h similarity index 100% rename from source/game/orderunitpair.h rename to source/ttrts/orderunitpair.h diff --git a/source/game/unit.cpp b/source/ttrts/unit.cpp similarity index 80% rename from source/game/unit.cpp rename to source/ttrts/unit.cpp index 39d9b48..b6708c5 100644 --- a/source/game/unit.cpp +++ b/source/ttrts/unit.cpp @@ -50,52 +50,6 @@ CUnit CUnit::GetUnitFromVis( unitvis_c vis ) return unit; } -// Get a string descriptor of a unit -std::string CUnit::GetStringFromUnit(const CUnit& unit ) -{ - static char buff[128]; - memset(buff,0,sizeof(buff)); - - snprintf(buff,128, UNIT_FORMATTER, - unit.unit_id, - (int)unit.player_id, - unit.unit_vis, - unit.dir, - unit.pos.x, - unit.pos.y ); - - return buff; -} - -// Get a unit from a string descriptor -CUnit CUnit::GetUnitFromString(const std::string& unit ) -{ - CUnit ret; - - unsigned int id; - int player; - char vis; - char dir; - unsigned int posx; - unsigned int posy; - - sscanf(unit.c_str(), UNIT_FORMATTER, - &id, - &player, - &vis, - &dir, - &posx, - &posy ); - - ret.unit_id = (unit_id_t)id; - ret.player_id = (player_t) player; - ret.unit_vis = (unitvis_c)vis; - ret.dir = (dir_c)dir; - ret.pos = uvector2(posx,posy); - - return ret; -} - // Plain constructor CUnit::CUnit() : unit_id ( get_unique_unit_id() ) diff --git a/source/game/unit.h b/source/ttrts/unit.h similarity index 93% rename from source/game/unit.h rename to source/ttrts/unit.h index b594836..7f6e77e 100644 --- a/source/game/unit.h +++ b/source/ttrts/unit.h @@ -18,10 +18,6 @@ public: // Factory function for creating units from a visual static CUnit GetUnitFromVis( unitvis_c vis ); - // Unit <--> string conversion functions - static std::string GetStringFromUnit(const CUnit& unit ); - static CUnit GetUnitFromString(const std::string& unit ); - // Constructor CUnit(); @@ -59,6 +55,9 @@ public: dir_c TurnRight(); dir_c TurnAround(); + // Force set an ID + inline void ForceSetID( unit_id_t id ) { unit_id = id; } + private: // Update my visual must be called when setting direction diff --git a/source/maths/vector2.h b/source/ttrts/vector2.h similarity index 98% rename from source/maths/vector2.h rename to source/ttrts/vector2.h index 40ce5ad..1cd2e34 100644 --- a/source/maths/vector2.h +++ b/source/ttrts/vector2.h @@ -1,7 +1,7 @@ #ifndef _VECTOR2_H_ #define _VECTOR2_H_ -#include "mathtypes.h" +#include "gametypes.h" struct uvector2; diff --git a/source/ttrts/version.h b/source/ttrts/version.h deleted file mode 100644 index b64c49c..0000000 --- a/source/ttrts/version.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _TTRTS_VERSION_H_ -#define _TTRTS_VERSION_H_ - -static const int sk_ttrts_version_major = TTRTS_VERSION_MAJOR; -static const int sk_ttrts_version_minor = TTRTS_VERSION_MINOR; -static const int sk_ttrts_version_patch = TTRTS_VERSION_PATCH; -static const char* sk_ttrts_version_string = TTRTS_VERSION_STRING; - -#endif //_TTRTS_VERSION_H_ \ No newline at end of file