Compare commits
100 commits
Author | SHA1 | Date | |
---|---|---|---|
|
ae8a3bf0c2 | ||
|
f13a8bd5c4 | ||
|
5e66596d30 | ||
|
3bb5766061 | ||
|
96153f8c60 | ||
|
96f5c08c14 | ||
|
d65ef46634 | ||
|
06057e6e52 | ||
|
1e38a5f107 | ||
|
0ead12c7dd | ||
|
1b2010faba | ||
|
30af43ae95 | ||
|
7811daa78b | ||
|
c51119c3e2 | ||
25e0fa04de | |||
3291bf126e | |||
|
a0ff8680a3 | ||
|
a7421fa07b | ||
|
1c97177956 | ||
|
3ed25cd37f | ||
|
b141314434 | ||
|
5c8666d4fb | ||
|
43d688a728 | ||
|
4055d85d99 | ||
|
1785ce2fc0 | ||
|
61c012370d | ||
|
b532f1c9c8 | ||
|
18cfcff264 | ||
|
b34b933dcd | ||
|
b43927a1da | ||
|
770502184c | ||
|
bccd043d2c | ||
|
a81b4ff8d0 | ||
|
e01a718ac6 | ||
|
56e767bb5b | ||
|
4285770d52 | ||
|
dcb9d68fb4 | ||
|
83b9990bcc | ||
|
b4240cf1c8 | ||
|
0619031cb6 | ||
|
2281bcb6cd | ||
|
8835bfb82a | ||
|
0962546a82 | ||
|
3b5599ffad | ||
|
ca5526b2b7 | ||
|
05c8318099 | ||
|
082810de4a | ||
|
189241853b | ||
|
faa7cdc501 | ||
3f892ec2aa | |||
3f76b6430b | |||
|
9c68372f64 | ||
|
66f5226797 | ||
|
cb46c84de5 | ||
|
ad803a5d7a | ||
|
2273c93f11 | ||
|
53e882ae12 | ||
|
31644b1f95 | ||
|
f1e4f432e6 | ||
|
2c0a393f77 | ||
|
71aab498dc | ||
|
3342300324 | ||
|
d9b9f3d7dd | ||
|
415361ac9c | ||
|
481153606b | ||
|
97ef46db16 | ||
|
a80c415932 | ||
|
485a05293c | ||
|
128dc14bb6 | ||
|
c0d5044f81 | ||
|
b50c838f28 | ||
|
286cc81f3c | ||
|
fc62785768 | ||
|
d172922698 | ||
|
c98ac95759 | ||
|
5737ae31be | ||
|
d54ccf3d2f | ||
|
78bc326b8d | ||
|
f9ee646c93 | ||
|
ac9ed13be8 | ||
|
5d07bb03f3 | ||
|
024253bb84 | ||
|
e90ddee67e | ||
|
263f6187d3 | ||
|
83020d9120 | ||
|
9e44d5144b | ||
|
d4f9470704 | ||
|
4aa51a64b6 | ||
|
f55bc5413a | ||
|
c38fb3dd41 | ||
|
f63dc8462f | ||
|
755fe01bf5 | ||
|
ca1705ad4a | ||
|
9551560bc0 | ||
|
24c8b6a5b8 | ||
|
321690faaa | ||
|
f68d16b8b9 | ||
|
19623c6711 | ||
|
b0af969b29 | ||
|
469e3ffa8a |
53 changed files with 2778 additions and 1234 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,4 +9,5 @@
|
||||||
*.user
|
*.user
|
||||||
*.sublime*
|
*.sublime*
|
||||||
*.idea
|
*.idea
|
||||||
|
*~
|
||||||
|
|
||||||
|
|
54
CMakeLists.txt
Normal file
54
CMakeLists.txt
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
cmake_minimum_required( VERSION 2.8.7 )
|
||||||
|
|
||||||
|
# Set version information
|
||||||
|
set( TTRTS_VERSION_MAJOR 0 )
|
||||||
|
set( TTRTS_VERSION_MINOR 4 )
|
||||||
|
set( TTRTS_VERSION_PATCH 0 )
|
||||||
|
|
||||||
|
# Set defaults for ttrts variables
|
||||||
|
set( TTRTS_MAPS "/usr/local/share/ttrts/maps/" )
|
||||||
|
set( TTRTS_GAMES "/tmp/" )
|
||||||
|
set( TTRTS_PORT 11715 )
|
||||||
|
|
||||||
|
# define these defaults in code
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_MAPS=${TTRTS_MAPS}" )
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_GAMES=${TTRTS_GAMES}" )
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTTRTS_PORT=${TTRTS_PORT}" )
|
||||||
|
|
||||||
|
# Use c++1y (14)
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" )
|
||||||
|
|
||||||
|
# Turn on all warnings
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder" )
|
||||||
|
|
||||||
|
# Turn off reorder warnings as they're kind of irrelevant
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" )
|
||||||
|
|
||||||
|
# This shouldn't be needed, but it looks like IDE's like clion can forget to set -g for Debug
|
||||||
|
if( CMAKE_BUILD_TYPE MATCHES "Debug" )
|
||||||
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g" )
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Add bash completion to install
|
||||||
|
install( FILES scripts/ttrts_complete DESTINATION /etc/bash_completion.d/ )
|
||||||
|
|
||||||
|
# Add target to generate man page
|
||||||
|
# Run the gen_usage script to generate our usage header
|
||||||
|
add_custom_target(
|
||||||
|
ttrts-gen-manpage ALL
|
||||||
|
${CMAKE_SOURCE_DIR}/scripts/gen_manpage.sh "${TTRTS_VERSION_MAJOR}" "${TTRTS_VERSION_MINOR}" "${TTRTS_VERSION_PATCH}" "ttrts.6" "${CMAKE_SOURCE_DIR}/source/README.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install the ttrts man page
|
||||||
|
if( ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" )
|
||||||
|
set ( MANPAGE_LOC share/man/man6 )
|
||||||
|
elseif( ${CMAKE_SYSTEM_NAME} MATCHES "Linux" )
|
||||||
|
set ( MANPAGE_LOC man/man6 )
|
||||||
|
else()
|
||||||
|
message(ERROR "Unsupported system detected")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install( FILES "${CMAKE_BINARY_DIR}/ttrts.6" DESTINATION ${MANPAGE_LOC} )
|
||||||
|
|
||||||
|
# Subprojects
|
||||||
|
add_subdirectory( source )
|
70
README.md
70
README.md
|
@ -1,19 +1,18 @@
|
||||||
# TTRTS v0.1.0
|
# TTRTS
|
||||||
|
|
||||||
*The Tiny Terminal RTS where the players write their AIs*
|
*The Tiny Terminal RTS where the players write their AIs*
|
||||||
|
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
## Introduction
|
## Introduction
|
||||||
A simple terminal based RTS game that uses text files to communicate game state and unit commands.
|
A simple terminal based RTS game that uses text files to communicate game state and unit commands.
|
||||||
|
|
||||||
TTRTS was is from the ground up designed to be a fun way to practice programming. Any programming language than can handle file I/O can be used to make an AI for TTRTS, and this extensibility allows for any type of programmer to have fun and enjoy designing and playing against their friends.
|
TTRTS is from the ground up designed to be a fun way to practice programming. Any programming language than can handle file I/O can be used to make an AI for TTRTS, and this extensibility allows for any type of programmer to enjoy designing and playing against their friends.
|
||||||
|
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
## Building TTRTS
|
## Building TTRTS
|
||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
* cmake - our build system uses cmake
|
* CMake - our build system uses cmake
|
||||||
* Linux/OSX - currently no support for Windows
|
* Linux/OSX - currently no support for Windows, tracked with [Issue #9](https://github.com/mdiluz/ttrts/issues/9)
|
||||||
|
|
||||||
#### To Build
|
#### To Build
|
||||||
$ git clone https://github.com/mdiluz/ttrts.git
|
$ git clone https://github.com/mdiluz/ttrts.git
|
||||||
|
@ -21,7 +20,66 @@ TTRTS was is from the ground up designed to be a fun way to practice programming
|
||||||
$ ./bootstrap.sh
|
$ ./bootstrap.sh
|
||||||
$ ./ttrts # To launch binary and display usage
|
$ ./ttrts # To launch binary and display usage
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
## Development
|
||||||
|
|
||||||
|
* master branch always stores latest stable release
|
||||||
|
* master/{hotfix} branches store in progress hotfixes for the stable branch
|
||||||
|
* dev branch stores in progress development
|
||||||
|
* dev/{feature} branches store features
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
#### v0.4.0
|
||||||
|
* Updated with network functionality
|
||||||
|
* Game can now be hosted with ttrts --server option
|
||||||
|
* Server can be connected to with ttrts --client
|
||||||
|
* Updated command line interface with new launcher script
|
||||||
|
* map file must now be specified with --map=FILE
|
||||||
|
* Slight refactor of libraries to account for new run targets
|
||||||
|
|
||||||
|
#### v0.3.2
|
||||||
|
* Fix bug when loading map files with walls
|
||||||
|
* Fix ttrts on OSX
|
||||||
|
* Install man files to correct location
|
||||||
|
* Update usage of sed to be compatible with BSD as well as GNU versions
|
||||||
|
* New maps and renames of old ones
|
||||||
|
|
||||||
|
#### v0.3.1
|
||||||
|
* Upgraded install target to repository
|
||||||
|
* libttrts static library binary in /usr/local/lib
|
||||||
|
* ttrts headers in /usr/local/include/ttrts
|
||||||
|
* bash completion into /etc/bash_completion.d/
|
||||||
|
* man page into /usr/local/man/man6
|
||||||
|
* maps into /usr/share/ttrts/maps
|
||||||
|
* client now supports env variable configuration
|
||||||
|
* TTRTS_MAPS for location of map files, defaults to /usr/share/ttrts/maps
|
||||||
|
* TTRTS_GAMES for gameplay directories, defaults to /tmp/
|
||||||
|
* Map files now have proper header
|
||||||
|
* NOTE: This version is compatible with v0.3.0, but old generated mapfiles will need the additional header line added
|
||||||
|
|
||||||
|
#### v0.3.0
|
||||||
|
* Additional functionality of walls
|
||||||
|
* Walls are noted in gamestate file on new "WALL:[X,Y]..." line
|
||||||
|
* Walls are impassable by all movement
|
||||||
|
* Units leave an impassable wall behind after movement
|
||||||
|
* Game can now end if no units are able to move
|
||||||
|
* Various C++ api simplifications
|
||||||
|
* Integration of perl api from [ttrts-players](https://github.com/mdiluz/ttrts-players)
|
||||||
|
|
||||||
|
#### v0.2.0
|
||||||
|
* All team references changed to player
|
||||||
|
* Order file format changed to Player_#_Turn_#.txt
|
||||||
|
* Unit descriptors now use pl: instead of tm:
|
||||||
|
* Various other C++ api corrections and refactors
|
||||||
|
|
||||||
|
#### v0.1.0
|
||||||
|
* First playable version of ttrts
|
||||||
|
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
## Further Information
|
## Further Information
|
||||||
|
|
||||||
See [the ttrts binary readme](source/ttrts/README.md) for full usage and game rules
|
See the ttrts binary [readme](source/ttrts/README.md) for full usage and game rules
|
||||||
|
|
||||||
|
See [ttrts-players](https://github.com/mdiluz/ttrts-players) for examples of AIs
|
||||||
|
|
306
api/perl/ttrts.pm
Normal file
306
api/perl/ttrts.pm
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
#! /usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
our $ttrts_perlai_versioncompat_major = 0;
|
||||||
|
our $ttrts_perlai_versioncompat_minor = 3;
|
||||||
|
|
||||||
|
our $headerDelimiter="~~~~";
|
||||||
|
|
||||||
|
our $VERBOSE = $ENV{"VERBOSE"};
|
||||||
|
|
||||||
|
# Format of the a gamestate header
|
||||||
|
our $headerFormatter = qr/==== ttrts v(\d+)\.(\d+)\.(\d+)+ ====
|
||||||
|
NAME:(.+)
|
||||||
|
SIZE:\[(\d+),(\d+)\]
|
||||||
|
TURN:(\d+)
|
||||||
|
(WALL:.*?)
|
||||||
|
$headerDelimiter/;
|
||||||
|
|
||||||
|
# Formatter for coords
|
||||||
|
our $coordFormatter = qr/\[\d+,\d+\]/;
|
||||||
|
|
||||||
|
# Format of a unit descriptor
|
||||||
|
our $unitFormatterNonCapture = qr/UNIT:\d+ pl:\d+ vs:[^ ]+ dr:[^ ]+ ps:\[\d+,\d+\]\n?/;
|
||||||
|
|
||||||
|
# Format of a unit descriptor
|
||||||
|
our $unitFormatter = qr/UNIT:(\d+) pl:(\d+) vs:([^ ]+) dr:([^ ]+) ps:\[(\d+),(\d+)\]\n?/;
|
||||||
|
|
||||||
|
# Get x and y
|
||||||
|
sub getPositionsXandYString
|
||||||
|
{
|
||||||
|
return (shift =~ /\[(\d+),(\d+)\]/);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all positions
|
||||||
|
sub getPositionStringsFromLine
|
||||||
|
{
|
||||||
|
return (shift =~ /$coordFormatter/gm );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get information about a unit from it's descriptor
|
||||||
|
sub getUnitInfo
|
||||||
|
{
|
||||||
|
return (shift =~ /$unitFormatter/);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get set of units from a string
|
||||||
|
sub GetUnitStringsFromGamestate
|
||||||
|
{
|
||||||
|
my $gamestate = shift;
|
||||||
|
|
||||||
|
my @units = ( $gamestate =~ /$unitFormatterNonCapture/gm );
|
||||||
|
|
||||||
|
foreach my $unit (@units)
|
||||||
|
{
|
||||||
|
chomp($unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return @units;
|
||||||
|
}
|
||||||
|
|
||||||
|
# in the format $major,$minor,$patch,$name,$sizex,$sizey,$turn,$invalidpositions+
|
||||||
|
sub GetGameInfoFromGamestate
|
||||||
|
{
|
||||||
|
my $header = shift;
|
||||||
|
(! defined $header) and die "GetGameInfoFromGamestate was not passed valid header parameter";
|
||||||
|
|
||||||
|
my @info = ($header =~ /$headerFormatter/ );
|
||||||
|
|
||||||
|
return @info;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the units from a turn file
|
||||||
|
sub GetUnitStringsFromFile
|
||||||
|
{
|
||||||
|
my $turnFile = shift or die "GetUnitStringsFromFile needs file parameter";
|
||||||
|
|
||||||
|
# Read in the whole file method from http://www.perlmonks.org/?node_id=1952
|
||||||
|
my $text;
|
||||||
|
{
|
||||||
|
local $/=undef;
|
||||||
|
open FILE, $turnFile or die "Couldn't open file: $!";
|
||||||
|
$text = <FILE>;
|
||||||
|
close FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUnitStringsFromGamestate($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check version numbers against ttrts.pm version
|
||||||
|
sub verifyVersion
|
||||||
|
{
|
||||||
|
my $version_major = shift;
|
||||||
|
(! defined $version_major) and die "verifyVersion needs version_major parameter";
|
||||||
|
my $version_minor = shift;
|
||||||
|
(! defined $version_minor) and die "verifyVersion needs version_minor parameter";
|
||||||
|
if( ($version_major != $ttrts_perlai_versioncompat_major)
|
||||||
|
or ($version_minor != $ttrts_perlai_versioncompat_minor) )
|
||||||
|
{
|
||||||
|
printf "ttrts.pm version does not match with this ttrts version\n";
|
||||||
|
die "ttrts.pm = v$ttrts_perlai_versioncompat_minor.$ttrts_perlai_versioncompat_major ttrts = v$version_major.$version_minor";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Get information from the header for this turn
|
||||||
|
sub GetGameInfoFromFile
|
||||||
|
{
|
||||||
|
my $turnFile = shift or die "GetGameInfoFromFile needs turnFile parameter";
|
||||||
|
|
||||||
|
# Read in the whole file method from http://www.perlmonks.org/?node_id=1952
|
||||||
|
my $text;
|
||||||
|
{
|
||||||
|
local $/=undef;
|
||||||
|
open FILE, $turnFile or die "Couldn't open file: $!";
|
||||||
|
$text = <FILE>;
|
||||||
|
close FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @info = GetGameInfoFromGamestate($text);
|
||||||
|
verifyVersion @info;
|
||||||
|
|
||||||
|
return @info;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get units from a specific player
|
||||||
|
sub GetPlayerUnits
|
||||||
|
{
|
||||||
|
my $thePlayer = shift;
|
||||||
|
(! defined $thePlayer) and die "GetPlayerUnits needs player parameter";
|
||||||
|
my @allUnits = @_;
|
||||||
|
(! @allUnits) and die "GetPlayerUnits needs units parameters";
|
||||||
|
my @myUnits;
|
||||||
|
|
||||||
|
for my $unit (@allUnits)
|
||||||
|
{
|
||||||
|
my ($unitplayer) = $unit =~ /pl:(\d+)/;
|
||||||
|
if ( $unitplayer == $thePlayer )
|
||||||
|
{
|
||||||
|
push(@myUnits,$unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @myUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub GetTurnFileName
|
||||||
|
{
|
||||||
|
my $turn = shift;
|
||||||
|
(! defined $turn) and die "GetTurnFileName needs turn parameter";
|
||||||
|
my $turnFile = "Turn_TURN.txt";
|
||||||
|
$turnFile =~ s/TURN/$turn/;
|
||||||
|
return $turnFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub GetCommandFileName
|
||||||
|
{
|
||||||
|
my $turn = shift;
|
||||||
|
(! defined $turn) and die "GetCommandFileName needs turn parameter";
|
||||||
|
my $player = shift;
|
||||||
|
(! defined $player) and die "GetCommandFileName needs player parameter";
|
||||||
|
my $cmdFileName = "Player_PLAYER_Turn_TURN.txt";
|
||||||
|
$cmdFileName =~ s/TURN/$turn/;
|
||||||
|
$cmdFileName =~ s/PLAYER/$player/;
|
||||||
|
return $cmdFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output the commands file
|
||||||
|
sub OutputCommandsFile
|
||||||
|
{
|
||||||
|
my $turn = shift;
|
||||||
|
(! defined $turn) and die "OutputCommandsFile needs turn parameter";
|
||||||
|
my $player = shift;
|
||||||
|
(! defined $player) and die "OutputCommandsFile needs player parameter";
|
||||||
|
my $commands = shift or die "OutputCommandsFile needs commands parameter";
|
||||||
|
|
||||||
|
# Get output file
|
||||||
|
our $cmdFileName = GetCommandFileName($turn,$player);
|
||||||
|
|
||||||
|
if (! -e $cmdFileName)
|
||||||
|
{
|
||||||
|
open(my $cmdFile, '>', $cmdFileName) or die "Couldn't open '$cmdFileName' $!";
|
||||||
|
print $cmdFile $commands;
|
||||||
|
print $cmdFile "END";
|
||||||
|
close $cmdFile;
|
||||||
|
|
||||||
|
$VERBOSE and printf "Outputted $cmdFileName\n";
|
||||||
|
printf "$commands";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
# Read in the whole file method from http://www.perlmonks.org/?node_id=1952
|
||||||
|
my $text;
|
||||||
|
{
|
||||||
|
local $/=undef;
|
||||||
|
open FILE, $cmdFileName or die "Couldn't open file: $!";
|
||||||
|
$text = <FILE>;
|
||||||
|
close FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$text =~ s/\nEND//;
|
||||||
|
|
||||||
|
printf "Replaying $cmdFileName\n";
|
||||||
|
printf "$text\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a game map
|
||||||
|
sub PrintGameFromGamestateString
|
||||||
|
{
|
||||||
|
my $gamestateString = shift or die "PrintGameFromGamestateString needs string parameter";
|
||||||
|
|
||||||
|
my @info = GetGameInfoFromGamestate($gamestateString);
|
||||||
|
my @units = GetUnitStringsFromGamestate($gamestateString);
|
||||||
|
|
||||||
|
# $major,$minor,$patch,$name,$sizex,$sizey,$turn,$invalidpositions+
|
||||||
|
my $gameX = $info[4];
|
||||||
|
my $gameY = $info[5];
|
||||||
|
|
||||||
|
# Shift into info to where the invalid positions are stored
|
||||||
|
my @invalids = getPositionStringsFromLine($info[7]);
|
||||||
|
|
||||||
|
my @map;
|
||||||
|
|
||||||
|
# Fill with blanks
|
||||||
|
for my $x (0 .. $gameX-1)
|
||||||
|
{
|
||||||
|
for my $y (0 .. $gameY-1)
|
||||||
|
{
|
||||||
|
$map[$x][$y] = "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill in all invalid coordinates
|
||||||
|
foreach my $coord (@invalids)
|
||||||
|
{
|
||||||
|
my @invalidPos = getPositionsXandYString($coord);
|
||||||
|
$map[$invalidPos[0]][$invalidPos[1]] = "~";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill with walls
|
||||||
|
foreach my $wall ( $info[8] =~ /\[(\d+,\d+)\]/g )
|
||||||
|
{
|
||||||
|
$wall =~ /(\d+),(\d+)/;
|
||||||
|
$map[$1][$2] = "|";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill with units
|
||||||
|
for my $unit (@units)
|
||||||
|
{
|
||||||
|
my ($id,$pl,$vs,$dr,$psx,$psy) = getUnitInfo($unit);
|
||||||
|
|
||||||
|
$pl += 31;
|
||||||
|
$vs = "\e[".$pl."m".$vs."\e[0m";
|
||||||
|
|
||||||
|
$map[$psx][$psy] = $vs;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print whole map bottom left is 0,0
|
||||||
|
for my $y ( reverse 0 .. $gameY-1 )
|
||||||
|
{
|
||||||
|
for my $x (0 .. $gameX-1)
|
||||||
|
{
|
||||||
|
printf($map[$x][$y]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a game map
|
||||||
|
sub PrintGameFromFile
|
||||||
|
{
|
||||||
|
my $turnFile = shift or die "PrintGameFromFile needs file parameter";
|
||||||
|
|
||||||
|
# Read in the whole file method from http://www.perlmonks.org/?node_id=1952
|
||||||
|
my $text;
|
||||||
|
{
|
||||||
|
local $/=undef;
|
||||||
|
open FILE, $turnFile or die "Couldn't open file: $!";
|
||||||
|
$text = <FILE>;
|
||||||
|
close FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintGameFromGamestateString($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a turn
|
||||||
|
sub PrintGameMapForTurn
|
||||||
|
{
|
||||||
|
my $turn = shift;
|
||||||
|
(! defined $turn) and die "PrintGameMapForTurn needs turn parameter";
|
||||||
|
$turn = GetTurnFileName($turn);
|
||||||
|
PrintGameFromFile( $turn );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for a file to exist
|
||||||
|
sub WaitForFile
|
||||||
|
{
|
||||||
|
my $file = shift or die "WaitForFile needs file parameter";
|
||||||
|
while( ! -e $file )
|
||||||
|
{
|
||||||
|
select(undef, undef, undef, 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
38
bootstrap.sh
38
bootstrap.sh
|
@ -1,38 +1,34 @@
|
||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Double check for cmakelist
|
||||||
|
if [ ! -e "CMakeLists.txt" ]; then
|
||||||
|
echo "TTRTS: No source cmakelist found"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run cmake
|
||||||
echo "TTRTS: Running cmake"
|
echo "TTRTS: Running cmake"
|
||||||
test ! -e build && mkdir build
|
test ! -e build && mkdir build
|
||||||
cd build/
|
cd build/
|
||||||
cmake ../source
|
cmake ..
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo "TTRTS: CMake failed, exiting Bootstrap"
|
echo "TTRTS: CMake failed, exiting Bootstrap"
|
||||||
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "TTRTS: Running make"
|
echo "TTRTS: Performing install"
|
||||||
make
|
sudo make install
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo "TTRTS: make failed, exiting Bootstrap"
|
echo "TTRTS: Install failed, check output"
|
||||||
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Run final test to make sure
|
||||||
echo "TTRTS: Running tests"
|
echo "TTRTS: Running tests"
|
||||||
./test/ttrts-test
|
./source/test/ttrts-test
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo "TTRTS: Tests failed, build must be broken"
|
echo "TTRTS: Tests failed, build must be broken"
|
||||||
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "TTRTS: Generating maps"
|
echo "TTRTS: Bootstrap complete"
|
||||||
test ! -e ../maps && mkdir ../maps
|
|
||||||
cd ../maps
|
|
||||||
./../build/gen/ttrts-gen
|
|
||||||
if [[ $? != 0 ]]; then
|
|
||||||
echo "TTRTS: Generating maps, exiting Bootstrap"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "TTRTS: Moving binaries"
|
|
||||||
cd ..
|
|
||||||
if [ ! -e build/ttrts/ttrts ]; then
|
|
||||||
echo "TTRTS: No TTRTS Binary found, something has gone wrong"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp build/ttrts/ttrts .
|
|
||||||
chmod a+x ttrts
|
|
||||||
|
|
44
scripts/gen_manpage.sh
Executable file
44
scripts/gen_manpage.sh
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#! /bin/bash
|
||||||
|
# Used to a man page from markdown
|
||||||
|
|
||||||
|
FILE="$4"
|
||||||
|
TEMP="$FILE.tmp"
|
||||||
|
|
||||||
|
echo ".\" Man page for the ttrts project" > $TEMP
|
||||||
|
echo ".\" this man page is auto-generated, do not edit directly" >> $TEMP
|
||||||
|
|
||||||
|
echo ".TH TTRTS\ v$1.$2.$3 6 $(date +%Y-%m-%d) http://mdiluz.github.io/ttrts/" >> $TEMP
|
||||||
|
|
||||||
|
# NOTE: For the OSX version of sed we use -E, which on linux appears be an undocumented switch for -r
|
||||||
|
# we also have to use [A-Za-z] instead of \w for some reason
|
||||||
|
# as well as escaped new line characters and literal tabs instead of \n and \t
|
||||||
|
|
||||||
|
# sections to section headers
|
||||||
|
# sub-sections in man page sub-sections
|
||||||
|
# 4-space lines to tabs
|
||||||
|
# tab starts removed
|
||||||
|
# Environment variables in bold
|
||||||
|
# User variables in italics
|
||||||
|
# remove all line-widths
|
||||||
|
# Put all back-ticks quotes in bold
|
||||||
|
# underline mapfile opt
|
||||||
|
# ensure name section uses correct
|
||||||
|
cat "$5" \
|
||||||
|
| sed -E 's/^# ([A-Za-z]+)/.SH \1/g' \
|
||||||
|
| sed -E 's/^##+ ([A-Za-z]+)/.SS \1/g' \
|
||||||
|
| sed -E 's/^ (.*)$/\
|
||||||
|
\1\
|
||||||
|
/g' \
|
||||||
|
| sed -E 's/^ //g' \
|
||||||
|
| sed -E 's/\$\{([A-Za-z]+)\}/\\fB\$\1\\fR/g' \
|
||||||
|
| sed -E 's/\{([A-Za-z]+)\}/\\fI\1\\fR/g' \
|
||||||
|
| sed -E 's/-----+//g' \
|
||||||
|
| sed -E 's/`(.*)`/\\fB\1\\fR/g' \
|
||||||
|
| sed -E 's/MAPFILE/\\fImapfile\\fR/g' \
|
||||||
|
| sed -E 's/HOSTNAME/\\fIhostname\\fR/g' \
|
||||||
|
| sed -E 's/ ttrts -/ ttrts \\-/g' >> $TEMP
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -e $FILE ] || [ ! -z "$( diff $FILE $TEMP )" ]; then
|
||||||
|
mv -f $TEMP $FILE
|
||||||
|
fi
|
13
scripts/gen_maps.sh
Executable file
13
scripts/gen_maps.sh
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#! /bin/bash
|
||||||
|
# Use to generate the ttrts maps
|
||||||
|
|
||||||
|
ttrtsgen=$1
|
||||||
|
|
||||||
|
test ! -e maps && mkdir maps # Make maps directory if needed
|
||||||
|
if [ ! -e maps ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd maps
|
||||||
|
|
||||||
|
$ttrtsgen
|
18
scripts/gen_usage.sh
Executable file
18
scripts/gen_usage.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#! /bin/bash
|
||||||
|
# Used to generate usage text from markdown
|
||||||
|
|
||||||
|
FILE="$1"
|
||||||
|
TEMP="${FILE}_tmp"
|
||||||
|
|
||||||
|
cat README.md \
|
||||||
|
| sed -E 's/^#+ //g' \
|
||||||
|
| sed -E 's/^ /\\t/g' \
|
||||||
|
| sed -E 's/^ /\\t/g' \
|
||||||
|
| sed -E 's/^/\"/' \
|
||||||
|
| sed -E 's/$/\\n\"/' \
|
||||||
|
> $TEMP
|
||||||
|
|
||||||
|
# If no difference
|
||||||
|
if [ ! -e $FILE ] || [ ! -z "$( diff $TEMP $FILE )" ]; then
|
||||||
|
mv -f $TEMP $FILE
|
||||||
|
fi
|
18
scripts/gen_version_header.sh
Executable file
18
scripts/gen_version_header.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
HEADER="// Auto generated ttrts version header
|
||||||
|
// do not edit manually
|
||||||
|
#ifndef _TTRTS_VERSION_H_
|
||||||
|
#define _TTRTS_VERSION_H_
|
||||||
|
|
||||||
|
#define TTRTS_VERSION_MAJOR $1
|
||||||
|
#define TTRTS_VERSION_MINOR $2
|
||||||
|
#define TTRTS_VERSION_PATCH $3
|
||||||
|
#define TTRTS_VERSION_STRING \"v$1.$2.$3\"
|
||||||
|
|
||||||
|
#endif //_TTRTS_VERSION_H_"
|
||||||
|
|
||||||
|
echo "$HEADER" > "version.h.tmp"
|
||||||
|
|
||||||
|
# If no difference
|
||||||
|
if [ ! -e version.h ] || [ ! -z "$( diff version.h version.h.tmp )" ]; then
|
||||||
|
mv -f version.h.tmp version.h
|
||||||
|
fi
|
23
scripts/ttrts_complete
Executable file
23
scripts/ttrts_complete
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
# ttrts completion
|
||||||
|
|
||||||
|
test ! -z TTRTS_MAPS && TTRTS_MAPS=/usr/share/ttrts/maps/
|
||||||
|
|
||||||
|
have ttrts &&
|
||||||
|
function _ttrts
|
||||||
|
{
|
||||||
|
commandnames="--server --client --host= --host=localhost "
|
||||||
|
for filename in ${TTRTS_MAPS}/*
|
||||||
|
do
|
||||||
|
map="${filename##*/}"
|
||||||
|
commandnames+="--map=$map "
|
||||||
|
done
|
||||||
|
|
||||||
|
local cur prev
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
COMPREPLY=( $(compgen -W "${commandnames}" -- ${cur}) )
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _ttrts ttrts
|
|
@ -1,36 +1,12 @@
|
||||||
cmake_minimum_required( VERSION 2.8.7 )
|
# Main libraries
|
||||||
|
|
||||||
# Set version information
|
|
||||||
set( TTRTS_VERSION_MAJOR 0 )
|
|
||||||
set( TTRTS_VERSION_MINOR 1 )
|
|
||||||
set( TTRTS_VERSION_PATCH 0 )
|
|
||||||
|
|
||||||
# Use c++1y (14)
|
|
||||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++1y" )
|
|
||||||
|
|
||||||
# Turn on all warnings
|
|
||||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder" )
|
|
||||||
|
|
||||||
# Turn off reorder warnings as they're kind of irrelevant
|
|
||||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" )
|
|
||||||
|
|
||||||
# This shouldn't be needed, but it looks like IDE's like clion can forget to set -g for Debug
|
|
||||||
if( CMAKE_BUILD_TYPE MATCHES "Debug" )
|
|
||||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g" )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Add definitions for the version number
|
|
||||||
add_definitions(
|
|
||||||
-DTTRTS_VERSION_MAJOR=${TTRTS_VERSION_MAJOR}
|
|
||||||
-DTTRTS_VERSION_MINOR=${TTRTS_VERSION_MINOR}
|
|
||||||
-DTTRTS_VERSION_PATCH=${TTRTS_VERSION_PATCH}
|
|
||||||
-DTTRTS_VERSION_STRING=\"${TTRTS_VERSION_MAJOR}.${TTRTS_VERSION_MINOR}.${TTRTS_VERSION_PATCH}\"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Subprojects
|
|
||||||
add_subdirectory( ttrts )
|
add_subdirectory( ttrts )
|
||||||
add_subdirectory( game )
|
add_subdirectory( system )
|
||||||
|
|
||||||
|
# Main binaries
|
||||||
|
add_subdirectory( launcher )
|
||||||
|
add_subdirectory( client )
|
||||||
|
add_subdirectory( server )
|
||||||
|
add_subdirectory( local )
|
||||||
|
|
||||||
# Auxhilary binaries
|
# Auxhilary binaries
|
||||||
add_subdirectory( test )
|
add_subdirectory( test )
|
||||||
|
|
130
source/README.md
130
source/README.md
|
@ -1,19 +1,121 @@
|
||||||
# Targets
|
# NAME
|
||||||
### ttrts
|
ttrts - Tiny Terminal RTS
|
||||||
Main TTRTS binary, runs from the command line and acts as host for games
|
|
||||||
|
|
||||||
### test (ttrts-test)
|
# SYNOPSIS
|
||||||
Test binary, to be compiled and run to test various functionality
|
ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE]
|
||||||
|
|
||||||
### gen (ttrts-gen)
|
# DESCRIPTION
|
||||||
Binary to generate example map files
|
ttrts is a tiny terminal based RTS that uses text files as order lists to control the units
|
||||||
|
|
||||||
# Libraries
|
This means that any user, program or cat that can read and write to text files can play the game
|
||||||
### game
|
|
||||||
Implementation of the RTS rules and simulation
|
|
||||||
|
|
||||||
### maths
|
# RETURN VALUE
|
||||||
Simple maths library for 2D calculations and types
|
ttrts will return -1 on error, or the winning player on completion
|
||||||
|
|
||||||
### scripts
|
# OPTIONS
|
||||||
Directory of scripts used in build process
|
--server - Run in server mode, must provide a map file
|
||||||
|
|
||||||
|
--client - Run in client mode, must provide a hostname for a running server
|
||||||
|
|
||||||
|
--map=MAPFILE - File to read in the initial game state. Local or in ${TTRTS_MAPS}
|
||||||
|
|
||||||
|
--host=HOSTNAME - Name of host to connect to in client mode
|
||||||
|
|
||||||
|
# USAGE
|
||||||
|
When invoked, ttrts will set up the game in a directory within ${TTRTS_GAMES} by the name of the map
|
||||||
|
|
||||||
|
The files in this directory can be read and interpreted by human, robot or cat
|
||||||
|
|
||||||
|
ttrts will then await order files from each participant
|
||||||
|
|
||||||
|
Once all order files have been received ttrts will calculate the turn and output a new gamestate file
|
||||||
|
|
||||||
|
In server and client mode, the client will output and read in these files while the server simulates the game
|
||||||
|
|
||||||
|
This process repeats until the game is over
|
||||||
|
|
||||||
|
# ENVIRONMENT
|
||||||
|
${TTRTS_MAPS} - Map file lookup location, defaults to `/usr/share/ttrts/maps/`
|
||||||
|
|
||||||
|
${TTRTS_GAMES} - Game directory for I/O, defaults to `/tmp/`
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
# FILES
|
||||||
|
`/usr/share/ttrts/maps/` holds a sample set of maps
|
||||||
|
|
||||||
|
## Gamestate File
|
||||||
|
Turn_{TURNNUMBER}.txt
|
||||||
|
|
||||||
|
### Contents
|
||||||
|
===== ttrts v{MAJOR}.{MINOR}.{PATCH} =====
|
||||||
|
NAME:{GAMENAME}
|
||||||
|
SIZE:[{X},{Y}]
|
||||||
|
TURN:{TURNNUMBER}
|
||||||
|
WALL:[{X},{Y}][{X},{Y}][{X},{Y}]...{repeat for all walls}
|
||||||
|
~~~~
|
||||||
|
UNIT:{ID} pl:{PLAYER} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}]
|
||||||
|
... {continue for all units}
|
||||||
|
END
|
||||||
|
|
||||||
|
## Order File
|
||||||
|
Player_{PLAYER_ID}_Turn_{TURN_NUMBER}.txt
|
||||||
|
|
||||||
|
### Contents
|
||||||
|
ORDER:{ORDER_CHAR} id:{UNIT_ID}
|
||||||
|
... {continue for all orders}
|
||||||
|
END
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
# SERVER/CLIENT
|
||||||
|
When in server or client mode, the game can be played across a network. If desired, a player could design an AI to act as a client instead of using the client mode and intermediary filesystem.
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
The server is accesible on port 11715
|
||||||
|
|
||||||
|
To perform the handshake the server will write to the socket with the format "player PLAYER_ID name GAME_NAME", it will expect this exact information to be written back to in reply.
|
||||||
|
|
||||||
|
Once handshake is performed, the server will write to the socket in the form of the Gamestate file as above.
|
||||||
|
|
||||||
|
The server will then wait for a new-line delimited and END terminated list of orders
|
||||||
|
|
||||||
|
This will be repeated until the game is over
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
# GAMEPLAY
|
||||||
|
|
||||||
|
The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board
|
||||||
|
|
||||||
|
Each turn, the client outputs a gamestate file and waits for an order file from each player
|
||||||
|
|
||||||
|
All commands are evaluated simultaneously with friendly fire enabled by default
|
||||||
|
|
||||||
|
The game is over when any of three conditions are met -
|
||||||
|
* All remaining units are controlled by a single player
|
||||||
|
* No units are left (draw)
|
||||||
|
* All units left are unable to move (draw)
|
||||||
|
|
||||||
|
# UNITS
|
||||||
|
Each unit occupies a single tile on the board, facing in a compass direction (NESW)
|
||||||
|
|
||||||
|
Units will only accept orders from their owner
|
||||||
|
|
||||||
|
Units can receive only a single order each turn
|
||||||
|
|
||||||
|
Units cannot occupy the same tile as other units/walls
|
||||||
|
|
||||||
|
# ORDERS
|
||||||
|
### F - Move unit [F]orward one space, leaving a wall
|
||||||
|
|
||||||
|
This wall will remain until the end of the game, blocking movement to that tile
|
||||||
|
|
||||||
|
Movement orders have no effect if impossible, eg.
|
||||||
|
* Attempting to move outside of map
|
||||||
|
* Attempting to move on to tile occupied by unit/wall
|
||||||
|
|
||||||
|
### L/R - Rotate unit [L]eft or [R]ight
|
||||||
|
|
||||||
|
Unit will rotate clockwise or counter-clockwise, this order cannot fail
|
||||||
|
|
||||||
|
### A - [A]ttack in straight line in front of unit
|
||||||
|
|
||||||
|
Attack will continue forward until unit can't progress, all units within the path of the attack are destroyed.
|
||||||
|
|
23
source/client/CMakeLists.txt
Normal file
23
source/client/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# ====================== ttrts =======================
|
||||||
|
# Project name
|
||||||
|
project( ttrts-client )
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
../system
|
||||||
|
../ttrts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the sources
|
||||||
|
set( SOURCES
|
||||||
|
client.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the executable
|
||||||
|
add_executable( ${PROJECT_NAME} ${SOURCES} )
|
||||||
|
|
||||||
|
# dependent on main ttrts libary
|
||||||
|
target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system )
|
||||||
|
|
||||||
|
# Installation target
|
||||||
|
install( TARGETS ${PROJECT_NAME} DESTINATION bin )
|
69
source/client/client.cpp
Normal file
69
source/client/client.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// must provide information
|
||||||
|
if (argc < 2)
|
||||||
|
fatal_error("Usage: ttrts-client HOST");
|
||||||
|
|
||||||
|
std::string hostname = argv[1];
|
||||||
|
|
||||||
|
sockaddr_in serv_addr; // Server address
|
||||||
|
memset(&serv_addr,0, sizeof(serv_addr));
|
||||||
|
|
||||||
|
// Set the server to AF_INET
|
||||||
|
serv_addr.sin_family = AF_INET;
|
||||||
|
// Set our server address port to the port number provided
|
||||||
|
serv_addr.sin_port = htons(TTRTS_PORT);
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: Connecting to "<<hostname<<std::endl;
|
||||||
|
int sockfd = ConnectToHostServer(hostname, serv_addr);
|
||||||
|
|
||||||
|
unsigned int player;
|
||||||
|
std::string gameNameString;
|
||||||
|
|
||||||
|
// Handshake with server to fetch player and gamestring
|
||||||
|
PerformClientHandshake(sockfd, player, gameNameString);
|
||||||
|
|
||||||
|
// output our information
|
||||||
|
player_t myPlayer = (player_t)player;
|
||||||
|
std::cout<<"TTRTS: I am player "<<std::to_string((int)myPlayer)<<std::endl;
|
||||||
|
std::cout<<"TTRTS: Game is "<<gameNameString<<std::endl;
|
||||||
|
|
||||||
|
// Clean out the games dir
|
||||||
|
CreateAndCleanGameDir(gameNameString);
|
||||||
|
|
||||||
|
// Buffer for messages
|
||||||
|
char buffer[1028];
|
||||||
|
memset(buffer,0,sizeof(buffer));
|
||||||
|
|
||||||
|
int n = 0; // return value for read and write calls
|
||||||
|
while ( n >= 0 )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Waiting for gamestate"<<std::endl;
|
||||||
|
std::string gamestate = WaitForGamestateMessage(sockfd);
|
||||||
|
|
||||||
|
// Output the gamestate file for this game
|
||||||
|
CTTRTSGame thisGame = GetGameFromString(gamestate);
|
||||||
|
OutputGameStateFile(thisGame);
|
||||||
|
|
||||||
|
// If game over, exit with out winning player and message
|
||||||
|
if(thisGame.GameOver())
|
||||||
|
exit( OutputGameEnd(thisGame) );
|
||||||
|
|
||||||
|
// Get the order file for this turn`
|
||||||
|
std::string orders = GetOrdersFromPlayerFile(thisGame,myPlayer);
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: Sending orders"<<std::endl;
|
||||||
|
std::cout<<orders<<std::endl;
|
||||||
|
// Write to the socket with the buffer
|
||||||
|
n = SendOrdersToServer(sockfd, orders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 2.8.7)
|
|
||||||
|
|
||||||
# game project
|
|
||||||
project( game )
|
|
||||||
|
|
||||||
include_directories(
|
|
||||||
../maths
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add the sources
|
|
||||||
set( SOURCES
|
|
||||||
game.cpp
|
|
||||||
unit.cpp
|
|
||||||
order.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library( game ${SOURCES} )
|
|
|
@ -1,17 +0,0 @@
|
||||||
TTRTS Gameplay
|
|
||||||
=================
|
|
||||||
|
|
||||||
The game takes place in a series of simultaneous turns on an arbitrarily sized 2D board.
|
|
||||||
|
|
||||||
Each player is in control of a set number of starting units, and each turn receives data on the status of the board.
|
|
||||||
|
|
||||||
Each player must then issue a command to each unit in their control.
|
|
||||||
|
|
||||||
All commands are evaluated simultaniously.
|
|
||||||
|
|
||||||
All attempted movement to the same square by two or more units will fail.
|
|
||||||
|
|
||||||
Friendly fire is enabled by default.
|
|
||||||
|
|
||||||
A player wins when all opposing units have been destroyed.
|
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
#ifndef _GAME_H_
|
|
||||||
#define _GAME_H_
|
|
||||||
|
|
||||||
#include "unit.h"
|
|
||||||
#include "gametypes.h"
|
|
||||||
#include "order.h"
|
|
||||||
|
|
||||||
#define GAME_HEADER_FORMATTER "NAME:%s\nSIZE:[%u,%u]\nTURN:%u"
|
|
||||||
#define GAME_HEADER_DELIMITER "~~~~\n"
|
|
||||||
|
|
||||||
// Type for order and unit pairs
|
|
||||||
struct OrderUnitPair
|
|
||||||
{
|
|
||||||
// Straight up move constructor
|
|
||||||
OrderUnitPair( OrderUnitPair&& other )
|
|
||||||
: unit ( std::move(other.unit) )
|
|
||||||
, order ( other.order )
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Multi parameter constructor
|
|
||||||
OrderUnitPair( CUnit&& u, COrder o )
|
|
||||||
: unit ( std::move(u) )
|
|
||||||
, order ( o )
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Move assignment operator
|
|
||||||
inline OrderUnitPair& operator=( OrderUnitPair&& rhs )
|
|
||||||
{
|
|
||||||
this->unit = std::move(rhs.unit);
|
|
||||||
this->order = std::move(rhs.order);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
CUnit unit; // The unit
|
|
||||||
COrder order; // Order for this unit from this turn
|
|
||||||
};
|
|
||||||
|
|
||||||
// Typedef for a vector of these unit pairs
|
|
||||||
typedef std::vector< OrderUnitPair > OrderUnitPairVector;
|
|
||||||
|
|
||||||
// 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 a win, returns invalid for no win state reached
|
|
||||||
// Note: this function will return invalid a draw was reached
|
|
||||||
// best practice would be to call with GetNumUnits() == 0
|
|
||||||
Team CheckForWin() 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( Team team, const std::string& orders );
|
|
||||||
int IssueOrders( Team team, const COrderVector& orders );
|
|
||||||
int IssueOrder( Team team, const COrder& 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 COrder& 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 COrder& 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 teams in the current game
|
|
||||||
std::vector<Team> GetTeams() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
// Verify any order or position - non-zero is error
|
|
||||||
int VerifyOrder( Team team, const COrder& order ) const;
|
|
||||||
int VerifyPos( uvector2 vec ) const;
|
|
||||||
|
|
||||||
// Get a units new position after an order
|
|
||||||
uvector2 GetNewPosition( const OrderUnitPair& pair ) const;
|
|
||||||
|
|
||||||
// Check for a pass through
|
|
||||||
static bool CheckForPassThrough( const CUnit& one, const CUnit& two );
|
|
||||||
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //_GAME_H_
|
|
|
@ -1,23 +0,0 @@
|
||||||
#ifndef _GAME_TYPES_H_
|
|
||||||
#define _GAME_TYPES_H_
|
|
||||||
|
|
||||||
#include <limits> // std::numeric_limits
|
|
||||||
|
|
||||||
// Type for a team IDs
|
|
||||||
enum class Team : 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,33 +0,0 @@
|
||||||
#include <string.h>
|
|
||||||
#include "order.h"
|
|
||||||
|
|
||||||
// Convert an order to a string
|
|
||||||
std::string GetStringFromOrder(const COrder& 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
|
|
||||||
COrder GetOrderFromString( const std::string& order )
|
|
||||||
{
|
|
||||||
COrder ret;
|
|
||||||
|
|
||||||
char corder;
|
|
||||||
unsigned int unit;
|
|
||||||
|
|
||||||
sscanf(order.c_str(), ORDER_FORMATTER,
|
|
||||||
&corder,
|
|
||||||
&unit);
|
|
||||||
|
|
||||||
ret.command = (command_c)corder;
|
|
||||||
ret.unit = unit;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
#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_t, 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_t::N,'^'},
|
|
||||||
{dir_t::E,'>'},
|
|
||||||
{dir_t::S,'v'},
|
|
||||||
{dir_t::W,'<'},
|
|
||||||
};
|
|
||||||
|
|
||||||
return sk_visMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// force a reset of the unit ID value
|
|
||||||
void forceResetUnitId()
|
|
||||||
{
|
|
||||||
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
|
|
||||||
// "U id:[unit_id] team:[team_id] player:[player_id] vis:[unit_vis] dir:[dir] pos:[pos.x],[pos.y]"
|
|
||||||
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.team_id,
|
|
||||||
unit.unit_vis,
|
|
||||||
unit.dir,
|
|
||||||
unit.pos.x,
|
|
||||||
unit.pos.y );
|
|
||||||
|
|
||||||
return buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a unit from a string descriptor
|
|
||||||
// "U id:[unit_id] team:[team_id] player:[player_id] vis:[unit_vis] dir:[dir] pos:[pos.x],[pos.y]"
|
|
||||||
CUnit CUnit::GetUnitFromString(const std::string& unit )
|
|
||||||
{
|
|
||||||
CUnit ret;
|
|
||||||
|
|
||||||
unsigned int id;
|
|
||||||
int team;
|
|
||||||
char vis;
|
|
||||||
char dir;
|
|
||||||
unsigned int posx;
|
|
||||||
unsigned int posy;
|
|
||||||
|
|
||||||
sscanf(unit.c_str(), UNIT_FORMATTER,
|
|
||||||
&id,
|
|
||||||
&team,
|
|
||||||
&vis,
|
|
||||||
&dir,
|
|
||||||
&posx,
|
|
||||||
&posy );
|
|
||||||
|
|
||||||
ret.unit_id = (unit_id_t)id;
|
|
||||||
ret.team_id = (Team)team;
|
|
||||||
ret.unit_vis = (unitVis_c)vis;
|
|
||||||
ret.dir = (dir_t)dir;
|
|
||||||
ret.pos = uvector2(posx,posy);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plain constructor
|
|
||||||
CUnit::CUnit()
|
|
||||||
: unit_id ( get_unique_unit_id() )
|
|
||||||
, team_id ( Team::NUM_INVALID )
|
|
||||||
, unit_vis ( unitVis_invalid )
|
|
||||||
, dir ( dir_t::S )
|
|
||||||
, pos ( { ucoord_invalid, ucoord_invalid } )
|
|
||||||
{
|
|
||||||
updateMyVisual();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
CUnit::CUnit(CUnit&& unit)
|
|
||||||
: unit_id ( std::move(unit.unit_id) )
|
|
||||||
, team_id ( std::move(unit.team_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) ;
|
|
||||||
team_id = std::move(unit.team_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)
|
|
||||||
&& (team_id == rhs.team_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_t CUnit::turnLeft()
|
|
||||||
{
|
|
||||||
switch( dir )
|
|
||||||
{
|
|
||||||
case dir_t::N:
|
|
||||||
dir = dir_t::W;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::E:
|
|
||||||
dir = dir_t::N;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::S:
|
|
||||||
dir = dir_t::E;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::W:
|
|
||||||
dir = dir_t::S;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMyVisual();
|
|
||||||
|
|
||||||
return getDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn unit right
|
|
||||||
dir_t CUnit::turnRight()
|
|
||||||
{
|
|
||||||
switch( dir )
|
|
||||||
{
|
|
||||||
case dir_t::N:
|
|
||||||
dir = dir_t::E;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::E:
|
|
||||||
dir = dir_t::S;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::S:
|
|
||||||
dir = dir_t::W;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::W:
|
|
||||||
dir = dir_t::N;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMyVisual();
|
|
||||||
|
|
||||||
return getDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn unit around
|
|
||||||
dir_t CUnit::turnAround()
|
|
||||||
{
|
|
||||||
switch( dir )
|
|
||||||
{
|
|
||||||
case dir_t::N:
|
|
||||||
dir = dir_t::S;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::E:
|
|
||||||
dir = dir_t::W;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::S:
|
|
||||||
dir = dir_t::N;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case dir_t::W:
|
|
||||||
dir = dir_t::E;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMyVisual();
|
|
||||||
|
|
||||||
return getDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the co-ordinate infront of the unit
|
|
||||||
uvector2 CUnit::getInFront() const
|
|
||||||
{
|
|
||||||
vector2 delta = vecFromDir(dir);
|
|
||||||
return pos + delta;
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
#ifndef _UNIT_H_
|
|
||||||
#define _UNIT_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "gametypes.h"
|
|
||||||
#include "vector2.h"
|
|
||||||
|
|
||||||
#define UNIT_FORMATTER "UNIT:%u tm:%u vs:%c dr:%c ps:[%u,%u]"
|
|
||||||
|
|
||||||
// force a reset of the unit ID value
|
|
||||||
void forceResetUnitId();
|
|
||||||
|
|
||||||
// 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); }
|
|
||||||
|
|
||||||
// Default dtor
|
|
||||||
~CUnit() = default;
|
|
||||||
|
|
||||||
// Getters for all the members
|
|
||||||
inline const unit_id_t& getID() const { return unit_id; }
|
|
||||||
inline const Team & getTeam() const { return team_id; }
|
|
||||||
inline const unitVis_c& getVisual() const { return unit_vis; }
|
|
||||||
inline const dir_t& getDir() const { return dir; }
|
|
||||||
inline const uvector2& getPos() const { return pos; }
|
|
||||||
|
|
||||||
// Set
|
|
||||||
inline Team setTeam(const Team & v) { return (team_id = v); }
|
|
||||||
inline unitVis_c setVisual(const unitVis_c& v) { return ( unit_vis = v ); }
|
|
||||||
inline dir_t setDir(const dir_t& 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_t turnLeft();
|
|
||||||
dir_t turnRight();
|
|
||||||
dir_t 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;
|
|
||||||
|
|
||||||
// Team ID
|
|
||||||
Team team_id;
|
|
||||||
|
|
||||||
// Direction
|
|
||||||
dir_t 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 )
|
|
||||||
&& (team_id != Team::NUM_INVALID )
|
|
||||||
&& (unit_vis != unitVis_invalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //_UNIT_H_
|
|
|
@ -3,8 +3,7 @@
|
||||||
project( ttrts-gen )
|
project( ttrts-gen )
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../game
|
../ttrts
|
||||||
../maths
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set( SOURCES
|
set( SOURCES
|
||||||
|
@ -14,4 +13,15 @@ set( SOURCES
|
||||||
# Add the executable
|
# Add the executable
|
||||||
add_executable( ttrts-gen ${SOURCES} )
|
add_executable( ttrts-gen ${SOURCES} )
|
||||||
|
|
||||||
target_link_libraries( ttrts-gen game )
|
target_link_libraries( ttrts-gen ttrts )
|
||||||
|
|
||||||
|
# Run the gen_usage script to generate our usage header
|
||||||
|
add_custom_target(
|
||||||
|
ttrts-gen-maps ALL
|
||||||
|
${CMAKE_SOURCE_DIR}/scripts/gen_maps.sh "${CMAKE_CURRENT_BINARY_DIR}/ttrts-gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(ttrts-gen-maps ${PROJECT_NAME})
|
||||||
|
|
||||||
|
# Installation target
|
||||||
|
install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/maps DESTINATION /usr/local/share/ttrts )
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include "formatters.h"
|
||||||
|
|
||||||
void AddUnitToGame( Team team, char vis, uvector2 vec, CTTRTSGame& game )
|
void AddUnitToGame( player_t player, char vis, uvector2 vec, CTTRTSGame& game )
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis(vis);
|
CUnit unit = CUnit::GetUnitFromVis(vis);
|
||||||
unit.setPos( vec );
|
unit.SetPos(vec);
|
||||||
unit.setTeam(team);
|
unit.SetPlayer(player);
|
||||||
game.AddUnit(std::move(unit));
|
game.AddUnit(std::move(unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,56 +16,150 @@ void OutputGame( CTTRTSGame&& game )
|
||||||
{
|
{
|
||||||
std::ofstream output;
|
std::ofstream output;
|
||||||
output.open (game.GetName() + ".txt");
|
output.open (game.GetName() + ".txt");
|
||||||
output << game.GetStateAsString();
|
output << GetStringFromGame(game);
|
||||||
output.close();
|
output.close();
|
||||||
|
|
||||||
forceResetUnitId();
|
__forceResetCUnitID();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
// Tiny 2v2 Game
|
// Tiny 1v1 Game
|
||||||
|
//------
|
||||||
|
//-G----
|
||||||
|
//----R-
|
||||||
|
//-G----
|
||||||
|
//----R-
|
||||||
|
//------
|
||||||
{
|
{
|
||||||
CTTRTSGame game(6, 6);
|
CTTRTSGame game(6, 6);
|
||||||
game.SetName("Tiny2v2");
|
game.SetName("Tiny2Player");
|
||||||
|
|
||||||
AddUnitToGame( Team::Red, '<', uvector2(4, 2), game);
|
AddUnitToGame( player_t::Red, '<', uvector2(4, 2), game);
|
||||||
AddUnitToGame( Team::Red, '<', uvector2(4, 4), game);
|
AddUnitToGame( player_t::Red, '<', uvector2(4, 4), game);
|
||||||
AddUnitToGame( Team::Green, '>', uvector2(1, 1), game);
|
AddUnitToGame( player_t::Green, '>', uvector2(1, 1), game);
|
||||||
AddUnitToGame( Team::Green, '>', uvector2(1, 3), game);
|
AddUnitToGame( player_t::Green, '>', uvector2(1, 3), game);
|
||||||
|
|
||||||
|
game.AddWall(uvector2(3,2));
|
||||||
|
game.AddWall(uvector2(3,3));
|
||||||
|
|
||||||
OutputGame(std::move(game));
|
OutputGame(std::move(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic 5v5 game
|
// Basic 1v1 game
|
||||||
|
// --------------------
|
||||||
|
// -G------------------
|
||||||
|
// ------------------R-
|
||||||
|
// -G------------------
|
||||||
|
// ------------------R-
|
||||||
|
// -G------------------
|
||||||
|
// ------------------R-
|
||||||
|
// -G------------------
|
||||||
|
// ------------------R-
|
||||||
|
// -G------------------
|
||||||
|
// ------------------R-
|
||||||
|
// --------------------
|
||||||
{
|
{
|
||||||
CTTRTSGame game(20, 12);
|
CTTRTSGame game(20, 12);
|
||||||
game.SetName("Big2v2");
|
game.SetName("Big2Player");
|
||||||
|
|
||||||
for ( ucoord_t y : { 2,4,6,8,10 } )
|
for ( ucoord_t y : { 2,4,6,8,10 } )
|
||||||
AddUnitToGame( Team::Red, '<', uvector2(18, y), game);
|
AddUnitToGame( player_t::Red, '<', uvector2(18, y), game);
|
||||||
for ( ucoord_t y : { 1,3,5,7,9 } )
|
for ( ucoord_t y : { 1,3,5,7,9 } )
|
||||||
AddUnitToGame( Team::Green, '>', uvector2(1, y), game);
|
AddUnitToGame( player_t::Green, '>', uvector2(1, y), game);
|
||||||
|
|
||||||
|
|
||||||
OutputGame(std::move(game));
|
OutputGame(std::move(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chess 10v10 game
|
// Sort of like Chess
|
||||||
|
//GG------
|
||||||
|
//------RR
|
||||||
|
//GG------
|
||||||
|
//------RR
|
||||||
|
//GG------
|
||||||
|
//------RR
|
||||||
|
//GG------
|
||||||
|
//------RR
|
||||||
{
|
{
|
||||||
CTTRTSGame game(8, 8);
|
CTTRTSGame game(8, 8);
|
||||||
game.SetName("Chess");
|
game.SetName("Chess");
|
||||||
|
|
||||||
for ( ucoord_t y : { 1,3,5,7 } ) {
|
for ( ucoord_t y : { 1,3,5,7 } ) {
|
||||||
AddUnitToGame(Team::Red, '<', uvector2(6, y), game);
|
AddUnitToGame(player_t::Red, '<', uvector2(6, y), game);
|
||||||
AddUnitToGame(Team::Red, '<', uvector2(7, y), game);
|
AddUnitToGame(player_t::Red, '<', uvector2(7, y), game);
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( ucoord_t y : { 0,2,4,6 } ) {
|
for ( ucoord_t y : { 0,2,4,6 } ) {
|
||||||
AddUnitToGame(Team::Green, '>', uvector2(0, y), game);
|
AddUnitToGame(player_t::Green, '>', uvector2(0, y), game);
|
||||||
AddUnitToGame(Team::Green, '>', uvector2(1, y), game);
|
AddUnitToGame(player_t::Green, '>', uvector2(1, y), game);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputGame(std::move(game));
|
OutputGame(std::move(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Medium 4 player game
|
||||||
|
//----------
|
||||||
|
//----------
|
||||||
|
//---GGGG---
|
||||||
|
//--R -- B--
|
||||||
|
//--R- -B--
|
||||||
|
//--R- -B--
|
||||||
|
//--R -- B--
|
||||||
|
//---YYYY---
|
||||||
|
//----------
|
||||||
|
//----------
|
||||||
|
{
|
||||||
|
CTTRTSGame game(10, 10);
|
||||||
|
game.SetName("Medium4Player");
|
||||||
|
|
||||||
|
for ( ucoord_t y : { 2,3,4,5 } ) {
|
||||||
|
AddUnitToGame(player_t::Red, '>', uvector2(2, y), game);
|
||||||
|
AddUnitToGame(player_t::Blue, '<', uvector2(7, y), game);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( ucoord_t x : { 2,3,4,5 } ) {
|
||||||
|
AddUnitToGame(player_t::Yellow, '^', uvector2(x,7), game);
|
||||||
|
AddUnitToGame(player_t::Green, 'v', uvector2(x,2), game);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagonal walls
|
||||||
|
game.AddWall(uvector2(3,3));
|
||||||
|
game.AddWall(uvector2(3,6));
|
||||||
|
game.AddWall(uvector2(6,3));
|
||||||
|
game.AddWall(uvector2(6,6));
|
||||||
|
|
||||||
|
// middle walls
|
||||||
|
game.AddWall(uvector2(4,4));
|
||||||
|
game.AddWall(uvector2(4,5));
|
||||||
|
game.AddWall(uvector2(5,4));
|
||||||
|
game.AddWall(uvector2(5,5));
|
||||||
|
|
||||||
|
OutputGame(std::move(game));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Medium 3 player game
|
||||||
|
//----------
|
||||||
|
//--------Y-
|
||||||
|
//--------Y-
|
||||||
|
//----------
|
||||||
|
//-G--------
|
||||||
|
//-G--------
|
||||||
|
//----------
|
||||||
|
//--------R-
|
||||||
|
//--------R-
|
||||||
|
//----------
|
||||||
|
{
|
||||||
|
CTTRTSGame game(10, 10);
|
||||||
|
game.SetName("Medium3Player");
|
||||||
|
|
||||||
|
AddUnitToGame(player_t::Red, '<', uvector2(8, 1), game);
|
||||||
|
AddUnitToGame(player_t::Red, '<', uvector2(8, 2), game);
|
||||||
|
AddUnitToGame(player_t::Green, '>', uvector2(1, 4), game);
|
||||||
|
AddUnitToGame(player_t::Green, '>', uvector2(1, 5), game);
|
||||||
|
AddUnitToGame(player_t::Yellow, '<', uvector2(8,7), game);
|
||||||
|
AddUnitToGame(player_t::Yellow, '<', uvector2(8,8), game);
|
||||||
|
|
||||||
|
OutputGame(std::move(game));
|
||||||
|
}
|
||||||
}
|
}
|
6
source/launcher/CMakeLists.txt
Normal file
6
source/launcher/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# ====================== ttrts =======================
|
||||||
|
# Project name
|
||||||
|
project( ttrts-perl-launch )
|
||||||
|
|
||||||
|
# Add bash completion to install
|
||||||
|
install( PROGRAMS ttrts.pl DESTINATION bin RENAME ttrts )
|
44
source/launcher/ttrts.pl
Executable file
44
source/launcher/ttrts.pl
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#! /usr/bin/perl
|
||||||
|
# Main ttrts launcher script
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use 5.0;
|
||||||
|
|
||||||
|
use Getopt::Long qw(GetOptions);
|
||||||
|
|
||||||
|
sub print_usage
|
||||||
|
{
|
||||||
|
print "Unknown option: @_\n" if ( @_ );
|
||||||
|
print "Usage: ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE]\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
our $VERBOSE = $ENV{"VERBOSE"};
|
||||||
|
|
||||||
|
our $server;
|
||||||
|
our $client;
|
||||||
|
our $host;
|
||||||
|
our $map;
|
||||||
|
|
||||||
|
print_usage() if ( @ARGV < 1 or
|
||||||
|
!GetOptions(
|
||||||
|
'client' => \$client,
|
||||||
|
'server' => \$server,
|
||||||
|
'host=s' => \$host,
|
||||||
|
'map=s' => \$map,
|
||||||
|
) );
|
||||||
|
|
||||||
|
# Verify we have the right parameters
|
||||||
|
print "Cannot run as both client and server\n" and exit if $client and $server;
|
||||||
|
print "Client requires hostname\n" and exit if $client and not $host;
|
||||||
|
print "Server requires mapfile\n" and exit if $server and not $map;
|
||||||
|
print "Running locally requires mapfile\n" and exit if not $server and not $client and not $map;
|
||||||
|
|
||||||
|
# Run client, server or local
|
||||||
|
my $res = -1;
|
||||||
|
$res = system("ttrts-client $host") if $client and $host;
|
||||||
|
$res = system("ttrts-server $map") if $server and $map;
|
||||||
|
$res = system("ttrts-local $map") if not $server and not $client and $map;
|
||||||
|
|
||||||
|
return $res
|
23
source/local/CMakeLists.txt
Normal file
23
source/local/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# ====================== ttrts =======================
|
||||||
|
# Project name
|
||||||
|
project( ttrts-local )
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
../system
|
||||||
|
../ttrts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the sources
|
||||||
|
set( SOURCES
|
||||||
|
local.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the executable
|
||||||
|
add_executable( ${PROJECT_NAME} ${SOURCES} )
|
||||||
|
|
||||||
|
# dependent on main ttrts libary
|
||||||
|
target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system pthread )
|
||||||
|
|
||||||
|
# Installation target
|
||||||
|
install( TARGETS ${PROJECT_NAME} DESTINATION bin )
|
62
source/local/local.cpp
Normal file
62
source/local/local.cpp
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#include "game.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// must provide information
|
||||||
|
if (argc < 2)
|
||||||
|
fatal_error("Usage: ttrts-local MAPFILE");
|
||||||
|
|
||||||
|
std::string gamefile = argv[1];
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: Launching with "<<gamefile<<std::endl;
|
||||||
|
CTTRTSGame game = GetGameFromFile(gamefile);
|
||||||
|
|
||||||
|
// Grab the players involved
|
||||||
|
auto players = game.GetPlayers();
|
||||||
|
|
||||||
|
// Default for games
|
||||||
|
std::string ttrts_games_dir = getGamesDir();
|
||||||
|
|
||||||
|
// Empty the current game directory
|
||||||
|
if ( CreateAndCleanGameDir(game.GetName()) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// While the game isn't finished
|
||||||
|
while ( ! game.GameOver() )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Starting turn "<<game.GetTurn()<<std::endl;
|
||||||
|
|
||||||
|
// Create a turn file
|
||||||
|
if( !OutputGameStateFile(game))
|
||||||
|
fatal_error("Error: Failed to output new turn file");
|
||||||
|
|
||||||
|
// Wait for order files
|
||||||
|
for( player_t player : players)
|
||||||
|
{
|
||||||
|
// Construct the player order filename
|
||||||
|
std::string orders = GetOrdersFromPlayerFile(game, player);
|
||||||
|
|
||||||
|
// Issue the orders to the game
|
||||||
|
if( game.IssueOrders(player, orders) )
|
||||||
|
std::cerr<<"Warning: Orders for player "<<(int) player <<" failed to correctly parse"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate turn
|
||||||
|
std::cout<<"TTRTS: Simulating this turn!"<<std::endl;
|
||||||
|
if ( game.SimulateToNextTurn() )
|
||||||
|
fatal_error("Failed to simulate game turn");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output final gamestate
|
||||||
|
OutputGameStateFile(game);
|
||||||
|
|
||||||
|
return OutputGameEnd( game );
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
#ifndef _BASETYPES_H_
|
|
||||||
#define _BASETYPES_H_
|
|
||||||
|
|
||||||
#include <limits> // std::numeric_limits
|
|
||||||
|
|
||||||
#include "stdlib.h" // for size_t
|
|
||||||
|
|
||||||
template<class T, size_t N>
|
|
||||||
constexpr size_t _countof(T (&)[N]) { return N; }
|
|
||||||
|
|
||||||
// Coordinate types
|
|
||||||
typedef short coord_t;
|
|
||||||
typedef unsigned short ucoord_t;
|
|
||||||
|
|
||||||
// Invalid values
|
|
||||||
static const coord_t coord_invalid = std::numeric_limits<coord_t>::max();
|
|
||||||
static const ucoord_t ucoord_invalid = std::numeric_limits<ucoord_t>::max();
|
|
||||||
|
|
||||||
// Direction representation
|
|
||||||
enum class dir_t : char
|
|
||||||
{
|
|
||||||
N = 'N',
|
|
||||||
S = 'S',
|
|
||||||
E = 'E',
|
|
||||||
W = 'W'
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //_BASETYPES_H_
|
|
|
@ -1,9 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
# Used to generate usage text from markdown
|
|
||||||
cat README.md \
|
|
||||||
| sed 's/^#* //g' \
|
|
||||||
| sed 's/\t/\\t/g' \
|
|
||||||
| sed ':a;N;$!ba;s/\n/\\n\n/g' \
|
|
||||||
| sed 's/^/\"/' \
|
|
||||||
| sed 's/$/\"/' \
|
|
||||||
> $1
|
|
23
source/server/CMakeLists.txt
Normal file
23
source/server/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# ====================== ttrts =======================
|
||||||
|
# Project name
|
||||||
|
project( ttrts-server )
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
../system
|
||||||
|
../ttrts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the sources
|
||||||
|
set( SOURCES
|
||||||
|
server.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the executable
|
||||||
|
add_executable( ${PROJECT_NAME} ${SOURCES} )
|
||||||
|
|
||||||
|
# dependent on main ttrts libary
|
||||||
|
target_link_libraries( ${PROJECT_NAME} ttrts ttrts-system pthread )
|
||||||
|
|
||||||
|
# Installation target
|
||||||
|
install( TARGETS ${PROJECT_NAME} DESTINATION bin )
|
98
source/server/server.cpp
Normal file
98
source/server/server.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
|
||||||
|
void RunServerForGame(CTTRTSGame &game)
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Setting up server"<<std::endl;
|
||||||
|
|
||||||
|
// Server side information
|
||||||
|
sockaddr_in serv_addr = GetLocalServerAddress();
|
||||||
|
|
||||||
|
// socket File descriptor
|
||||||
|
int sockfd = SetUpServerListeningSocket(serv_addr);
|
||||||
|
|
||||||
|
// Get information about the game
|
||||||
|
std::vector<player_t> players = game.GetPlayers();
|
||||||
|
unsigned int numClients = players.size();
|
||||||
|
auto player_iterator = players.begin();
|
||||||
|
|
||||||
|
// game mutex
|
||||||
|
std::mutex gameMutex;
|
||||||
|
|
||||||
|
// Set of clients
|
||||||
|
std::vector<ClientInfo> myClients;
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: Waiting for "<<numClients<<" players"<<std::endl;
|
||||||
|
|
||||||
|
// Loop while we're connecting the clients
|
||||||
|
while ( myClients.size() < numClients )
|
||||||
|
{
|
||||||
|
// information for each client
|
||||||
|
ClientInfo clientInfo;
|
||||||
|
clientInfo.player = *player_iterator;
|
||||||
|
player_iterator++;
|
||||||
|
|
||||||
|
clientInfo = WaitForClientConnection(sockfd, game.GetName(), clientInfo);
|
||||||
|
|
||||||
|
// Add out client info to our list
|
||||||
|
myClients.push_back(clientInfo);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: All players connected"<< std::endl;
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: Hit enter to begin...";
|
||||||
|
std::cin.ignore();
|
||||||
|
|
||||||
|
// Loop for each turn
|
||||||
|
while ( !game.GameOver() )
|
||||||
|
{
|
||||||
|
// Send data to clients
|
||||||
|
std::cout<<"TTRTS: Broadcasting Gamestate"<< std::endl;
|
||||||
|
SendGamestateToClients(myClients, game, gameMutex);
|
||||||
|
|
||||||
|
// Wait for orders from clients
|
||||||
|
std::cout<<"TTRTS: Waiting for orders from players"<< std::endl;
|
||||||
|
WaitForOrdersFromClients(myClients, game, gameMutex);
|
||||||
|
|
||||||
|
std::cout<<"TTRTS: All orders recieved, simulating turn"<< std::endl;
|
||||||
|
|
||||||
|
// Step to the next turn
|
||||||
|
gameMutex.lock();
|
||||||
|
game.SimulateToNextTurn();
|
||||||
|
gameMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send final state to all the clients
|
||||||
|
SendGamestateToClients(myClients, game, gameMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// argv[1] needs to be a valid game file
|
||||||
|
if( argc < 2 )
|
||||||
|
fatal_error("Usage: ttrts-server MAPFILE");
|
||||||
|
|
||||||
|
// Set up game
|
||||||
|
CTTRTSGame game = GetGameFromFile(argv[1]);
|
||||||
|
if(game.GetNumUnits() == 0)
|
||||||
|
fatal_error("game not valid");
|
||||||
|
|
||||||
|
RunServerForGame(game);
|
||||||
|
|
||||||
|
// Return winning player and output game end
|
||||||
|
return OutputGameEnd(game);
|
||||||
|
}
|
21
source/system/CMakeLists.txt
Normal file
21
source/system/CMakeLists.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
cmake_minimum_required(VERSION 2.8.7)
|
||||||
|
|
||||||
|
# Main ttrts library
|
||||||
|
project( ttrts-system )
|
||||||
|
|
||||||
|
# Include the maths
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
../ttrts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add our sources
|
||||||
|
set( SOURCES
|
||||||
|
net.cpp
|
||||||
|
filesystem.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add this library
|
||||||
|
add_library( ${PROJECT_NAME} ${SOURCES} )
|
||||||
|
|
||||||
|
target_link_libraries( ${PROJECT_NAME} ttrts pthread )
|
25
source/system/error.h
Normal file
25
source/system/error.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef _TTRTS_ERROR_H_
|
||||||
|
#define _TTRTS_ERROR_H_
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
//======================================================================================================================
|
||||||
|
// Error functions
|
||||||
|
|
||||||
|
// For local fatal errors
|
||||||
|
inline void fatal_error(const char *msg)
|
||||||
|
{
|
||||||
|
std::cerr<<msg<<std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For system fatal errors (ie. functions that set errno)
|
||||||
|
inline void fatal_perror(const char *msg)
|
||||||
|
{
|
||||||
|
perror(msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
248
source/system/filesystem.cpp
Normal file
248
source/system/filesystem.cpp
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
#include "filesystem.h"
|
||||||
|
#include "net.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <formatters.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
// time for waiting between file stats
|
||||||
|
static const std::chrono::milliseconds sk_waitTime = std::chrono::milliseconds(100);
|
||||||
|
|
||||||
|
// Check if a file exists
|
||||||
|
bool FileExists( const std::string& name )
|
||||||
|
{
|
||||||
|
struct stat buffer;
|
||||||
|
return (stat (name.c_str(), &buffer) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a file to exist
|
||||||
|
void WaitForFile( const std::string& name, const std::chrono::milliseconds& time )
|
||||||
|
{
|
||||||
|
while( !FileExists(name) ) std::this_thread::sleep_for(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OutputGameStateFile(CTTRTSGame &game)
|
||||||
|
{
|
||||||
|
char turnFileName[128];
|
||||||
|
snprintf(turnFileName,128,"%s%s/Turn_%i.txt", getGamesDir().c_str(),game.GetName().c_str(),game.GetTurn());
|
||||||
|
std::ofstream turnFile(turnFileName, std::ios_base::trunc); // truncate to overwrite if a file exists
|
||||||
|
|
||||||
|
if ( turnFile.bad() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the turn description
|
||||||
|
std::string turnDescriptor = GetStringFromGame(game);
|
||||||
|
|
||||||
|
turnFile<<turnDescriptor;
|
||||||
|
turnFile.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getMapsDir()
|
||||||
|
{
|
||||||
|
std::string maps = STRINGIFY(TTRTS_MAPS);
|
||||||
|
if( getenv("TTRTS_MAPS") )
|
||||||
|
{
|
||||||
|
maps = getenv("TTRTS_MAPS");
|
||||||
|
|
||||||
|
// Additional trailing slash
|
||||||
|
if( maps.back() != '/' )
|
||||||
|
maps += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getGamesDir()
|
||||||
|
{
|
||||||
|
std::string dir = STRINGIFY(TTRTS_GAMES);
|
||||||
|
if( getenv("TTRTS_GAMES") )
|
||||||
|
{
|
||||||
|
dir = getenv("TTRTS_GAMES");
|
||||||
|
|
||||||
|
// Additional trailing slash
|
||||||
|
if( dir.back() != '/' )
|
||||||
|
dir += "/";
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTTRTSGame GetGameFromFile( const std::string& filename )
|
||||||
|
{
|
||||||
|
std::string gamefile = filename;
|
||||||
|
|
||||||
|
// Default for maps
|
||||||
|
std::string ttrts_maps_dir = getMapsDir();
|
||||||
|
|
||||||
|
// If file path is not local path and file doesn't exist
|
||||||
|
if( gamefile.find("/") == std::string::npos
|
||||||
|
&& access( gamefile.c_str(), F_OK ) == -1 )
|
||||||
|
{
|
||||||
|
gamefile = ttrts_maps_dir + gamefile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not good
|
||||||
|
if( access( gamefile.c_str(), F_OK ) == -1 )
|
||||||
|
fatal_perror("Could not open game file");
|
||||||
|
|
||||||
|
std::ifstream file(gamefile);
|
||||||
|
|
||||||
|
std::string gameDescriptor;
|
||||||
|
|
||||||
|
// Reserve the string needed up front
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
gameDescriptor.reserve(file.tellg());
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
// Grab the string from the file
|
||||||
|
gameDescriptor.assign((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
if( gameDescriptor.size() == 0 )
|
||||||
|
fatal_error("failed to read in any information from gamefile");
|
||||||
|
|
||||||
|
// Create the game
|
||||||
|
return GetGameFromString(gameDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player)
|
||||||
|
{
|
||||||
|
std::string gameDir = getGamesDir();
|
||||||
|
|
||||||
|
char playerOrderFileName[128];
|
||||||
|
snprintf(playerOrderFileName, 128, "%s%s/Player_%i_Turn_%i.txt", gameDir.c_str(),game.GetName().c_str(),(int) player, game.GetTurn());
|
||||||
|
|
||||||
|
// Wait for the player order file to be created
|
||||||
|
std::clog<<"TTRTS: Waiting for "<< playerOrderFileName << std::endl;
|
||||||
|
bool hasOrderFile = false;
|
||||||
|
while(!hasOrderFile)
|
||||||
|
{
|
||||||
|
WaitForFile(playerOrderFileName,sk_waitTime); // Wait for the file
|
||||||
|
|
||||||
|
// File must have END
|
||||||
|
// Method taken from http://stackoverflow.com/questions/11876290/c-fastest-way-to-read-only-last-line-of-text-file
|
||||||
|
std::ifstream turnFile(playerOrderFileName);
|
||||||
|
turnFile.seekg(-1, std::ios_base::end);
|
||||||
|
|
||||||
|
// Loop back from the end of file
|
||||||
|
bool keepLooping = true;
|
||||||
|
while(keepLooping) {
|
||||||
|
char ch;
|
||||||
|
turnFile.get(ch); // Get current byte's data
|
||||||
|
|
||||||
|
if((int)turnFile.tellg() <= 1) { // If the data was at or before the 0th byte
|
||||||
|
turnFile.seekg(0); // The first line is the last line
|
||||||
|
keepLooping = false; // So stop there
|
||||||
|
}
|
||||||
|
else if(ch == '\n') { // If the data was a newline
|
||||||
|
keepLooping = false; // Stop at the current position.
|
||||||
|
}
|
||||||
|
else { // If the data was neither a newline nor at the 0 byte
|
||||||
|
turnFile.seekg(-2, std::ios_base::cur); // Move to the front of that data, then to the front of the data before it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab this line
|
||||||
|
std::string lastLine;
|
||||||
|
getline(turnFile,lastLine);
|
||||||
|
if(lastLine == "END")
|
||||||
|
hasOrderFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream turnFile(playerOrderFileName);
|
||||||
|
|
||||||
|
// Reserve the full order string
|
||||||
|
std::string orders;
|
||||||
|
turnFile.seekg(0, std::ios_base::end);
|
||||||
|
orders.reserve(turnFile.tellg());
|
||||||
|
turnFile.seekg(0, std::ios_base::beg);
|
||||||
|
|
||||||
|
// Grab the string from the file
|
||||||
|
orders.assign((std::istreambuf_iterator<char>(turnFile)), std::istreambuf_iterator<char>());
|
||||||
|
return orders;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CreateAndCleanGameDir(const std::string& gameName)
|
||||||
|
{
|
||||||
|
std::string gameDir = getGamesDir()+gameName;
|
||||||
|
struct stat info;
|
||||||
|
int ret = stat( gameDir.c_str(), &info );
|
||||||
|
if( ret == 0 && info.st_mode & S_IFDIR )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: " << gameDir << " game directory already exists"<<std::endl;
|
||||||
|
std::cout<<"TTRTS: Confirm to delete contents [y/N] ";
|
||||||
|
std::string input;
|
||||||
|
std::cin>>input;
|
||||||
|
if( !input.size() || std::tolower(input[0]) != 'y' )
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if ( ret == 0 )
|
||||||
|
{
|
||||||
|
fatal_error("TTRTS_GAMES exists but is not directory \nAborting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the game directory
|
||||||
|
char cmd2[128];
|
||||||
|
snprintf(cmd2,128, "test -d %s || mkdir %s",gameDir.c_str(),gameDir.c_str());
|
||||||
|
if( system(cmd2) == -1)
|
||||||
|
fatal_error("Error: Failed to create the game directory");
|
||||||
|
|
||||||
|
// Clean out the game directory
|
||||||
|
char cmd1[128];
|
||||||
|
snprintf(cmd1,128, "rm -rf %s/*",gameDir.c_str());
|
||||||
|
if ( system(cmd1) == -1 )
|
||||||
|
fatal_error("Error: Failed to clean the game directory");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OutputGameEnd(const CTTRTSGame &game) {
|
||||||
|
std::cout<<"TTRTS: Game Over!"<< std::endl;
|
||||||
|
|
||||||
|
// Get the winning player
|
||||||
|
player_t winningPlayer = game.GetWinningPlayer();
|
||||||
|
|
||||||
|
// Print the winner!
|
||||||
|
if ( winningPlayer != player_t::NUM_INVALID )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: It was a draw!"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)winningPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OutputgameEnd(const CTTRTSGame &game) {
|
||||||
|
std::cout<<"TTRTS: Game Over!"<< std::endl;
|
||||||
|
|
||||||
|
// Get the winning player
|
||||||
|
player_t winningPlayer = game.GetWinningPlayer();
|
||||||
|
|
||||||
|
// Print the winner!
|
||||||
|
if ( winningPlayer != player_t::NUM_INVALID )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: It was a draw!"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)winningPlayer;
|
||||||
|
}
|
29
source/system/filesystem.h
Normal file
29
source/system/filesystem.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef _TTRTS_FILESYSTEM_H_
|
||||||
|
#define _TTRTS_FILESYSTEM_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
#define STRINGIFY(x) _STRINGIFY(x)
|
||||||
|
#define _STRINGIFY(x) #x
|
||||||
|
|
||||||
|
bool FileExists( const std::string& name );
|
||||||
|
|
||||||
|
void WaitForFile( const std::string& name, const std::chrono::milliseconds& time );
|
||||||
|
|
||||||
|
bool OutputGameStateFile(CTTRTSGame &game);
|
||||||
|
|
||||||
|
std::string GetOrdersFromPlayerFile(const CTTRTSGame &game, player_t &player);
|
||||||
|
|
||||||
|
CTTRTSGame GetGameFromFile( const std::string& file );
|
||||||
|
|
||||||
|
std::string getMapsDir();
|
||||||
|
std::string getGamesDir();
|
||||||
|
|
||||||
|
int runFromFilesystem(int argc, char* argv[]);
|
||||||
|
|
||||||
|
int CreateAndCleanGameDir(const std::string& gameName);
|
||||||
|
|
||||||
|
#endif
|
261
source/system/net.cpp
Normal file
261
source/system/net.cpp
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
#include "net.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut)
|
||||||
|
{
|
||||||
|
char buffer[1028]; // buffer for orders
|
||||||
|
|
||||||
|
std::clog<<"TTRTS: Waiting for player "<<(int)info.player<<" at "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
|
||||||
|
|
||||||
|
std::string orders;
|
||||||
|
|
||||||
|
while ( orders.find("END") == std::string::npos )
|
||||||
|
{
|
||||||
|
memset(buffer,0,sizeof(buffer));
|
||||||
|
|
||||||
|
// Read in the new socket
|
||||||
|
// read will block until the client has called write
|
||||||
|
// up to the full size of the buffer
|
||||||
|
if (read(info.clientsockfd,buffer,sizeof(buffer)-1) < 0)
|
||||||
|
fatal_perror("ERROR reading from client");
|
||||||
|
|
||||||
|
// Append the received orders
|
||||||
|
orders+=buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::clog<<"TTRTS: Recieved orders from "<<inet_ntoa(info.cli_addr.sin_addr)<<std::endl;
|
||||||
|
|
||||||
|
mut.lock();
|
||||||
|
game.IssueOrders(info.player , orders);
|
||||||
|
mut.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForOrdersFromClients(std::vector<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex)
|
||||||
|
{
|
||||||
|
// Spawn threads
|
||||||
|
std::vector<std::thread> clientThreads;
|
||||||
|
for(auto client : myClients)
|
||||||
|
{
|
||||||
|
std::thread clientThread(WaitForOrdersFromClient, client, std::ref(game), ref(gameMutex));
|
||||||
|
clientThreads.push_back(move(clientThread));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join up all the threads
|
||||||
|
for ( std::thread& thread : clientThreads )
|
||||||
|
{
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendGamestateToClients(std::vector<ClientInfo> &myClients, const CTTRTSGame &game, std::mutex &gameMutex)
|
||||||
|
{
|
||||||
|
gameMutex.lock();
|
||||||
|
std::string gamestate_string = GetStringFromGame(game);
|
||||||
|
gameMutex.unlock();
|
||||||
|
|
||||||
|
for (auto client : myClients)
|
||||||
|
{
|
||||||
|
// Write to the socket with the buffer
|
||||||
|
if ( write( client.clientsockfd, gamestate_string.c_str(), gamestate_string.length() ) < 0 )
|
||||||
|
fatal_perror("ERROR sending to client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString)
|
||||||
|
{
|
||||||
|
char handshakeBuffer[128];
|
||||||
|
memset(handshakeBuffer,0,sizeof(handshakeBuffer));
|
||||||
|
|
||||||
|
if (read(sockfd, handshakeBuffer,sizeof(handshakeBuffer)-1) < 0)
|
||||||
|
fatal_perror("ERROR recieving handshake from server");
|
||||||
|
|
||||||
|
std::string handshake(handshakeBuffer);
|
||||||
|
|
||||||
|
if ( write( sockfd, handshake.c_str(), handshake.length()+1 ) < 0 )
|
||||||
|
fatal_perror("ERROR sending handshake to server");
|
||||||
|
|
||||||
|
char gameName[64];
|
||||||
|
if ( sscanf(handshake.c_str(),TTRTS_HANDSHAKE_FORMAT,&player,gameName) < 2 )
|
||||||
|
fatal_error("Handshake failed");
|
||||||
|
|
||||||
|
gameNameString = gameName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerformServerHandshake(const ClientInfo &client, const std::string &game)
|
||||||
|
{
|
||||||
|
char handshake[64];
|
||||||
|
snprintf(handshake, sizeof(handshake), TTRTS_HANDSHAKE_FORMAT,(unsigned int)client.player,game.c_str());
|
||||||
|
|
||||||
|
// Send handshake
|
||||||
|
if ( write( client.clientsockfd,handshake,sizeof(handshake) ) < 0 )
|
||||||
|
fatal_perror("ERROR sending to client");
|
||||||
|
|
||||||
|
// Receive handshake
|
||||||
|
char buffer[64];
|
||||||
|
if ( read(client.clientsockfd,buffer,sizeof(buffer)-1) < 0 )
|
||||||
|
fatal_perror("ERROR reading from client");
|
||||||
|
|
||||||
|
// Verify handshake
|
||||||
|
if ( std::string(buffer) != std::string(handshake) )
|
||||||
|
fatal_error("Error in client handshake");
|
||||||
|
|
||||||
|
std::clog<<"TTRTS: Success on handshake with player "<<(int)client.player<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TryBindSocket(int sockfd, const sockaddr_in &serv_addr)
|
||||||
|
{
|
||||||
|
std::clog<<"TTRTS: Binding to socket"<< std::endl;
|
||||||
|
int retry = 1;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if(retry > 10)
|
||||||
|
fatal_error("Binding failed");
|
||||||
|
|
||||||
|
// Attempt to bind our listening socket
|
||||||
|
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) >= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::cerr<<"Warning: Binding failed on try "<<retry<< std::endl;
|
||||||
|
sleep(retry);
|
||||||
|
retry++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo)
|
||||||
|
{
|
||||||
|
socklen_t clilen = sizeof(sockaddr_in);
|
||||||
|
|
||||||
|
// accept waits for a connection from a client
|
||||||
|
// it returns a new socket file descriptor for this connection
|
||||||
|
// client information will be stored in cli_addr
|
||||||
|
clientInfo.clientsockfd = accept(sockfd, (sockaddr *) &clientInfo.cli_addr, &clilen);
|
||||||
|
if (clientInfo.clientsockfd < 0)
|
||||||
|
fatal_perror("ERROR on accept");
|
||||||
|
|
||||||
|
std::clog<<"TTRTS: Client connected from "<<inet_ntoa(clientInfo.cli_addr.sin_addr)<<" socket "<<clientInfo.clientsockfd<< std::endl;
|
||||||
|
|
||||||
|
// Handshake with client
|
||||||
|
PerformServerHandshake(clientInfo, game);
|
||||||
|
return clientInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int SetUpServerListeningSocket(const sockaddr_in &serv_addr)
|
||||||
|
{
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
std::clog<<"TTRTS: Opening socket"<< std::endl;
|
||||||
|
// Create a new socket
|
||||||
|
// AF_INET is general internet socket domain
|
||||||
|
// SOCK_STREAM as messages will be read in on this socket, SOCK_DGRAM would be for packets
|
||||||
|
// 0 is for default protocol
|
||||||
|
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sockfd < 0)
|
||||||
|
fatal_perror("ERROR opening socket");
|
||||||
|
|
||||||
|
// bind our socket to this server address
|
||||||
|
TryBindSocket(sockfd, serv_addr);
|
||||||
|
|
||||||
|
// Listen on the socket for messages
|
||||||
|
// Second param is length of backlog queue, the maximum number of connections
|
||||||
|
// that can be waiting while the process is handling a single connection
|
||||||
|
// max is usually set to 5
|
||||||
|
listen(sockfd,5);
|
||||||
|
return sockfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sockaddr_in GetLocalServerAddress()
|
||||||
|
{
|
||||||
|
sockaddr_in serv_addr; // Server address
|
||||||
|
|
||||||
|
// empty the server address
|
||||||
|
memset(&serv_addr,0, sizeof(serv_addr));
|
||||||
|
// Set the server address family to AF_INET
|
||||||
|
serv_addr.sin_family = AF_INET;
|
||||||
|
// htons swaps from host byte order to network byte order
|
||||||
|
serv_addr.sin_port = htons(TTRTS_PORT);
|
||||||
|
// The host for this address is this current machine's IP, INADDR_ANY fetches this
|
||||||
|
serv_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
return serv_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr)
|
||||||
|
{
|
||||||
|
// Create a new socket
|
||||||
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sockfd < 0)
|
||||||
|
fatal_perror("ERROR opening socket");
|
||||||
|
|
||||||
|
// Get the hostent information for the host by name
|
||||||
|
hostent *server = gethostbyname(hostname.c_str());
|
||||||
|
if (server == NULL)
|
||||||
|
fatal_error("ERROR, no such host");
|
||||||
|
|
||||||
|
// copy the server address into our server_addr struct
|
||||||
|
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||||
|
|
||||||
|
// Attempt to connect to the server using the socket and server address info
|
||||||
|
if (connect(sockfd, (const sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
|
||||||
|
fatal_perror("ERROR connecting");
|
||||||
|
|
||||||
|
return sockfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WaitForGamestateMessage(int sockfd)
|
||||||
|
{
|
||||||
|
std::string gamestate;
|
||||||
|
char gamestateBuffer[1028];
|
||||||
|
while( gamestate.find("END") == std::string::npos )
|
||||||
|
{
|
||||||
|
memset(gamestateBuffer,0,sizeof(gamestateBuffer));
|
||||||
|
|
||||||
|
// Receive gamestate
|
||||||
|
if (read(sockfd,gamestateBuffer,sizeof(gamestateBuffer)-1) < 0)
|
||||||
|
fatal_perror("ERROR reading from client");
|
||||||
|
|
||||||
|
gamestate+=gamestateBuffer;
|
||||||
|
}
|
||||||
|
return gamestate;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SendOrdersToServer(int sockfd, const std::string &orders)
|
||||||
|
{
|
||||||
|
int n = write(sockfd,orders.c_str(),orders.length());
|
||||||
|
if (n < 0)
|
||||||
|
fatal_perror("ERROR writing to socket");
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int OutputGameEnd( CTTRTSGame& game )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Game Over!"<<std::endl;
|
||||||
|
|
||||||
|
// Get the winning player
|
||||||
|
player_t winningPlayer = game.GetWinningPlayer();
|
||||||
|
|
||||||
|
// Print the winner!
|
||||||
|
if ( winningPlayer != player_t::NUM_INVALID )
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: Winner:"<<(int) winningPlayer <<std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout<<"TTRTS: It was a draw!"<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)winningPlayer;
|
||||||
|
}
|
81
source/system/net.h
Normal file
81
source/system/net.h
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#ifndef _TTRTS_NET_H_
|
||||||
|
#define _TTRTS_NET_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <game.h>
|
||||||
|
#include <formatters.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define TTRTS_HANDSHAKE_FORMAT "player %u name %s"
|
||||||
|
|
||||||
|
//======================================================================================================================
|
||||||
|
// Structs for net management
|
||||||
|
|
||||||
|
// Struct for net client info
|
||||||
|
struct ClientInfo
|
||||||
|
{
|
||||||
|
sockaddr_in cli_addr;
|
||||||
|
int clientsockfd;
|
||||||
|
player_t player;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//======================================================================================================================
|
||||||
|
// Server side function
|
||||||
|
|
||||||
|
// Get the address of a local server
|
||||||
|
sockaddr_in GetLocalServerAddress();
|
||||||
|
|
||||||
|
// Set up a new listening socket for the server
|
||||||
|
int SetUpServerListeningSocket(const sockaddr_in &serv_addr);
|
||||||
|
|
||||||
|
// Wait for client connection on listening socket sockfd
|
||||||
|
// Will fill clientInfo with client information
|
||||||
|
ClientInfo &WaitForClientConnection(int sockfd, const std::string &game, ClientInfo &clientInfo);
|
||||||
|
|
||||||
|
// Wait for orders from a client, will not return until client has send valid orders
|
||||||
|
// Will automatically add orders to the game
|
||||||
|
void WaitForOrdersFromClient(const ClientInfo info, CTTRTSGame &game, std::mutex &mut);
|
||||||
|
|
||||||
|
// Iterates through a list of clients calling WaitForOrdersFromClient
|
||||||
|
void WaitForOrdersFromClients(std::vector<ClientInfo> &myClients, CTTRTSGame &game, std::mutex &gameMutex);
|
||||||
|
|
||||||
|
// Sends current gamestate to each client
|
||||||
|
void SendGamestateToClients(std::vector<ClientInfo> &myClients, const CTTRTSGame &game, std::mutex &gameMutex);
|
||||||
|
|
||||||
|
// Tries to bind to a socket, will attempt 10 times with longer waits between
|
||||||
|
void TryBindSocket(int sockfd, const sockaddr_in &serv_addr);
|
||||||
|
|
||||||
|
// Perform the server side handshake with a client
|
||||||
|
void PerformServerHandshake(const ClientInfo &client, const std::string &game);
|
||||||
|
|
||||||
|
//======================================================================================================================
|
||||||
|
// Client side functions
|
||||||
|
|
||||||
|
// Connect to the host, returns new socket for communication
|
||||||
|
int ConnectToHostServer(const std::string &hostname, sockaddr_in &serv_addr);
|
||||||
|
|
||||||
|
// Perform the client side handshake with the server
|
||||||
|
void PerformClientHandshake(int sockfd, unsigned int &player, std::string &gameNameString);
|
||||||
|
|
||||||
|
// Wait for gamestate message from host
|
||||||
|
std::string WaitForGamestateMessage(int sockfd);
|
||||||
|
|
||||||
|
// Send orders to the server
|
||||||
|
int SendOrdersToServer(int sockfd, const std::string &orders);
|
||||||
|
|
||||||
|
//======================================================================================================================
|
||||||
|
// Other functions
|
||||||
|
int OutputGameEnd( CTTRTSGame& game );
|
||||||
|
|
||||||
|
#endif
|
|
@ -4,8 +4,7 @@
|
||||||
project( ttrts-test )
|
project( ttrts-test )
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
../game
|
../ttrts
|
||||||
../maths
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set( SOURCES
|
set( SOURCES
|
||||||
|
@ -15,4 +14,4 @@ set( SOURCES
|
||||||
# Add the executable
|
# Add the executable
|
||||||
add_executable( ttrts-test ${SOURCES} )
|
add_executable( ttrts-test ${SOURCES} )
|
||||||
|
|
||||||
target_link_libraries( ttrts-test game )
|
target_link_libraries( ttrts-test ttrts )
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <formatters.h>
|
||||||
#include <iostream> // std::cout
|
#include <iostream> // std::cout
|
||||||
|
|
||||||
#include "order.h"
|
#include "order.h"
|
||||||
|
@ -8,8 +9,8 @@ const char* tests()
|
||||||
// Test if we can properly set a unit's visual
|
// Test if we can properly set a unit's visual
|
||||||
{
|
{
|
||||||
CUnit unit;
|
CUnit unit;
|
||||||
unit.setFromVisual('v');
|
unit.SetFromVisual('v');
|
||||||
if( unit.getVisual() != 'v' )
|
if(unit.GetVisual() != 'v' )
|
||||||
return "failed to properly create V unit";
|
return "failed to properly create V unit";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ const char* tests()
|
||||||
{
|
{
|
||||||
CUnit unit;
|
CUnit unit;
|
||||||
CUnit unit2;
|
CUnit unit2;
|
||||||
if( unit.getID() == unit2.getID() )
|
if(unit.GetID() == unit2.GetID() )
|
||||||
return "Unit IDs the same";
|
return "Unit IDs the same";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +26,8 @@ const char* tests()
|
||||||
{
|
{
|
||||||
CUnit unit1;
|
CUnit unit1;
|
||||||
|
|
||||||
std::string unit1Desc = CUnit::GetStringFromUnit(unit1);
|
std::string unit1Desc = GetStringFromUnit(unit1);
|
||||||
CUnit unit2 = CUnit::GetUnitFromString(unit1Desc);
|
CUnit unit2 = GetUnitFromString(unit1Desc);
|
||||||
|
|
||||||
if ( unit1 != unit2 )
|
if ( unit1 != unit2 )
|
||||||
return "Failed to convert an empty unit to string and back";
|
return "Failed to convert an empty unit to string and back";
|
||||||
|
@ -35,12 +36,12 @@ const char* tests()
|
||||||
// Test custom unit conversion
|
// Test custom unit conversion
|
||||||
{
|
{
|
||||||
CUnit unit1;
|
CUnit unit1;
|
||||||
unit1.setFromVisual('v');
|
unit1.SetFromVisual('v');
|
||||||
unit1.setTeam(Team::Green);
|
unit1.SetPlayer(player_t::Green);
|
||||||
unit1.setPos( uvector2(5,10) );
|
unit1.SetPos(uvector2(5, 10));
|
||||||
|
|
||||||
std::string unit1Desc = CUnit::GetStringFromUnit(unit1);
|
std::string unit1Desc = GetStringFromUnit(unit1);
|
||||||
CUnit unit2 = CUnit::GetUnitFromString(unit1Desc);
|
CUnit unit2 = GetUnitFromString(unit1Desc);
|
||||||
|
|
||||||
if ( unit1 != unit2 )
|
if ( unit1 != unit2 )
|
||||||
return "Failed to convert custom unit to string and back";
|
return "Failed to convert custom unit to string and back";
|
||||||
|
@ -49,17 +50,17 @@ const char* tests()
|
||||||
// Test if we can successfully create a unit from a visual
|
// Test if we can successfully create a unit from a visual
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis('v');
|
CUnit unit = CUnit::GetUnitFromVis('v');
|
||||||
if( unit.getVisual() != 'v' )
|
if(unit.GetVisual() != 'v' )
|
||||||
return "failed to properly create V unit with factory";
|
return "failed to properly create V unit with factory";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test if we can successfully convert orders back and forth
|
// Test if we can successfully convert orders back and forth
|
||||||
{
|
{
|
||||||
COrder order;
|
SOrder order;
|
||||||
order.command = command_c::F;
|
order.command = command_c::F;
|
||||||
order.unit = 10;
|
order.unit = 10;
|
||||||
std::string order_string = GetStringFromOrder(order);
|
std::string order_string = GetStringFromOrder(order);
|
||||||
COrder order2 = GetOrderFromString(order_string);
|
SOrder order2 = GetOrderFromString(order_string);
|
||||||
|
|
||||||
if ( order2 != order )
|
if ( order2 != order )
|
||||||
return "failed order string conversion test";
|
return "failed order string conversion test";
|
||||||
|
@ -81,16 +82,16 @@ const char* tests()
|
||||||
|
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis('^');
|
CUnit unit = CUnit::GetUnitFromVis('^');
|
||||||
unit.setPos( {2,2} );
|
unit.SetPos({2, 2});
|
||||||
unit.setTeam(Team::Red);
|
unit.SetPlayer(player_t::Red);
|
||||||
|
|
||||||
game.AddUnit(std::move(unit));
|
game.AddUnit(std::move(unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis('^');
|
CUnit unit = CUnit::GetUnitFromVis('^');
|
||||||
unit.setPos( {2,2} );
|
unit.SetPos({2, 2});
|
||||||
unit.setTeam(Team::Red);
|
unit.SetPlayer(player_t::Red);
|
||||||
|
|
||||||
if( !game.AddUnit(std::move(unit)) )
|
if( !game.AddUnit(std::move(unit)) )
|
||||||
return "Game should have rejected unit placed on the same spot";
|
return "Game should have rejected unit placed on the same spot";
|
||||||
|
@ -105,11 +106,11 @@ const char* tests()
|
||||||
CTTRTSGame game( 5, 5 );
|
CTTRTSGame game( 5, 5 );
|
||||||
|
|
||||||
CUnit unit = CUnit::GetUnitFromVis('>');
|
CUnit unit = CUnit::GetUnitFromVis('>');
|
||||||
const unit_id_t id = unit.getID();
|
const unit_id_t id = unit.GetID();
|
||||||
COrder order;
|
SOrder order;
|
||||||
|
|
||||||
unit.setPos( {2,2} );
|
unit.SetPos({2, 2});
|
||||||
unit.setTeam(Team::Red);
|
unit.SetPlayer(player_t::Red);
|
||||||
|
|
||||||
if ( game.AddUnit(std::move(unit)) )
|
if ( game.AddUnit(std::move(unit)) )
|
||||||
return "Game failed to add valid unit";
|
return "Game failed to add valid unit";
|
||||||
|
@ -117,13 +118,13 @@ const char* tests()
|
||||||
order.unit = id;
|
order.unit = id;
|
||||||
order.command = command_c::F;
|
order.command = command_c::F;
|
||||||
|
|
||||||
if( game.IssueOrder(Team::Red,order) )
|
if( game.IssueOrder(player_t::Red,order) )
|
||||||
return "Game failed to issue valid order";
|
return "Game failed to issue valid order";
|
||||||
|
|
||||||
if (game.SimulateToNextTurn() )
|
if (game.SimulateToNextTurn() )
|
||||||
return "Game failed to simulate valid turn";
|
return "Game failed to simulate valid turn";
|
||||||
|
|
||||||
if( game.GetUnitByIDConst(id).getPos() != uvector2{3,2} )
|
if(game.GetUnitByIDConst(id).GetPos() != uvector2{3,2} )
|
||||||
return "Simple movement order failed";
|
return "Simple movement order failed";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -136,11 +137,11 @@ const char* tests()
|
||||||
unit_id_t id;
|
unit_id_t id;
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis('>');
|
CUnit unit = CUnit::GetUnitFromVis('>');
|
||||||
id = unit.getID();
|
id = unit.GetID();
|
||||||
COrder order;
|
SOrder order;
|
||||||
|
|
||||||
unit.setPos( {0,0} );
|
unit.SetPos({0, 0});
|
||||||
unit.setTeam(Team::Blue);
|
unit.SetPlayer(player_t::Blue);
|
||||||
|
|
||||||
if ( game.AddUnit(std::move(unit)) )
|
if ( game.AddUnit(std::move(unit)) )
|
||||||
return "Game failed to add valid unit";
|
return "Game failed to add valid unit";
|
||||||
|
@ -148,14 +149,14 @@ const char* tests()
|
||||||
order.unit = id;
|
order.unit = id;
|
||||||
order.command = command_c::A;
|
order.command = command_c::A;
|
||||||
|
|
||||||
if( game.IssueOrder(Team::Blue,order) )
|
if( game.IssueOrder(player_t::Blue,order) )
|
||||||
return "Game failed to issue valid order";
|
return "Game failed to issue valid order";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CUnit unit = CUnit::GetUnitFromVis('<');
|
CUnit unit = CUnit::GetUnitFromVis('<');
|
||||||
|
|
||||||
unit.setPos( {1,0} );
|
unit.SetPos({1, 0});
|
||||||
unit.setTeam(Team::Red);
|
unit.SetPlayer(player_t::Red);
|
||||||
|
|
||||||
if ( game.AddUnit(std::move(unit)) )
|
if ( game.AddUnit(std::move(unit)) )
|
||||||
return "Game failed to add valid unit";
|
return "Game failed to add valid unit";
|
||||||
|
@ -166,19 +167,19 @@ const char* tests()
|
||||||
if ( game.GetNumUnits() != 1 )
|
if ( game.GetNumUnits() != 1 )
|
||||||
return "Game failed to kill a unit when it logically should have";
|
return "Game failed to kill a unit when it logically should have";
|
||||||
|
|
||||||
if ( game.GetUnitByIndex(0).getDir() != dir_t::E )
|
if (game.GetUnitByIndex(0).GetDir() != dir_c::E )
|
||||||
return "Game killed the wrong unit";
|
return "Game killed the wrong unit";
|
||||||
|
|
||||||
if ( game.GetUnitByIndex(0).getID() != id )
|
if (game.GetUnitByIndex(0).GetID() != id )
|
||||||
return "Game killed the wrong unit";
|
return "Game killed the wrong unit";
|
||||||
|
|
||||||
if ( game.CheckForWin() != Team::Blue )
|
if ( game.GetWinningPlayer() != player_t::Blue )
|
||||||
return "Game failed to recognise a win for the right Team";
|
return "Game failed to recognise a win for the right Player";
|
||||||
|
|
||||||
std::string game_string = game.GetStateAsString();
|
std::string game_string = GetStringFromGame(game);
|
||||||
CTTRTSGame game2 = CTTRTSGame::CreateFromString(game_string);
|
CTTRTSGame game2 = GetGameFromString(game_string);
|
||||||
|
|
||||||
std::string game2_string = game2.GetStateAsString();
|
std::string game2_string = GetStringFromGame(game2);
|
||||||
|
|
||||||
// Try matching up the game descriptors
|
// Try matching up the game descriptors
|
||||||
if( game_string != game2_string )
|
if( game_string != game2_string )
|
||||||
|
|
|
@ -1,29 +1,46 @@
|
||||||
# ====================== ttrts =======================
|
cmake_minimum_required(VERSION 2.8.7)
|
||||||
# Project name
|
|
||||||
|
# Main ttrts library
|
||||||
project( ttrts )
|
project( ttrts )
|
||||||
|
|
||||||
|
# Include the maths
|
||||||
include_directories(
|
include_directories(
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
../maths
|
|
||||||
../game
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add the sources
|
# Add our sources
|
||||||
set( SOURCES
|
set( SOURCES
|
||||||
main.cpp
|
game.cpp
|
||||||
|
unit.cpp
|
||||||
|
formatters.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add the executable
|
# Add this library
|
||||||
add_executable( ${PROJECT_NAME} ${SOURCES} )
|
add_library( ttrts ${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(
|
add_custom_target(
|
||||||
gen_ttrts_usage
|
ttrts-version-header ALL
|
||||||
cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_SOURCE_DIR}/scripts/gen_usage.sh "${CMAKE_CURRENT_BINARY_DIR}/usage.h"
|
${CMAKE_SOURCE_DIR}/scripts/gen_version_header.sh ${TTRTS_VERSION_MAJOR} ${TTRTS_VERSION_MINOR} ${TTRTS_VERSION_PATCH}
|
||||||
)
|
)
|
||||||
add_dependencies(${PROJECT_NAME} gen_ttrts_usage)
|
|
||||||
|
# ttrts is dependent on this
|
||||||
|
add_dependencies( ${PROJECT_NAME} ttrts-version-header )
|
||||||
|
|
||||||
|
# Install headers
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
game.h
|
||||||
|
unit.h
|
||||||
|
order.h
|
||||||
|
formatters.h
|
||||||
|
vector2.h
|
||||||
|
orderunitpair.h
|
||||||
|
gametypes.h
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
|
|
||||||
|
DESTINATION
|
||||||
|
include/ttrts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install the ttrts static lib
|
||||||
|
install( TARGETS ttrts DESTINATION lib )
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
## NAME
|
|
||||||
ttrts - Tiny Terminal RTS v0.1.0
|
|
||||||
|
|
||||||
## SYNOPSYS
|
|
||||||
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.
|
|
||||||
|
|
||||||
## USAGE
|
|
||||||
When invoked, ttrts will set up a full game and output a
|
|
||||||
single file representing the current gamestate into a
|
|
||||||
local directory called `ttrts_{GAME_NAME}`.
|
|
||||||
|
|
||||||
This file can be read in and interpretted by human, robot
|
|
||||||
or cat. ttrts will wait for orders files to be placed in
|
|
||||||
it's current working directory.
|
|
||||||
|
|
||||||
Once orders have been set for each player taking part
|
|
||||||
ttrts will calculate the new game state and output a new
|
|
||||||
gamestate file for the next turn.
|
|
||||||
|
|
||||||
This process repeats until a winner is chosen!
|
|
||||||
|
|
||||||
## OPTIONS
|
|
||||||
MAPFILE - File to read in the initial game state from
|
|
||||||
|
|
||||||
--------------------------------------------------------------
|
|
||||||
|
|
||||||
## GAMESTATE FILE FORMAT
|
|
||||||
### Name
|
|
||||||
Turn_{TURN_NUMBER}.txt
|
|
||||||
### Contents
|
|
||||||
===== ttrts v{MAJOR}.{MINOR}.{PATCH} =====
|
|
||||||
NAME:{GAMENAME}
|
|
||||||
SIZE:[{X},{Y}]
|
|
||||||
TURN:{TURN_NUMBER}
|
|
||||||
... {any extra properties could go here}
|
|
||||||
~~~~
|
|
||||||
UNIT:{ID} tm:{TEAM} vs:{VIS} dr:{DIR(NESW)} ps:[{X},{Y}]
|
|
||||||
... {continue for all units}
|
|
||||||
END
|
|
||||||
|
|
||||||
## ORDER FILE FORMAT
|
|
||||||
### Name
|
|
||||||
Turn_{TURN_NUMBER}_Team_{TEAM_NUMBER}.txt
|
|
||||||
### Contents
|
|
||||||
ORDER:{ORDER_CHAR} id:{UNIT_ID}
|
|
||||||
... {continue for all orders}
|
|
||||||
END
|
|
||||||
|
|
||||||
### Orders
|
|
||||||
F - move unit [F]orward one space
|
|
||||||
L/R - rotate unit [L]eft or [R]ight
|
|
||||||
A - [A]ttack in straight line in front of unit
|
|
212
source/ttrts/formatters.cpp
Normal file
212
source/ttrts/formatters.cpp
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
#include "formatters.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
// Get the game information as a string
|
||||||
|
std::string GetStringFromGame( const CTTRTSGame& game )
|
||||||
|
{
|
||||||
|
// Grab the walls
|
||||||
|
std::string walls;
|
||||||
|
if( game.GetWalls().size() == 0 )
|
||||||
|
{
|
||||||
|
walls = "NONE";
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( auto wall : game.GetWalls() )
|
||||||
|
{
|
||||||
|
char pos[16];
|
||||||
|
if( snprintf(pos, 16, GAME_POS_FORMATTER , wall.x, wall.y ) < 0 )
|
||||||
|
{
|
||||||
|
return "BUFFER OVERFLOW";
|
||||||
|
}
|
||||||
|
walls += pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Print out the header
|
||||||
|
char header[512];
|
||||||
|
if ( snprintf(header, 512, GAME_HEADER_FORMATTER ,
|
||||||
|
TTRTS_VERSION_MAJOR,
|
||||||
|
TTRTS_VERSION_MINOR,
|
||||||
|
TTRTS_VERSION_PATCH,
|
||||||
|
game.GetName().c_str(),
|
||||||
|
game.GetDimensions().x,
|
||||||
|
game.GetDimensions().y,
|
||||||
|
game.GetTurn(),
|
||||||
|
walls.c_str() ) < 0 )
|
||||||
|
{
|
||||||
|
return "BUFFER OVERFLOW";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather unit information
|
||||||
|
std::string units;
|
||||||
|
for ( const SOrderUnitPair & pair : game.GetOrderUnitPairs() )
|
||||||
|
{
|
||||||
|
units += GetStringFromUnit(pair.unit);
|
||||||
|
units += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the header and units
|
||||||
|
std::string state(header);
|
||||||
|
state += '\n';
|
||||||
|
state += GAME_HEADER_DELIMITER;
|
||||||
|
state += units;
|
||||||
|
state += "END";
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the game information as a string
|
||||||
|
CTTRTSGame GetGameFromString( const std::string& input )
|
||||||
|
{
|
||||||
|
size_t headerEnd = input.find(GAME_HEADER_DELIMITER);
|
||||||
|
|
||||||
|
std::string header = input.substr(0, headerEnd);
|
||||||
|
std::string units = input.substr(headerEnd + strlen(GAME_HEADER_DELIMITER));
|
||||||
|
|
||||||
|
// Grab information from the header
|
||||||
|
unsigned int major;
|
||||||
|
unsigned int minor;
|
||||||
|
unsigned int patch;
|
||||||
|
char name[64];
|
||||||
|
unsigned int turn;
|
||||||
|
unsigned int sizex;
|
||||||
|
unsigned int sizey;
|
||||||
|
char walls[512];
|
||||||
|
if( sscanf(header.c_str(), GAME_HEADER_FORMATTER, &major, &minor, &patch, name, &sizex, &sizey, &turn, walls) != 8 )
|
||||||
|
{
|
||||||
|
std::cerr<<"Error: Failed to properly read game state from text"<<std::endl;
|
||||||
|
return CTTRTSGame(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( major != TTRTS_VERSION_MAJOR
|
||||||
|
|| minor != TTRTS_VERSION_MINOR )
|
||||||
|
{
|
||||||
|
std::cerr<<"Error: ttrts map/binary version missmatch BINARY:v"<<major<<"."<<minor<<"."<<patch<<" FILE:"<<TTRTS_VERSION_STRING<<std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uvector2> walls_vector;
|
||||||
|
{
|
||||||
|
std::string walls_str = walls;
|
||||||
|
size_t pos;
|
||||||
|
while ( ( pos = walls_str.find(']') ) != std::string::npos )
|
||||||
|
{
|
||||||
|
std::string pos_string = walls_str.substr(0,pos+1);
|
||||||
|
|
||||||
|
// Use scanf to extract positions
|
||||||
|
|
||||||
|
unsigned int x;
|
||||||
|
unsigned int y;
|
||||||
|
if( sscanf(pos_string.c_str(), GAME_POS_FORMATTER, &x, &y ) != 2 )
|
||||||
|
{
|
||||||
|
return CTTRTSGame(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase this coordinate
|
||||||
|
walls_str.erase(0,pos+1);
|
||||||
|
|
||||||
|
// Append our list
|
||||||
|
walls_vector.push_back({(ucoord_t)x,(ucoord_t)y});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CTTRTSGame game(sizex,sizey);
|
||||||
|
game.SetName(name);
|
||||||
|
game.SetTurn(turn);
|
||||||
|
|
||||||
|
// For each line, construct a unit
|
||||||
|
{
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = units.find('\n')) != std::string::npos) {
|
||||||
|
std::string unit_string = units.substr(0, pos);
|
||||||
|
units.erase(0, pos + 1);
|
||||||
|
game.AddUnit(GetUnitFromString(unit_string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all walls
|
||||||
|
for ( auto wall : walls_vector)
|
||||||
|
{
|
||||||
|
game.AddWall(wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a string descriptor of a unit
|
||||||
|
std::string GetStringFromUnit(const CUnit& unit )
|
||||||
|
{
|
||||||
|
static char buff[128];
|
||||||
|
memset(buff,0,sizeof(buff));
|
||||||
|
|
||||||
|
snprintf(buff,128, UNIT_FORMATTER,
|
||||||
|
unit.GetID(),
|
||||||
|
(int)unit.GetPlayer(),
|
||||||
|
unit.GetVisual(),
|
||||||
|
unit.GetDir(),
|
||||||
|
unit.GetPos().x,
|
||||||
|
unit.GetPos().y );
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a unit from a string descriptor
|
||||||
|
CUnit GetUnitFromString(const std::string& unit )
|
||||||
|
{
|
||||||
|
CUnit ret;
|
||||||
|
|
||||||
|
unsigned int id;
|
||||||
|
int player;
|
||||||
|
char vis;
|
||||||
|
char dir;
|
||||||
|
unsigned int posx;
|
||||||
|
unsigned int posy;
|
||||||
|
|
||||||
|
sscanf(unit.c_str(), UNIT_FORMATTER,
|
||||||
|
&id,
|
||||||
|
&player,
|
||||||
|
&vis,
|
||||||
|
&dir,
|
||||||
|
&posx,
|
||||||
|
&posy );
|
||||||
|
|
||||||
|
ret.ForceSetID((unit_id_t)id);
|
||||||
|
ret.SetPlayer((player_t) player);
|
||||||
|
ret.SetVisual((unitvis_c)vis);
|
||||||
|
ret.SetDir((dir_c)dir);
|
||||||
|
ret.SetPos(uvector2(posx,posy));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an order to a string
|
||||||
|
std::string GetStringFromOrder(const SOrder & order )
|
||||||
|
{
|
||||||
|
static char buff[128];
|
||||||
|
memset(buff,0,sizeof(buff));
|
||||||
|
|
||||||
|
snprintf(buff,128, ORDER_FORMATTER,
|
||||||
|
order.command,
|
||||||
|
order.unit);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a string to an order
|
||||||
|
SOrder GetOrderFromString( const std::string& order )
|
||||||
|
{
|
||||||
|
SOrder ret;
|
||||||
|
|
||||||
|
char corder;
|
||||||
|
unsigned int unit;
|
||||||
|
|
||||||
|
sscanf(order.c_str(), ORDER_FORMATTER,
|
||||||
|
&corder,
|
||||||
|
&unit);
|
||||||
|
|
||||||
|
ret.command = (command_c)corder;
|
||||||
|
ret.unit = unit;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
29
source/ttrts/formatters.h
Normal file
29
source/ttrts/formatters.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef _TTRTS_FORMATTERS_H_
|
||||||
|
#define _TTRTS_FORMATTERS_H_
|
||||||
|
|
||||||
|
#include "order.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "unit.h"
|
||||||
|
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
#define GAME_POS_FORMATTER "[%u,%u]"
|
||||||
|
|
||||||
|
#define GAME_HEADER_FORMATTER "==== ttrts v%u.%u.%u ====\nNAME:%s\nSIZE:" GAME_POS_FORMATTER "\nTURN:%u\nWALL:%s"
|
||||||
|
#define GAME_HEADER_DELIMITER "~~~~\n"
|
||||||
|
|
||||||
|
#define UNIT_FORMATTER "UNIT:%u pl:%u vs:%c dr:%c ps:" GAME_POS_FORMATTER
|
||||||
|
|
||||||
|
// order <--> string conversion functions
|
||||||
|
std::string GetStringFromOrder(const SOrder & order );
|
||||||
|
SOrder GetOrderFromString( const std::string& order );
|
||||||
|
|
||||||
|
// game <--> string conversion functions
|
||||||
|
CTTRTSGame GetGameFromString( const std::string& input );
|
||||||
|
std::string GetStringFromGame( const CTTRTSGame& game );
|
||||||
|
|
||||||
|
// unit <--> string conversion functions
|
||||||
|
std::string GetStringFromUnit(const CUnit& unit );
|
||||||
|
CUnit GetUnitFromString(const std::string& unit );
|
||||||
|
|
||||||
|
#endif
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "formatters.h"
|
||||||
|
|
||||||
CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r )
|
CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r )
|
||||||
: dimensions( c,r )
|
: dimensions( c,r )
|
||||||
|
@ -16,6 +17,7 @@ CTTRTSGame::CTTRTSGame(CTTRTSGame&& game)
|
||||||
, dimensions(std::move(game.dimensions))
|
, dimensions(std::move(game.dimensions))
|
||||||
, turn(std::move(game.turn))
|
, turn(std::move(game.turn))
|
||||||
, name(std::move(game.name))
|
, name(std::move(game.name))
|
||||||
|
, m_walls(std::move(game.m_walls))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +32,7 @@ CTTRTSGame& CTTRTSGame::operator=(CTTRTSGame&& game)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpret a string of orders
|
// Interpret a string of orders
|
||||||
int CTTRTSGame::IssueOrders( Team team, const std::string& _orders )
|
int CTTRTSGame::IssueOrders( player_t player, const std::string& _orders )
|
||||||
{
|
{
|
||||||
COrderVector orderVector;
|
COrderVector orderVector;
|
||||||
|
|
||||||
|
@ -48,22 +50,22 @@ int CTTRTSGame::IssueOrders( Team team, const std::string& _orders )
|
||||||
orders.erase(0,pos+1);
|
orders.erase(0,pos+1);
|
||||||
|
|
||||||
// Create an order from the string and push it back
|
// Create an order from the string and push it back
|
||||||
COrder order = GetOrderFromString( sorder );
|
SOrder order = GetOrderFromString( sorder );
|
||||||
orderVector.push_back(order);
|
orderVector.push_back(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call our add order by vector method
|
// Call our add order by vector method
|
||||||
return IssueOrders(team,orderVector);
|
return IssueOrders(player,orderVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue orders by vector to the game
|
// Issue orders by vector to the game
|
||||||
int CTTRTSGame::IssueOrders( Team team, const COrderVector& orders )
|
int CTTRTSGame::IssueOrders( player_t player, const COrderVector& orders )
|
||||||
{
|
{
|
||||||
// verify all the orders
|
// verify all the orders
|
||||||
for ( auto order : orders )
|
for ( auto order : orders )
|
||||||
{
|
{
|
||||||
// If any order returns non-zero, back out
|
// If any order returns non-zero, back out
|
||||||
if ( IssueOrder(team,order) )
|
if ( IssueOrder(player,order) )
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,16 +73,16 @@ int CTTRTSGame::IssueOrders( Team team, const COrderVector& orders )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a single order
|
// Issue a single order
|
||||||
int CTTRTSGame::IssueOrder( Team team, const COrder& order )
|
int CTTRTSGame::IssueOrder( player_t player, const SOrder & order )
|
||||||
{
|
{
|
||||||
// Verify the order
|
// Verify the order
|
||||||
if ( VerifyOrder(team,order) )
|
if ( VerifyOrder(player,order) )
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// Get the right unit for the order
|
// Get the right unit for the order
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
if ( pair.unit.getID() == order.unit )
|
if (pair.unit.GetID() == order.unit )
|
||||||
{
|
{
|
||||||
pair.order = order;
|
pair.order = order;
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -92,7 +94,7 @@ int CTTRTSGame::IssueOrder( Team team, const COrder& order )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify a position
|
// Verify a position
|
||||||
int CTTRTSGame::VerifyPos(uvector2 vec) const
|
int CTTRTSGame::VerifyPosIsValidMovement(uvector2 vec) const
|
||||||
{
|
{
|
||||||
// Simply check if within the bounds of our dimensions for now
|
// Simply check if within the bounds of our dimensions for now
|
||||||
if ( ( vec.x >= dimensions.x )
|
if ( ( vec.x >= dimensions.x )
|
||||||
|
@ -101,22 +103,31 @@ int CTTRTSGame::VerifyPos(uvector2 vec) const
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check within our invalid positions
|
||||||
|
for ( const uvector2& invalid : m_walls)
|
||||||
|
{
|
||||||
|
if( vec == invalid )
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get a units new position
|
// Get a units new position
|
||||||
uvector2 CTTRTSGame::GetNewPosition( const OrderUnitPair& pair ) const
|
uvector2 CTTRTSGame::GetNewPosition( const SOrderUnitPair & pair ) const
|
||||||
{
|
{
|
||||||
// Grab the order
|
// Grab the order
|
||||||
switch ( pair.order.command)
|
switch ( pair.order.command)
|
||||||
{
|
{
|
||||||
// For forward orders, grab in front
|
// For forward orders, grab in front
|
||||||
case command_c::F:
|
case command_c::F:
|
||||||
return pair.unit.getInFront();
|
return pair.unit.GetInFront();
|
||||||
// For all other orders, just grab the old position
|
// For all other orders, just grab the old position
|
||||||
default:
|
default:
|
||||||
return pair.unit.getPos();
|
return pair.unit.GetPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +138,7 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
// Attempt all movement orders
|
// Attempt all movement orders
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
switch ( pair.order.command)
|
switch ( pair.order.command)
|
||||||
{
|
{
|
||||||
|
@ -137,15 +148,15 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
uvector2 newpos = GetNewPosition(pair);
|
uvector2 newpos = GetNewPosition(pair);
|
||||||
|
|
||||||
// Verify the position is even available
|
// Verify the position is even available
|
||||||
bool possible = ( VerifyPos(newpos) == 0 );
|
bool possible = (VerifyPosIsValidMovement(newpos) == 0 );
|
||||||
|
|
||||||
if ( possible )
|
if ( possible )
|
||||||
{
|
{
|
||||||
// If any unit is in this spot, or moving unit moving to said spot, reject this
|
// If any unit is in this spot, or moving unit moving to said spot, reject this
|
||||||
for ( const OrderUnitPair& pair2 : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair2 : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
// Skip myself
|
// Skip myself
|
||||||
if( pair.unit.getID() == pair2.unit.getID() ) continue;
|
if(pair.unit.GetID() == pair2.unit.GetID() ) continue;
|
||||||
|
|
||||||
if( GetNewPosition(pair2) == newpos )
|
if( GetNewPosition(pair2) == newpos )
|
||||||
{
|
{
|
||||||
|
@ -158,7 +169,9 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
// If the movement is still possible
|
// If the movement is still possible
|
||||||
if ( possible )
|
if ( possible )
|
||||||
{
|
{
|
||||||
pair.unit.setPos(newpos);
|
// Create a wall at our old position
|
||||||
|
AddWall(pair.unit.GetPos());
|
||||||
|
pair.unit.SetPos(newpos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -168,20 +181,20 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn all units that need turning
|
// Turn all units that need turning
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
switch ( pair.order.command)
|
switch ( pair.order.command)
|
||||||
{
|
{
|
||||||
case command_c::L:
|
case command_c::L:
|
||||||
{
|
{
|
||||||
// Simply turn left
|
// Simply turn left
|
||||||
pair.unit.turnLeft();
|
pair.unit.TurnLeft();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case command_c::R:
|
case command_c::R:
|
||||||
{
|
{
|
||||||
// Simply turn right
|
// Simply turn right
|
||||||
pair.unit.turnRight();
|
pair.unit.TurnRight();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -196,15 +209,15 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
// Assume no more charging
|
// Assume no more charging
|
||||||
charging = false;
|
charging = false;
|
||||||
// Initially move all units
|
// Initially move all units
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
if ( pair.order.command == command_c::A )
|
if ( pair.order.command == command_c::A )
|
||||||
{
|
{
|
||||||
uvector2 newpos = pair.unit.getInFront();
|
uvector2 newpos = pair.unit.GetInFront();
|
||||||
// If move would be within the arena
|
// If move would be within the arena
|
||||||
if ( ( newpos.x <= dimensions.x-1 ) && ( newpos.y <= dimensions.y-1 ) )
|
if (VerifyPosIsValidMovement(newpos) == 0 )
|
||||||
{
|
{
|
||||||
pair.unit.setPos(newpos);
|
pair.unit.SetPos(newpos);
|
||||||
|
|
||||||
// Unit moved, so more charging needs to be done
|
// Unit moved, so more charging needs to be done
|
||||||
charging = true;
|
charging = true;
|
||||||
|
@ -215,16 +228,16 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
std::vector< unit_id_t > toKill; // Vector to store which units to kill
|
std::vector< unit_id_t > toKill; // Vector to store which units to kill
|
||||||
|
|
||||||
// Initially move all units to check for pass through
|
// Initially move all units to check for pass through
|
||||||
for ( OrderUnitPair& pair1 : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair1 : m_OrderUnitPairs )
|
||||||
if ( pair1.order.command == command_c::A )
|
if ( pair1.order.command == command_c::A )
|
||||||
for ( OrderUnitPair& pair2 : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair2 : m_OrderUnitPairs )
|
||||||
if ( pair1.unit.getID() != pair2.unit.getID() // Don't check the same units
|
if (pair1.unit.GetID() != pair2.unit.GetID() // Don't check the same units
|
||||||
&& pair2.order.command == command_c::A )
|
&& pair2.order.command == command_c::A )
|
||||||
{
|
{
|
||||||
if( CheckForPassThrough(pair1.unit,pair2.unit) )
|
if( CheckForPassThrough(pair1.unit,pair2.unit) )
|
||||||
{
|
{
|
||||||
toKill.push_back(pair1.unit.getID());
|
toKill.push_back(pair1.unit.GetID());
|
||||||
toKill.push_back(pair2.unit.getID());
|
toKill.push_back(pair2.unit.GetID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,21 +246,21 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
toKill.clear();
|
toKill.clear();
|
||||||
|
|
||||||
// Check for all matching spots
|
// Check for all matching spots
|
||||||
for ( OrderUnitPair& pair1 : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair1 : m_OrderUnitPairs )
|
||||||
for ( OrderUnitPair& pair2 : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair2 : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
if( pair1.unit.getID() == pair2.unit.getID() ) continue; // Don't check the same units
|
if(pair1.unit.GetID() == pair2.unit.GetID() ) continue; // Don't check the same units
|
||||||
|
|
||||||
if( pair1.unit.getPos() == pair2.unit.getPos() )
|
if(pair1.unit.GetPos() == pair2.unit.GetPos() )
|
||||||
{
|
{
|
||||||
if( pair1.order.command == command_c::A )
|
if( pair1.order.command == command_c::A )
|
||||||
{
|
{
|
||||||
toKill.push_back(pair2.unit.getID());
|
toKill.push_back(pair2.unit.GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
if( pair2.order.command == command_c::A )
|
if( pair2.order.command == command_c::A )
|
||||||
{
|
{
|
||||||
toKill.push_back(pair1.unit.getID());
|
toKill.push_back(pair1.unit.GetID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,8 +271,8 @@ int CTTRTSGame::SimulateToNextTurn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear all orders
|
// Clear all orders
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
pair.order = COrder();
|
pair.order = SOrder();
|
||||||
|
|
||||||
// Increment the current turn
|
// Increment the current turn
|
||||||
turn++;
|
turn++;
|
||||||
|
@ -280,7 +293,7 @@ void CTTRTSGame::KillAll( std::vector< unit_id_t >& vec )
|
||||||
it != m_OrderUnitPairs.end();
|
it != m_OrderUnitPairs.end();
|
||||||
it++ )
|
it++ )
|
||||||
{
|
{
|
||||||
if( (*it).unit.getID() == id )
|
if((*it).unit.GetID() == id )
|
||||||
{
|
{
|
||||||
// Remove the unit from our alive unit pairs
|
// Remove the unit from our alive unit pairs
|
||||||
m_OrderUnitPairs.erase(it);
|
m_OrderUnitPairs.erase(it);
|
||||||
|
@ -294,28 +307,28 @@ void CTTRTSGame::KillAll( std::vector< unit_id_t >& vec )
|
||||||
// Check if two units passed through each other
|
// Check if two units passed through each other
|
||||||
bool CTTRTSGame::CheckForPassThrough( const CUnit& one, const CUnit& two )
|
bool CTTRTSGame::CheckForPassThrough( const CUnit& one, const CUnit& two )
|
||||||
{
|
{
|
||||||
uvector2 pos1 = one.getPos();
|
uvector2 pos1 = one.GetPos();
|
||||||
uvector2 pos2 = two.getPos();
|
uvector2 pos2 = two.GetPos();
|
||||||
dir_t dir1 = one.getDir();
|
dir_c dir1 = one.GetDir();
|
||||||
dir_t dir2 = two.getDir();
|
dir_c dir2 = two.GetDir();
|
||||||
|
|
||||||
if( pos1.x == pos2.x ) { // Same col
|
if( pos1.x == pos2.x ) { // Same col
|
||||||
if (pos1.y == (pos2.y + 1)) {
|
if (pos1.y == (pos2.y + 1)) {
|
||||||
if (dir1 == dir_t::N && dir2 == dir_t::S)
|
if (dir1 == dir_c::N && dir2 == dir_c::S)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (pos1.y == (pos2.y - 1)) {
|
else if (pos1.y == (pos2.y - 1)) {
|
||||||
if (dir1 == dir_t::S && dir2 == dir_t::N)
|
if (dir1 == dir_c::S && dir2 == dir_c::N)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( pos1.y == pos2.y ) { // Same row
|
else if( pos1.y == pos2.y ) { // Same row
|
||||||
if( pos1.x == (pos2.x+1) ) {
|
if( pos1.x == (pos2.x+1) ) {
|
||||||
if( dir1 == dir_t::E && dir2 == dir_t::W )
|
if( dir1 == dir_c::E && dir2 == dir_c::W )
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if( pos1.x == (pos2.x-1) ) {
|
else if( pos1.x == (pos2.x-1) ) {
|
||||||
if( dir1 == dir_t::E && dir2 == dir_t::W )
|
if( dir1 == dir_c::E && dir2 == dir_c::W )
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,23 +340,23 @@ bool CTTRTSGame::CheckForPassThrough( const CUnit& one, const CUnit& two )
|
||||||
int CTTRTSGame::AddUnit( CUnit&& unit )
|
int CTTRTSGame::AddUnit( CUnit&& unit )
|
||||||
{
|
{
|
||||||
// Verify the unit
|
// Verify the unit
|
||||||
if( !unit.valid() )
|
if( !unit.Valid() )
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// Verify if the unit can be placed on the current board
|
// Verify if the unit can be placed on the current board
|
||||||
const uvector2 pos = unit.getPos();
|
const uvector2 pos = unit.GetPos();
|
||||||
if( (pos.x >= dimensions.x) || (pos.y >= dimensions.y) )
|
if( (pos.x >= dimensions.x) || (pos.y >= dimensions.y) )
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
// If any unit's position matches, reject this
|
// If any unit's position matches, reject this
|
||||||
for ( const OrderUnitPair& pair: m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair: m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
if( pair.unit.getPos() == unit.getPos() )
|
if(pair.unit.GetPos() == unit.GetPos() )
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the unit with a blank order
|
// Add the unit with a blank order
|
||||||
m_OrderUnitPairs.push_back( OrderUnitPair(std::move(unit), COrder()) );
|
m_OrderUnitPairs.push_back( SOrderUnitPair(std::move(unit), SOrder()) );
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -366,7 +379,7 @@ int CTTRTSGame::AddUnits( CUnitVector&& units )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify any order
|
// Verify any order
|
||||||
int CTTRTSGame::VerifyOrder( Team team, const COrder& order ) const
|
int CTTRTSGame::VerifyOrder( player_t player, const SOrder & order ) const
|
||||||
{
|
{
|
||||||
int ret = 1;
|
int ret = 1;
|
||||||
|
|
||||||
|
@ -374,11 +387,11 @@ int CTTRTSGame::VerifyOrder( Team team, const COrder& order ) const
|
||||||
const unit_id_t unitID = order.unit;
|
const unit_id_t unitID = order.unit;
|
||||||
|
|
||||||
// Attempt to find the unit
|
// Attempt to find the unit
|
||||||
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
// Accept if we have the unit
|
// Accept if we have the unit
|
||||||
if ( pair.unit.getID() == unitID
|
if (pair.unit.GetID() == unitID
|
||||||
&& pair.unit.getTeam() == team )
|
&& pair.unit.GetPlayer() == player)
|
||||||
{
|
{
|
||||||
ret = 0;
|
ret = 0;
|
||||||
break;
|
break;
|
||||||
|
@ -392,10 +405,10 @@ int CTTRTSGame::VerifyOrder( Team team, const COrder& order ) const
|
||||||
// Get unit by unit ID
|
// Get unit by unit ID
|
||||||
const CUnit& CTTRTSGame::GetUnitByIDConst( unit_id_t id ) const
|
const CUnit& CTTRTSGame::GetUnitByIDConst( unit_id_t id ) const
|
||||||
{
|
{
|
||||||
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
// Attempt the unit add
|
// Attempt the unit add
|
||||||
if ( pair.unit.getID() == id )
|
if (pair.unit.GetID() == id )
|
||||||
return pair.unit;
|
return pair.unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,27 +418,27 @@ const CUnit& CTTRTSGame::GetUnitByIDConst( unit_id_t id ) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an order by unit ID
|
// Get an order by unit ID
|
||||||
const COrder& CTTRTSGame::GetOrderByIDConst( unit_id_t id ) const
|
const SOrder & CTTRTSGame::GetOrderByIDConst( unit_id_t id ) const
|
||||||
{
|
{
|
||||||
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
// Attempt the unit add
|
// Attempt the unit add
|
||||||
if ( pair.unit.getID() == id )
|
if (pair.unit.GetID() == id )
|
||||||
return pair.order;
|
return pair.order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return an invalid unit
|
// Return an invalid unit
|
||||||
static COrder invalid_order;
|
static SOrder invalid_order;
|
||||||
return invalid_order;
|
return invalid_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get unit by unit ID
|
// Get unit by unit ID
|
||||||
CUnit& CTTRTSGame::GetUnitByID( unit_id_t id )
|
CUnit& CTTRTSGame::GetUnitByID( unit_id_t id )
|
||||||
{
|
{
|
||||||
for ( OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
// Attempt the unit add
|
// Attempt the unit add
|
||||||
if ( pair.unit.getID() == id )
|
if (pair.unit.GetID() == id )
|
||||||
return pair.unit;
|
return pair.unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,114 +447,86 @@ CUnit& CTTRTSGame::GetUnitByID( unit_id_t id )
|
||||||
return invalid_unit;
|
return invalid_unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a vector of the teams in the current game
|
// Get a vector of the players in the current game
|
||||||
std::vector<Team> CTTRTSGame::GetTeams() const
|
std::vector<player_t> CTTRTSGame::GetPlayers() const
|
||||||
{
|
{
|
||||||
std::vector<Team> teams;
|
std::vector<player_t> players;
|
||||||
teams.reserve(GetNumUnits());
|
players.reserve(GetNumUnits());
|
||||||
|
|
||||||
// Grab all teams
|
// Grab all players
|
||||||
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
teams.push_back(pair.unit.getTeam());
|
players.push_back(pair.unit.GetPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove dupes
|
// Remove dupes
|
||||||
std::sort( teams.begin(), teams.end() );
|
std::sort( players.begin(), players.end() );
|
||||||
teams.erase( std::unique( teams.begin(), teams.end() ), teams.end() );
|
players.erase( std::unique( players.begin(), players.end() ), players.end() );
|
||||||
|
|
||||||
return teams;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a win state
|
// Check if we have a win state
|
||||||
Team CTTRTSGame::CheckForWin() const
|
player_t CTTRTSGame::GetWinningPlayer() const
|
||||||
{
|
{
|
||||||
// Array of units for each Team
|
// Array of units for each Player
|
||||||
unsigned int units[(int) Team::NUM_INVALID];
|
unsigned int units[(int) player_t::NUM_INVALID];
|
||||||
memset(units,0,sizeof(units));
|
memset(units,0,sizeof(units));
|
||||||
|
|
||||||
// Count up all the units for each Team
|
// Count up all the units for each Player
|
||||||
for ( const OrderUnitPair& pair : m_OrderUnitPairs )
|
for ( const SOrderUnitPair & pair : m_OrderUnitPairs )
|
||||||
{
|
{
|
||||||
const int team = (int)pair.unit.getTeam();
|
const int player = (int) pair.unit.GetPlayer();
|
||||||
units[team] += 1;
|
units[player] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default winning Team to invalid (no win)
|
// Default winning Player to invalid (no win)
|
||||||
Team winningTeam = Team::NUM_INVALID;
|
player_t winningPlayer = player_t::NUM_INVALID;
|
||||||
|
|
||||||
// For each of the teams
|
// For each of the players
|
||||||
for ( unsigned int i = 0; i < _countof(units); i++ )
|
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 there are still units in this Player, and the winning Player hasn't been set
|
||||||
if( units[i] > 0 && winningTeam == Team::NUM_INVALID )
|
if( units[i] > 0 && winningPlayer == player_t::NUM_INVALID )
|
||||||
{
|
{
|
||||||
winningTeam = (Team)i;
|
winningPlayer = (player_t)i;
|
||||||
}
|
}
|
||||||
// Otherwise, if there are units in this Team and the winning Team HAS been set
|
// Otherwise, if there are units in this Player and the winning Player HAS been set
|
||||||
else if ( units[i] > 0 )
|
else if ( units[i] > 0 )
|
||||||
{
|
{
|
||||||
// Set back to invalid and break out of the loop
|
// Set back to invalid and break out of the loop
|
||||||
winningTeam = Team::NUM_INVALID;
|
winningPlayer = player_t::NUM_INVALID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return winningTeam;
|
return winningPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the game information as a string
|
// Check if any of the units can move
|
||||||
std::string CTTRTSGame::GetStateAsString() const
|
bool CTTRTSGame::UnitsCanMove() const
|
||||||
{
|
{
|
||||||
// Print out the header
|
for( const SOrderUnitPair& pair: m_OrderUnitPairs )
|
||||||
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);
|
uvector2 pos = pair.unit.GetPos();
|
||||||
units += '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the header and units
|
// Assume if unit is adjacent to any valid tile, then it can move there
|
||||||
std::string state(header);
|
if( VerifyPosIsValidMovement(pos + vector2(1, 0) ) == 0
|
||||||
state += '\n';
|
|| VerifyPosIsValidMovement(pos + vector2(0, 1)) == 0
|
||||||
state += GAME_HEADER_DELIMITER;
|
|| VerifyPosIsValidMovement(pos + vector2(-1, 0)) == 0
|
||||||
state += units;
|
|| VerifyPosIsValidMovement(pos + vector2(0, -1)) == 0 )
|
||||||
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);
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string header = input.substr(0, headerEnd);
|
return false;
|
||||||
std::string units = input.substr(headerEnd + strlen(GAME_HEADER_DELIMITER));
|
}
|
||||||
|
|
||||||
// Grab information from the header
|
// Check if the game is over
|
||||||
char buf[64];
|
bool CTTRTSGame::GameOver() const
|
||||||
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);
|
return ( (GetWinningPlayer() != player_t::NUM_INVALID ) // We have a winning player
|
||||||
units.erase(0,pos+1);
|
|| GetNumUnits() == 0
|
||||||
game.AddUnit(CUnit::GetUnitFromString(unit_string));
|
|| !UnitsCanMove() ); // OR we have no units
|
||||||
}
|
|
||||||
|
|
||||||
return game;
|
|
||||||
}
|
}
|
107
source/ttrts/game.h
Normal file
107
source/ttrts/game.h
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#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:
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
inline const OrderUnitPairVector& GetOrderUnitPairs() const { return m_OrderUnitPairs; }
|
||||||
|
|
||||||
|
// 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_
|
45
source/ttrts/gametypes.h
Normal file
45
source/ttrts/gametypes.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef _GAME_TYPES_H_
|
||||||
|
#define _GAME_TYPES_H_
|
||||||
|
|
||||||
|
#include <limits> // std::numeric_limits
|
||||||
|
#include "stdlib.h" // for size_t
|
||||||
|
|
||||||
|
// Type for a Player IDs
|
||||||
|
enum class player_t : char
|
||||||
|
{
|
||||||
|
Red = 0,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
Blue,
|
||||||
|
NUM_INVALID
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type for unit IDs
|
||||||
|
typedef unsigned short unit_id_t;
|
||||||
|
static const unit_id_t unit_id_invalid = std::numeric_limits<unit_id_t>::max();
|
||||||
|
|
||||||
|
// Typedef for unit visual representations
|
||||||
|
typedef char unitvis_c;
|
||||||
|
static const unitvis_c unitvis_invalid = std::numeric_limits<unitvis_c>::max();
|
||||||
|
|
||||||
|
// Coordinate types
|
||||||
|
typedef short coord_t;
|
||||||
|
static const coord_t coord_invalid = std::numeric_limits<coord_t>::max();
|
||||||
|
|
||||||
|
typedef unsigned short ucoord_t;
|
||||||
|
static const ucoord_t ucoord_invalid = std::numeric_limits<ucoord_t>::max();
|
||||||
|
|
||||||
|
// Direction representation
|
||||||
|
enum class dir_c : char
|
||||||
|
{
|
||||||
|
N = 'N',
|
||||||
|
S = 'S',
|
||||||
|
E = 'E',
|
||||||
|
W = 'W'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function for count of an array
|
||||||
|
template<class T, size_t N>
|
||||||
|
constexpr size_t _countof(T (&)[N]) { return N; }
|
||||||
|
|
||||||
|
#endif //_GAME_TYPES_H_
|
|
@ -1,234 +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::cerr<<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 teams involved
|
|
||||||
auto teams = game.GetTeams();
|
|
||||||
|
|
||||||
// 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 hasn't been won
|
|
||||||
Team winningTeam;
|
|
||||||
while ( ((winningTeam = game.CheckForWin()) == Team::NUM_INVALID) // We have a winning team
|
|
||||||
&& game.GetNumUnits() > 0 ) // We have no units left
|
|
||||||
{
|
|
||||||
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( Team team : teams )
|
|
||||||
{
|
|
||||||
// Construct the team order filename
|
|
||||||
char teamOrderFileName[128];
|
|
||||||
snprintf(teamOrderFileName, 128, "%s/Turn_%i_Team_%i.txt", gameDir.c_str(), game.GetTurn(), (int) team);
|
|
||||||
|
|
||||||
// Wait for the team order file to be created
|
|
||||||
std::cout<<"Waiting for "<<teamOrderFileName<<std::endl;
|
|
||||||
bool hasOrderFile = false;
|
|
||||||
while(!hasOrderFile)
|
|
||||||
{
|
|
||||||
WaitForFile(teamOrderFileName,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(teamOrderFileName);
|
|
||||||
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(teamOrderFileName);
|
|
||||||
|
|
||||||
// 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(team, orders) )
|
|
||||||
std::cerr<<"Warning: Orders for team "<<(int)team<<" 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);
|
|
||||||
|
|
||||||
// Print the winner!
|
|
||||||
if ( winningTeam != Team::NUM_INVALID )
|
|
||||||
{
|
|
||||||
std::cout<<"Game over! Winner:"<<(int)winningTeam<<std::endl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout<<"Game over! It was a draw!"<<std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
|
@ -19,10 +19,10 @@ enum class command_c : char
|
||||||
};
|
};
|
||||||
|
|
||||||
// Container for an order
|
// Container for an order
|
||||||
struct COrder
|
struct SOrder
|
||||||
{
|
{
|
||||||
// Base constructor makes invalid order
|
// Base constructor makes invalid order
|
||||||
COrder()
|
SOrder()
|
||||||
: unit ( unit_id_invalid )
|
: unit ( unit_id_invalid )
|
||||||
, command( command_c::NUM_INVALID )
|
, command( command_c::NUM_INVALID )
|
||||||
{}
|
{}
|
||||||
|
@ -34,21 +34,17 @@ struct COrder
|
||||||
command_c command;
|
command_c command;
|
||||||
|
|
||||||
// Basic operators
|
// Basic operators
|
||||||
inline bool operator==( const COrder& rhs ) const;
|
inline bool operator==( const SOrder & rhs ) const;
|
||||||
inline bool operator!=( const COrder& rhs ) const { return !(*this==rhs); }
|
inline bool operator!=( const SOrder & rhs ) const { return !(*this==rhs); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simple == operator
|
// Simple == operator
|
||||||
inline bool COrder::operator== ( const COrder& rhs ) const
|
inline bool SOrder::operator== ( const SOrder & rhs ) const
|
||||||
{
|
{
|
||||||
return ( unit == rhs.unit ) && ( command == rhs.command);
|
return ( unit == rhs.unit ) && ( command == rhs.command);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typedef a vector of orders
|
// Typedef a vector of orders
|
||||||
typedef std::vector<COrder> COrderVector;
|
typedef std::vector<SOrder> COrderVector;
|
||||||
|
|
||||||
// string <--> order conversion functions
|
|
||||||
std::string GetStringFromOrder(const COrder& order );
|
|
||||||
COrder GetOrderFromString( const std::string& order );
|
|
||||||
|
|
||||||
#endif //_ORDERS_H_
|
#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_
|
217
source/ttrts/unit.cpp
Normal file
217
source/ttrts/unit.cpp
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
94
source/ttrts/unit.h
Normal file
94
source/ttrts/unit.h
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
#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 );
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Force set an ID
|
||||||
|
inline void ForceSetID( unit_id_t id ) { unit_id = id; }
|
||||||
|
|
||||||
|
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,7 +1,7 @@
|
||||||
#ifndef _VECTOR2_H_
|
#ifndef _VECTOR2_H_
|
||||||
#define _VECTOR2_H_
|
#define _VECTOR2_H_
|
||||||
|
|
||||||
#include "mathtypes.h"
|
#include "gametypes.h"
|
||||||
|
|
||||||
struct uvector2;
|
struct uvector2;
|
||||||
|
|
||||||
|
@ -60,20 +60,20 @@ struct uvector2
|
||||||
|
|
||||||
inline vector2::operator uvector2() const { return { (ucoord_t)x, (ucoord_t)y }; }
|
inline vector2::operator uvector2() const { return { (ucoord_t)x, (ucoord_t)y }; }
|
||||||
|
|
||||||
inline vector2 vecFromDir( dir_t dir )
|
inline vector2 vecFromDir( dir_c dir )
|
||||||
{
|
{
|
||||||
switch( dir )
|
switch( dir )
|
||||||
{
|
{
|
||||||
case dir_t::N:
|
case dir_c::N:
|
||||||
return { 0,1 };
|
return { 0,1 };
|
||||||
|
|
||||||
case dir_t::E:
|
case dir_c::E:
|
||||||
return { 1,0 };
|
return { 1,0 };
|
||||||
|
|
||||||
case dir_t::S:
|
case dir_c::S:
|
||||||
return { 0,-1 };
|
return { 0,-1 };
|
||||||
|
|
||||||
case dir_t::W:
|
case dir_c::W:
|
||||||
return { -1,0 };
|
return { -1,0 };
|
||||||
|
|
||||||
default:
|
default:
|
|
@ -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