Compare commits

...

45 commits

Author SHA1 Message Date
mdiluzio
ae8a3bf0c2 Update changelog for v0.4.0 2015-01-10 17:29:22 +00:00
mdiluzio
f13a8bd5c4 Ensure that hostname is also properly highlighted 2015-01-10 17:25:01 +00:00
mdiluzio
5e66596d30 Update the Readme with new server and client communication options 2015-01-10 17:23:48 +00:00
mdiluzio
3bb5766061 Fix completion script to take into account non-positional arguments 2015-01-10 17:06:23 +00:00
mdiluzio
96153f8c60 Update launcher script with handing of return value 2015-01-10 17:04:55 +00:00
mdiluzio
96f5c08c14 Update completion script with new parameters 2015-01-10 16:59:45 +00:00
mdiluzio
d65ef46634 Fix some bugs in generation scripts 2015-01-10 16:56:42 +00:00
mdiluzio
06057e6e52 Add perl launcher script and new Readme 2015-01-10 16:56:27 +00:00
mdiluzio
1e38a5f107 Update CMakelists to reflect refactor 2015-01-10 16:56:01 +00:00
mdiluzio
0ead12c7dd Huge refactor, pulling server and local out into their own binaries 2015-01-10 16:55:30 +00:00
mdiluzio
1b2010faba Use some version checking on generator scripts, preventing a full rebuild on every build 2015-01-10 15:11:36 +00:00
Marc Di Luzio
30af43ae95 Fix walls in strings and move constructor 2015-01-07 13:25:16 +00:00
Marc Di Luzio
7811daa78b Merge branch 'net' of https://github.com/mdiluz/ttrts into net 2015-01-07 12:40:44 +00:00
Marc Di Luzio
c51119c3e2 Add TTRTS: Prefix to output 2015-01-07 12:40:41 +00:00
25e0fa04de Merge branch 'net' of https://github.com/mdiluz/ttrts into net 2015-01-07 12:40:07 +00:00
3291bf126e Compare write result correctly with 0
Fixes client sending of orders
2015-01-07 12:39:27 +00:00
JackUnthank
a0ff8680a3 Make the AI print out the walls not the map.
Don’t worry I wasn’t doing this all afternoon I had a spare 5 minutes
whilst waiting for some .DAT files to md5. =P
2015-01-06 17:01:15 +00:00
Marc Di Luzio
a7421fa07b Guard against missing file param for server 2015-01-06 12:39:56 +00:00
mdiluzio
1c97177956 Major cleanup of client code, extracting various functionality 2015-01-04 13:09:37 +00:00
mdiluzio
3ed25cd37f Fix up all logging output
Remove debug logging
Use cerr or clog in the correct places
refactor a few more functions
2015-01-04 11:59:58 +00:00
mdiluzio
b141314434 Some more refactoring with additional comments
Server now performs handshake instantly
2015-01-04 11:22:54 +00:00
mdiluzio
5c8666d4fb More refactoring by pulling code out of client and server files 2015-01-04 11:10:08 +00:00
mdiluzio
43d688a728 Refactor running game server into own function 2015-01-03 22:43:39 +00:00
mdiluzio
4055d85d99 Use new fatal_error and fatal_perror functions for errors 2015-01-03 22:30:52 +00:00
mdiluzio
1785ce2fc0 Add game name to handshake and formalise handshake format 2015-01-03 20:04:31 +00:00
mdiluzio
61c012370d Fix filesystem game method 2015-01-03 19:31:42 +00:00
mdiluzio
b532f1c9c8 Make client output and read in game state and order files 2015-01-03 19:27:08 +00:00
mdiluzio
18cfcff264 Improve both client and server code to account for END of messages, allowing for incomplete packets with TCP 2015-01-03 18:55:44 +00:00
mdiluzio
b34b933dcd Pull base netcode functions out into net files 2015-01-02 19:51:14 +00:00
mdiluzio
b43927a1da add user input for starting the game 2015-01-02 19:46:09 +00:00
mdiluzio
770502184c Make client and server perform a handshake to agree on player IDs 2015-01-02 19:31:14 +00:00
mdiluzio
bccd043d2c Fix server functions being used wrongly 2015-01-02 19:05:58 +00:00
mdiluzio
a81b4ff8d0 Handle end game state properly 2015-01-02 19:00:28 +00:00
mdiluzio
e01a718ac6 Fix bug where we passed client info to the thread by ref, like an ape 2015-01-02 18:57:47 +00:00
mdiluzio
56e767bb5b Clients now connect and recieve gamestate information 2015-01-02 17:14:16 +00:00
mdiluzio
4285770d52 Allow multiple client connections on different threads 2015-01-02 16:18:18 +00:00
mdiluzio
dcb9d68fb4 Revert back to cleaner copy of server code 2015-01-02 16:09:24 +00:00
mdiluzio
83b9990bcc some more cleanup and using TTRTS_PORT 2015-01-02 15:25:30 +00:00
mdiluzio
b4240cf1c8 pull in very simple client/server demo code
inspired by http://www.linuxhowtos.org/C_C++/socket.htm
2015-01-02 15:13:05 +00:00
mdiluzio
0619031cb6 Use argc and argv passed down 2015-01-02 15:07:35 +00:00
mdiluzio
2281bcb6cd More refactoring of functionality into seperate files, with stubs for server and client 2015-01-02 15:03:29 +00:00
mdiluzio
8835bfb82a On second thoughts move filesystem game code out to a filesystem cpp 2015-01-02 14:09:29 +00:00
mdiluzio
0962546a82 Move current client code out to server file, still uses old file-system method 2015-01-02 14:04:37 +00:00
mdiluzio
3b5599ffad Remove references to non-existent maths 2015-01-02 14:04:11 +00:00
mdiluzio
ca5526b2b7 Add base files for net code 2015-01-02 13:55:23 +00:00
32 changed files with 1193 additions and 436 deletions

