Compare commits

..

No commits in common. "master" and "v0.3.0" have entirely different histories.

49 changed files with 749 additions and 1822 deletions

1
.gitignore vendored
View file

@ -9,5 +9,4 @@
*.user
*.sublime*
*.idea
*~

View file

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

View file

@ -1,4 +1,4 @@
# TTRTS
# TTRTS v0.3.0
*The Tiny Terminal RTS where the players write their AIs*
-----------------------------------------------------------
@ -11,7 +11,7 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An
## Building TTRTS
#### Requirements
* CMake - our build system uses cmake
* cmake - our build system uses cmake
* Linux/OSX - currently no support for Windows, tracked with [Issue #9](https://github.com/mdiluz/ttrts/issues/9)
#### To Build
@ -23,43 +23,15 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An
-----------------------------------------------------------
## Development
* master branch always stores latest stable release
* [master](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](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
## 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
@ -68,13 +40,13 @@ TTRTS is from the ground up designed to be a fun way to practice programming. An
* Various C++ api simplifications
* Integration of perl api from [ttrts-players](https://github.com/mdiluz/ttrts-players)
#### v0.2.0
## v0.2.0
* All team references changed to player
* Order file format changed to Player_#_Turn_#.txt
* Unit descriptors now use pl: instead of tm:
* Various other C++ api corrections and refactors
#### v0.1.0
## v0.1.0
* First playable version of ttrts
-----------------------------------------------------------

View file

@ -238,13 +238,6 @@ 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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 3 )
set( TTRTS_VERSION_PATCH 0 )
# Use c++1y (14)
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" )
# Turn on all warnings
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder" )
# Turn off reorder warnings as they're kind of irrelevant
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" )
# This shouldn't be needed, but it looks like IDE's like clion can forget to set -g for Debug
if( CMAKE_BUILD_TYPE MATCHES "Debug" )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g" )
endif()
# Add definitions for the version number
add_definitions(
-DTTRTS_VERSION_MAJOR=${TTRTS_VERSION_MAJOR}
-DTTRTS_VERSION_MINOR=${TTRTS_VERSION_MINOR}
-DTTRTS_VERSION_PATCH=${TTRTS_VERSION_PATCH}
-DTTRTS_VERSION_STRING=\"${TTRTS_VERSION_MAJOR}.${TTRTS_VERSION_MINOR}.${TTRTS_VERSION_PATCH}\"
)
# Subprojects
add_subdirectory( ttrts )
add_subdirectory( game )
# Auxhilary binaries
add_subdirectory( test )
add_subdirectory( gen )
add_subdirectory( gen )

View file

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

View file

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

View file

@ -1,69 +0,0 @@
#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

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 2.8.7)
# game project
project( game )
include_directories(
../maths
)
# Add the sources
set( SOURCES
game.cpp
unit.cpp
order.cpp
)
add_library( game ${SOURCES} )

View file

@ -2,7 +2,6 @@
#include <algorithm>
#include <string.h>
#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))
{
}
@ -503,6 +501,118 @@ player_t CTTRTSGame::GetWinningPlayer() const
return winningPlayer;
}
// Get the game information as a string
std::string CTTRTSGame::GetStateAsString() const
{
// Grab the walls
std::string walls;
if( m_walls.size() == 0 )
{
walls = "NONE";
}
for ( auto wall : m_walls)
{
char pos[16];
if( snprintf(pos, 16, GAME_POS_FORMATTER , wall.x, wall.y ) < 0 )
{
return "BUFFER OVERFLOW";
}
walls += pos;
}
// Print out the header
char header[512];
if ( snprintf(header, 512, GAME_HEADER_FORMATTER , name.c_str(), dimensions.x, dimensions.y, turn, walls.c_str() ) < 0 )
{
return "BUFFER OVERFLOW";
}
// Gather unit information
std::string units;
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
units += CUnit::GetStringFromUnit(pair.unit);
units += '\n';
}
// Append the header and units
std::string state(header);
state += '\n';
state += GAME_HEADER_DELIMITER;
state += units;
state += "END";
return state;
}
// Get the game information as a string
CTTRTSGame CTTRTSGame::CreateFromString( const std::string& input )
{
size_t headerEnd = input.find(GAME_HEADER_DELIMITER);
std::string header = input.substr(0, headerEnd);
std::string units = input.substr(headerEnd + strlen(GAME_HEADER_DELIMITER));
// Grab information from the header
char name[64];
unsigned int turn;
unsigned int sizex;
unsigned int sizey;
char walls[512];
if( sscanf(header.c_str(), GAME_HEADER_FORMATTER, name, &sizex, &sizey, &turn, walls) != 5 )
{
return CTTRTSGame(0,0);
}
std::vector<uvector2> walls_vector;
{
std::string walls_str = walls;
size_t pos;
while ( ( pos = walls_str.find(']') ) != std::string::npos )
{
std::string pos_string = walls_str.substr(1,pos);
// Use scanf to extract positions
unsigned int x;
unsigned int y;
if( sscanf(pos_string.c_str(), GAME_POS_FORMATTER, &x, &y ) != 2 )
{
return CTTRTSGame(0,0);
}
// Erase this coordinate
walls_str.erase(0,pos+1);
// Append our list
walls_vector.push_back({(ucoord_t)x,(ucoord_t)y});
}
}
CTTRTSGame game(sizex,sizey);
game.SetName(name);
game.SetTurn(turn);
// For each line, construct a unit
{
size_t pos;
while ((pos = units.find('\n')) != std::string::npos) {
std::string unit_string = units.substr(0, pos);
units.erase(0, pos + 1);
game.AddUnit(CUnit::GetUnitFromString(unit_string));
}
}
// Add all walls
for ( auto wall : walls_vector)
{
game.AddWall(wall);
}
return game;
}
// Check if any of the units can move
bool CTTRTSGame::UnitsCanMove() const
{

View file

@ -13,6 +13,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);
@ -35,6 +38,9 @@ public:
// Check if any of the units can move
bool UnitsCanMove() const;
// Get the game information as a string
std::string GetStateAsString() const;
// Issue orders to the game, returns non-zero if orders are incorrect
int IssueOrders( player_t player, const std::string& orders );
int IssueOrders( player_t player, const COrderVector& orders );
@ -55,8 +61,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; }

