diff --git a/.gitignore b/.gitignore index 502564d..379896d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ *.user *.sublime* *.idea -*~ diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 97a47ba..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -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 a6b299b..3b3d8ee 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# TTRTS +# TTRTS v0.2.0 + *The Tiny Terminal RTS where the players write their AIs* ----------------------------------------------------------- @@ -11,7 +12,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 @@ -19,67 +20,10 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An $ cd ttrts $ ./bootstrap.sh $ ./ttrts # To launch binary and display usage - ------------------------------------------------------------ -## Development - -* master branch always stores latest stable release -* master/{hotfix} branches store in progress hotfixes for the stable branch -* dev branch stores in progress development -* dev/{feature} branches store features - ------------------------------------------------------------ -## 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 - * 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 -* Units leave an impassable wall behind after movement -* Game can now end if no units are able to move -* Various C++ api simplifications -* Integration of perl api from [ttrts-players](https://github.com/mdiluz/ttrts-players) - -#### 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 -* First playable version of ttrts - + ----------------------------------------------------------- ## Further Information -See the ttrts binary [readme](source/ttrts/README.md) for full usage and game rules +See [the ttrts binary readme](source/ttrts/README.md) for full usage and game rules -See [ttrts-players](https://github.com/mdiluz/ttrts-players) for examples of AIs +See [my ttrts-players repository](https://github.com/mdiluz/ttrts-players) for examples of players diff --git a/api/perl/ttrts.pm b/api/perl/ttrts.pm deleted file mode 100644 index 793269b..0000000 --- a/api/perl/ttrts.pm +++ /dev/null @@ -1,306 +0,0 @@ -#! /usr/bin/perl -use strict; -use warnings; - -our $ttrts_perlai_versioncompat_major = 0; -our $ttrts_perlai_versioncompat_minor = 3; - -our $headerDelimiter="~~~~"; - -our $VERBOSE = $ENV{"VERBOSE"}; - -# Format of the a gamestate header -our $headerFormatter = qr/==== ttrts v(\d+)\.(\d+)\.(\d+)+ ==== -NAME:(.+) -SIZE:\[(\d+),(\d+)\] -TURN:(\d+) -(WALL:.*?) -$headerDelimiter/; - -# Formatter for coords -our $coordFormatter = qr/\[\d+,\d+\]/; - -# Format of a unit descriptor -our $unitFormatterNonCapture = qr/UNIT:\d+ pl:\d+ vs:[^ ]+ dr:[^ ]+ ps:\[\d+,\d+\]\n?/; - -# Format of a unit descriptor -our $unitFormatter = qr/UNIT:(\d+) pl:(\d+) vs:([^ ]+) dr:([^ ]+) ps:\[(\d+),(\d+)\]\n?/; - -# Get x and y -sub getPositionsXandYString -{ - return (shift =~ /\[(\d+),(\d+)\]/); -} - -# Get all positions -sub getPositionStringsFromLine -{ - return (shift =~ /$coordFormatter/gm ); -} - -# Get information about a unit from it's descriptor -sub getUnitInfo -{ - return (shift =~ /$unitFormatter/); -} - -# Get set of units from a string -sub GetUnitStringsFromGamestate -{ - my $gamestate = shift; - - my @units = ( $gamestate =~ /$unitFormatterNonCapture/gm ); - - foreach my $unit (@units) - { - chomp($unit); - } - - return @units; -} - -# in the format $major,$minor,$patch,$name,$sizex,$sizey,$turn,$invalidpositions+ -sub GetGameInfoFromGamestate -{ - my $header = shift; - (! defined $header) and die "GetGameInfoFromGamestate was not passed valid header parameter"; - - my @info = ($header =~ /$headerFormatter/ ); - - return @info; -} - -# Get the units from a turn file -sub GetUnitStringsFromFile -{ - my $turnFile = shift or die "GetUnitStringsFromFile needs file parameter"; - - # Read in the whole file method from http://www.perlmonks.org/?node_id=1952 - my $text; - { - local $/=undef; - open FILE, $turnFile or die "Couldn't open file: $!"; - $text = ; - close FILE; - } - - return GetUnitStringsFromGamestate($text); -} - -# Check version numbers against ttrts.pm version -sub verifyVersion -{ - my $version_major = shift; - (! defined $version_major) and die "verifyVersion needs version_major parameter"; - my $version_minor = shift; - (! defined $version_minor) and die "verifyVersion needs version_minor parameter"; - if( ($version_major != $ttrts_perlai_versioncompat_major) - or ($version_minor != $ttrts_perlai_versioncompat_minor) ) - { - printf "ttrts.pm version does not match with this ttrts version\n"; - die "ttrts.pm = v$ttrts_perlai_versioncompat_minor.$ttrts_perlai_versioncompat_major ttrts = v$version_major.$version_minor"; - } -} - - -# Get information from the header for this turn -sub GetGameInfoFromFile -{ - my $turnFile = shift or die "GetGameInfoFromFile needs turnFile parameter"; - - # Read in the whole file method from http://www.perlmonks.org/?node_id=1952 - my $text; - { - local $/=undef; - open FILE, $turnFile or die "Couldn't open file: $!"; - $text = ; - close FILE; - } - - my @info = GetGameInfoFromGamestate($text); - verifyVersion @info; - - return @info; -} - -# Get units from a specific player -sub GetPlayerUnits -{ - my $thePlayer = shift; - (! defined $thePlayer) and die "GetPlayerUnits needs player parameter"; - my @allUnits = @_; - (! @allUnits) and die "GetPlayerUnits needs units parameters"; - my @myUnits; - - for my $unit (@allUnits) - { - my ($unitplayer) = $unit =~ /pl:(\d+)/; - if ( $unitplayer == $thePlayer ) - { - push(@myUnits,$unit); - } - } - - return @myUnits; -} - -sub GetTurnFileName -{ - my $turn = shift; - (! defined $turn) and die "GetTurnFileName needs turn parameter"; - my $turnFile = "Turn_TURN.txt"; - $turnFile =~ s/TURN/$turn/; - return $turnFile; -} - -sub GetCommandFileName -{ - my $turn = shift; - (! defined $turn) and die "GetCommandFileName needs turn parameter"; - my $player = shift; - (! defined $player) and die "GetCommandFileName needs player parameter"; - my $cmdFileName = "Player_PLAYER_Turn_TURN.txt"; - $cmdFileName =~ s/TURN/$turn/; - $cmdFileName =~ s/PLAYER/$player/; - return $cmdFileName; -} - -# Output the commands file -sub OutputCommandsFile -{ - my $turn = shift; - (! defined $turn) and die "OutputCommandsFile needs turn parameter"; - my $player = shift; - (! defined $player) and die "OutputCommandsFile needs player parameter"; - my $commands = shift or die "OutputCommandsFile needs commands parameter"; - - # Get output file - our $cmdFileName = GetCommandFileName($turn,$player); - - if (! -e $cmdFileName) - { - open(my $cmdFile, '>', $cmdFileName) or die "Couldn't open '$cmdFileName' $!"; - print $cmdFile $commands; - print $cmdFile "END"; - close $cmdFile; - - $VERBOSE and printf "Outputted $cmdFileName\n"; - printf "$commands"; - } - else - { - # Read in the whole file method from http://www.perlmonks.org/?node_id=1952 - my $text; - { - local $/=undef; - open FILE, $cmdFileName or die "Couldn't open file: $!"; - $text = ; - close FILE; - } - - $text =~ s/\nEND//; - - printf "Replaying $cmdFileName\n"; - printf "$text\n"; - } -} - -# Print a game map -sub PrintGameFromGamestateString -{ - my $gamestateString = shift or die "PrintGameFromGamestateString needs string parameter"; - - my @info = GetGameInfoFromGamestate($gamestateString); - my @units = GetUnitStringsFromGamestate($gamestateString); - - # $major,$minor,$patch,$name,$sizex,$sizey,$turn,$invalidpositions+ - my $gameX = $info[4]; - my $gameY = $info[5]; - - # Shift into info to where the invalid positions are stored - my @invalids = getPositionStringsFromLine($info[7]); - - my @map; - - # Fill with blanks - for my $x (0 .. $gameX-1) - { - for my $y (0 .. $gameY-1) - { - $map[$x][$y] = "-"; - } - } - - # Fill in all invalid coordinates - foreach my $coord (@invalids) - { - my @invalidPos = getPositionsXandYString($coord); - $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) - { - my ($id,$pl,$vs,$dr,$psx,$psy) = getUnitInfo($unit); - - $pl += 31; - $vs = "\e[".$pl."m".$vs."\e[0m"; - - $map[$psx][$psy] = $vs; - } - - # Print whole map bottom left is 0,0 - for my $y ( reverse 0 .. $gameY-1 ) - { - for my $x (0 .. $gameX-1) - { - printf($map[$x][$y]); - } - printf("\n"); - } -} - -# Print a game map -sub PrintGameFromFile -{ - my $turnFile = shift or die "PrintGameFromFile needs file parameter"; - - # Read in the whole file method from http://www.perlmonks.org/?node_id=1952 - my $text; - { - local $/=undef; - open FILE, $turnFile or die "Couldn't open file: $!"; - $text = ; - close FILE; - } - - PrintGameFromGamestateString($text); -} - -# Print a turn -sub PrintGameMapForTurn -{ - my $turn = shift; - (! defined $turn) and die "PrintGameMapForTurn needs turn parameter"; - $turn = GetTurnFileName($turn); - PrintGameFromFile( $turn ); -} - -# Wait for a file to exist -sub WaitForFile -{ - my $file = shift or die "WaitForFile needs file parameter"; - while( ! -e $file ) - { - select(undef, undef, undef, 0.01); - } -} - -return 1; \ No newline at end of file diff --git a/bootstrap.sh b/bootstrap.sh index 701f47b..9b9a839 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,34 +1,38 @@ #! /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 .. +cmake ../source if [[ $? != 0 ]]; then echo "TTRTS: CMake failed, exiting Bootstrap" - exit fi -echo "TTRTS: Performing install" -sudo make install +echo "TTRTS: Running make" +make if [[ $? != 0 ]]; then - echo "TTRTS: Install failed, check output" - exit + echo "TTRTS: make failed, exiting Bootstrap" fi -# Run final test to make sure echo "TTRTS: Running tests" -./source/test/ttrts-test +./test/ttrts-test if [[ $? != 0 ]]; then echo "TTRTS: Tests failed, build must be broken" - exit fi -echo "TTRTS: Bootstrap complete" +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 diff --git a/scripts/gen_manpage.sh b/scripts/gen_manpage.sh deleted file mode 100755 index e699063..0000000 --- a/scripts/gen_manpage.sh +++ /dev/null @@ -1,44 +0,0 @@ -#! /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 deleted file mode 100755 index bb02331..0000000 --- a/scripts/gen_maps.sh +++ /dev/null @@ -1,13 +0,0 @@ -#! /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 deleted file mode 100755 index 5e33166..0000000 --- a/scripts/gen_usage.sh +++ /dev/null @@ -1,18 +0,0 @@ -#! /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 deleted file mode 100755 index aec60e2..0000000 --- a/scripts/gen_version_header.sh +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100755 index d257ed4..0000000 --- a/scripts/ttrts_complete +++ /dev/null @@ -1,23 +0,0 @@ -# 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 d73e509..f22f656 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,13 +1,37 @@ -# Main libraries -add_subdirectory( ttrts ) -add_subdirectory( system ) +cmake_minimum_required( VERSION 2.8.7 ) -# Main binaries -add_subdirectory( launcher ) -add_subdirectory( client ) -add_subdirectory( server ) -add_subdirectory( local ) +# Set version information +set( TTRTS_VERSION_MAJOR 0 ) +set( TTRTS_VERSION_MINOR 2 ) +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 +add_subdirectory( ttrts ) +add_subdirectory( game ) # Auxhilary binaries add_subdirectory( test ) -add_subdirectory( gen ) \ No newline at end of file +add_subdirectory( gen ) diff --git a/source/README.md b/source/README.md index 22b8dea..a18fa94 100644 --- a/source/README.md +++ b/source/README.md @@ -1,121 +1,19 @@ -# NAME - ttrts - Tiny Terminal RTS +# Targets +### ttrts +Main TTRTS binary, runs from the command line and acts as host for games -# 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 +### test (ttrts-test) +Test binary, to be compiled and run to test various functionality - This means that any user, program or cat that can read and write to text files can play the game +### gen (ttrts-gen) +Binary to generate example map files -# RETURN VALUE - ttrts will return -1 on error, or the winning player on completion +# Libraries +### game +Implementation of the RTS rules and simulation -# OPTIONS - --server - Run in server mode, must provide a map file +### maths +Simple maths library for 2D calculations and types - --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. +### scripts +Directory of scripts used in build process diff --git a/source/client/CMakeLists.txt b/source/client/CMakeLists.txt deleted file mode 100644 index 1ae8054..0000000 --- a/source/client/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# ====================== 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 deleted file mode 100644 index 116a5a0..0000000 --- a/source/client/client.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#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 "formatters.h" CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r ) : dimensions( c,r ) @@ -17,7 +16,6 @@ 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)) { } @@ -94,7 +92,7 @@ int CTTRTSGame::IssueOrder( player_t player, const SOrder & order ) } // Verify a position -int CTTRTSGame::VerifyPosIsValidMovement(uvector2 vec) const +int CTTRTSGame::VerifyPos(uvector2 vec) const { // Simply check if within the bounds of our dimensions for now if ( ( vec.x >= dimensions.x ) @@ -103,15 +101,6 @@ int CTTRTSGame::VerifyPosIsValidMovement(uvector2 vec) const return 1; } - // Check within our invalid positions - for ( const uvector2& invalid : m_walls) - { - if( vec == invalid ) - { - return 2; - } - } - return 0; } @@ -148,7 +137,7 @@ int CTTRTSGame::SimulateToNextTurn() uvector2 newpos = GetNewPosition(pair); // Verify the position is even available - bool possible = (VerifyPosIsValidMovement(newpos) == 0 ); + bool possible = ( VerifyPos(newpos) == 0 ); if ( possible ) { @@ -169,8 +158,6 @@ int CTTRTSGame::SimulateToNextTurn() // If the movement is still possible if ( possible ) { - // Create a wall at our old position - AddWall(pair.unit.GetPos()); pair.unit.SetPos(newpos); } } @@ -215,7 +202,7 @@ int CTTRTSGame::SimulateToNextTurn() { uvector2 newpos = pair.unit.GetInFront(); // If move would be within the arena - if (VerifyPosIsValidMovement(newpos) == 0 ) + if ( ( newpos.x <= dimensions.x-1 ) && ( newpos.y <= dimensions.y-1 ) ) { pair.unit.SetPos(newpos); @@ -467,7 +454,7 @@ std::vector CTTRTSGame::GetPlayers() const } // Check if we have a win state -player_t CTTRTSGame::GetWinningPlayer() const +player_t CTTRTSGame::CheckForWin() const { // Array of units for each Player unsigned int units[(int) player_t::NUM_INVALID]; @@ -503,30 +490,58 @@ player_t CTTRTSGame::GetWinningPlayer() const return winningPlayer; } -// Check if any of the units can move -bool CTTRTSGame::UnitsCanMove() const +// Get the game information as a string +std::string CTTRTSGame::GetStateAsString() const { - for( const SOrderUnitPair& pair: m_OrderUnitPairs ) - { - uvector2 pos = pair.unit.GetPos(); + // Print out the header + char header[64]; + snprintf(header, 512, GAME_HEADER_FORMATTER , name.c_str(), dimensions.x, dimensions.y, turn ); - // Assume if unit is adjacent to any valid tile, then it can move there - if( VerifyPosIsValidMovement(pos + vector2(1, 0) ) == 0 - || VerifyPosIsValidMovement(pos + vector2(0, 1)) == 0 - || VerifyPosIsValidMovement(pos + vector2(-1, 0)) == 0 - || VerifyPosIsValidMovement(pos + vector2(0, -1)) == 0 ) - { - return true; - } + // Gather unit information + std::string units; + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + units += CUnit::GetStringFromUnit(pair.unit); + units += '\n'; } - return false; + // Append the header and units + std::string state(header); + state += '\n'; + state += GAME_HEADER_DELIMITER; + state += units; + state += "END"; + + return state; } -// Check if the game is over -bool CTTRTSGame::GameOver() const +// Get the game information as a string +CTTRTSGame CTTRTSGame::CreateFromString( const std::string& input ) { - return ( (GetWinningPlayer() != player_t::NUM_INVALID ) // We have a winning player - || GetNumUnits() == 0 - || !UnitsCanMove() ); // OR we have no units + 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 buf[64]; + unsigned int turn; + unsigned int sizex; + unsigned int sizey; + sscanf(header.c_str(), GAME_HEADER_FORMATTER, buf, &sizex, &sizey, &turn ); + + CTTRTSGame game(sizex,sizey); + game.SetName(buf); + 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)); + } + + return game; } \ No newline at end of file diff --git a/source/ttrts/game.h b/source/game/game.h similarity index 77% rename from source/ttrts/game.h rename to source/game/game.h index 1401b33..99896ea 100644 --- a/source/ttrts/game.h +++ b/source/game/game.h @@ -6,6 +6,9 @@ #include "order.h" #include "orderunitpair.h" +#define GAME_HEADER_FORMATTER "NAME:%s\nSIZE:[%u,%u]\nTURN:%u" +#define GAME_HEADER_DELIMITER "~~~~\n" + // Full TTRTS Game class // Stores information about the game // Can convert from a string or to a string @@ -13,6 +16,9 @@ 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); @@ -24,16 +30,13 @@ public: // Returns non-zero if simulation failed int SimulateToNextTurn(); - // Check for winning player, returns invalid for no win state reached - // Note: this function will return invalid if a draw was reached - // do not rely on this to test for end state - player_t GetWinningPlayer() const; + // Check for a win, returns invalid for no win state reached + // Note: this function will return invalid a draw was reached + // best practice would be to call with GetNumUnits() == 0 + player_t CheckForWin() const; - // Check if the game is over - bool GameOver() const; - - // 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 ); @@ -55,8 +58,6 @@ 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; } @@ -72,12 +73,6 @@ public: // Get a vector of the players in the current game std::vector GetPlayers() const; - // Get the vector of wall positions - inline std::vector GetWalls() const { return m_walls; } - - // Add an invalid position - inline void AddWall(uvector2 vec) { m_walls.push_back(vec); } - private: // Check for a pass through @@ -85,7 +80,7 @@ private: // Verify any order or position - non-zero is error int VerifyOrder( player_t player, const SOrder & order ) const; - int VerifyPosIsValidMovement(uvector2 vec) const; + int VerifyPos( uvector2 vec ) const; // Get a units new position after an order uvector2 GetNewPosition( const SOrderUnitPair & pair ) const; @@ -100,7 +95,6 @@ private: unsigned int turn; // Int to store the current turn uvector2 dimensions; // Dimensions of the game OrderUnitPairVector m_OrderUnitPairs; // Vector to store all units and orders - std::vector m_walls; // Vector of wall positions }; diff --git a/source/game/gametypes.h b/source/game/gametypes.h new file mode 100644 index 0000000..0ee4e5b --- /dev/null +++ b/source/game/gametypes.h @@ -0,0 +1,23 @@ +#ifndef _GAME_TYPES_H_ +#define _GAME_TYPES_H_ + +#include // std::numeric_limits + +// 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 new file mode 100644 index 0000000..8e0e82f --- /dev/null +++ b/source/game/order.cpp @@ -0,0 +1,33 @@ +#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/ttrts/order.h b/source/game/order.h similarity index 87% rename from source/ttrts/order.h rename to source/game/order.h index 1ad995a..845e27f 100644 --- a/source/ttrts/order.h +++ b/source/game/order.h @@ -47,4 +47,8 @@ inline bool SOrder::operator== ( const SOrder & rhs ) const // Typedef a vector of orders typedef std::vector COrderVector; +// string <--> order conversion functions +std::string GetStringFromOrder(const SOrder & order ); +SOrder GetOrderFromString( const std::string& order ); + #endif //_ORDERS_H_ diff --git a/source/ttrts/orderunitpair.h b/source/game/orderunitpair.h similarity index 100% rename from source/ttrts/orderunitpair.h rename to source/game/orderunitpair.h diff --git a/source/ttrts/unit.cpp b/source/game/unit.cpp similarity index 80% rename from source/ttrts/unit.cpp rename to source/game/unit.cpp index b6708c5..39d9b48 100644 --- a/source/ttrts/unit.cpp +++ b/source/game/unit.cpp @@ -50,6 +50,52 @@ 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/ttrts/unit.h b/source/game/unit.h similarity index 90% rename from source/ttrts/unit.h rename to source/game/unit.h index 7f6e77e..ce9295f 100644 --- a/source/ttrts/unit.h +++ b/source/game/unit.h @@ -7,6 +7,8 @@ #include "gametypes.h" #include "vector2.h" +#define UNIT_FORMATTER "UNIT:%u pl:%u vs:%c dr:%c ps:[%u,%u]" + // force a reset of the unit ID value void __forceResetCUnitID(); @@ -18,6 +20,10 @@ 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(); @@ -55,9 +61,6 @@ 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/gen/CMakeLists.txt b/source/gen/CMakeLists.txt index 67167f0..9eff3b8 100644 --- a/source/gen/CMakeLists.txt +++ b/source/gen/CMakeLists.txt @@ -3,7 +3,8 @@ project( ttrts-gen ) include_directories( - ../ttrts + ../game + ../maths ) set( SOURCES @@ -13,15 +14,4 @@ set( SOURCES # Add the executable add_executable( ttrts-gen ${SOURCES} ) -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 ) +target_link_libraries( ttrts-gen game ) \ No newline at end of file diff --git a/source/gen/gen.cpp b/source/gen/gen.cpp index 0f782b8..ac7ab2e 100644 --- a/source/gen/gen.cpp +++ b/source/gen/gen.cpp @@ -2,7 +2,6 @@ #include #include -#include "formatters.h" void AddUnitToGame( player_t player, char vis, uvector2 vec, CTTRTSGame& game ) { @@ -16,7 +15,7 @@ void OutputGame( CTTRTSGame&& game ) { std::ofstream output; output.open (game.GetName() + ".txt"); - output << GetStringFromGame(game); + output << game.GetStateAsString(); output.close(); __forceResetCUnitID(); @@ -24,44 +23,23 @@ void OutputGame( CTTRTSGame&& game ) int main() { - // Tiny 1v1 Game - //------ - //-G---- - //----R- - //-G---- - //----R- - //------ + // Tiny 2v2 Game { CTTRTSGame game(6, 6); - game.SetName("Tiny2Player"); + game.SetName("Tiny2v2"); 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 1v1 game - // -------------------- - // -G------------------ - // ------------------R- - // -G------------------ - // ------------------R- - // -G------------------ - // ------------------R- - // -G------------------ - // ------------------R- - // -G------------------ - // ------------------R- - // -------------------- + // Basic 5v5 game { CTTRTSGame game(20, 12); - game.SetName("Big2Player"); + game.SetName("Big2v2"); for ( ucoord_t y : { 2,4,6,8,10 } ) AddUnitToGame( player_t::Red, '<', uvector2(18, y), game); @@ -72,15 +50,7 @@ int main() OutputGame(std::move(game)); } - // Sort of like Chess - //GG------ - //------RR - //GG------ - //------RR - //GG------ - //------RR - //GG------ - //------RR + // Chess 10v10 game { CTTRTSGame game(8, 8); game.SetName("Chess"); @@ -97,69 +67,4 @@ 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 deleted file mode 100644 index c6726c7..0000000 --- a/source/launcher/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# ====================== 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 deleted file mode 100755 index 886fed6..0000000 --- a/source/launcher/ttrts.pl +++ /dev/null @@ -1,44 +0,0 @@ -#! /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 deleted file mode 100644 index 172b736..0000000 --- a/source/local/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# ====================== 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 deleted file mode 100644 index b2697e7..0000000 --- a/source/local/local.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#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/ttrts/vector2.h b/source/maths/vector2.h similarity index 98% rename from source/ttrts/vector2.h rename to source/maths/vector2.h index 1cd2e34..40ce5ad 100644 --- a/source/ttrts/vector2.h +++ b/source/maths/vector2.h @@ -1,7 +1,7 @@ #ifndef _VECTOR2_H_ #define _VECTOR2_H_ -#include "gametypes.h" +#include "mathtypes.h" struct uvector2; diff --git a/source/scripts/gen_usage.sh b/source/scripts/gen_usage.sh new file mode 100755 index 0000000..a183af7 --- /dev/null +++ b/source/scripts/gen_usage.sh @@ -0,0 +1,9 @@ +#! /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 deleted file mode 100644 index 40260b5..0000000 --- a/source/server/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# ====================== 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 deleted file mode 100644 index 890451c..0000000 --- a/source/server/server.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#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 deleted file mode 100644 index 29c23b9..0000000 --- a/source/system/net.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#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 3e6a60c..3779ca8 100644 --- a/source/test/CMakeLists.txt +++ b/source/test/CMakeLists.txt @@ -4,7 +4,8 @@ project( ttrts-test ) include_directories( - ../ttrts + ../game + ../maths ) set( SOURCES @@ -14,4 +15,4 @@ set( SOURCES # Add the executable add_executable( ttrts-test ${SOURCES} ) -target_link_libraries( ttrts-test ttrts ) +target_link_libraries( ttrts-test game ) diff --git a/source/test/test.cpp b/source/test/test.cpp index 5cbb700..b4d197c 100644 --- a/source/test/test.cpp +++ b/source/test/test.cpp @@ -1,4 +1,3 @@ -#include #include // std::cout #include "order.h" @@ -26,8 +25,8 @@ const char* tests() { CUnit unit1; - std::string unit1Desc = GetStringFromUnit(unit1); - CUnit unit2 = GetUnitFromString(unit1Desc); + std::string unit1Desc = CUnit::GetStringFromUnit(unit1); + CUnit unit2 = CUnit::GetUnitFromString(unit1Desc); if ( unit1 != unit2 ) return "Failed to convert an empty unit to string and back"; @@ -40,8 +39,8 @@ const char* tests() unit1.SetPlayer(player_t::Green); unit1.SetPos(uvector2(5, 10)); - std::string unit1Desc = GetStringFromUnit(unit1); - CUnit unit2 = GetUnitFromString(unit1Desc); + std::string unit1Desc = CUnit::GetStringFromUnit(unit1); + CUnit unit2 = CUnit::GetUnitFromString(unit1Desc); if ( unit1 != unit2 ) return "Failed to convert custom unit to string and back"; @@ -173,13 +172,13 @@ const char* tests() if (game.GetUnitByIndex(0).GetID() != id ) return "Game killed the wrong unit"; - if ( game.GetWinningPlayer() != player_t::Blue ) + if ( game.CheckForWin() != player_t::Blue ) return "Game failed to recognise a win for the right Player"; - std::string game_string = GetStringFromGame(game); - CTTRTSGame game2 = GetGameFromString(game_string); + std::string game_string = game.GetStateAsString(); + CTTRTSGame game2 = CTTRTSGame::CreateFromString(game_string); - std::string game2_string = GetStringFromGame(game2); + std::string game2_string = game2.GetStateAsString(); // Try matching up the game descriptors if( game_string != game2_string ) diff --git a/source/ttrts/CMakeLists.txt b/source/ttrts/CMakeLists.txt index 93a6d94..d7aa9da 100644 --- a/source/ttrts/CMakeLists.txt +++ b/source/ttrts/CMakeLists.txt @@ -1,46 +1,29 @@ -cmake_minimum_required(VERSION 2.8.7) - -# Main ttrts library +# ====================== ttrts ======================= +# Project name project( ttrts ) -# Include the maths include_directories( - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ../maths + ../game ) -# Add our sources -set( SOURCES - game.cpp - unit.cpp - formatters.cpp +# Add the sources +set( SOURCES + main.cpp ) -# Add this library -add_library( ttrts ${SOURCES} ) +# Add the executable +add_executable( ${PROJECT_NAME} ${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( - ttrts-version-header ALL - ${CMAKE_SOURCE_DIR}/scripts/gen_version_header.sh ${TTRTS_VERSION_MAJOR} ${TTRTS_VERSION_MINOR} ${TTRTS_VERSION_PATCH} + gen_ttrts_usage + cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_SOURCE_DIR}/scripts/gen_usage.sh "${CMAKE_CURRENT_BINARY_DIR}/usage.h" ) - -# 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 ) +add_dependencies(${PROJECT_NAME} gen_ttrts_usage) \ No newline at end of file diff --git a/source/ttrts/README.md b/source/ttrts/README.md new file mode 100644 index 0000000..11e135f --- /dev/null +++ b/source/ttrts/README.md @@ -0,0 +1,59 @@ +## NAME + ttrts - Tiny Terminal RTS v0.2.0 + +## SYNOPSYS + 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. + +## USAGE + When invoked, ttrts will set up a full game and output a + single file representing the current gamestate into a + local directory called `ttrts_{GAME_NAME}`. + + This file can be read in and interpretted by human, robot + or cat. ttrts will wait for orders files to be placed in + it's current working directory. + + Once orders have been set for each player taking part + ttrts will calculate the new game state and output a new + gamestate file for the next turn. + + This process repeats until a winner is chosen! + +## OPTIONS + MAPFILE - File to read in the initial game state from + +-------------------------------------------------------------- + +## GAMESTATE FILE FORMAT +### Name + Turn_{TURN_NUMBER}.txt +### Contents + ===== ttrts v{MAJOR}.{MINOR}.{PATCH} ===== + NAME:{GAMENAME} + SIZE:[{X},{Y}] + TURN:{TURN_NUMBER} + ... {any extra properties could go here} + ~~~~ + UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}] + ... {continue for all units} + END + +## ORDER FILE FORMAT +### Name + Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt +### Contents + ORDER:{ORDER_CHAR} id:{UNIT_ID} + ... {continue for all orders} + END + +### Orders + F - move unit [F]orward one space + L/R - rotate unit [L]eft or [R]ight + A - [A]ttack in straight line in front of unit diff --git a/source/ttrts/formatters.cpp b/source/ttrts/formatters.cpp deleted file mode 100644 index 332c5ac..0000000 --- a/source/ttrts/formatters.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#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 deleted file mode 100644 index 31cf9e7..0000000 --- a/source/ttrts/formatters.h +++ /dev/null @@ -1,29 +0,0 @@ -#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/ttrts/gametypes.h b/source/ttrts/gametypes.h deleted file mode 100644 index b01318d..0000000 --- a/source/ttrts/gametypes.h +++ /dev/null @@ -1,45 +0,0 @@ -#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 new file mode 100644 index 0000000..91fbf1b --- /dev/null +++ b/source/ttrts/main.cpp @@ -0,0 +1,234 @@ +#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..."< 0 ) // We have no units left + { + std::cout<<"Starting turn "<(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"<