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

@ -1,30 +1,19 @@
# ====================== ttrts =======================
# Project name
cmake_minimum_required(VERSION 2.8.7)
# Main ttrts library
project( ttrts )
# Include the maths
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
../maths
../game
)
# Add the sources
set( SOURCES
main.cpp
# Add our sources
set( SOURCES
game.cpp
unit.cpp
order.cpp
)
# 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-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)
# Add this library
add_library( ttrts ${SOURCES} )

View file

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

642
source/ttrts/game.cpp Normal file
View file

@ -0,0 +1,642 @@
#include "game.h"
#include <algorithm>
#include <string.h>
CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r )
: dimensions( c,r )
, turn (0), name ( "Custom_Game" )
{
}
// Move constructor
CTTRTSGame::CTTRTSGame(CTTRTSGame&& game)
: m_OrderUnitPairs(std::move(game.m_OrderUnitPairs))
, dimensions(std::move(game.dimensions))
, turn(std::move(game.turn))
, name(std::move(game.name))
{
}
CTTRTSGame& CTTRTSGame::operator=(CTTRTSGame&& game)
{
m_OrderUnitPairs = std::move(game.m_OrderUnitPairs);
dimensions = std::move(game.dimensions);
turn = std::move(game.turn);
name = std::move(game.name);
return *this;
}
// Interpret a string of orders
int CTTRTSGame::IssueOrders( player_t player, const std::string& _orders )
{
COrderVector orderVector;
// Copy the const orders into a buffer we can edit
std::string orders = _orders;
// Find a line end
size_t pos;
while ( (pos = orders.find('\n')) != std::string::npos )
{
// Grab the string up to the line end
const std::string sorder = orders.substr(0, pos);
// Erase all of string up to and including the line end
orders.erase(0,pos+1);
// Create an order from the string and push it back
SOrder order = GetOrderFromString( sorder );
orderVector.push_back(order);
}
// Call our add order by vector method
return IssueOrders(player,orderVector);
}
// Issue orders by vector to the game
int CTTRTSGame::IssueOrders( player_t player, const COrderVector& orders )
{
// verify all the orders
for ( auto order : orders )
{
// If any order returns non-zero, back out
if ( IssueOrder(player,order) )
return 1;
}
return 0;
}
// Issue a single order
int CTTRTSGame::IssueOrder( player_t player, const SOrder & order )
{
// Verify the order
if ( VerifyOrder(player,order) )
return 1;
// Get the right unit for the order
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
{
if (pair.unit.GetID() == order.unit )
{
pair.order = order;
return 0;
}
}
// Unit was not found, return 2
return 2;
}
// Verify a position
int CTTRTSGame::VerifyPosIsValidMovement(uvector2 vec) const
{
// Simply check if within the bounds of our dimensions for now
if ( ( vec.x >= dimensions.x )
|| ( vec.y >= dimensions.y ) )
{
return 1;
}
// Check within our invalid positions
for ( const uvector2& invalid : m_walls)
{
if( vec == invalid )
{
return 2;
}
}
return 0;
}
// Get a units new position
uvector2 CTTRTSGame::GetNewPosition( const SOrderUnitPair & pair ) const
{
// Grab the order
switch ( pair.order.command)
{
// For forward orders, grab in front
case command_c::F:
return pair.unit.GetInFront();
// For all other orders, just grab the old position
default:
return pair.unit.GetPos();
}
}
// Simulate and progress to the next turn
// Returns non-zero if simulation failed
int CTTRTSGame::SimulateToNextTurn()
{
int error = 0;
// Attempt all movement orders
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
{
switch ( pair.order.command)
{
case command_c::F:
{
// Verify new unit position will be on the board
uvector2 newpos = GetNewPosition(pair);
// Verify the position is even available
bool possible = (VerifyPosIsValidMovement(newpos) == 0 );
if ( possible )
{
// If any unit is in this spot, or moving unit moving to said spot, reject this
for ( const SOrderUnitPair & pair2 : m_OrderUnitPairs )
{
// Skip myself
if(pair.unit.GetID() == pair2.unit.GetID() ) continue;
if( GetNewPosition(pair2) == newpos )
{
possible = false;
break;
}
}
}
// If the movement is still possible
if ( possible )
{
// Create a wall at our old position
AddWall(pair.unit.GetPos());
pair.unit.SetPos(newpos);
}
}
break;
default:
break;
}
}
// Turn all units that need turning
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
{
switch ( pair.order.command)
{
case command_c::L:
{
// Simply turn left
pair.unit.TurnLeft();
}
break;
case command_c::R:
{
// Simply turn right
pair.unit.TurnRight();
}
break;
default:
break;
}
}
// Iterate through all charge states
bool charging = true;
while(charging)
{
// Assume no more charging
charging = false;
// Initially move all units
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
{
if ( pair.order.command == command_c::A )
{
uvector2 newpos = pair.unit.GetInFront();
// If move would be within the arena
if (VerifyPosIsValidMovement(newpos) == 0 )
{
pair.unit.SetPos(newpos);
// Unit moved, so more charging needs to be done
charging = true;
}
}
}
std::vector< unit_id_t > toKill; // Vector to store which units to kill
// Initially move all units to check for pass through
for ( SOrderUnitPair & pair1 : m_OrderUnitPairs )
if ( pair1.order.command == command_c::A )
for ( SOrderUnitPair & pair2 : m_OrderUnitPairs )
if (pair1.unit.GetID() != pair2.unit.GetID() // Don't check the same units
&& pair2.order.command == command_c::A )
{
if( CheckForPassThrough(pair1.unit,pair2.unit) )
{
toKill.push_back(pair1.unit.GetID());
toKill.push_back(pair2.unit.GetID());
}
}
// Kill all units to kill
KillAll(toKill);
toKill.clear();
// Check for all matching spots
for ( SOrderUnitPair & pair1 : m_OrderUnitPairs )
for ( SOrderUnitPair & pair2 : m_OrderUnitPairs )
{
if(pair1.unit.GetID() == pair2.unit.GetID() ) continue; // Don't check the same units
if(pair1.unit.GetPos() == pair2.unit.GetPos() )
{
if( pair1.order.command == command_c::A )
{
toKill.push_back(pair2.unit.GetID());
}
if( pair2.order.command == command_c::A )
{
toKill.push_back(pair1.unit.GetID());
}
}
}
// Kill all units to kill
KillAll(toKill);
toKill.clear();
}
// Clear all orders
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
pair.order = SOrder();
// Increment the current turn
turn++;
return error;
}
// Kill all units in list
void CTTRTSGame::KillAll( std::vector< unit_id_t >& vec )
{
// Sort and erase all duplicates
std::sort( vec.begin(), vec.end() );
vec.erase( std::unique( vec.begin(), vec.end() ), vec.end() );
for ( auto id : vec )
{
for ( OrderUnitPairVector::iterator it = m_OrderUnitPairs.begin();
it != m_OrderUnitPairs.end();
it++ )
{
if((*it).unit.GetID() == id )
{
// Remove the unit from our alive unit pairs
m_OrderUnitPairs.erase(it);
break;
}
}
}
}
// Check if two units passed through each other
bool CTTRTSGame::CheckForPassThrough( const CUnit& one, const CUnit& two )
{
uvector2 pos1 = one.GetPos();
uvector2 pos2 = two.GetPos();
dir_c dir1 = one.GetDir();
dir_c dir2 = two.GetDir();
if( pos1.x == pos2.x ) { // Same col
if (pos1.y == (pos2.y + 1)) {
if (dir1 == dir_c::N && dir2 == dir_c::S)
return true;
}
else if (pos1.y == (pos2.y - 1)) {
if (dir1 == dir_c::S && dir2 == dir_c::N)
return true;
}
}
else if( pos1.y == pos2.y ) { // Same row
if( pos1.x == (pos2.x+1) ) {
if( dir1 == dir_c::E && dir2 == dir_c::W )
return true;
}
else if( pos1.x == (pos2.x-1) ) {
if( dir1 == dir_c::E && dir2 == dir_c::W )
return true;
}
}
return false;
}
// Add a unit, nonzero return value indicates error
int CTTRTSGame::AddUnit( CUnit&& unit )
{
// Verify the unit
if( !unit.Valid() )
return 1;
// Verify if the unit can be placed on the current board
const uvector2 pos = unit.GetPos();
if( (pos.x >= dimensions.x) || (pos.y >= dimensions.y) )
return 2;
// If any unit's position matches, reject this
for ( const SOrderUnitPair & pair: m_OrderUnitPairs )
{
if(pair.unit.GetPos() == unit.GetPos() )
return 3;
}
// Add the unit with a blank order
m_OrderUnitPairs.push_back( SOrderUnitPair(std::move(unit), SOrder()) );
return 0;
}
// Add a units, nonzero return value indicates error
int CTTRTSGame::AddUnits( CUnitVector&& units )
{
CUnitVector::iterator it;
for ( it = units.begin(); it != units.end(); it++ )
{
// Attempt the unit add
if ( AddUnit( std::move(*it) ) )
return 1;
}
// All units added successfully
return 0;
}
// Verify any order
int CTTRTSGame::VerifyOrder( player_t player, const SOrder & order ) const
{
int ret = 1;
// Grab the unit ID
const unit_id_t unitID = order.unit;
// Attempt to find the unit
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
// Accept if we have the unit
if (pair.unit.GetID() == unitID
&& pair.unit.GetPlayer() == player)
{
ret = 0;
break;
}
}
// for now, as long as the unit exists we can attempt the order
return ret;
}
// Get unit by unit ID
const CUnit& CTTRTSGame::GetUnitByIDConst( unit_id_t id ) const
{
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
// Attempt the unit add
if (pair.unit.GetID() == id )
return pair.unit;
}
// Return an invalid unit
static CUnit invalid_unit;
return invalid_unit;
}
// Get an order by unit ID
const SOrder & CTTRTSGame::GetOrderByIDConst( unit_id_t id ) const
{
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
// Attempt the unit add
if (pair.unit.GetID() == id )
return pair.order;
}
// Return an invalid unit
static SOrder invalid_order;
return invalid_order;
}
// Get unit by unit ID
CUnit& CTTRTSGame::GetUnitByID( unit_id_t id )
{
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
{
// Attempt the unit add
if (pair.unit.GetID() == id )
return pair.unit;
}
// Return an invalid unit
static CUnit invalid_unit;
return invalid_unit;
}
// Get a vector of the players in the current game
std::vector<player_t> CTTRTSGame::GetPlayers() const
{
std::vector<player_t> players;
players.reserve(GetNumUnits());
// Grab all players
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
players.push_back(pair.unit.GetPlayer());
}
// Remove dupes
std::sort( players.begin(), players.end() );
players.erase( std::unique( players.begin(), players.end() ), players.end() );
return players;
}
// Check if we have a win state
player_t CTTRTSGame::GetWinningPlayer() const
{
// Array of units for each Player
unsigned int units[(int) player_t::NUM_INVALID];
memset(units,0,sizeof(units));
// Count up all the units for each Player
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
{
const int player = (int) pair.unit.GetPlayer();
units[player] += 1;
}
// Default winning Player to invalid (no win)
player_t winningPlayer = player_t::NUM_INVALID;
// For each of the players
for ( unsigned int i = 0; i < _countof(units); i++ )
{
// if there are still units in this Player, and the winning Player hasn't been set
if( units[i] > 0 && winningPlayer == player_t::NUM_INVALID )
{
winningPlayer = (player_t)i;
}
// Otherwise, if there are units in this Player and the winning Player HAS been set
else if ( units[i] > 0 )
{
// Set back to invalid and break out of the loop
winningPlayer = player_t::NUM_INVALID;
break;
}
}
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
{
for( const SOrderUnitPair& pair: m_OrderUnitPairs )
{
uvector2 pos = pair.unit.GetPos();
// Assume if unit is adjacent to any valid tile, then it can move there
if( VerifyPosIsValidMovement(pos + vector2(1, 0) ) == 0
|| VerifyPosIsValidMovement(pos + vector2(0, 1)) == 0
|| VerifyPosIsValidMovement(pos + vector2(-1, 0)) == 0
|| VerifyPosIsValidMovement(pos + vector2(0, -1)) == 0 )
{
return true;
}
}
return false;
}
// Check if the game is over
bool CTTRTSGame::GameOver() const
{
return ( (GetWinningPlayer() != player_t::NUM_INVALID ) // We have a winning player
|| GetNumUnits() == 0
|| !UnitsCanMove() ); // OR we have no units
}

111
source/ttrts/game.h Normal file
View file

@ -0,0 +1,111 @@
#ifndef _GAME_H_
#define _GAME_H_
#include "unit.h"
#include "gametypes.h"
#include "order.h"
#include "orderunitpair.h"
// Full TTRTS Game class
// Stores information about the game
// Can convert from a string or to a string
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);
// move asignment operator
CTTRTSGame& operator=(CTTRTSGame&& game);
// Simulate and progress to the next turn
// Returns non-zero if simulation failed
int SimulateToNextTurn();
// Check for winning player, returns invalid for no win state reached
// Note: this function will return invalid if a draw was reached
// do not rely on this to test for end state
player_t GetWinningPlayer() const;
// Check if the game is over
bool GameOver() const;
// Check if any of the units can move
bool UnitsCanMove() const;
// Get the game information as a string
std::string GetStateAsString() const;
// Issue orders to the game, returns non-zero if orders are incorrect
int IssueOrders( player_t player, const std::string& orders );
int IssueOrders( player_t player, const COrderVector& orders );
int IssueOrder( player_t player, const SOrder & order );
// Add a units to the game, nonzero return value indicates error
int AddUnit( CUnit&& unit );
int AddUnits( CUnitVector&& units );
// Get the number of units
inline unsigned int GetNumUnits() const { return m_OrderUnitPairs.size(); }
// Get unit and orderby index as above (not unit ID)
inline const CUnit& GetUnitByIndex( unsigned int i ) const { return m_OrderUnitPairs[i].unit; }
inline const SOrder & GetOrdersByIndex( unsigned int i ) const { return m_OrderUnitPairs[i].order; }
// Get a unit by it's ID
const CUnit& GetUnitByIDConst( unit_id_t id ) const;
const SOrder & GetOrderByIDConst( unit_id_t id ) const;
// Get dimensions
inline const uvector2& GetDimensions() const { return dimensions; }
// Set the game name
// NOTE: Names with spaces not allowed
inline std::string SetName( const std::string& in ) { return (name = in); }
inline std::string GetName() const { return name; }
// Set the turn of the game
inline int SetTurn( int in ) { return (turn = in); }
inline int GetTurn() const { return turn; }
// Get a vector of the players in the current game
std::vector<player_t> GetPlayers() const;
// Get the vector of wall positions
inline std::vector<uvector2> GetWalls() const { return m_walls; }
// Add an invalid position
inline void AddWall(uvector2 vec) { m_walls.push_back(vec); }
private:
// Check for a pass through
static bool CheckForPassThrough( const CUnit& one, const CUnit& two );
// Verify any order or position - non-zero is error
int VerifyOrder( player_t player, const SOrder & order ) const;
int VerifyPosIsValidMovement(uvector2 vec) const;
// Get a units new position after an order
uvector2 GetNewPosition( const SOrderUnitPair & pair ) const;
// Kill all units in list
void KillAll( std::vector< unit_id_t >& vec );
// Get unit by unit ID
CUnit& GetUnitByID( unit_id_t id );
std::string name; // Game Name
unsigned int turn; // Int to store the current turn
uvector2 dimensions; // Dimensions of the game
OrderUnitPairVector m_OrderUnitPairs; // Vector to store all units and orders
std::vector<uvector2> m_walls; // Vector of wall positions
};
#endif //_GAME_H_