View file

@ -2,8 +2,18 @@ cmake_minimum_required( VERSION 2.8.7 )
# Set version information
set( TTRTS_VERSION_MAJOR 0 )
set( TTRTS_VERSION_MINOR 3 )
set( TTRTS_VERSION_PATCH 2 )
set( TTRTS_VERSION_MINOR 4 )
set( TTRTS_VERSION_PATCH 0 )
# Set defaults for ttrts variables
set( TTRTS_MAPS "/usr/local/share/ttrts/maps/" )
set( TTRTS_GAMES "/tmp/" )
set( TTRTS_PORT 11715 )
# define these defaults in code
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_MAPS=${TTRTS_MAPS}" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_GAMES=${TTRTS_GAMES}" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_PORT=${TTRTS_PORT}" )
# Use c++1y (14)
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" )
@ -26,7 +36,7 @@ install( FILES scripts/ttrts_complete DESTINATION /etc/bash_completion.d/ )
# Run the gen_usage script to generate our usage header
add_custom_target(
ttrts-gen-manpage ALL
${CMAKE_SOURCE_DIR}/scripts/gen_manpage.sh "${TTRTS_VERSION_MAJOR}" "${TTRTS_VERSION_MINOR}" "${TTRTS_VERSION_PATCH}" "ttrts.6" "${CMAKE_SOURCE_DIR}/source/client/README.md"
${CMAKE_SOURCE_DIR}/scripts/gen_manpage.sh "${TTRTS_VERSION_MAJOR}" "${TTRTS_VERSION_MINOR}" "${TTRTS_VERSION_PATCH}" "ttrts.6" "${CMAKE_SOURCE_DIR}/source/README.md"
)
# Install the ttrts man page
@ -41,9 +51,4 @@ endif()
install( FILES "${CMAKE_BINARY_DIR}/ttrts.6" DESTINATION ${MANPAGE_LOC} )
# Subprojects
add_subdirectory( source/ttrts )
add_subdirectory( source/client )
# Auxhilary binaries
add_subdirectory( source/test )
add_subdirectory( source/gen )
add_subdirectory( source )

View file

@ -31,6 +31,14 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An
-----------------------------------------------------------
## Changelog
#### v0.4.0
* Updated with network functionality
* Game can now be hosted with ttrts --server option
* Server can be connected to with ttrts --client
* Updated command line interface with new launcher script
* map file must now be specified with --map=FILE
* Slight refactor of libraries to account for new run targets
#### v0.3.2
* Fix bug when loading map files with walls
* Fix ttrts on OSX

View file

