Re-organise source directory and targets
ttrts -> client game -> ttrts
This commit is contained in:
parent
415361ac9c
commit
d9b9f3d7dd
18 changed files with 58 additions and 47 deletions
|
@ -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} )
|
|
@ -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
642
source/ttrts/game.cpp
Normal 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
111
source/ttrts/game.h
Normal 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
30
source/ttrts/gametypes.h
Normal 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_
|
|
@ -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
33
source/ttrts/order.cpp
Normal 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
54
source/ttrts/order.h
Normal 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_
|
39
source/ttrts/orderunitpair.h
Normal file
39
source/ttrts/orderunitpair.h
Normal 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
263
source/ttrts/unit.cpp
Normal 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
95
source/ttrts/unit.h
Normal 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_
|
|
@ -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_
|
Loading…
Add table
Add a link
Reference in a new issue