30
source/ttrts/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_

View file

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

33
source/ttrts/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;
}

54
source/ttrts/order.h Normal file
View file

@ -0,0 +1,54 @@
#ifndef _ORDERS_H_
#define _ORDERS_H_
#include <vector>
#include <string>
#include "gametypes.h"
#define ORDER_FORMATTER "ORDER:%c id:%u"
// Type for all orders ( as a char )
enum class command_c : char
{
F = 'F', // Move forward one square
L = 'L', // Turn left
R = 'R', // Turn right
A = 'A', // Attack forwards until a unit or edge of the arena is hit
NUM_INVALID
};
// Container for an order
struct SOrder
{
// Base constructor makes invalid order
SOrder()
: unit ( unit_id_invalid )
, command( command_c::NUM_INVALID )
{}
// Unit order is for
unit_id_t unit;
// Order command issued
command_c command;
// Basic operators
inline bool operator==( const SOrder & rhs ) const;
inline bool operator!=( const SOrder & rhs ) const { return !(*this==rhs); }
};
// Simple == operator
inline bool SOrder::operator== ( const SOrder & rhs ) const
{
return ( unit == rhs.unit ) && ( command == rhs.command);
}
// 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

@ -0,0 +1,39 @@
#ifndef _TTRTS_ORDERUNITPAIR_H_
#define _TTRTS_ORDERUNITPAIR_H_
#include "order.h"
#include "unit.h"
#include <vector>
// Type for order and unit pairs
struct SOrderUnitPair
{
// Straight up move constructor
SOrderUnitPair( SOrderUnitPair && other )
: unit ( std::move(other.unit) )
, order ( other.order )
{}
// Multi parameter constructor
SOrderUnitPair( CUnit&& u, SOrder o )
: unit ( std::move(u) )
, order ( o )
{}
// Move assignment operator
inline SOrderUnitPair & operator=( SOrderUnitPair && rhs )
{
this->unit = std::move(rhs.unit);
this->order = std::move(rhs.order);
return *this;
}
CUnit unit; // The unit
SOrder order; // Order for this unit from this turn
};
// Typedef for a vector of these unit pairs
typedef std::vector<SOrderUnitPair> OrderUnitPairVector;
#endif // _TTRTS_ORDERUNITPAIR_H_