@ -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)
{

View file

@ -16,7 +16,7 @@ if [[ $? != 0 ]]; then
exit
fi
echo "Performing install"
echo "TTRTS: Performing install"
sudo make install
if [[ $? != 0 ]]; then
echo "TTRTS: Install failed, check output"

View file

@ -1,10 +1,13 @@
#! /bin/bash
# Used to a man page from markdown
echo ".\" Man page for the ttrts project" > $4
echo ".\" this man page is auto-generated, do not edit directly" >> $4
FILE="$4"
TEMP="$FILE.tmp"
echo ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" >> $4
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
@ -32,4 +35,10 @@ cat "$5" \
| sed -E 's/-----+//g' \
| sed -E 's/`(.*)`/\\fB\1\\fR/g' \
| sed -E 's/MAPFILE/\\fImapfile\\fR/g' \
| sed -E 's/ ttrts -/ ttrts \\-/g' >> $4
| 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

View file

@ -1,10 +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\"/' \
> $1
> $TEMP
# If no difference
if [ ! -e $FILE ] || [ ! -z "$( diff $TEMP $FILE )" ]; then
mv -f $TEMP $FILE
fi

View file

@ -10,4 +10,9 @@ HEADER="// Auto generated ttrts version header
#endif //_TTRTS_VERSION_H_"
echo "$HEADER" > "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

View file

@ -5,11 +5,11 @@ test ! -z TTRTS_MAPS && TTRTS_MAPS=/usr/share/ttrts/maps/
have ttrts &&
function _ttrts
{
commandnames=""
commandnames="--server --client --host= --host=localhost "
for filename in ${TTRTS_MAPS}/*
do
map="${filename##*/}"
commandnames+="$map "
commandnames+="--map=$map "
done
local cur prev

13
source/CMakeLists.txt Normal file
View file

@ -0,0 +1,13 @@
# Main libraries
add_subdirectory( ttrts )
add_subdirectory( system )
# Main binaries
add_subdirectory( launcher )
add_subdirectory( client )
add_subdirectory( server )
add_subdirectory( local )
# Auxhilary binaries
add_subdirectory( test )
add_subdirectory( gen )

View file

@ -1,13 +1,121 @@
# Targets
### client
Main TTRTS binary, runs from the command line and acts as host for games
# NAME
ttrts - Tiny Terminal RTS
### test
Test binary, to be compiled and run to test various functionality
# SYNOPSIS
ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE]
# DESCRIPTION
ttrts is a tiny terminal based RTS that uses text files as order lists to control the units
### gen
Binary to generate example map files
This means that any user, program or cat that can read and write to text files can play the game
# Libraries
### ttrts
Implementation of the RTS rules and simulation
# RETURN VALUE
ttrts will return -1 on error, or the winning player on completion
# OPTIONS
--server - Run in server mode, must provide a map file
--client - Run in client mode, must provide a hostname for a running server
--map=MAPFILE - File to read in the initial game state. Local or in ${TTRTS_MAPS}
--host=HOSTNAME - Name of host to connect to in client mode
# USAGE
When invoked, ttrts will set up the game in a directory within ${TTRTS_GAMES} by the name of the map
The files in this directory can be read and interpreted by human, robot or cat
ttrts will then await order files from each participant
Once all order files have been received ttrts will calculate the turn and output a new gamestate file
In server and client mode, the client will output and read in these files while the server simulates the game
This process repeats until the game is over
# ENVIRONMENT
${TTRTS_MAPS} - Map file lookup location, defaults to `/usr/share/ttrts/maps/`
${TTRTS_GAMES} - Game directory for I/O, defaults to `/tmp/`
-----------------------------------------------------------
# FILES
`/usr/share/ttrts/maps/` holds a sample set of maps
## Gamestate File
Turn_{TURNNUMBER}.txt
### Contents
===== ttrts v{MAJOR}.{MINOR}.{PATCH} =====
NAME:{GAMENAME}
SIZE:[{X},{Y}]
TURN:{TURNNUMBER}
WALL:[{X},{Y}][{X},{Y}][{X},{Y}]...{repeat for all walls}
~~~~
UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}]
... {continue for all units}
END
## Order File
Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt
### Contents
ORDER:{ORDER_CHAR} id:{UNIT_ID}
... {continue for all orders}
END
-----------------------------------------------------------
# SERVER/CLIENT
When in server or client mode, the game can be played across a network. If desired, a player could design an AI to act as a client instead of using the client mode and intermediary filesystem.
## Protocol
The server is accesible on port 11715
To perform the handshake the server will write to the socket with the format "player PLAYER_ID name GAME_NAME", it will expect this exact information to be written back to in reply.
Once handshake is performed, the server will write to the socket in the form of the Gamestate file as above.
The server will then wait for a new-line delimited and END terminated list of orders
This will be repeated until the game is over
-----------------------------------------------------------
# GAMEPLAY
The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board
Each turn, the client outputs a gamestate file and waits for an order file from each player
All commands are evaluated simultaneously with friendly fire enabled by default
The game is over when any of three conditions are met -
* All remaining units are controlled by a single player
* No units are left (draw)
* All units left are unable to move (draw)
# UNITS
Each unit occupies a single tile on the board, facing in a compass direction (NESW)
Units will only accept orders from their owner
Units can receive only a single order each turn
Units cannot occupy the same tile as other units/walls
# ORDERS
### F - Move unit [F]orward one space, leaving a wall
This wall will remain until the end of the game, blocking movement to that tile
Movement orders have no effect if impossible, eg.
* Attempting to move outside of map
* Attempting to move on to tile occupied by unit/wall
### L/R - Rotate unit [L]eft or [R]ight
Unit will rotate clockwise or counter-clockwise, this order cannot fail
### A - [A]ttack in straight line in front of unit
Attack will continue forward until unit can't progress, all units within the path of the attack are destroyed.

View file