30
source/game/gametypes.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef _GAME_TYPES_H_
#define _GAME_TYPES_H_
#include <limits> // std::numeric_limits
#define GAME_POS_FORMATTER "[%u,%u]"
#define GAME_HEADER_FORMATTER "NAME:%s\nSIZE:" GAME_POS_FORMATTER "\nTURN:%u\nWALL:%s"
#define GAME_HEADER_DELIMITER "~~~~\n"
#define UNIT_FORMATTER "UNIT:%u pl:%u vs:%c dr:%c ps:" GAME_POS_FORMATTER
// Type for a Player IDs
enum class player_t : char
{
Red = 0,
Green,
Yellow,
Blue,
NUM_INVALID
};
typedef unsigned short unit_id_t; // Type for unit IDs
typedef char unitvis_c; // Typedef for unit visual representations
static const unit_id_t unit_id_invalid = std::numeric_limits<unit_id_t>::max();
static const unitvis_c unitvis_invalid = std::numeric_limits<unitvis_c>::max();
#endif //_GAME_TYPES_H_

33
source/game/order.cpp Normal file
View file

@ -0,0 +1,33 @@
#include <string.h>
#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;
}

View file

@ -47,4 +47,8 @@ inline bool SOrder::operator== ( const SOrder & rhs ) const
// Typedef a vector of orders
typedef std::vector<SOrder> COrderVector;
// string <--> order conversion functions
std::string GetStringFromOrder(const SOrder & order );
SOrder GetOrderFromString( const std::string& order );
#endif //_ORDERS_H_

View file

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

View file

@ -18,6 +18,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 +59,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

View file

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

View file

@ -2,7 +2,6 @@
#include <iostream>
#include <fstream>
#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));
}
}

View file

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

View file

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

View file

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

View file

@ -1,62 +0,0 @@
#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 );
}

