Re-organise source directory and targets

ttrts -> client
game -> ttrts
This commit is contained in:
Marc Di Luzio 2014-12-30 13:24:19 +00:00
parent 415361ac9c
commit d9b9f3d7dd
18 changed files with 58 additions and 47 deletions

View file

@ -0,0 +1,34 @@
# ====================== ttrts =======================
# Project name
project( ttrts-client )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../maths
../ttrts
)
# Add the sources
set( SOURCES
main.cpp
)
# Add the executable
add_executable( ${PROJECT_NAME} ${SOURCES} )
# Set our output name to ttrts
set_target_properties( ${PROJECT_NAME} PROPERTIES OUTPUT_NAME ttrts )
# dependent on main ttrts libary
target_link_libraries( ${PROJECT_NAME} ttrts )
# 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)

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

@ -0,0 +1,102 @@
# ttrts - Tiny Terminal RTS
## 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

235
source/client/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/client/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_