@ -3,40 +3,21 @@
project( ttrts-client )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../maths
${CMAKE_CURRENT_BINARY_DIR}
../system
../ttrts
)
# Add the sources
set( SOURCES
main.cpp
client.cpp
)
# Set defaults for ttrts maps and games
set( TTRTS_MAPS "/usr/local/share/ttrts/maps/" )
set( TTRTS_GAMES "/tmp/" )
# 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}" )
# Add the executable
add_executable( ${PROJECT_NAME} ${SOURCES} )
# Set our output name to ttrts
set_target_properties( ${PROJECT_NAME} PROPERTIES OUTPUT_NAME ttrts )
# dependent on main ttrts libary
target_link_libraries( ${PROJECT_NAME} ttrts )
target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system )
# Installation target
install( TARGETS ${PROJECT_NAME} DESTINATION bin )
# Run the gen_usage script to generate our usage header
add_custom_target(
ttrts-client-usage
cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_SOURCE_DIR}/scripts/gen_usage.sh "${CMAKE_CURRENT_BINARY_DIR}/usage.h"
)
add_dependencies(${PROJECT_NAME} ttrts-client-usage)

View file

@ -1,98 +0,0 @@
# NAME
ttrts - Tiny Terminal RTS
# SYNOPSIS
ttrts MAPFILE
# DESCRIPTION
ttrts is a tiny terminal based RTS that uses text files as order lists to control the units
This means that any user, program or cat that can read and write to text files can play the game
# RETURN VALUE
ttrts will return -1 on error, or the winning player on completion
# OPTIONS
MAPFILE - File to read in the initial game state. Local or in ${TTRTS_MAPS}
# USAGE
When invoked, ttrts will set up the game in a directory within ${TTRTS_GAMES} by the name of the map
The files in this directory can be read and interpreted by human, robot or cat
ttrts will then await order files from each participant
Once all order files have been received ttrts will calculate the turn and output a new gamestate file
This process repeats until the game is over
# ENVIRONMENT
${TTRTS_MAPS} - Map file lookup location, defaults to `/usr/share/ttrts/maps/`
${TTRTS_GAMES} - Game directory for I/O, defaults to `/tmp/`
-----------------------------------------------------------
# FILES
`/usr/share/ttrts/maps/` holds a sample set of maps
## Gamestate File
Turn_{TURNNUMBER}.txt
### Contents
===== ttrts v{MAJOR}.{MINOR}.{PATCH} =====
NAME:{GAMENAME}
SIZE:[{X},{Y}]
TURN:{TURNNUMBER}
WALL:[{X},{Y}][{X},{Y}][{X},{Y}]...{repeat for all walls}
~~~~
UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}]
... {continue for all units}
END
## Order File
Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt
### Contents
ORDER:{ORDER_CHAR} id:{UNIT_ID}
... {continue for all orders}
END
-----------------------------------------------------------
# GAMEPLAY
The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board
Each turn, the client outputs a gamestate file and waits for an order file from each player
All commands are evaluated simultaneously with friendly fire enabled by default
The game is over when any of three conditions are met -
* All remaining units are controlled by a single player
* No units are left (draw)
* All units left are unable to move (draw)
# UNITS
Each unit occupies a single tile on the board, facing in a compass direction (NESW)
Units will only accept orders from their owner
Units can receive only a single order each turn
Units cannot occupy the same tile as other units/walls
# ORDERS
### F - Move unit [F]orward one space, leaving a wall
This wall will remain until the end of the game, blocking movement to that tile
Movement orders have no effect if impossible, eg.
* Attempting to move outside of map
* Attempting to move on to tile occupied by unit/wall
### L/R - Rotate unit [L]eft or [R]ight
Unit will rotate clockwise or counter-clockwise, this order cannot fail
### A - [A]ttack in straight line in front of unit
Attack will continue forward until unit can't progress, all units within the path of the attack are destroyed.

69
source/client/client.cpp Normal file
View file

@ -0,0 +1,69 @@
#include <iostream>
#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 "<<hostname<<std::endl;
int sockfd = ConnectToHostServer(hostname, serv_addr);
unsigned int player;
std::string gameNameString;
// Handshake with server to fetch player and gamestring
PerformClientHandshake(sockfd, player, gameNameString);
// output our information
player_t myPlayer = (player_t)player;
std::cout<<"TTRTS: I am player "<<std::to_string((int)myPlayer)<<std::endl;
std::cout<<"TTRTS: Game is "<<gameNameString<<std::endl;
// Clean out the games dir
CreateAndCleanGameDir(gameNameString);
// Buffer for messages
char buffer[1028];
memset(buffer,0,sizeof(buffer));
int n = 0; // return value for read and write calls
while ( n >= 0 )
{
std::cout<<"TTRTS: Waiting for gamestate"<<std::endl;
std::string gamestate = WaitForGamestateMessage(sockfd);
// Output the gamestate file for this game
CTTRTSGame thisGame = GetGameFromString(gamestate);
OutputGameStateFile(thisGame);
// If game over, exit with out winning player and message
if(thisGame.GameOver())
exit( OutputGameEnd(thisGame) );
// Get the order file for this turn`
std::string orders = GetOrdersFromPlayerFile(thisGame,myPlayer);
std::cout<<"TTRTS: Sending orders"<<std::endl;
std::cout<<orders<<std::endl;
// Write to the socket with the buffer
n = SendOrdersToServer(sockfd, orders);
}
return 0;
}