28
source/maths/mathtypes.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef _BASETYPES_H_
#define _BASETYPES_H_
#include <limits> // std::numeric_limits
#include "stdlib.h" // for size_t
template<class T, size_t N>
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<coord_t>::max();
static const ucoord_t ucoord_invalid = std::numeric_limits<ucoord_t>::max();
// Direction representation
enum class dir_c : char
{
N = 'N',
S = 'S',
E = 'E',
W = 'W'
};
#endif //_BASETYPES_H_

View file

@ -1,7 +1,7 @@
#ifndef _VECTOR2_H_
#define _VECTOR2_H_
#include "gametypes.h"
#include "mathtypes.h"
struct uvector2;

9
source/scripts/gen_usage.sh Executable file
View file

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

View file

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

View file

@ -1,98 +0,0 @@
#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

@ -1,21 +0,0 @@
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 )

View file

@ -1,25 +0,0 @@
#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

@ -1,248 +0,0 @@
#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

@ -1,29 +0,0 @@
#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

View file

@ -1,261 +0,0 @@
#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;
}

View file

@ -1,81 +0,0 @@
#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,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 )

View file

@ -1,4 +1,3 @@
#include <formatters.h>
#include <iostream> // 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";
@ -176,10 +175,10 @@ const char* tests()
if ( game.GetWinningPlayer() != player_t::Blue )
return "Game failed to recognise a win for the right Player";
std::string game_string = 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 )

View file

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

102
source/ttrts/README.md Normal file
View file

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

View file

@ -1,212 +0,0 @@
#include "formatters.h"
#include <iostream>
#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"<<std::endl;
return CTTRTSGame(0,0);
}
if( major != TTRTS_VERSION_MAJOR
|| minor != TTRTS_VERSION_MINOR )
{
std::cerr<<"Error: ttrts map/binary version missmatch BINARY:v"<<major<<"."<<minor<<"."<<patch<<" FILE:"<<TTRTS_VERSION_STRING<<std::endl;
}
std::vector<uvector2> 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;
}

View file

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

View file

@ -1,45 +0,0 @@
#ifndef _GAME_TYPES_H_
#define _GAME_TYPES_H_
#include <limits> // 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<unit_id_t>::max();
// Typedef for unit visual representations
typedef char unitvis_c;
static const unitvis_c unitvis_invalid = std::numeric_limits<unitvis_c>::max();
// Coordinate types
typedef short coord_t;
static const coord_t coord_invalid = std::numeric_limits<coord_t>::max();
typedef unsigned short ucoord_t;
static const ucoord_t ucoord_invalid = std::numeric_limits<ucoord_t>::max();
// Direction representation
enum class dir_c : char
{
N = 'N',
S = 'S',
E = 'E',
W = 'W'
};
// Helper function for count of an array
template<class T, size_t N>
constexpr size_t _countof(T (&)[N]) { return N; }
#endif //_GAME_TYPES_H_

235
source/ttrts/main.cpp Normal file
View file

@ -0,0 +1,235 @@
#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <sys/stat.h>
#include <stdio.h>
#include <stdbool.h>
#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<<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];
std::ifstream file(gameFile);
if( file.bad() )
{
std::cerr<<"Error: "<<gameFile<<" file not found"<<std::endl;
return 1;
}
std::cout<<"Launching TTRTS!"<<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 = CTTRTSGame::CreateFromString(gameDescriptor);
// Grab the players involved
auto players = game.GetPlayers();
// Current game directory
std::string gameDir = "ttrts_" + 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 << " 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());
system(cmd2);
// Clean out the game directory
char cmd1[128];
snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str());
system(cmd1);
// 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 0;
};

9
source/ttrts/version.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef _TTRTS_VERSION_H_
#define _TTRTS_VERSION_H_
static const int sk_ttrts_version_major = TTRTS_VERSION_MAJOR;
static const int sk_ttrts_version_minor = TTRTS_VERSION_MINOR;
static const int sk_ttrts_version_patch = TTRTS_VERSION_PATCH;
static const char* sk_ttrts_version_string = TTRTS_VERSION_STRING;
#endif //_TTRTS_VERSION_H_