Fix a case where game folder existed in verious forms Fix bug where game would not correctly end at draw state Fix bug where the team order file was looked for in the wrong directory Extract gamestate function and properly handle game ending scenarios Stop hitting yourselves Fixed bug where units would not move forward because they were hitting themselves
546 lines
No EOL
14 KiB
C++
546 lines
No EOL
14 KiB
C++
#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( Team team, 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
|
|
COrder order = GetOrderFromString( sorder );
|
|
orderVector.push_back(order);
|
|
}
|
|
|
|
// Call our add order by vector method
|
|
return IssueOrders(team,orderVector);
|
|
}
|
|
|
|
// Issue orders by vector to the game
|
|
int CTTRTSGame::IssueOrders( Team team, const COrderVector& orders )
|
|
{
|
|
// verify all the orders
|
|
for ( auto order : orders )
|
|
{
|
|
// If any order returns non-zero, back out
|
|
if ( IssueOrder(team,order) )
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Issue a single order
|
|
int CTTRTSGame::IssueOrder( Team team, const COrder& order )
|
|
{
|
|
// Verify the order
|
|
if ( VerifyOrder(team,order) )
|
|
return 1;
|
|
|
|
// Get the right unit for the order
|
|
for ( OrderUnitPair& 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::VerifyPos(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;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Get a units new position
|
|
uvector2 CTTRTSGame::GetNewPosition( const OrderUnitPair& 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 ( OrderUnitPair& 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 = ( VerifyPos(newpos) == 0 );
|
|
|
|
if ( possible )
|
|
{
|
|
// If any unit is in this spot, or moving unit moving to said spot, reject this
|
|
for ( const OrderUnitPair& 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 )
|
|
{
|
|
pair.unit.setPos(newpos);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Turn all units that need turning
|
|
for ( OrderUnitPair& 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 ( OrderUnitPair& pair : m_OrderUnitPairs )
|
|
{
|
|
if ( pair.order.command == command_c::A )
|
|
{
|
|
uvector2 newpos = pair.unit.getInFront();
|
|
// If move would be within the arena
|
|
if ( ( newpos.x <= dimensions.x-1 ) && ( newpos.y <= dimensions.y-1 ) )
|
|
{
|
|
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 ( OrderUnitPair& pair1 : m_OrderUnitPairs )
|
|
if ( pair1.order.command == command_c::A )
|
|
for ( OrderUnitPair& 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 ( OrderUnitPair& pair1 : m_OrderUnitPairs )
|
|
for ( OrderUnitPair& 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 ( OrderUnitPair& pair : m_OrderUnitPairs )
|
|
pair.order = COrder();
|
|
|
|
// 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_t dir1 = one.getDir();
|
|
dir_t dir2 = two.getDir();
|
|
|
|
if( pos1.x == pos2.x ) { // Same col
|
|
if (pos1.y == (pos2.y + 1)) {
|
|
if (dir1 == dir_t::N && dir2 == dir_t::S)
|
|
return true;
|
|
}
|
|
else if (pos1.y == (pos2.y - 1)) {
|
|
if (dir1 == dir_t::S && dir2 == dir_t::N)
|
|
return true;
|
|
}
|
|
}
|
|
else if( pos1.y == pos2.y ) { // Same row
|
|
if( pos1.x == (pos2.x+1) ) {
|
|
if( dir1 == dir_t::E && dir2 == dir_t::W )
|
|
return true;
|
|
}
|
|
else if( pos1.x == (pos2.x-1) ) {
|
|
if( dir1 == dir_t::E && dir2 == dir_t::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 OrderUnitPair& pair: m_OrderUnitPairs )
|
|
{
|
|
if( pair.unit.getPos() == unit.getPos() )
|
|
return 3;
|
|
}
|
|
|
|
// Add the unit with a blank order
|
|
m_OrderUnitPairs.push_back( OrderUnitPair(std::move(unit), COrder()) );
|
|
|
|
|
|
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( Team team, const COrder& order ) const
|
|
{
|
|
int ret = 1;
|
|
|
|
// Grab the unit ID
|
|
const unit_id_t unitID = order.unit;
|
|
|
|
// Attempt to find the unit
|
|
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
|
{
|
|
// Accept if we have the unit
|
|
if ( pair.unit.getID() == unitID
|
|
&& pair.unit.getTeam() == team )
|
|
{
|
|
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 OrderUnitPair& 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 COrder& CTTRTSGame::GetOrderByIDConst( unit_id_t id ) const
|
|
{
|
|
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
|
{
|
|
// Attempt the unit add
|
|
if ( pair.unit.getID() == id )
|
|
return pair.order;
|
|
}
|
|
|
|
// Return an invalid unit
|
|
static COrder invalid_order;
|
|
return invalid_order;
|
|
}
|
|
|
|
// Get unit by unit ID
|
|
CUnit& CTTRTSGame::GetUnitByID( unit_id_t id )
|
|
{
|
|
for ( OrderUnitPair& 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 teams in the current game
|
|
std::vector<Team> CTTRTSGame::GetTeams() const
|
|
{
|
|
std::vector<Team> teams;
|
|
teams.reserve(GetNumUnits());
|
|
|
|
// Grab all teams
|
|
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
|
{
|
|
teams.push_back(pair.unit.getTeam());
|
|
}
|
|
|
|
// Remove dupes
|
|
std::sort( teams.begin(), teams.end() );
|
|
teams.erase( std::unique( teams.begin(), teams.end() ), teams.end() );
|
|
|
|
return teams;
|
|
}
|
|
|
|
// Check if we have a win state
|
|
Team CTTRTSGame::CheckForWin() const
|
|
{
|
|
// Array of units for each Team
|
|
unsigned int units[(int) Team::NUM_INVALID];
|
|
memset(units,0,sizeof(units));
|
|
|
|
// Count up all the units for each Team
|
|
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
|
{
|
|
const int team = (int)pair.unit.getTeam();
|
|
units[team] += 1;
|
|
}
|
|
|
|
// Default winning Team to invalid (no win)
|
|
Team winningTeam = Team::NUM_INVALID;
|
|
|
|
// For each of the teams
|
|
for ( unsigned int i = 0; i < _countof(units); i++ )
|
|
{
|
|
// if there are still units in this Team, and the winning Team hasn't been set
|
|
if( units[i] > 0 && winningTeam == Team::NUM_INVALID )
|
|
{
|
|
winningTeam = (Team)i;
|
|
}
|
|
// Otherwise, if there are units in this Team and the winning Team HAS been set
|
|
else if ( units[i] > 0 )
|
|
{
|
|
// Set back to invalid and break out of the loop
|
|
winningTeam = Team::NUM_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return winningTeam;
|
|
}
|
|
|
|
// Get the game information as a string
|
|
std::string CTTRTSGame::GetStateAsString() const
|
|
{
|
|
// Print out the header
|
|
char header[64];
|
|
snprintf(header, 512, GAME_HEADER_FORMATTER , name.c_str(), dimensions.x, dimensions.y, turn );
|
|
|
|
// Gather unit information
|
|
std::string units;
|
|
for ( const OrderUnitPair& 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;
|
|
|
|
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 buf[64];
|
|
unsigned int turn;
|
|
unsigned int sizex;
|
|
unsigned int sizey;
|
|
sscanf(header.c_str(), GAME_HEADER_FORMATTER, buf, &sizex, &sizey, &turn );
|
|
|
|
CTTRTSGame game(sizex,sizey);
|
|
game.SetName(buf);
|
|
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));
|
|
}
|
|
|
|
return game;
|
|
} |