View file

@ -1,277 +0,0 @@
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include <stdio.h>
#include <stdbool.h>
#include <formatters.h>
#include <unistd.h>
#include <stdlib.h>
#include "game.h"
#define STRINGIFY(x) _STRINGIFY(x)
#define _STRINGIFY(x) #x
static const char* sk_usage =
#include "usage.h"
;
// Verbose mode
static const bool env_verbose = getenv("VERBOSE");
// time for waiting between file stats
static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100);
// Check if a file exists
inline bool FileExists( const std::string& name )
{
struct stat buffer;
return (stat (name.c_str(), &buffer) == 0);
}
// Wait for a file to exist
inline void WaitForFile( const std::string& name, const std::chrono::milliseconds& time )
{
while( !FileExists(name) ) std::this_thread::sleep_for(time);
}
bool OutputGameStateFile(CTTRTSGame &game, std::string &gameDir)
{
char turnFileName[128];
snprintf(turnFileName,128,"%s/Turn_%i.txt",gameDir.c_str(),game.GetTurn());
std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists
if ( turnFile.bad() )
{
return false;
}
// Output the turn description
std::string turnDescriptor = GetStringFromGame(game);
turnFile<<turnDescriptor;
turnFile.close();
return true;
}
// Main program entry point
int main(int argc, char* argv[])
{
// If no args, print usage
if ( argc == 1 )
{
std::cout<<sk_usage<<std::endl;
return -1;
}
// Attempt to open the game file
std::string gameFile = argv[1];
// Default for maps
std::string ttrts_maps_dir = STRINGIFY(TTRTS_MAPS);
if( getenv("TTRTS_MAPS") )
{
ttrts_maps_dir = getenv("TTRTS_MAPS");
// Additional trailing slash
if( ttrts_maps_dir.back() != '/' )
ttrts_maps_dir += "/";
}
// Default for games
std::string ttrts_games_dir = STRINGIFY(TTRTS_GAMES);
if( getenv("TTRTS_GAMES") )
{
ttrts_games_dir = getenv("TTRTS_GAMES");
// Additional trailing slash
if( ttrts_games_dir.back() != '/' )
ttrts_games_dir += "/";
}
// If file path is not local path and file doesn't exist
if( gameFile.find("/") == std::string::npos
&& access( gameFile.c_str(), F_OK ) == -1 )
{
gameFile = ttrts_maps_dir + gameFile;
}
// If still not good
if( access( gameFile.c_str(), F_OK ) == -1 )
{
std::cerr<<"Error: "<<gameFile<<" file not found"<<std::endl;
return -1;
}
std::ifstream file(gameFile);
std::cout<<"Launching TTRTS with "<<gameFile<<std::endl;
std::string gameDescriptor;
// Reserve the string needed up front
file.seekg(0, std::ios::end);
gameDescriptor.reserve(file.tellg());
file.seekg(0, std::ios::beg);
// Grab the string from the file
gameDescriptor.assign((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
if( gameDescriptor.size() == 0 )
{
std::cerr<<"Error: failed to read in any information from "<<gameFile<<std::endl;
return -1;
}
// Create the game
CTTRTSGame game = GetGameFromString(gameDescriptor);
// Grab the players involved
auto players = game.GetPlayers();
// Current game directory
std::string gameDir = ttrts_games_dir + game.GetName();
// Empty the current game directory
struct stat info;
int ret = stat( gameDir.c_str(), &info );
if( ret == 0 && info.st_mode & S_IFDIR )
{
std::cout<< gameDir << " game directory already exists"<<std::endl;
std::cout<<"Confirm to delete contents [y/N] ";
std::string input;
std::cin>>input;
if( !input.size() || std::tolower(input[0]) != 'y' )
{
std::cerr<<"Aborting..."<<std::endl;
return -1;
}
}
else if ( ret == 0 )
{
std::cerr<< gameDir << " exists but is not directory \nAborting..."<<std::endl;
return -1;
}
// 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)
{
std::cerr<<"Error: Failed to create the game directory"<<std::endl;
return -1;
}
// Clean out the game directory
char cmd1[128];
snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str());
if ( system(cmd1) == -1 )
{
std::cerr<<"Error: Failed to clean the game directory"<<std::endl;
return -1;
}
// While the game isn't finished
while ( ! game.GameOver() )
{
std::cout<<"Starting turn "<<game.GetTurn()<<std::endl;
// Create a turn file
if( !OutputGameStateFile(game, gameDir))
{
std::cerr<<"Error: Failed to output new turn file" << std::endl;
return 1;
}
// Wait for order files
for( player_t player : players)
{
// Construct the player order filename
char playerOrderFileName[128];
snprintf(playerOrderFileName, 128, "%s/Player_%i_Turn_%i.txt", gameDir.c_str(), (int) player, game.GetTurn());
// Wait for the player order file to be created
std::cout<<"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;
std::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::end);
orders.reserve(turnFile.tellg());
turnFile.seekg(0, std::ios::beg);
// Grab the string from the file
orders.assign((std::istreambuf_iterator<char>(turnFile)),std::istreambuf_iterator<char>());
// Issue the orders to the game
if( game.IssueOrders(player, orders) )
std::cerr<<"Warning: Orders for player "<<(int) player <<" failed to correctly parse"<<std::endl;
}
// Simulate turn
std::cout<<"Simulating this turn!"<<std::endl;
if ( game.SimulateToNextTurn() )
{
std::cerr << "Error: Failed to simulate for turn "<<game.GetTurn()<<std::endl;
return -1;
}
}
// Output final gamestate
OutputGameStateFile(game, gameDir);
// Get the winning player
player_t winningPlayer = game.GetWinningPlayer();
// Print the winner!
if ( winningPlayer != player_t::NUM_INVALID )
{
std::cout<<"Game over! Winner:"<<(int) winningPlayer <<std::endl;
}
else
{
std::cout<<"Game over! It was a draw!"<<std::endl;
}
return (int)winningPlayer;
};