263
source/ttrts/unit.cpp Normal file
View file

@ -0,0 +1,263 @@
#include <string.h>
#include "unit.h"
#include <map> // for std::map
namespace
{
// Helper function for generating unique unit ids during static init
unit_id_t get_unique_unit_id(unit_id_t* set = nullptr)
{
static unit_id_t p = 0;
// If we have a set value, then set our int
if( set )
p = *set;
return p++;
}
// Map of visual representation of unit V
typedef std::map<dir_c, unitvis_c> dir_to_vis_map;
// Helper function to get the vis map during static init
const dir_to_vis_map& get_vis_map_V()
{
static const dir_to_vis_map sk_visMap =
{
{dir_c::N,'^'},
{dir_c::E,'>'},
{dir_c::S,'v'},
{dir_c::W,'<'},
};
return sk_visMap;
}
}
// force a reset of the unit ID value
void __forceResetCUnitID()
{
unit_id_t i = 0;
get_unique_unit_id(&i);
}
// Get a unit from a visual
CUnit CUnit::GetUnitFromVis( unitvis_c vis )
{
CUnit unit;
unit.SetFromVisual(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() )
, player_id ( player_t::NUM_INVALID )
, unit_vis (unitvis_invalid)
, dir ( dir_c::S )
, pos ( { ucoord_invalid, ucoord_invalid } )
{
UpdateMyVisual();
}
// Move constructor
CUnit::CUnit(CUnit&& unit)
: unit_id ( std::move(unit.unit_id) )
, player_id ( std::move(unit.player_id) )
, unit_vis ( std::move(unit.unit_vis) )
, dir ( std::move(unit.dir) )
, pos ( std::move(unit.pos) )
{
UpdateMyVisual();
}
// Move asignment operator
CUnit& CUnit::operator=(CUnit&& unit)
{
unit_id = std::move(unit.unit_id) ;
player_id = std::move(unit.player_id) ;
unit_vis = std::move(unit.unit_vis) ;
dir = std::move(unit.dir) ;
pos = std::move(unit.pos) ;
return *this;
}
// Equals operator
bool CUnit::operator==(const CUnit& rhs)
{
return (unit_id == rhs.unit_id)
&& (player_id == rhs.player_id)
&& (unit_vis == rhs.unit_vis)
&& (dir == rhs.dir)
&& (pos == rhs.pos);
}
// Update the visual representation of the unit
unitvis_c CUnit::UpdateMyVisual()
{
// Start at invalid
SetVisual(unitvis_invalid);
dir_to_vis_map::const_iterator it = get_vis_map_V().find(dir);
// If found set to new vis
if( it != get_vis_map_V().end() )
SetVisual(it->second);
return GetVisual();
}
// Set the unit from visual
bool CUnit::SetFromVisual(const unitvis_c &vis)
{
dir_to_vis_map::const_iterator it;
for( it = get_vis_map_V().begin(); it != get_vis_map_V().end(); it++ )
{
if( it->second == vis )
{
dir = it->first;
UpdateMyVisual();
return true;
}
}
// No matching direction to visual
return false;
}
// Turn unit left
dir_c CUnit::TurnLeft()
{
switch( dir )
{
case dir_c::N:
dir = dir_c::W;
break;
case dir_c::E:
dir = dir_c::N;
break;
case dir_c::S:
dir = dir_c::E;
break;
case dir_c::W:
dir = dir_c::S;
break;
}
UpdateMyVisual();
return GetDir();
}
// Turn unit right
dir_c CUnit::TurnRight()
{
switch( dir )
{
case dir_c::N:
dir = dir_c::E;
break;
case dir_c::E:
dir = dir_c::S;
break;
case dir_c::S:
dir = dir_c::W;
break;
case dir_c::W:
dir = dir_c::N;
break;
}
UpdateMyVisual();
return GetDir();
}
// Turn unit around
dir_c CUnit::TurnAround()
{
switch( dir )
{
case dir_c::N:
dir = dir_c::S;
break;
case dir_c::E:
dir = dir_c::W;
break;
case dir_c::S:
dir = dir_c::N;
break;
case dir_c::W:
dir = dir_c::E;
break;
}
UpdateMyVisual();
return GetDir();
}
// Get the co-ordinate infront of the unit
uvector2 CUnit::GetInFront() const
{
vector2 delta = vecFromDir(dir);
return pos + delta;
}

95
source/ttrts/unit.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef _UNIT_H_
#define _UNIT_H_
#include <string>
#include <vector>
#include "gametypes.h"
#include "vector2.h"
// force a reset of the unit ID value
void __forceResetCUnitID();
// Base unit type
class CUnit
{
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();
// Move constructor and move assignment. CUnit cannot be copied
CUnit(CUnit&& unit);
CUnit& operator=(CUnit&& unit);
bool operator==(const CUnit& rhs);
bool operator!=(const CUnit& rhs) { return !(*this == rhs); }
// Getters for all the members
inline const unit_id_t& GetID() const { return unit_id; }
inline const player_t & GetPlayer() const { return player_id; }
inline const unitvis_c & GetVisual() const { return unit_vis; }
inline const dir_c & GetDir() const { return dir; }
inline const uvector2& GetPos() const { return pos; }
// Set
inline player_t SetPlayer(const player_t &v) { return (player_id = v); }
inline unitvis_c SetVisual(const unitvis_c &v) { return (unit_vis = v); }
inline dir_c SetDir(const dir_c &v) { return (dir = v); }
inline void SetPos(const uvector2 &v) { pos = v; }
// Get the co-ordinate in front of the unit
uvector2 GetInFront() const;
// Check unit is valid
inline bool Valid() const;
// Set a unit based solely on it's visual
bool SetFromVisual(const unitvis_c &vis);
// Orientation methods
dir_c TurnLeft();
dir_c TurnRight();
dir_c TurnAround();
private:
// Update my visual must be called when setting direction
unitvis_c UpdateMyVisual();
// Unit ID
unit_id_t unit_id;
// Visual
unitvis_c unit_vis;
// Player ID
player_t player_id;
// Direction
dir_c dir;
// Position
uvector2 pos;
};
// Typedef for a vector of units
typedef std::vector< CUnit > CUnitVector;
typedef std::vector< unit_id_t > CUnitIDVector;
// Simple validation
inline bool CUnit::Valid() const
{
return (unit_id != unit_id_invalid )
&& (player_id != player_t::NUM_INVALID )
&& (unit_vis != unitvis_invalid);
}
#endif //_UNIT_H_

View file

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