View file

@ -3,8 +3,7 @@
project( ttrts-gen )
include_directories(
../ttrts
../maths
../ttrts
)
set( SOURCES
@ -25,4 +24,4 @@ add_custom_target(
add_dependencies(ttrts-gen-maps ${PROJECT_NAME})
# Installation target
install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/maps DESTINATION /usr/local/share/ttrts )
install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/maps DESTINATION /usr/local/share/ttrts )

View file

@ -33,7 +33,7 @@ int main()
//------
{
CTTRTSGame game(6, 6);
game.SetName("Tiny2player");
game.SetName("Tiny2Player");
AddUnitToGame( player_t::Red, '<', uvector2(4, 2), game);
AddUnitToGame( player_t::Red, '<', uvector2(4, 4), game);

View file

@ -0,0 +1,6 @@
# ====================== ttrts =======================
# Project name
project( ttrts-perl-launch )
# Add bash completion to install
install( PROGRAMS ttrts.pl DESTINATION bin RENAME ttrts )

44
source/launcher/ttrts.pl Executable file
View file

@ -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

View file

@ -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 )

62
source/local/local.cpp Normal file
View file

@ -0,0 +1,62 @@
#include "game.h"
#include "filesystem.h"
#include "error.h"
#include "net.h"
#include <iostream>
// =====================================================================================================================
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 "<<gamefile<<std::endl;
CTTRTSGame game = GetGameFromFile(gamefile);
// Grab the players involved
auto players = game.GetPlayers();
// Default for games
std::string ttrts_games_dir = getGamesDir();
// Empty the current game directory
if ( CreateAndCleanGameDir(game.GetName()) < 0)
return -1;
// While the game isn't finished
while ( ! game.GameOver() )
{
std::cout<<"TTRTS: Starting turn "<<game.GetTurn()<<std::endl;
// Create a turn file
if( !OutputGameStateFile(game))
fatal_error("Error: Failed to output new turn file");
// Wait for order files
for( player_t player : players)
{
// Construct the player order filename
std::string orders = GetOrdersFromPlayerFile(game, player);
// Issue the orders to the game
if( game.IssueOrders(player, orders) )
std::cerr<<"Warning: Orders for player "<<(int) player <<" failed to correctly parse"<<std::endl;
}
// Simulate turn
std::cout<<"TTRTS: Simulating this turn!"<<std::endl;
if ( game.SimulateToNextTurn() )
fatal_error("Failed to simulate game turn");
}
// Output final gamestate
OutputGameStateFile(game);
return OutputGameEnd( game );
}

View file

@ -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 )

98
source/server/server.cpp Normal file
View file

@ -0,0 +1,98 @@
#include "error.h"
#include <thread>
#include <vector>
#include <iostream>
#include <mutex>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include "net.h"
#include "filesystem.h"
void RunServerForGame(CTTRTSGame &game)
{
std::cout<<"TTRTS: Setting up server"<<std::endl;
// Server side information
sockaddr_in serv_addr = GetLocalServerAddress();
// socket File descriptor
int sockfd = SetUpServerListeningSocket(serv_addr);
// Get information about the game
std::vector<player_t> players = game.GetPlayers();
unsigned int numClients = players.size();
auto player_iterator = players.begin();
// game mutex
std::mutex gameMutex;
// Set of clients
std::vector<ClientInfo> myClients;
std::cout<<"TTRTS: Waiting for "<<numClients<<" players"<<std::endl;
// Loop while we're connecting the clients
while ( myClients.size() < numClients )
{
// information for each client
ClientInfo clientInfo;
clientInfo.player = *player_iterator;
player_iterator++;
clientInfo = WaitForClientConnection(sockfd, game.GetName(), clientInfo);
// Add out client info to our list
myClients.push_back(clientInfo);
}
std::cout<<"TTRTS: All players connected"<< std::endl;
std::cout<<"TTRTS: Hit enter to begin...";
std::cin.ignore();
// Loop for each turn
while ( !game.GameOver() )
{
// Send data to clients
std::cout<<"TTRTS: Broadcasting Gamestate"<< std::endl;
SendGamestateToClients(myClients, game, gameMutex);
// Wait for orders from clients
std::cout<<"TTRTS: Waiting for orders from players"<< std::endl;
WaitForOrdersFromClients(myClients, game, gameMutex);
std::cout<<"TTRTS: All orders recieved, simulating turn"<< std::endl;
// Step to the next turn
gameMutex.lock();
game.SimulateToNextTurn();
gameMutex.unlock();
}
// Send final state to all the clients
SendGamestateToClients(myClients, game, gameMutex);
}
int main(int argc, char* argv[])
{
// argv[1] needs to be a valid game file
if( argc < 2 )
fatal_error("Usage: ttrts-server MAPFILE");
// Set up game
CTTRTSGame game = GetGameFromFile(argv[1]);
if(game.GetNumUnits() == 0)
fatal_error("game not valid");
RunServerForGame(game);
// Return winning player and output game end
return OutputGameEnd(game);
}

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 2.8.7)
# Main ttrts library
project( ttrts-system )
# Include the maths
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../ttrts
)
# Add our sources
set( SOURCES
net.cpp
filesystem.cpp
)
# Add this library
add_library( ${PROJECT_NAME} ${SOURCES} )
target_link_libraries( ${PROJECT_NAME} ttrts pthread )

25
source/system/error.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef _TTRTS_ERROR_H_
#define _TTRTS_ERROR_H_
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
//======================================================================================================================
// Error functions
// For local fatal errors
inline void fatal_error(const char *msg)
{
std::cerr<<msg<<std::endl;
exit(1);
}
// For system fatal errors (ie. functions that set errno)
inline void fatal_perror(const char *msg)
{
perror(msg);
exit(1);
}
#endif

View file

@ -0,0 +1,248 @@
#include "filesystem.h"
#include "net.h"
#include "error.h"
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include <stdio.h>
#include <stdbool.h>
#include <formatters.h>
#include <unistd.h>
#include <stdlib.h>
// =====================================================================================================================
// 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<<turnDescriptor;
turnFile.close();
return true;
}
std::string getMapsDir()
{
std::string maps = STRINGIFY(TTRTS_MAPS);
if( getenv("TTRTS_MAPS") )
{
maps = getenv("TTRTS_MAPS");
// Additional trailing slash
if( maps.back() != '/' )
maps += "/";
}
return maps;
}
std::string getGamesDir()
{
std::string dir = STRINGIFY(TTRTS_GAMES);
if( getenv("TTRTS_GAMES") )
{
dir = getenv("TTRTS_GAMES");
// Additional trailing slash
if( dir.back() != '/' )
dir += "/";
}
return dir;
}
CTTRTSGame GetGameFromFile( const std::string& filename )
{
std::string gamefile = filename;
// Default for maps
std::string ttrts_maps_dir = getMapsDir();
// If file path is not local path and file doesn't exist
if( gamefile.find("/") == std::string::npos
&& access( gamefile.c_str(), F_OK ) == -1 )
{
gamefile = ttrts_maps_dir + gamefile;
}
// If still not good
if( access( gamefile.c_str(), F_OK ) == -1 )
fatal_perror("Could not open game file");
std::ifstream file(gamefile);
std::string gameDescriptor;
// Reserve the string needed up front
file.seekg(0, std::ios::end);
gameDescriptor.reserve(file.tellg());
file.seekg(0, std::ios::beg);
// Grab the string from the file
gameDescriptor.assign((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
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<char>(turnFile)), std::istreambuf_iterator<char>());
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"<<std::endl;
std::cout<<"TTRTS: Confirm to delete contents [y/N] ";
std::string input;
std::cin>>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 <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}
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 <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}

View file

@ -0,0 +1,29 @@
#ifndef _TTRTS_FILESYSTEM_H_
#define _TTRTS_FILESYSTEM_H_
#include <string>
#include <chrono>
#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

261
source/system/net.cpp Normal file
View file

@ -0,0 +1,261 @@
#include "net.h"
#include "error.h"
#include <netdb.h>
#include <iostream>
#include <thread>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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 "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
std::string orders;
while ( orders.find("END") == std::string::npos )
{
memset(buffer,0,sizeof(buffer));
// Read in the new socket
// read will block until the client has called write
// up to the full size of the buffer
if (read(info.clientsockfd,buffer,sizeof(buffer)-1) < 0)
fatal_perror("ERROR reading from client");
// Append the received orders
orders+=buffer;
}
std::clog<<"TTRTS: Recieved orders from "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
mut.lock();
game.IssueOrders(info.player , orders);
mut.unlock();
}
void WaitForOrdersFromClients(std::vector<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex)
{
// Spawn threads
std::vector<std::thread> 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<ClientInfo> &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 "<<retry<< std::endl;
sleep(retry);
retry++;
}
}
ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo)
{
socklen_t clilen = sizeof(sockaddr_in);
// accept waits for a connection from a client
// it returns a new socket file descriptor for this connection
// client information will be stored in cli_addr
clientInfo.clientsockfd = accept(sockfd, (sockaddr *) &clientInfo.cli_addr, &clilen);
if (clientInfo.clientsockfd < 0)
fatal_perror("ERROR on accept");
std::clog<<"TTRTS: Client connected from "<<inet_ntoa(clientInfo.cli_addr.sin_addr)<<" socket "<<clientInfo.clientsockfd<< std::endl;
// Handshake with client
PerformServerHandshake(clientInfo, game);
return clientInfo;
}
int SetUpServerListeningSocket(const sockaddr_in &serv_addr)
{
int sockfd;
std::clog<<"TTRTS: Opening socket"<< std::endl;
// Create a new socket
// AF_INET is general internet socket domain
// SOCK_STREAM as messages will be read in on this socket, SOCK_DGRAM would be for packets
// 0 is for default protocol
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
fatal_perror("ERROR opening socket");
// bind our socket to this server address
TryBindSocket(sockfd, serv_addr);
// Listen on the socket for messages
// Second param is length of backlog queue, the maximum number of connections
// that can be waiting while the process is handling a single connection
// max is usually set to 5
listen(sockfd,5);
return sockfd;
}
sockaddr_in GetLocalServerAddress()
{
sockaddr_in serv_addr; // Server address
// empty the server address
memset(&serv_addr,0, sizeof(serv_addr));
// Set the server address family to AF_INET
serv_addr.sin_family = AF_INET;
// htons swaps from host byte order to network byte order
serv_addr.sin_port = htons(TTRTS_PORT);
// The host for this address is this current machine's IP, INADDR_ANY fetches this
serv_addr.sin_addr.s_addr = INADDR_ANY;
return serv_addr;
}
int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr)
{
// Create a new socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
fatal_perror("ERROR opening socket");
// Get the hostent information for the host by name
hostent *server = gethostbyname(hostname.c_str());
if (server == NULL)
fatal_error("ERROR, no such host");
// copy the server address into our server_addr struct
memcpy(&serv_addr.sin_addr.s_addr, server->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!"<<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 <<std::endl;
}
else
{
std::cout<<"TTRTS: It was a draw!"<<std::endl;
}
return (int)winningPlayer;
}

81
source/system/net.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef _TTRTS_NET_H_
#define _TTRTS_NET_H_
#include <vector>
#include <mutex>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <game.h>
#include <formatters.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#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<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex);
// Sends current gamestate to each client
void SendGamestateToClients(std::vector<ClientInfo> &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

View file

@ -4,8 +4,7 @@
project( ttrts-test )
include_directories(
../ttrts
../maths
../ttrts
)
set( SOURCES

View file

@ -5,8 +5,7 @@ project( ttrts )
# Include the maths
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../maths
${CMAKE_CURRENT_BINARY_DIR}
)
# Add our sources
@ -44,4 +43,4 @@ install(
)
# Install the ttrts static lib
install( TARGETS ttrts DESTINATION lib )
install( TARGETS ttrts DESTINATION lib )

View file

@ -92,7 +92,7 @@ CTTRTSGame GetGameFromString( const std::string& input )
size_t pos;
while ( ( pos = walls_str.find(']') ) != std::string::npos )
{
std::string pos_string = walls_str.substr(0,pos);
std::string pos_string = walls_str.substr(0,pos+1);
// Use scanf to extract positions

View file

@ -17,6 +17,7 @@ CTTRTSGame::CTTRTSGame(CTTRTSGame&& game)
, dimensions(std::move(game.dimensions))
, turn(std::move(game.turn))
, name(std::move(game.name))
, m_walls(std::move(game.m_walls))
{
}